mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-22 19:00:01 +08:00
add http client
This commit is contained in:
parent
5901ff197d
commit
f1a41022cb
BIN
src/Http/.DS_Store
vendored
Normal file
BIN
src/Http/.DS_Store
vendored
Normal file
Binary file not shown.
152
src/Http/HttpClient.cpp
Normal file
152
src/Http/HttpClient.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
//
|
||||
// HttpClient.cpp
|
||||
// ZLMediaKit
|
||||
//
|
||||
// Created by xzl on 2017/5/4.
|
||||
//
|
||||
|
||||
#include "HttpClient.h"
|
||||
#include "Rtsp/Rtsp.h"
|
||||
|
||||
namespace ZL {
|
||||
namespace Http {
|
||||
|
||||
|
||||
HttpClient::HttpClient(){
|
||||
}
|
||||
HttpClient::~HttpClient(){
|
||||
}
|
||||
void HttpClient::sendRequest(const string &strUrl){
|
||||
auto protocol = FindField(strUrl.data(), NULL , "://");
|
||||
uint16_t defaultPort;
|
||||
bool isHttps;
|
||||
if (strcasecmp(protocol.data(), "http") == 0) {
|
||||
defaultPort = 80;
|
||||
isHttps = false;
|
||||
}else if(strcasecmp(protocol.data(), "https") ==0 ){
|
||||
defaultPort = 443;
|
||||
isHttps = true;
|
||||
}else{
|
||||
auto strErr = StrPrinter << "非法的协议:" << protocol << endl;
|
||||
throw std::invalid_argument(strErr);
|
||||
}
|
||||
|
||||
auto host = FindField(strUrl.data(), "://", "/");
|
||||
if (host.empty()) {
|
||||
host = FindField(strUrl.data(), "://", NULL);
|
||||
}
|
||||
_path = FindField(strUrl.data(), host.data(), NULL);
|
||||
if (_path.empty()) {
|
||||
_path = "/";
|
||||
}
|
||||
auto port = atoi(FindField(host.data(), ":", NULL).data());
|
||||
if (port <= 0) {
|
||||
//默认端口
|
||||
port = defaultPort;
|
||||
} else {
|
||||
//服务器域名
|
||||
host = FindField(host.data(), NULL, ":");
|
||||
}
|
||||
_header.emplace(string("Host"),host);
|
||||
_header.emplace(string("Tools"),"ZLMediaKit");
|
||||
_header.emplace(string("Connection"),"keep-alive");
|
||||
_header.emplace(string("Accept"),"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
|
||||
_header.emplace(string("Accept-Encoding"),"gzip, deflate, sdch, br");
|
||||
_header.emplace(string("Accept-Language"),"zh-CN,zh;q=0.8");
|
||||
_header.emplace(string("User-Agent"),"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36");
|
||||
|
||||
if(!_body.empty()){
|
||||
_header.emplace(string("Content-Length"),std::to_string(_body.size()));
|
||||
_header.emplace(string("Content-Type"),"application/x-www-form-urlencoded; charset=UTF-8");
|
||||
}
|
||||
|
||||
bool bChanged = (_lastHost != host + ":" + to_string(port)) || (_isHttps != isHttps);
|
||||
_lastHost = host + ":" + to_string(port);
|
||||
_isHttps = isHttps;
|
||||
|
||||
if(!alive() || bChanged){
|
||||
InfoL << "reconnet:" << _lastHost;
|
||||
startConnect(host, port);
|
||||
}else{
|
||||
SockException ex;
|
||||
onConnect(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void HttpClient::onConnect(const SockException &ex) {
|
||||
if(ex){
|
||||
onDisconnect(ex);
|
||||
return;
|
||||
}
|
||||
_recvedBodySize = -1;
|
||||
_recvedResponse.clear();
|
||||
send(_method + " ");
|
||||
send(_path + " HTTP/1.1\r\n");
|
||||
for (auto &pr : _header) {
|
||||
send(pr.first + ": ");
|
||||
send(pr.second + "\r\n");
|
||||
}
|
||||
send("\r\n");
|
||||
if (!_body.empty()) {
|
||||
send(_body);
|
||||
}
|
||||
}
|
||||
void HttpClient::onRecv(const Socket::Buffer::Ptr &pBuf) {
|
||||
onRecvBytes(pBuf->data(),pBuf->size());
|
||||
}
|
||||
|
||||
void HttpClient::onErr(const SockException &ex) {
|
||||
onDisconnect(ex);
|
||||
}
|
||||
|
||||
void HttpClient::onRecvBytes(const char* data, int size) {
|
||||
if(_recvedBodySize == -1){
|
||||
//还没有收到http body,这只是http头
|
||||
auto lastLen = _recvedResponse.size();
|
||||
_recvedResponse.append(data,size);
|
||||
auto pos = _recvedResponse.find("\r\n\r\n",lastLen);
|
||||
if(pos == string::npos){
|
||||
//http 头还未收到
|
||||
return;
|
||||
}
|
||||
_parser.Parse(_recvedResponse.data());
|
||||
onResponseHeader(_parser.Url(),_parser.getValues());
|
||||
|
||||
_totalBodySize = atoll(((HttpHeader &)_parser.getValues())["Content-Length"].data());
|
||||
_recvedBodySize = _recvedResponse.size() - pos - 4;
|
||||
if(_totalBodySize < _recvedBodySize){
|
||||
//http body 比声明的大 这个不可能的
|
||||
FatalL;
|
||||
shutdown();
|
||||
return;
|
||||
}
|
||||
if (_recvedBodySize) {
|
||||
//_recvedResponse里面包含body负载
|
||||
onResponseBody(_recvedResponse.data() + _recvedResponse.size() - _recvedBodySize, _recvedBodySize,_recvedBodySize,_totalBodySize);
|
||||
}
|
||||
|
||||
if(_recvedBodySize >= _totalBodySize){
|
||||
onResponseCompleted();
|
||||
}
|
||||
_recvedResponse.clear();
|
||||
return;
|
||||
}
|
||||
//http body
|
||||
if(_recvedBodySize < _totalBodySize){
|
||||
_recvedBodySize += size;
|
||||
onResponseBody(data,size,_recvedBodySize,_totalBodySize);
|
||||
if(_recvedBodySize >= _totalBodySize){
|
||||
onResponseCompleted();
|
||||
}
|
||||
return;
|
||||
}
|
||||
//http body 比声明的大 这个不可能的
|
||||
FatalL;
|
||||
shutdown();
|
||||
}
|
||||
|
||||
|
||||
} /* namespace Http */
|
||||
} /* namespace ZL */
|
||||
|
130
src/Http/HttpClient.h
Normal file
130
src/Http/HttpClient.h
Normal file
@ -0,0 +1,130 @@
|
||||
//
|
||||
// HttpClient.h
|
||||
// ZLMediaKit
|
||||
//
|
||||
// Created by xzl on 2017/5/4.
|
||||
//
|
||||
|
||||
#ifndef Http_HttpClient_h
|
||||
#define Http_HttpClient_h
|
||||
|
||||
#include <string.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include "Rtsp/Rtsp.h"
|
||||
#include "Util/util.h"
|
||||
#include "Network/TcpClient.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ZL::Util;
|
||||
using namespace ZL::Network;
|
||||
|
||||
namespace ZL {
|
||||
namespace Http {
|
||||
|
||||
class HttpArgs : public map<string,string>
|
||||
{
|
||||
public:
|
||||
HttpArgs(){}
|
||||
virtual ~HttpArgs(){}
|
||||
string make() const {
|
||||
string ret;
|
||||
for(auto &pr : *this){
|
||||
ret.append(pr.first);
|
||||
ret.append("=");
|
||||
ret.append(pr.second);
|
||||
ret.append("&");
|
||||
}
|
||||
if(ret.size()){
|
||||
ret.pop_back();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
class HttpClient : public TcpClient
|
||||
{
|
||||
public:
|
||||
typedef StrCaseMap HttpHeader;
|
||||
typedef std::shared_ptr<HttpClient> Ptr;
|
||||
HttpClient();
|
||||
virtual ~HttpClient();
|
||||
virtual void sendRequest(const string &url);
|
||||
void clear(){
|
||||
_header.clear();
|
||||
_body.clear();
|
||||
_method.clear();
|
||||
_path.clear();
|
||||
_recvedResponse.clear();
|
||||
_parser.Clear();
|
||||
}
|
||||
void setMethod(const string &method){
|
||||
_method = method;
|
||||
}
|
||||
void setHeader(const HttpHeader &header){
|
||||
_header = _header;
|
||||
}
|
||||
void addHeader(const string &key,const string &val){
|
||||
_header.emplace(key,val);
|
||||
}
|
||||
void setBody(const string &body){
|
||||
_body = body;
|
||||
}
|
||||
const string &responseStatus(){
|
||||
return _parser.Url();
|
||||
}
|
||||
const HttpHeader &responseHeader(){
|
||||
return _parser.getValues();
|
||||
}
|
||||
protected:
|
||||
bool _isHttps;
|
||||
|
||||
virtual void onResponseHeader(const string &status,const HttpHeader &headers){
|
||||
DebugL << status;
|
||||
};
|
||||
virtual void onResponseBody(const char *buf,size_t size,size_t recvedSize,size_t totalSize){
|
||||
DebugL << size << " " << recvedSize << " " << totalSize;
|
||||
};
|
||||
virtual void onResponseCompleted(){
|
||||
DebugL;
|
||||
}
|
||||
virtual void onRecvBytes(const char *data,int size);
|
||||
virtual void onDisconnect(const SockException &ex){}
|
||||
private:
|
||||
virtual void onConnect(const SockException &ex) override;
|
||||
virtual void onRecv(const Socket::Buffer::Ptr &pBuf) override;
|
||||
virtual void onErr(const SockException &ex) override;
|
||||
|
||||
//send
|
||||
HttpHeader _header;
|
||||
string _body;
|
||||
string _method;
|
||||
string _path;
|
||||
|
||||
//recv
|
||||
string _recvedResponse;
|
||||
size_t _recvedBodySize;
|
||||
size_t _totalBodySize;
|
||||
Parser _parser;
|
||||
|
||||
string _lastHost;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} /* namespace Http */
|
||||
} /* namespace ZL */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif /* Http_HttpClient_h */
|
72
src/Http/HttpClientImp.cpp
Normal file
72
src/Http/HttpClientImp.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* HttpClientImp.cpp
|
||||
*
|
||||
* Created on: 2017年5月4日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#include <Http/HttpClientImp.h>
|
||||
|
||||
namespace ZL {
|
||||
namespace Http {
|
||||
|
||||
HttpClientImp::HttpClientImp() {
|
||||
// TODO Auto-generated constructor stub
|
||||
|
||||
}
|
||||
|
||||
HttpClientImp::~HttpClientImp() {
|
||||
}
|
||||
|
||||
void HttpClientImp::sendRequest(const string& url) {
|
||||
HttpClient::sendRequest(url);
|
||||
if(_isHttps){
|
||||
#ifndef ENABLE_OPENSSL
|
||||
shutdown();
|
||||
throw std::invalid_argument("不支持HTTPS协议");
|
||||
#else
|
||||
_sslBox.reset(new SSL_Box(false));
|
||||
_sslBox->setOnDecData([this](const char *data, uint32_t len){
|
||||
HttpClient::onRecvBytes(data,len);
|
||||
});
|
||||
_sslBox->setOnEncData([this](const char *data, uint32_t len){
|
||||
HttpClient::send(data,len);
|
||||
});
|
||||
#endif //ENABLE_OPENSSL
|
||||
|
||||
}else{
|
||||
#ifdef ENABLE_OPENSSL
|
||||
_sslBox.reset();
|
||||
#endif //ENABLE_OPENSSL
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_OPENSSL
|
||||
void HttpClientImp::onRecvBytes(const char* data, int size) {
|
||||
if(_sslBox){
|
||||
_sslBox->onRecv(data,size);
|
||||
}else{
|
||||
HttpClient::onRecvBytes(data,size);
|
||||
}
|
||||
}
|
||||
|
||||
int HttpClientImp::send(const string& str) {
|
||||
if(_sslBox){
|
||||
_sslBox->onSend(str.data(),str.size());
|
||||
return str.size();
|
||||
}
|
||||
return HttpClient::send(str);
|
||||
}
|
||||
|
||||
int HttpClientImp::send(const char* str, int len) {
|
||||
if(_sslBox){
|
||||
_sslBox->onSend(str,len);
|
||||
return len;
|
||||
}
|
||||
return HttpClient::send(str,len);
|
||||
|
||||
}
|
||||
#endif //ENABLE_OPENSSL
|
||||
|
||||
} /* namespace Http */
|
||||
} /* namespace ZL */
|
38
src/Http/HttpClientImp.h
Normal file
38
src/Http/HttpClientImp.h
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* HttpClientImp.h
|
||||
*
|
||||
* Created on: 2017年5月4日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#ifndef SRC_HTTP_HTTPCLIENTIMP_H_
|
||||
#define SRC_HTTP_HTTPCLIENTIMP_H_
|
||||
|
||||
#include "HttpClient.h"
|
||||
#ifdef ENABLE_OPENSSL
|
||||
#include "Util/SSLBox.h"
|
||||
using namespace ZL::Util;
|
||||
#endif //ENABLE_OPENSSL
|
||||
|
||||
namespace ZL {
|
||||
namespace Http {
|
||||
|
||||
class HttpClientImp: public HttpClient {
|
||||
public:
|
||||
typedef std::shared_ptr<HttpClientImp> Ptr;
|
||||
HttpClientImp();
|
||||
virtual ~HttpClientImp();
|
||||
virtual void sendRequest(const string &url) override;
|
||||
private:
|
||||
#ifdef ENABLE_OPENSSL
|
||||
virtual void onRecvBytes(const char *data,int size) override;
|
||||
virtual int send(const string &str) override;
|
||||
virtual int send(const char *str, int len) override;
|
||||
std::shared_ptr<SSL_Box> _sslBox;
|
||||
#endif //ENABLE_OPENSSL
|
||||
};
|
||||
|
||||
} /* namespace Http */
|
||||
} /* namespace ZL */
|
||||
|
||||
#endif /* SRC_HTTP_HTTPCLIENTIMP_H_ */
|
87
src/Http/HttpDownloader.cpp
Normal file
87
src/Http/HttpDownloader.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* HttpDownloader.cpp
|
||||
*
|
||||
* Created on: 2017年5月5日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#include "HttpDownloader.h"
|
||||
#include "Util/MD5.h"
|
||||
#include "Util/File.h"
|
||||
|
||||
using namespace ZL::Util;
|
||||
|
||||
namespace ZL {
|
||||
namespace Http {
|
||||
|
||||
HttpDownloader::HttpDownloader() {
|
||||
|
||||
}
|
||||
|
||||
HttpDownloader::~HttpDownloader() {
|
||||
closeFile();
|
||||
}
|
||||
|
||||
void HttpDownloader::startDownload(const string& url, const string& filePath,bool bAppend) {
|
||||
_filePath = filePath;
|
||||
if(_filePath.empty()){
|
||||
_filePath = exeDir() + "/HttpDownloader/" + MD5(url).hexdigest();
|
||||
}
|
||||
_saveFile = File::createfile_file(_filePath.data(),bAppend ? "ab" : "wb");
|
||||
if(!_saveFile){
|
||||
auto strErr = StrPrinter << "打开文件失败:" << filePath << endl;
|
||||
throw std::runtime_error(strErr);
|
||||
}
|
||||
if(bAppend){
|
||||
auto currentLen = ftell(_saveFile);
|
||||
addHeader("Range", StrPrinter << "bytes=" << currentLen << "-" << endl);
|
||||
}
|
||||
setMethod("GET");
|
||||
sendRequest(url);
|
||||
}
|
||||
|
||||
void HttpDownloader::onResponseHeader(const string& status,const HttpHeader& headers) {
|
||||
if(status != "200" && status != "206"){
|
||||
//失败
|
||||
shutdown();
|
||||
closeFile();
|
||||
File::delete_file(_filePath.data());
|
||||
if(_onResult){
|
||||
auto errMsg = StrPrinter << "Http Status:" << status << endl;
|
||||
_onResult(Err_other,errMsg.data(),_filePath.data());
|
||||
_onResult = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpDownloader::onResponseBody(const char* buf, size_t size, size_t recvedSize, size_t totalSize) {
|
||||
if(_saveFile){
|
||||
fwrite(buf,size,1,_saveFile);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpDownloader::onResponseCompleted() {
|
||||
closeFile();
|
||||
if(_onResult){
|
||||
_onResult(Err_success,"success",_filePath.data());
|
||||
_onResult = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpDownloader::onDisconnect(const SockException &ex) {
|
||||
closeFile();
|
||||
if(_onResult){
|
||||
_onResult(ex.getErrCode(),ex.what(),_filePath.data());
|
||||
_onResult = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpDownloader::closeFile() {
|
||||
if(_saveFile){
|
||||
fclose(_saveFile);
|
||||
_saveFile = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace Http */
|
||||
} /* namespace ZL */
|
46
src/Http/HttpDownloader.h
Normal file
46
src/Http/HttpDownloader.h
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* HttpDownloader.h
|
||||
*
|
||||
* Created on: 2017年5月5日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#ifndef SRC_HTTP_HTTPDOWNLOADER_H_
|
||||
#define SRC_HTTP_HTTPDOWNLOADER_H_
|
||||
|
||||
#include "HttpClientImp.h"
|
||||
|
||||
namespace ZL {
|
||||
namespace Http {
|
||||
|
||||
class HttpDownloader: public HttpClientImp {
|
||||
public:
|
||||
typedef std::shared_ptr<HttpDownloader> Ptr;
|
||||
typedef std::function<void(int code,const char *errMsg,const char *filePath)> onDownloadResult;
|
||||
HttpDownloader();
|
||||
virtual ~HttpDownloader();
|
||||
//开始下载文件,默认断点续传方式下载
|
||||
void startDownload(const string &url,const string &filePath = "",bool bAppend = true);
|
||||
void startDownload(const string &url,const onDownloadResult &cb){
|
||||
setOnResult(cb);
|
||||
startDownload(url);
|
||||
}
|
||||
void setOnResult(const onDownloadResult &cb){
|
||||
_onResult = cb;
|
||||
}
|
||||
private:
|
||||
void onResponseHeader(const string &status,const HttpHeader &headers) override;
|
||||
void onResponseBody(const char *buf,size_t size,size_t recvedSize,size_t totalSize) override;
|
||||
void onResponseCompleted() override;
|
||||
void onDisconnect(const SockException &ex) override;
|
||||
void closeFile();
|
||||
|
||||
FILE *_saveFile = nullptr;
|
||||
string _filePath;
|
||||
onDownloadResult _onResult;
|
||||
};
|
||||
|
||||
} /* namespace Http */
|
||||
} /* namespace ZL */
|
||||
|
||||
#endif /* SRC_HTTP_HTTPDOWNLOADER_H_ */
|
50
src/Http/HttpRequester.cpp
Normal file
50
src/Http/HttpRequester.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// HttpRequester.cpp
|
||||
// ZLMediaKit
|
||||
//
|
||||
// Created by xzl on 2017/5/5.
|
||||
//
|
||||
|
||||
#include "HttpRequester.h"
|
||||
|
||||
namespace ZL{
|
||||
namespace Http{
|
||||
|
||||
HttpRequester::HttpRequester(){
|
||||
|
||||
}
|
||||
HttpRequester::~HttpRequester(){
|
||||
|
||||
}
|
||||
|
||||
void HttpRequester::onResponseHeader(const string &status,const HttpHeader &headers) {
|
||||
_strRecvBody.clear();
|
||||
}
|
||||
|
||||
void HttpRequester::onResponseBody(const char *buf,size_t size,size_t recvedSize,size_t totalSize) {
|
||||
_strRecvBody.append(buf,size);
|
||||
}
|
||||
|
||||
void HttpRequester::onResponseCompleted() {
|
||||
if(_onResult){
|
||||
_onResult(SockException(),responseStatus(),responseHeader(),_strRecvBody);
|
||||
_onResult = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequester::onDisconnect(const SockException &ex){
|
||||
if(_onResult){
|
||||
_onResult(ex,responseStatus(),responseHeader(),_strRecvBody);
|
||||
_onResult = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequester::startRequester(const string &url,const HttpRequesterResult &onResult){
|
||||
_onResult = onResult;
|
||||
sendRequest(url);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}//namespace Http
|
||||
}//namespace ZL
|
40
src/Http/HttpRequester.h
Normal file
40
src/Http/HttpRequester.h
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// HttpRequester_h
|
||||
// ZLMediaKit
|
||||
//
|
||||
// Created by xzl on 2017/5/5.
|
||||
//
|
||||
|
||||
#ifndef Htt_HttpRequester_h
|
||||
#define Htt_HttpRequester_h
|
||||
|
||||
#include "HttpClientImp.h"
|
||||
|
||||
namespace ZL{
|
||||
namespace Http{
|
||||
|
||||
class HttpRequester : public HttpClientImp
|
||||
{
|
||||
public:
|
||||
typedef std::shared_ptr<HttpRequester> Ptr;
|
||||
typedef std::function<void(const SockException &ex,const string &status,const HttpHeader &header,const string &strRecvBody)> HttpRequesterResult;
|
||||
HttpRequester();
|
||||
virtual ~HttpRequester();
|
||||
|
||||
void startRequester(const string &url,const HttpRequesterResult &onResult);
|
||||
private:
|
||||
void onResponseHeader(const string &status,const HttpHeader &headers) override;
|
||||
void onResponseBody(const char *buf,size_t size,size_t recvedSize,size_t totalSize) override;
|
||||
void onResponseCompleted() override;
|
||||
void onDisconnect(const SockException &ex) override;
|
||||
|
||||
string _strRecvBody;
|
||||
HttpRequesterResult _onResult;
|
||||
|
||||
|
||||
};
|
||||
|
||||
}//namespace Http
|
||||
}//namespace ZL
|
||||
|
||||
#endif /* Htt_HttpRequester_h */
|
@ -15,7 +15,6 @@
|
||||
|
||||
using namespace std;
|
||||
using namespace ZL::Network;
|
||||
using namespace ZL::Network;
|
||||
|
||||
namespace ZL {
|
||||
namespace Http {
|
||||
@ -23,7 +22,7 @@ namespace Http {
|
||||
|
||||
class HttpSession: public TcpLimitedSession<MAX_TCP_SESSION> {
|
||||
public:
|
||||
typedef map<string,string> KeyValue;
|
||||
typedef StrCaseMap KeyValue;
|
||||
typedef std::function<void(const string &codeOut,
|
||||
const KeyValue &headerOut,
|
||||
const string &contentOut)> HttpResponseInvoker;
|
||||
|
Loading…
Reference in New Issue
Block a user