libevent evhttp实现的http客户端发送POST请求

本文介绍libevent-2.1.5版本中evhttp模块的使用方法,包括设置回调函数、请求流程和两个实战案例。通过示例代码,展示了如何初始化环境、创建请求和连接对象、添加请求头、发送请求并处理响应。

基本环境

使用版本为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;
}

 

 

### 使用 libevent 实现 HTTP 功能 libevent 是一个高性能事件驱动网络库,支持多种协议和功能扩展。通过其内置的 `evhttp` 模块,可以轻松实现 HTTP 服务器或客户端。 #### HTTP 服务器实现 以下是基于 libeventHTTP 服务器基本实现方法: 1. **初始化 event_base** 需要先创建一个 `struct event_base*` 对象作为事件循环的核心[^1]。 2. **设置 evhttp** 调用 `evhttp_new()` 创建一个新的 HTTP 服务对象,并将其绑定到指定地址和端口上。 3. **注册回调函数** 使用 `evhttp_set_cb()` 或 `evhttp_set_gencb()` 注册处理请求的回调函数。这些回调会在接收到特定路径的请求时被调用。 4. **启动事件循环** 调用 `event_base_dispatch()` 启动事件循环以监听并响应来自客户端HTTP 请求。 下面是简单的 HTTP 服务器代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <event2/event.h> #include <event2/http.h> void request_handler(struct evhttp_request *req, void *arg) { const char *response_body = "Hello from the server!"; struct evbuffer *buf = evbuffer_new(); evbuffer_add_printf(buf, "%s", response_body); evhttp_send_reply(req, HTTP_OK, "OK", buf); // 发送回复给客户端 evbuffer_free(buf); } int main(int argc, char **argv) { struct event_base *base; struct evhttp *http; base = event_base_new(); // 初始化 event_base http = evhttp_new(base); if (!http || !base) { fprintf(stderr, "Error initializing libevent\n"); return 1; } int port = 8080; // 设置监听端口 if (evhttp_bind_socket(http, "0.0.0.0", port)) { // 绑定 IP 和端口 fprintf(stderr, "Could not bind to port %d\n", port); return 1; } evhttp_set_gencb(http, request_handler, NULL); // 设置通用回调函数 printf("Starting server on port %d...\n", port); event_base_dispatch(base); // 开始事件循环 evhttp_free(http); event_base_free(base); return 0; } ``` --- #### HTTP 客户端实现 对于 HTTP 客户端的功能,则可以通过 `evhttp_connection` 来完成请求发送与接收操作。 1. **建立连接** 利用 `evhttp_connection_base_new()` 函数来构建新的 HTTP 连接实例[^2]。 2. **发起请求** 可以使用 `evhttp_make_request()` 方法向目标 URL 提交 GET/POST 等类型的请求。 3. **解析返回的数据包** 当远程主机回应之后,在自定义的回调里读取相应的内容体以及状态码等信息。 下面是一个基础版的 HTTP 客户端例子: ```c #include <stdio.h> #include <stdlib.h> #include <event2/event.h> #include <event2/http.h> #include <event2/buffer.h> void on_http_complete(struct evhttp_request *req, void *ctx) { if (req == NULL) { puts("Request failed."); return; } enum evhttp_command_type type = EVHTTP_REQ_GET; int status_code = evhttp_request_get_response_code(req); printf("Response code: %d (%s)\n", status_code, evhttp_request_get_response_code_line(req)); struct evbuffer *buff = evhttp_request_get_input_buffer(req); size_t len = evbuffer_get_length(buff); char *data = malloc(len + 1); evbuffer_copyout(buff, data, len); data[len] = '\0'; printf("Received body:\n%s\n", data); free(data); } int main(void){ struct event_base *base = event_base_new(); struct evhttp_connection *conn = evhttp_connection_base_new(base, NULL, "www.example.com", 80); struct evhttp_request *request = evhttp_request_new(on_http_complete, NULL); evhttp_add_header(request->output_headers,"Host","www.example.com"); evhttp_make_request(conn,request,EVHTTP_REQ_GET,"/"); event_base_dispatch(base); evhttp_connection_free(conn); event_base_free(base); return EXIT_SUCCESS; } ``` --- #### 编译说明 为了成功编译上述程序,请确保安装了 libevent 库及其开发头文件。可采用如下命令进行编译(假设已配置好环境变量 `$LIBEVENT`): ```bash gcc -o http_server http_server.c -I$LIBEVENT/include -levent ``` 或者如果项目较大建议编写 Makefile 文件简化过程如引用中的描述那样。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值