基本环境
使用版本为libevent-2.1.5,目前为beta版,其中evhttp和旧版区别在于新增了如下接口
// 设置回调函数,在包头读取完成后回调
void evhttp_request_set_header_cb (struct evhttp_request *, int(*cb)(struct evhttp_request *, void *))
// 设置回调函数,在body有数据返回后回调
void evhttp_request_set_chunked_cb (struct evhttp_request *, void(*cb)(struct evhttp_request *, void *))
这样的好处是可以在合适的时机回调我们注册的回调函数,比如下载1G的文件,在之前的版本只有下载完成后才会回调,现在每下载一部分数据就会回调一次,让上层应用更加灵活,尤其在http代理时,可以做到边下载边回复
2.1.5版本的完整接口文档详见http://www.wangafu.net/~nickm/libevent-2.1/doxygen/html/http_8h.html
请求流程
http客户端使用到的接口函数及请求流程如下
1,初始化event_base和evdns_base
struct event_base *event_base_new(void);
struct evdns_base * evdns_base_new(struct event_base *event_base, int initialize_nameservers);
2,创建evhttp_request对象,并设置回调函数,这里的回调函数是和数据接收相关的
struct evhttp_request *evhttp_request_new(void (*cb)(struct evhttp_request *, void *), void *arg);
void evhttp_request_set_header_cb(struct evhttp_request *, int (*cb)(struct evhttp_request *, void *));
void evhttp_request_set_chunked_cb(struct evhttp_request *, void (*cb)(struct evhttp_request *, void *));
void evhttp_request_set_error_cb(struct evhttp_request *, void (*)(enum evhttp_request_error, void *));
3,创建evhttp_connection对象,并设置回调函数,这里的回调函数是和连接状态相关的
struct evhttp_connection *evhttp_connection_base_new(struct event_base *base,
struct evdns_base *dnsbase, const char *address, unsigned short port);
void evhttp_connection_set_closecb(struct evhttp_connection *evcon,
void (*)(struct evhttp_connection *, void *), void *);
4,有选择的向evhttp_request添加包头字段、
int evhttp_add_header(struct evkeyvalq *headers, const char *key, const char *value);
5,发送请求
int evhttp_make_request(struct evhttp_connection *evcon,
struct evhttp_request *req,
enum evhttp_cmd_type type, const char *uri);
6,派发事件
int event_base_dispatch(struct event_base *);
案例一:
#include "event2/http.h"
#include "event2/http_struct.h"
#include "event2/event.h"
#include "event2/buffer.h"
#include "event2/dns.h"
#include "event2/thread.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <sys/queue.h>
#include <event.h>
void RemoteReadCallback(struct evhttp_request* remote_rsp, void* arg)
{
event_base_loopexit((struct event_base*)arg, NULL);
}
int ReadHeaderDoneCallback(struct evhttp_request* remote_rsp, void* arg)
{
fprintf(stderr, "< HTTP/1.1 %d %s\n", evhttp_request_get_response_code(remote_rsp), evhttp_request_get_response_code_line(remote_rsp));
struct evkeyvalq* headers = evhttp_request_get_input_headers(remote_rsp);
struct evkeyval* header;
TAILQ_FOREACH(header, headers, next)
{
fprintf(stderr, "< %s: %s\n", header->key, header->value);
}
fprintf(stderr, "< \n");
return 0;
}
void ReadChunkCallback(struct evhttp_request* remote_rsp, void* arg)
{
char buf[4096];
struct evbuffer* evbuf = evhttp_request_get_input_buffer(remote_rsp);
int n = 0;
while ((n = evbuffer_remove(evbuf, buf, 4096)) > 0)
{
fwrite(buf, n, 1, stdout);
}
}
void RemoteRequestErrorCallback(enum evhttp_request_error error, void* arg)
{
fprintf(stderr, "request failed\n");
event_base_loopexit((struct event_base*)arg, NULL);
}
void RemoteConnectionCloseCallback(struct evhttp_connection* connection, void* arg)
{
fprintf(stderr, "remote connection closed\n");
event_base_loopexit((struct event_base*)arg, NULL);
}
int main(int argc, char** argv)
{
if (argc != 2)
{
printf("usage:%s url", argv[1]);
return 1;
}
char* url = argv[1];
struct evhttp_uri* uri = evhttp_uri_parse(url);
if (!uri)
{
fprintf(stderr, "parse url failed!\n");
return 1;
}
struct event_base* base = event_base_new();
if (!base)
{
fprintf(stderr, "create event base failed!\n");
return 1;
}
struct evdns_base* dnsbase = evdns_base_new(base, 1);
if (!dnsbase)
{
fprintf(stderr, "create dns base failed!\n");
}
assert(dnsbase);
struct evhttp_request* request = evhttp_request_new(RemoteReadCallback, base);
evhttp_request_set_header_cb(request, ReadHeaderDoneCallback);
evhttp_request_set_chunked_cb(request, ReadChunkCallback);
evhttp_request_set_error_cb(request, RemoteRequestErrorCallback);
const char* host = evhttp_uri_get_host(uri);
if (!host)
{
fprintf(stderr, "parse host failed!\n");
return 1;
}
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 == NULL || strlen(path) == 0)
{
request_url = "/";
}
printf("url:%s host:%s port:%d path:%s request_url:%s\n", url, host, port, path, request_url);
struct evhttp_connection* connection = evhttp_connection_base_new(base, dnsbase, host, port);
if (!connection)
{
fprintf(stderr, "create evhttp connection failed!\n");
return 1;
}
evhttp_connection_set_closecb(connection, RemoteConnectionCloseCallback, base);
evhttp_add_header(evhttp_request_get_output_headers(request), "Host", host);
evhttp_make_request(connection, request, EVHTTP_REQ_GET, request_url);
event_base_dispatch(base);
return 0;
}
运行示例,这里只打印了包头字段
$ ./http_client http://www.qq.com >/dev/null
< HTTP/1.1 200 OK
< Server: squid/3.4.3
< Content-Type: text/html; charset=GB2312
< Cache-Control: max-age=60
< Expires: Fri, 05 Aug 2016 08:48:31 GMT
< Date: Fri, 05 Aug 2016 08:47:31 GMT
< Transfer-Encoding: chunked
< Connection: keep-alive
< Connection: Transfer-Encoding
<
案例二:
/* Base on code from:
http://archives.seul.org/libevent/users/Sep-2010/msg00050.html
*/
#include "MITLogModule.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/http.h>
#include <event2/http_struct.h>
#include <event2/keyvalq_struct.h>
// (default)
#define HTTP_CONTENT_TYPE_URL_ENCODED "application/x-www-form-urlencoded"
// (use for files: picture, mp3, tar-file etc.)
#define HTTP_CONTENT_TYPE_FORM_DATA "multipart/form-data"
// (use for plain text)
#define HTTP_CONTENT_TYPE_TEXT_PLAIN "text/plain"
#define REQUEST_POST_FLAG 2
#define REQUEST_GET_FLAG 3
struct http_request_get {
struct evhttp_uri *uri;
struct event_base *base;
struct evhttp_connection *cn;
struct evhttp_request *req;
};
struct http_request_post {
struct evhttp_uri *uri;
struct event_base *base;
struct evhttp_connection *cn;
struct evhttp_request *req;
char *content_type;
char *post_data;
};
/************************** Ahead Declare ******************************/
void http_requset_post_cb(struct evhttp_request *req, void *arg);
void http_requset_get_cb(struct evhttp_request *req, void *arg);
int start_url_request(struct http_request_get *http_req, int req_get_flag);
/************************** Tools Function ******************************/
static inline void print_request_head_info(struct evkeyvalq *header)
{
struct evkeyval *first_node = header->tqh_first;
while (first_node) {
MITLog_DetPrintf(MITLOG_LEVEL_COMMON,"key:%s value:%s", first_node->key, first_node->value);
first_node = first_node->next.tqe_next;
}
}
static inline void print_uri_parts_info(const struct evhttp_uri * http_uri)
{
MITLog_DetPrintf(MITLOG_LEVEL_COMMON,"scheme:%s", evhttp_uri_get_scheme(http_uri));
MITLog_DetPrintf(MITLOG_LEVEL_COMMON,"host:%s", evhttp_uri_get_host(http_uri));
MITLog_DetPrintf(MITLOG_LEVEL_COMMON,"path:%s", evhttp_uri_get_path(http_uri));
MITLog_DetPrintf(MITLOG_LEVEL_COMMON,"port:%d", evhttp_uri_get_port(http_uri));
MITLog_DetPrintf(MITLOG_LEVEL_COMMON,"query:%s", evhttp_uri_get_query(http_uri));
MITLog_DetPrintf(MITLOG_LEVEL_COMMON,"userinfo:%s", evhttp_uri_get_userinfo(http_uri));
MITLog_DetPrintf(MITLOG_LEVEL_COMMON,"fragment:%s", evhttp_uri_get_fragment(http_uri));
}
/************************** Request Function ******************************/
void http_requset_post_cb(struct evhttp_request *req, void *arg)
{
struct http_request_post *http_req_post = (struct http_request_post *)arg;
switch(req->response_code)
{
case HTTP_OK:
{
struct evbuffer* buf = evhttp_request_get_input_buffer(req);
size_t len = evbuffer_get_length(buf);
MITLog_DetPuts(MITLOG_LEVEL_COMMON, "print the head info:");
print_request_head_info(req->output_headers);
MITLog_DetPrintf(MITLOG_LEVEL_COMMON,"len:%zu body size:%zu", len, req->body_size);
char *tmp = malloc(len+1);
memcpy(tmp, evbuffer_pullup(buf, -1), len);
tmp[len] = '\0';
MITLog_DetPuts(MITLOG_LEVEL_COMMON, "print the body:");
MITLog_DetPrintf(MITLOG_LEVEL_COMMON,"HTML BODY:%s", tmp);
free(tmp);
event_base_loopexit(http_req_post->base, 0);
break;
}
case HTTP_MOVEPERM:
MITLog_DetPrintf(MITLOG_LEVEL_ERROR, "%s", "the uri moved permanently");
break;
case HTTP_MOVETEMP:
{
const char *new_location = evhttp_find_header(req->input_headers, "Location");
struct evhttp_uri *new_uri = evhttp_uri_parse(new_location);
evhttp_uri_free(http_req_post->uri);
http_req_post->uri = new_uri;
start_url_request((struct http_request_get *)http_req_post, REQUEST_POST_FLAG);
return;
}
default:
event_base_loopexit(http_req_post->base, 0);
return;
}
}
void http_requset_get_cb(struct evhttp_request *req, void *arg)
{
struct http_request_get *http_req_get = (struct http_request_get *)arg;
switch(req->response_code)
{
case HTTP_OK:
{
struct evbuffer* buf = evhttp_request_get_input_buffer(req);
size_t len = evbuffer_get_length(buf);
MITLog_DetPuts(MITLOG_LEVEL_COMMON, "print the head info:");
print_request_head_info(req->output_headers);
MITLog_DetPrintf(MITLOG_LEVEL_COMMON,"len:%zu body size:%zu", len, req->body_size);
char *tmp = malloc(len+1);
memcpy(tmp, evbuffer_pullup(buf, -1), len);
tmp[len] = '\0';
MITLog_DetPuts(MITLOG_LEVEL_COMMON, "print the body:");
MITLog_DetPrintf(MITLOG_LEVEL_COMMON,"HTML BODY:%s", tmp);
free(tmp);
event_base_loopexit(http_req_get->base, 0);
break;
}
case HTTP_MOVEPERM:
MITLog_DetPrintf(MITLOG_LEVEL_ERROR, "%s", "the uri moved permanently");
break;
case HTTP_MOVETEMP:
{
const char *new_location = evhttp_find_header(req->input_headers, "Location");
struct evhttp_uri *new_uri = evhttp_uri_parse(new_location);
evhttp_uri_free(http_req_get->uri);
http_req_get->uri = new_uri;
start_url_request(http_req_get, REQUEST_GET_FLAG);
return;
}
default:
event_base_loopexit(http_req_get->base, 0);
return;
}
}
int start_url_request(struct http_request_get *http_req, int req_get_flag)
{
if (http_req->cn)
evhttp_connection_free(http_req->cn);
int port = evhttp_uri_get_port(http_req->uri);
http_req->cn = evhttp_connection_base_new(http_req->base,
NULL,
evhttp_uri_get_host(http_req->uri),
(port == -1 ? 80 : port));
/**
* Request will be released by evhttp connection
* See info of evhttp_make_request()
*/
if (req_get_flag == REQUEST_POST_FLAG) {
http_req->req = evhttp_request_new(http_requset_post_cb, http_req);
} else if (req_get_flag == REQUEST_GET_FLAG) {
http_req->req = evhttp_request_new(http_requset_get_cb, http_req);
}
if (req_get_flag == REQUEST_POST_FLAG) {
const char *path = evhttp_uri_get_path(http_req->uri);
evhttp_make_request(http_req->cn, http_req->req, EVHTTP_REQ_POST,
path ? path : "/");
/** Set the post data */
struct http_request_post *http_req_post = (struct http_request_post *)http_req;
evbuffer_add(http_req_post->req->output_buffer, http_req_post->post_data, strlen(http_req_post->post_data));
evhttp_add_header(http_req_post->req->output_headers, "Content-Type", http_req_post->content_type);
} else if (req_get_flag == REQUEST_GET_FLAG) {
const char *query = evhttp_uri_get_query(http_req->uri);
const char *path = evhttp_uri_get_path(http_req->uri);
size_t len = (query ? strlen(query) : 0) + (path ? strlen(path) : 0) + 1;
char *path_query = NULL;
if (len > 1) {
path_query = calloc(len, sizeof(char));
sprintf(path_query, "%s?%s", path, query);
}
evhttp_make_request(http_req->cn, http_req->req, EVHTTP_REQ_GET,
path_query ? path_query: "/");
}
/** Set the header properties */
evhttp_add_header(http_req->req->output_headers, "Host", evhttp_uri_get_host(http_req->uri));
return 0;
}
/************************** New/Free Function ******************************/
/**
* @param get_flag: refer REQUEST_GET_*
*
*/
void *http_request_new(struct event_base* base, const char *url, int req_get_flag, \
const char *content_type, const char* data)
{
int len = 0;
if (req_get_flag == REQUEST_GET_FLAG) {
len = sizeof(struct http_request_get);
} else if(req_get_flag == REQUEST_POST_FLAG) {
len = sizeof(struct http_request_post);
}
struct http_request_get *http_req_get = calloc(1, len);
http_req_get->uri = evhttp_uri_parse(url);
print_uri_parts_info(http_req_get->uri);
http_req_get->base = base;
if (req_get_flag == REQUEST_POST_FLAG) {
struct http_request_post *http_req_post = (struct http_request_post *)http_req_get;
if (content_type == NULL) {
content_type = HTTP_CONTENT_TYPE_URL_ENCODED;
}
http_req_post->content_type = strdup(content_type);
if (data == NULL) {
http_req_post->post_data = NULL;
} else {
http_req_post->post_data = strdup(data);
}
}
return http_req_get;
}
void http_request_free(struct http_request_get *http_req_get, int req_get_flag)
{
evhttp_connection_free(http_req_get->cn);
evhttp_uri_free(http_req_get->uri);
if (req_get_flag == REQUEST_GET_FLAG) {
free(http_req_get);
} else if(req_get_flag == REQUEST_POST_FLAG) {
struct http_request_post *http_req_post = (struct http_request_post*)http_req_get;
if (http_req_post->content_type) {
free(http_req_post->content_type);
}
if (http_req_post->post_data) {
free(http_req_post->post_data);
}
free(http_req_post);
}
http_req_get = NULL;
}
/************************** Start POST/GET Function ******************************/
/**
* @param content_type: refer HTTP_CONTENT_TYPE_*
*/
void *start_http_requset(struct event_base* base, const char *url, int req_get_flag, \
const char *content_type, const char* data)
{
struct http_request_get *http_req_get = http_request_new(base, url, req_get_flag, content_type, data);
start_url_request(http_req_get, req_get_flag);
return http_req_get;
}
int main(int argc, char *argv[])
{
MITLogOpen("LibeventHttpClient");
struct event_base* base = event_base_new();
struct http_request_post *http_req_post = start_http_requset(base,
"http://172.16.239.93:8899/base/truck/delete",
REQUEST_POST_FLAG,
HTTP_CONTENT_TYPE_URL_ENCODED,
"name=winlin&code=1234");
struct http_request_get *http_req_get = start_http_requset(base,
"http://127.0.0.1?name=winlin",
REQUEST_GET_FLAG,
NULL, NULL);
event_base_dispatch(base);
http_request_free((struct http_request_get *)http_req_post, REQUEST_POST_FLAG);
http_request_free(http_req_get, REQUEST_GET_FLAG);
event_base_free(base);
MITLogClose();
return 0;
}