现在越来越多的应用里需要使用http的请求与服务,对于C++而言,也已经有了很多第三方库对此进行支持。在诸多的第三方库里,异步实现也成了主流,主要原因是同步阻塞的模式在现在的大多数应用里是不适用的。在近来的自己参与的多个项目中,由于接入Http的时间不同,因而在使用过程中也选择了不同的第三方库。之前一次总结了Mongoose实现HttpServer,这次又是使用libevent。之所以选择封装,是为了避免在后续的工作中,避免重复使用生涩的第三方库,因为第三方库为了更加的完善,接口设计较多。对于每个要使用的人都要先了解其接口的话,使用成本就有点大了。因为之前也只是使用libevent实现http service, 但之前已经抛上来过http service的代码,感觉再次抛http service的代码感觉有些重复,但进来增加了http client的代码,所以就直接抛上来供大家吐槽吐槽。废话不多说,先将自己封装的代码奉上:
// File: BasicHttp.hpp
// Description: ---
// Notes: ---
// Author: Haust <wyy123_2008@qq.com>
// Revision: 2015-09-19 by Haust
#ifndef __BASIC_HTTP__
#define __BASIC_HTTP__
#include <event2/event.h>
#include <event2/http.h>
#include <string>
#include <map>
class HttpServer
{
public:
HttpServer(void);
virtual ~HttpServer(void);
virtual bool Initialize(std::string host, unsigned short port);
virtual bool Start();
virtual void Shutdown();
virtual void Update();
typedef void (*http_handle_callback)(const char *uri, const unsigned char *data, int len);
static bool RigisterCallback(const char *uri, http_handle_callback callback);
static void HttpReply(const char *uri, const char *data, int len);
typedef std::map<std::string, http_handle_callback> HTTP_CALLBACK_MAP;
typedef HTTP_CALLBACK_MAP::iterator HTTP_CALLBACK_MAP_IT;
typedef std::map<std::string, struct evhttp_request*> HTTP_REQUEST_MAP;
typedef HTTP_REQUEST_MAP::iterator HTTP_REQUEST_MAP_IT;
protected:
static void HttpHandle(struct evhttp_request* request, void* arg);
protected:
struct event_base* m_event_base;
struct evhttp* m_http;
std::string m_host;
unsigned short m_port;
static HTTP_CALLBACK_MAP m_callback_map;
static HTTP_REQUEST_MAP m_request_map;
};
class HttpClient
{
public:
enum HttpRequestType
{
HTTP_REQUEST_GET = EVHTTP_REQ_GET,
HTTP_REQUEST_POST = EVHTTP_REQ_POST,
};
typedef void (*http_request_cb)(int response_code, const unsigned char *data, size_t len, void* arg);
struct HttpRequest
{
std::string url;
struct evhttp_uri* uri;
struct evhttp_connection *cn;
struct evhttp_request *req;
HttpRequestType type;
http_request_cb call_back;
HttpClient* client;
void* arg;
};
public:
HttpClient();
virtual ~HttpClient();
virtual bool Initialize();
virtual void Update();
bool HttpRequestUrl(const std::string& url, http_request_cb call_back, void* arg = NULL, const std::string& post_data = "", const std::string& content_type = "");
private:
static void DefaultCallBack(int response_code, const unsigned char *data, size_t len, void* arg);
static void HttpRequestCB(struct evhttp_request* req, void *arg);
bool HttpRequestUrl(HttpRequest* request);
private:
struct event_base* m_event_base;
};
#endif // !__BASIC_HTTP__
实现文件如下:
// File: BasicHttp.cpp
// Description: ---
// Notes: ---
// Author: Haust <wyy123_2008@qq.com>
// Revision: 2015-09-19 by Haust
#include "BasicHttp.hpp"
#include <event2/thread.h>
#include <event2/keyvalq_struct.h>
#include <event2/http_struct.h>
#include <event2/buffer.h>
#include <string>
#include <map>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
HttpServer::HTTP_CALLBACK_MAP HttpServer::m_callback_map;
HttpServer::HTTP_REQUEST_MAP HttpServer::m_request_map;
HttpServer::HttpServer(void)
:m_event_base(NULL)
,m_http(NULL)
{
}
HttpServer::~HttpServer(void)
{
}
bool HttpServer::Initialize(string host, unsigned port)
{
m_host = host;
m_port = port;
return true;
}
bool HttpServer::Start()
{
evthread_use_pthreads();
bool ret = false;
int configflag = EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST;
struct event_config* eventconfig = NULL;
do
{
eventconfig = event_config_new();
if (!eventconfig)
{
printf("event_config_new() failed!!!\n");
break;
}
event_config_set_flag(eventconfig, configflag);
event_config_set_num_cpus_hint(eventconfig, 4);
m_event_base = event_base_new_with_config(eventconfig);
if (!m_event_base)
{
printf("event_base_new_with_config() failed!!!\n");
break;
}
event_config_free(eventconfig);
m_http = evhttp_new(m_event_base);
if (!m_http)
{
printf("evhttp_new() failed!!!\n");
break;
}
evhttp_set_gencb(m_http, HttpHandle, NULL);
if (!evhttp_bind_socket_with_handle(m_http, m_host.c_str(), m_port))
{
printf("could not bind to %s:%d\n", m_host.c_str(), m_port);
break;
}
ret = true;
} while (!ret);
if (!ret)
{
Shutdown();
}
return ret;
}
void HttpServer::Shutdown()
{
if (m_event_base)
{
event_base_free(m_event_base);
m_event_base = NULL;
}
if (m_http)
{
evhttp_free(m_http);
m_http = NULL;
}
}
void HttpServer::Update()
{
if (m_event_base)
{
event_base_loopexit(m_event_base, NULL);
event_base_dispatch(m_event_base);
}
}
bool HttpServer::RigisterCallback(const char *uri, http_handle_callback callback)
{
m_callback_map[uri] = callback;
return true;
}
void HttpServer::HttpReply(const char *uri, const char* data, int len)
{
HTTP_REQUEST_MAP_IT it = m_request_map.find(uri);
if (m_request_map.end() != it)
{
struct evhttp_request* req = it->second;
if (NULL != data)
{
struct evbuffer *databuf=evbuffer_new();
evbuffer_add(databuf, data, len);
evhttp_send_reply(req, HTTP_OK, "OK", databuf);
evbuffer_free(databuf);
}
else
{
evhttp_send_reply(req, HTTP_OK, "OK", NULL);
}
if (1 == evhttp_request_is_owned(req))
{
evhttp_request_free(req);
}
m_request_map.erase(it);
}
printf("Http reply, uri:%s, %s\n", uri, data);
}
void HttpServer::HttpHandle(struct evhttp_request* request, void* arg)
{
struct evbuffer* evinput = evhttp_request_get_input_buffer(request);
int length = (int)evbuffer_get_length(evinput);
unsigned char* data = new unsigned char[length];
memset(data, 0, length);
evbuffer_remove(evinput, data, length);
const evhttp_uri* http_uri = evhttp_request_get_evhttp_uri(request);
std::string path = evhttp_uri_get_path(http_uri);
HTTP_CALLBACK_MAP_IT it = m_callback_map.find(path);
if (m_callback_map.end() != it)
{
const char *uri = evhttp_request_get_uri(request);
it->second(uri, data, length);
}
m_request_map[path] = request;
delete []data;
}
///////////////////////////////////////////////////////////////////////////
static inline void print_uri_parts_info(const struct evhttp_uri* uri)
{
printf( "scheme:%s\n", evhttp_uri_get_scheme(uri));
printf( "host:%s\n", evhttp_uri_get_host(uri));
printf( "path:%s\n", evhttp_uri_get_path(uri));
printf( "port:%d\n", evhttp_uri_get_port(uri));
printf( "query:%s\n", evhttp_uri_get_query(uri));
printf( "userinfo:%s\n", evhttp_uri_get_userinfo(uri));
printf( "fragment:%s\n", evhttp_uri_get_fragment(uri));
}
HttpClient::HttpClient()
{
m_event_base = NULL;
}
HttpClient::~HttpClient()
{
if (NULL != m_event_base)
{
event_base_free(m_event_base);
}
}
bool HttpClient::Initialize()
{
m_event_base = event_base_new();
if (NULL == m_event_base)
{
return false;
}
return true;
}
void HttpClient::Update()
{
if(NULL != m_event_base)
{
event_base_loopexit(m_event_base, NULL);
event_base_dispatch(m_event_base);
}
}
bool HttpClient::HttpRequestUrl(const std::string& url, http_request_cb call_back, void* arg, const std::string& post_data, const std::string& content_type)
{
if (NULL == m_event_base)
{
printf("Not initialize");
return false;
}
printf("HttpRquestUrl: %s\n", url.c_str());
HttpRequest* request = new HttpRequest();
//request->uri = evhttp_uri_parse(url.c_str());
//内部使用去掉一切URL合法检查,只做URL解析
request->uri = evhttp_uri_parse_with_flags(url.c_str(), EVHTTP_URI_NONCONFORMANT);
if(NULL == request->uri)
{
printf("parse url failed!");
delete request;
return false;
}
request->call_back = NULL == call_back?DefaultCallBack:call_back;
request->client = this;
request->arg = arg;
request->req = evhttp_request_new(HttpRequestCB, request);
if(NULL == request->req)
{
printf("evhttp_request_new failed!");
delete request;
return false;
}
int port = evhttp_uri_get_port(request->uri);
request->cn = evhttp_connection_base_new(m_event_base, NULL, evhttp_uri_get_host(request->uri), -1 == port ? 80 : port);
if (NULL == request->cn)
{
printf("evhttp_connection_base_new failed!");
delete request;
return false;
}
const char *path = evhttp_uri_get_path(request->uri);
bool post = !post_data.empty();
if(post)
{
request->type = HTTP_REQUEST_POST;
evbuffer_add(request->req->output_buffer, post_data.c_str(), post_data.length());
evhttp_add_header(request->req->output_headers, "Content-Type", content_type.empty()?"text/plain":content_type.c_str());
evhttp_make_request(request->cn, request->req, EVHTTP_REQ_POST, path?path:"/");
}
else
{
request->type = HTTP_REQUEST_GET;
const char *query = evhttp_uri_get_query(request->uri);
std::string query_path = std::string(NULL != path?path:"");
if (!query_path.empty())
{
if (NULL != query)
{
query_path += std::string("?") + query;
}
}
else
{
query_path = "/";
}
evhttp_make_request(request->cn, request->req, EVHTTP_REQ_GET, query_path.c_str());
}
evhttp_add_header(request->req->output_headers, "Host", evhttp_uri_get_host(request->uri));
return true;
}
bool HttpClient::HttpRequestUrl(HttpRequest* request)
{
if (NULL == m_event_base)
{
printf("Not initialize");
return false;
}
if(NULL != request->cn)
evhttp_connection_free(request->cn);
int port = evhttp_uri_get_port(request->uri);
request->cn = evhttp_connection_base_new(m_event_base,
NULL,
evhttp_uri_get_host(request->uri),
-1 == port ? 80 : port);
const char *path = evhttp_uri_get_path(request->uri);
if(request->type == HTTP_REQUEST_POST)
{
evhttp_make_request(request->cn, request->req, EVHTTP_REQ_POST, path?path:"/");
}
else
{
const char *query = evhttp_uri_get_query(request->uri);
std::string query_path = std::string(NULL != path?path:"");
if (!query_path.empty())
{
if (NULL != query)
{
query_path += std::string("?") + query;
}
}
else
{
query_path = "/";
}
evhttp_make_request(request->cn, request->req, EVHTTP_REQ_GET, query_path.c_str());
}
evhttp_remove_header(request->req->output_headers, "Host");
evhttp_add_header(request->req->output_headers, "Host", evhttp_uri_get_host(request->uri));
return true;
}
void HttpClient::DefaultCallBack(int response_code, const unsigned char *data, size_t len, void *arg)
{
printf("ret:%d %s\n", response_code, (char*)data);
}
void HttpClient::HttpRequestCB(struct evhttp_request* req, void *arg)
{
HttpRequest* request = (HttpRequest*)arg;
switch (req->response_code)
{
case HTTP_OK:
{
struct evbuffer* evinput = evhttp_request_get_input_buffer(req);
size_t length = evbuffer_get_length(evinput);
unsigned char* data = new unsigned char[length];
memset(data, 0, length);
evbuffer_remove(evinput, data, length);
request->call_back(req->response_code, data, length, request->arg);
delete []data;
evhttp_connection_free(request->cn);
evhttp_uri_free(request->uri);
//evhttp_request_free(request->req);
delete request;
}
break;
case HTTP_MOVETEMP:
case HTTP_MOVEPERM:
{
const char *new_location = evhttp_find_header(req->input_headers, "Location");
struct evhttp_uri *new_uri = evhttp_uri_parse(new_location);
evhttp_uri_free(request->uri);
request->uri = new_uri;
request->client->HttpRequestUrl(request);
return;
}
break;
default:
{
request->call_back(req->response_code, (unsigned char*)req->response_code_line, strlen(req->response_code_line), request->arg);
event_base_loopexit(request->client->m_event_base, NULL);
}
break;
}
}
实际,单纯看上面的代码,就会发现虽然lievent对于http的实现非常丰富,并且接口使用也非常简单,但实际如果在使用过程中,没有经过测试代码的测试,很难保证代码能够顺利执行。这也就是第三方库即使再简单易用,我们有时候也会发现自己使用存在很多问题的缘故。因此封装是很简单的一种使用方式了。这样就避免了以后每次加对应的接口的时候去接触生涩的接口了