这篇文章主要是讲解一下最核心的http请求和http ack部分的逻辑。在这里我封装了一个核心功能类HttpClientHelper,对外提供了几个方法:
1.bool is_start() 用来判断改服务是否启动
2.bool start() 启动服务
3.void stop() 停止服务
4.get()、post()、download()、upload() http操作方法
HttpClientHelper头文件如下:
#ifndef HTTPCLIENTHELPER_H
#define HTTPCLIENTHELPER_H
#include <thread>
#include <mutex>
#include <fstream>
#include "HttpClientCommon.h"
struct event_base;
struct evdns_base;
struct evhttp_request;
class http_client_timer;
class HttpClientHelper
{
public:
//http请求信息
struct Struct_HttpInfo
{
Enum_HttpType http_type;
std::string http_url;
std::unordered_map<std::string, std::string> header;
std::string data;
int timeout_second;
std::shared_ptr<Struct_HttpResult> http_result = std::make_shared<Struct_HttpResult>();
HttpCallBack http_callback;
std::shared_ptr<std::ofstream> file_stream;
};
HttpClientHelper();
~HttpClientHelper();
bool start();
bool is_start();
void stop();
void ack_head(evhttp_request*);
void ack_body(evhttp_request*);
void ack(evhttp_request*);
void error(evhttp_request*);
void get(std::string http_url, HttpCallBack http_callback,
size_t timeout_second = 10,
std::unordered_map<std::string, std::string> headers = { {"Content-Type","application/x-www-form-urlencoded;charset=UTF-8"} });
void post(std::string http_url, std::unordered_map<std::string, std::string> datas, HttpCallBack http_callback,
size_t timeout_second = 10,
std::unordered_map<std::string, std::string> headers = { {"Content-Type","application/json;charset=UTF-8"} });
void download(std::string http_url, std::string download_path, HttpCallBack http_callback,
size_t timeout_second = 10,
std::unordered_map<std::string, std::string> headers = { {"Content-Type","application/x-www-form-urlencoded;charset=UTF-8"} });
void upload(std::string http_url, std::unordered_map<std::string, Struct_MultiHttpPart> http_parts, HttpCallBack http_callback,
size_t timeout_second = 10);
private:
void add_http_info(evhttp_request*, std::shared_ptr<Struct_HttpInfo>);
std::shared_ptr<Struct_HttpInfo> find_http_info(evhttp_request*);
void remove_http_info(evhttp_request *);
void start_timer(evhttp_request* http_request, size_t second);
void remove_timer(evhttp_request* http_request);
void run_in_thread();
void make_request(std::shared_ptr<Struct_HttpInfo> http_info);
std::unordered_map<evhttp_request*, std::shared_ptr<Struct_HttpInfo>> m_http_requests;
event_base *m_httpBase = nullptr;
evdns_base *m_httpDns = nullptr;
std::mutex m_http_lock;
bool m_http_start = false;
std::shared_ptr<std::thread> m_http_thread;
std::shared_ptr<http_client_timer> m_http_timer;
};
#endif // !HTTPCLIENTHELPER_H
启动服务逻辑比较简单,就是启动一个线程,在里面做http事件处理
if (m_httpBase)
{
return true;
}
if (!m_http_thread)
{
m_http_thread = std::make_shared<std::thread>(&HttpClientHelper::run_in_thread, this);
}
if (!m_http_timer)
{
m_http_timer = std::make_shared<http_client_timer>();
}
线程的代码主要逻辑是创建创建httpbase和httpdns,然后就一直做事件派发
void HttpClientHelper::run_in_thread()
{
m_http_start = false;
m_httpBase = event_base_new();
if (!m_httpBase)
{
fprintf(stderr, "create event base failed!\n");
return ;
}
m_httpDns = evdns_base_new(m_httpBase, 1);
if (!m_httpDns)
{
fprintf(stderr, "create dns base failed!\n");
return ;
}
m_http_start = true;
event_base_dispatch(m_httpBase);
}
停止服务就是,退出http事件派发和释放对象
if (m_httpDns)
{
//evdns_base_free(m_httpDns, 0);
m_httpDns = nullptr;
}
if (m_httpBase)
{
event_base_loopexit(m_httpBase, nullptr);
event_base_free(m_httpBase);
m_httpBase = nullptr;
}
if (m_http_thread && m_http_thread->joinable())
{
m_http_thread->join();
}
if (m_http_timer)
{
m_http_timer->stop();
}
m_http_start = false;
get、post、download、upload的逻辑都相似:创建一个请求对象,然后放到请求队列中,等待线程处理
get逻辑如下:
void HttpClientHelper::get(std::string http_url, HttpCallBack http_callback, size_t timeout_second, std::unordered_map<std::string, std::string> headers)
{
if (!m_httpBase)
{
return;
}
std::shared_ptr<Struct_HttpInfo> http_info = std::make_shared<Struct_HttpInfo>();
http_info->http_url = http_url;
http_info->http_type = HTTPTYPE_GET;
http_info->timeout_second = timeout_second;
http_info->http_callback = http_callback;
http_info->header = headers;
ThreadPool::getInstance().putTask([this, http_info] {
make_request(http_info);
});
}
post比get稍微复杂一点,这里需要把请求数据map,拼装成json格式的字符串,实际上可以使用json库来处理,我这里就简单的拼装了一下:
void HttpClientHelper::post(std::string http_url, std::unordered_map<std::string, std::string> datas, HttpCallBack http_callback, size_t timeout_second, std::unordered_map<std::string, std::string> headers)
{
if (!m_httpBase)
{
return;
}
std::shared_ptr<Struct_HttpInfo> http_info = std::make_shared<Struct_HttpInfo>();
http_info->http_url = http_url;
http_info->http_type = HTTPTYPE_POST;
http_info->timeout_second = timeout_second;
http_info->http_callback = http_callback;
http_info->header = headers;
int count = 0;
std::string http_data;
for (auto it = datas.begin(); it != datas.end(); ++it)
{
if (count++)
{
http_data.append(",");
}
http_data.append(R"(")").append(it->first).append(R"(":")").append(it->second).append(R"(")");
}
if (http_data.length())
{
http_info->data.append("{").append(http_data).append("}");
}
ThreadPool::getInstance().putTask([this, http_info] {
make_request(http_info);
});
}
同样,download也和get一样简单:
void HttpClientHelper::download(std::string http_url, std::string download_path, HttpCallBack http_callback, size_t timeout_second, std::unordered_map<std::string, std::string> headers)
{
if (!m_httpBase)
{
return;
}
std::shared_ptr<Struct_HttpInfo> http_info = std::make_shared<Struct_HttpInfo>();
http_info->http_url = http_url;
http_info->http_type = HTTPTYPE_DOWNLOAD;
http_info->timeout_second = timeout_second;
http_info->http_callback = http_callback;
http_info->http_result->http_result = download_path;
http_info->header = headers;
ThreadPool::getInstance().putTask([this, http_info] {
make_request(http_info);
});
}
upload还没来得及弄,后面再补吧
最复杂的就是请求事件的处理了,需要根据请求事件的数据,创建一个http请求对象,然后调用libevent的evhttp_make_request方法进行请求
void HttpClientHelper::make_request(std::shared_ptr<Struct_HttpInfo> http_info)
{
if (!m_httpBase)
{
return;
}
struct evhttp_request* request = evhttp_request_new(request_finished, this);
evhttp_request_set_header_cb(request, http_head_finish);
evhttp_request_set_chunked_cb(request, http_body_finished);
evhttp_request_set_error_cb(request, http_request_error);
add_http_info(request, http_info);
const char *url = http_info->http_url.c_str();
evhttp_uri* uri = evhttp_uri_parse(url);
const char* host = evhttp_uri_get_host(uri);
int port = evhttp_uri_get_port(uri);
if (port < 0) port = 80;
const char* request_url = url;
const char* path = evhttp_uri_get_path(uri);
if (!path || strlen(path) == 0)
{
request_url = "/";
}
evhttp_connection* connection = evhttp_connection_base_new(m_httpBase, m_httpDns, host, port);
if (!connection)
{
fprintf(stderr, "create evhttp connection failed!\n");
return;
}
auto http_headers = evhttp_request_get_output_headers(request);
evhttp_add_header(http_headers, "Host", host);
for (auto it = http_info->header.begin(); it != http_info->header.end(); ++it)
{
evhttp_add_header(http_headers, it->first.c_str(), it->second.c_str());
}
//timeout
start_timer(request, http_info->timeout_second);
//evhttp_connection_set_timeout(connection, http_info->timeout_second);
if (http_info->http_type == HTTPTYPE_POST)
{
auto buffer = evhttp_request_get_output_buffer(request);
evbuffer_add(buffer, http_info->data.c_str(), strlen(http_info->data.c_str()));
evhttp_make_request(connection, request, EVHTTP_REQ_POST, request_url);
}
else if(http_info->http_type == HTTPTYPE_GET || http_info->http_type == HTTPTYPE_DOWNLOAD)
{
evhttp_make_request(connection, request, EVHTTP_REQ_GET, request_url);
}
}
这里写了4个http事件回调函数分别如下:
int http_head_finish(struct evhttp_request* remote_rsp, void* arg)
{
HttpClientHelper *http_client = (HttpClientHelper*)arg;
if (http_client)
{
http_client->ack_head(remote_rsp);
}
return 0;
}
void http_body_finished(struct evhttp_request* remote_rsp, void* arg)
{
HttpClientHelper *http_client = (HttpClientHelper*)arg;
if (http_client)
{
http_client->ack_body(remote_rsp);
}
}
void request_finished(struct evhttp_request* remote_rsp, void* arg)
{
HttpClientHelper *http_client = (HttpClientHelper*)arg;
if (http_client)
{
http_client->ack(remote_rsp);
}
}
void http_request_error(evhttp_request_error http_error, void *arg)
{
std::cout << http_error << " " << arg << std::endl;
}
问题:
实际测试中,发现中文报文显示时是乱码,这里需要和服务端定一个编码格式。比如说是utf8,然后我们收到报文时,把它转成宽字符即可。
std::string utf8_to_string(char *data, int len)
{
int nwLen = MultiByteToWideChar(CP_UTF8, 0, data, -1, NULL, 0);
wchar_t * pwBuf = new wchar_t[nwLen + 1];
memset(pwBuf, 0, nwLen * 2 + 2);
MultiByteToWideChar(CP_UTF8, 0, data, len, pwBuf, nwLen);
int nLen = WideCharToMultiByte(CP_ACP, 0, pwBuf, -1, NULL, NULL, NULL, NULL);
char * pBuf = new char[nLen + 1];
memset(pBuf, 0, nLen + 1);
WideCharToMultiByte(CP_ACP, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);
std::string retStr = pBuf;
delete[]pBuf;
delete[]pwBuf;
pBuf = NULL;
pwBuf = NULL;
return retStr;
}
由于时间比较赶,先简单记录一下吧,后面抽时间再整理。