1. 程式人生 > >libevent evhttp學習——http客戶端

libevent evhttp學習——http客戶端

基本環境

使用版本為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代理時,可以做到邊下載邊回覆

請求流程

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;
}

編譯

g++ http_client.cpp -I/opt/local/libevent-2.1.5/include -L/opt/local/libevent-2.1.5/lib -levent -g -o http_client

執行示例,這裡只打印了包頭欄位

 $ ./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
<