libevent库使得高并发响应HTTP Server的编写变得很容易。
因此实际中,使用libevent可以为任何应用(如数据库)提供一个HTTP based的网络接口,方便多个clients采用任何支持HTTP protocol的语言与server进行交互
最近将之前用gsoap写的webserver改为使用libevent库。gsoap是为了实现soap协议,如果客户端只调用restful风格的接口的话,用gsoap实现http server有很多多余的东西了,而且处理起来也不太方便。
虽然之前使用过libevent,不过evhttp还是第一次用,还是一如既往的好用。再此简单记录下,方便以后取用。
代码(https://github.com/xuleicode/lb,单线程,多线程。供自己参考)
整个过程包括如下几步:
- 初始化
- 创建HTTP Server
- 指定回调函数(根据情况可指定多个),
- 进入事件循环
- 在回调函数中,可以获取客户端请求(request的HTTP
Header和参数等),进行相应处理后,再将结果发送给客户端(response的HTTP Header和内容)。
#include <evhttp.h>
void httpd_handler(struct evhttp_request *req, void *arg);
void specific_handler(struct evhttp_request *req, void *arg);
int starthttp(){
/* 使用libevent创建HTTP Server */
//初始化event API
event_init();
//创建一个http server
struct evhttp *httpd;
//默认参数
char *httpd_option_listen = "0.0.0.0";
int httpd_option_port = 8080;
int httpd_option_daemon = 0;
int httpd_option_timeout = 120; //in seconds
httpd = evhttp_start(httpd_option_listen, httpd_option_port);
evhttp_set_timeout(httpd, httpd_option_timeout);
//
//evhttp_set_allowed_methods( httpd , EVHTTP_REQ_GET);
//指定generic callback
evhttp_set_gencb(httpd, httpd_handler, NULL);
//Set a callback for a specified URI
evhttp_set_cb(httpd, "/spec", specific_handler, NULL);
//循环处理events
event_dispatch();
evhttp_free(httpd);
return 0;
}
//linux编译:需要levent库
g++ -o myhttpd -Wall -levent myhttpd.cpp
//windwos 下需要添加依赖库
ws2_32.lib
wsock32.lib
libevent.lib
libevent_core.lib
libevent_extras.lib
//并且windows下使用libevnet网络时需要
//初始化winsocket
#ifdef WIN32
WSADATA wsaData;
if(WSAStartup(MAKEWORD(2,2) , &wsaData) != 0)
{
return -1;
}
#endif
//和释放
#ifdef WIN32
WSACleanup();
#endif
主要结构体
evhttp_request *req 中包含了http请求的所有内容
struct evkeyvalq *input_headers; //保存客户端请求的HTTP headers(key-value pairs)
struct evkeyvalq *output_headers; //保存将要发送到客户端的HTTP headers(key-value pairs)
//客户端的ip和port
char *remote_host;
u_short remote_port;
enum evhttp_request_kind kind; //可以是EVHTTP_REQUEST或EVHTTP_RESPONSE//enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE };
enum evhttp_cmd_type type; //可以是EVHTTP_REQ_GET, EVHTTP_REQ_POST或EVHTTP_REQ_HEAD等,详见最后
char *uri; //客户端请求的uri
char major; //HTTP major number
char minor; //HTTP major number
int response_code; //HTTP response code
char *response_code_line; //readable response
struct evbuffer *input_buffer; //客户端POST的数据
struct evbuffer *output_buffer; //输出到客户端的数据
...
//还有一些回调函数指针
保存参数的结构体
/*
* Key-Value pairs. Can be used for HTTP headers but also for
* query argument parsing.
*/
struct evkeyval {
TAILQ_ENTRY(evkeyval) next;
char *key;
char *value;
};
宏TAILQ_ENTRY(evkeyval)被定义为:
#define TAILQ_ENTRY(type)
struct {
struct type *tqe_next; //next element
struct type **tqe_prev; //address of previous next element
}
buffer结构体
//buffer.h
struct evbuffer
#ifdef EVENT_IN_DOXYGEN_
{}
#endif
;
//evbuffer-internal.h文件
struct evbuffer_chain;
struct evbuffer {
struct evbuffer_chain *first;
struct evbuffer_chain *last;
//二级指针。使用*last_with_datap时,指向的是链表中最后一个有数据的evbuffer_chain。
//所以last_with_datap存储的是倒数第二个evbuffer_chain的next成员地址。
//一开始buffer->last_with_datap = &buffer->first;此时first为NULL。所以当链表没有节点时
//*last_with_datap为NULL。当只有一个节点时*last_with_datap就是first。
struct evbuffer_chain **last_with_datap;
size_t total_len;//链表中所有chain的总字节数
...
};
struct evbuffer_chain {
struct evbuffer_chain *next;
size_t buffer_len;//buffer的大小
//错开不使用的空间。该成员的值一般等于0
ev_off_t misalign;
//evbuffer_chain已存数据的字节数
//所以要从buffer + misalign + off的位置开始写入数据
size_t off;
...
unsigned char *buffer;
};
另外定义宏方便获取evbuffer中保存的内容和大小:
#define EVBUFFER_LENGTH(x) (x)->off
#define EVBUFFER_DATA(x) (x)->buffer
例如,获取客户端POST数据的内容和大小:
EVBUFFER_DATA(res->input_buffer);
EVBUFFER_LENGTH(res->input_buffer);//这个和res->body_size相同
另外struct evbuffer用如下函数创建添加和释放:
struct evbuffer *buf;
buf = evbuffer_new();
//往buffer中添加内容
evbuffer_add_printf(buf, "I got it! you just requested: %s\n", req->uri); //Append a formatted string to the end of an evbuffer.
//将内容输出到客户端
evhttp_send_reply(req, HTTP_OK, "OK", buf);
//释放掉buf
evbuffer_free(buf);
关键函数
获取客户端请求的URI
//使用
req->uri
//或使用函数
const char *evhttp_request_uri(struct evhttp_request *req);//即(evhttp_request_uri(req);)。
对获取的URI进行解析和其他操作
使用函数
void evhttp_parse_query(const char *uri, struct evkeyvalq *args);
可对uri的参数进行解析,结果保存在struct evkeyvalq的key-value pairs中,例如:
char *uri = "http://foo.com/?id=1&infor=hello";
struct evkeyvalq args;
evhttp_parse_query(uri, &args);
//然后通过evhttp_find_header等函数获取各个参数及对应的值
evhttp_find_header(&args, "id"); //得到test
evhttp_find_header(&args, "infor"); //得到some thing
如下两个函数对URI进行encode和decode:
char *evhttp_encode_uri(const char *uri);
char *evhttp_decode_uri(const char *uri);
URI encode的结果是所有非alphanumeric及-_的字符都被类似于%和一个2位16进制字符替换(其中空格被+号替换)。如上两个函数返回的字符串需要free掉。
Escape特殊的HTML字符
char *evhttp_htmlescape(const char *html);
特殊字符:&被替换为&;”被替换为";’被替换为'; <被替换为<;>被替换为>。该函数返回的字符串需要free掉。
处理HTTP headers相关的函数
HTTP headers保存在req的input_headers中,这个是struct evkeyvalq 的结构体(key-value pairs),使用如下函数可对其进行修改:
const char *evhttp_find_header(const struct evkeyvalq *, const char *);
int evhttp_remove_header(struct evkeyvalq *, const char *);
int evhttp_add_header(struct evkeyvalq *, const char *, const char *);
void evhttp_clear_headers(struct evkeyvalq *);
设定只识别get请求,
evhttp_set_allowed_methods( httpd , EVHTTP_REQ_GET);
设置后,只处理get请求,其他请求返回405 Method not allowed,我自己使用的时候发现返回的是501 Not Implemented
请求类型如下:
enum evhttp_cmd_type {
EVHTTP_REQ_GET = 1 << 0,
EVHTTP_REQ_POST = 1 << 1,
EVHTTP_REQ_HEAD = 1 << 2,
EVHTTP_REQ_PUT = 1 << 3,
EVHTTP_REQ_DELETE = 1 << 4,
EVHTTP_REQ_OPTIONS = 1 << 5,
EVHTTP_REQ_TRACE = 1 << 6,
EVHTTP_REQ_CONNECT = 1 << 7,
EVHTTP_REQ_PATCH = 1 << 8
};