libevent实现http server

来源:互联网 发布:实战nginx 下载 编辑:程序博客网 时间:2024/04/30 05:05

    libevent 是一个事件触发的网络库,适用于 windows、linux、bsd 、Android 等多种平台,内部使用 select、epoll、kqueue 、完成端口等系统调用管理事件机制。著名分布式缓存软件 memcached 也是 libevent based 。

    最近在学习 libevent ,之前基于 libevent 实现了一个 http client ,没有用到 bufferevent 。这次实现了一个 http server ,很简单,只支持 GET 方法,不支持 Range 请求,但完全自己实现,是一个完整可用的示例。这里使用 libevent-2.1.3-alpha 。

    我关于 libevent 的其它文章,列在这里供参考:

  • libevent实现http client
  • libevent实现echoclient
  • libevent http client
  • libevent 在 Android 上的一个改进

    使用 libevent 实现一个 http server ,有这么几个步骤:

  1. 监听
  2. 启动事件循环
  3. 接受连接
  4. 解析 http 请求
  5. 回应客户端

    关于监听, libevent 提供了 evconnlistener ,使用起来非常简单,通过一些设置,调用 evconnlistener_new_bind 即可完成一个服务端 socket 的创建,可以参考官方文档Connection Listeners 。下面是启动 server 的代码:

int start_http_server(struct event_base *evbase){    int bind_times = 0;    struct sockaddr_in sin;    memset(&sin, 0, sizeof(sin));    sin.sin_family = AF_INET;#ifdef WIN32    sin.sin_addr.S_un.S_addr = inet_addr(g_host);#else    sin.sin_addr.s_addr = inet_addr(g_host);#endif    sin.sin_port = htons(g_port);trybind:    g_listener = evconnlistener_new_bind(                evbase, _accept_connection, 0,                LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_EXEC|LEV_OPT_DEFERRED_ACCEPT, -1,                (struct sockaddr*)&sin, sizeof(sin));    if (!g_listener)    {        if(bind_times++ >=3)        {            printf("couldn\'t create listener\n");            return 1;        }        else        {            sin.sin_port = 0;            goto trybind;        }    }    else if(bind_times > 0)    {        socklen_t len = sizeof(sin);        getsockname(evconnlistener_get_fd(g_listener),                    (struct sockaddr*)&sin, &len);        g_port = ntohs(sin.sin_port);    }    evconnlistener_set_error_cb(g_listener, _accept_error_cb);    return 0;}

    关于事件循环,event_base_new 可以创建一个 event_base 实例, event_base_loop 可以进入事件循环。下面是 main() 函数中关于事件循环的代码:

    g_evbase = event_base_new();    if( 0 == start_http_server(g_evbase) )    {        event_base_loop(g_evbase, EVLOOP_NO_EXIT_ON_EMPTY);        printf("httpserver exit now.\n");    }    else    {        printf("httpserver, start server failed\n");    }    event_base_free(g_evbase);
    上面的代码中,启动事件循环时传递了一个标志 EVLOOP_NO_EXIT_ON_EMPTY ,对于服务器程序,这是必须的,否则在没有待处理事件时,事件循环会立即退出。

    通过给 evconnlistener 设置一些回调,就可以接受连接、处理错误。下面是相关代码:

static void _accept_connection(struct evconnlistener *listener,                               evutil_socket_t fd, struct sockaddr *addr                               , int socklen, void * ctx){    char address[64];    struct http_connection *conn;    struct sockaddr_in sin;    short port = 0;    /* get address and port*/    memcpy(&sin, addr, sizeof(sin));    sprintf(address, "%s", inet_ntoa(sin.sin_addr));    port = ntohs(sin.sin_port);#ifdef HTTP_SERVER_DEBUG    printf("httpserver, accept one connection from %s:%d\n", address, port);#endif    conn = new_http_connection(evconnlistener_get_base(listener),                               fd,                               address,                               port);}static void _accept_error_cb(struct evconnlistener *listener, void *ctx){    int err = EVUTIL_SOCKET_ERROR();    printf("httpserver, got an error %d (%s) on the listener.\n"              , err, evutil_socket_error_to_string(err));}
    在创建调用 evconnlistener_new_bind 时我们传入了 _accept_connection 函数,当有连接进来时,_accept_connection 调用 new_http_connection 函数来处理。错误处理回调 _accept_error_cb 是通过 evconnlistener_set_error_cb 设置的,在上面的错误处理回调函数中,我们仅仅是输出了一条日志。

    解析 http 请求,这里还是使用 《使用http_parser解析URL》一文中提到的http_parser 。先看下 new_http_connection 函数的实现:

struct http_connection * new_http_connection(                                             struct event_base *evbase,                                             evutil_socket_t fd,                                             char *address, int port){    struct http_connection * conn = (struct http_connection*)malloc(sizeof(struct http_connection));    conn->evbase = evbase;    conn->fd = fd;    conn->peer_address = strdup(address);    conn->peer_port = port;    conn->tv_timeout.tv_sec = 10;    conn->tv_timeout.tv_usec = 0;    conn->bev = bufferevent_socket_new(evbase, fd, BEV_OPT_CLOSE_ON_FREE);    bufferevent_setcb(conn->bev, _read_callback, _write_callback, _event_callback, conn);    bufferevent_enable(conn->bev, EV_READ|EV_TIMEOUT);    bufferevent_set_timeouts(conn->bev, &conn->tv_timeout, &conn->tv_timeout);    conn->parser_settings.on_message_begin = onHttpMessageBegin;    conn->parser_settings.on_url = onHttpUrl;    conn->parser_settings.on_header_field = onHttpHeaderField;    conn->parser_settings.on_header_value = onHttpHeaderValue;    conn->parser_settings.on_headers_complete = onHttpHeadersComplete;    conn->parser_settings.on_body = onHttpBody;    conn->parser_settings.on_message_complete = onHttpMessageComplete;    conn->cur_header_tag = 0;    conn->cur_tag_cap = 0;    conn->cur_tag_size = 0;    conn->cur_header_value = 0;    conn->cur_value_cap = 0;    conn->cur_value_size = 0;    conn->header_tags = 0;    conn->header_size = 0;    conn->header_capacity = 0;    conn->header_values = 0;    conn->fp = 0;    conn->data_size = 0;    conn->remain = 0;    conn->method = 0;    conn->path = 0;    conn->query_string = 0;    conn->status_code = 0;    conn->parser.data = conn;    http_parser_init(&conn->parser, HTTP_REQUEST);    return conn;}
    上面的代码根据传入的 socket 描述符和 event_base 完成了传入连接的配置工作。主要有几部分:

  • 创建 bufferevent
  • 设置读写回调
  • 配置 http_parser_setting ,主要是一些回调函数,http_parser 分析数据后酌情调用
  • 初始化 http_parser,调用 http_parser_init,注意传入类型是 HTTP_REQUEST

    上面代码中的结构体 struct http_connection 保存了一个连接相关的所有数据,其定义如下:

struct http_connection {    struct http_parser parser;    struct http_parser_settings parser_settings;    char *cur_header_tag;    int cur_tag_cap;    int cur_tag_size;    char *cur_header_value;    int cur_value_cap;    int cur_value_size;    char buffer[BUFFER_SIZE];    struct event_base *evbase;    evutil_socket_t fd;    struct bufferevent *bev;    struct timeval tv_timeout;    char *peer_address;    int peer_port;    int state;    unsigned write_enabled:1;    unsigned user_stop:1;    /* request info */    const char * method;    char * path;    char * query_string;    char version[4];    char **header_tags;    char **header_values;    int header_capacity;    int header_size;    /* response info */    FILE *fp;    long remain;    int data_size;    int status_code;};
    需要说明的是,这里只是个示例, http 请求、响应、连接处理全部放在了一起,看起来比较方面。

    关于 http 头部、数据解析, http_parser 会为我们做好一切,我们只要保存即可。

    关于 http 响应,需要我们自己构建状态行、必要的头部信息(如 Content-Length )。

    所有这些,请参考文后的代码。

    回应客户端的这里使用 bufferevent socket 。 libevent 抽象了一种缓冲机制,来给大多数应用场景提供方便,关于 bufferevents ,请参考官方文档(http://www.wangafu.net/~nickm/libevent-book/Ref6_bufferevent.html)。

    使用 bufferevent socket 处理连接非常简单,只需要设置读写回调即可,这在上面已经提到,不再赘述。

    到这里为止,关于一个 http server 的所有事情就说完了。下面是 http_connection.c 的所有代码( new_http_connection 函数的代码在前面),可以正常运行。为使代码比较清晰,这里关于错误、封装、解耦等都简化处理了。

#define HTTP_HEADER_BEGIN        0x1#define HTTP_HEADER_COMPLETE     0x2#define HTTP_MESSAGE_BEGIN       0x4#define HTTP_MESSAGE_COMPLETE    0x8#define STATUS_CODE(code, str) case code: return str;static const char * _status_string(int code){    switch(code)    {    STATUS_CODE(100, "Continue")    STATUS_CODE(101, "Switching Protocols")    STATUS_CODE(102, "Processing")                 // RFC 2518) obsoleted by RFC 4918    STATUS_CODE(200, "OK")    STATUS_CODE(201, "Created")    STATUS_CODE(202, "Accepted")    STATUS_CODE(203, "Non-Authoritative Information")    STATUS_CODE(204, "No Content")    STATUS_CODE(205, "Reset Content")    STATUS_CODE(206, "Partial Content")    STATUS_CODE(207, "Multi-Status")               // RFC 4918    STATUS_CODE(300, "Multiple Choices")    STATUS_CODE(301, "Moved Permanently")    STATUS_CODE(302, "Moved Temporarily")    STATUS_CODE(303, "See Other")    STATUS_CODE(304, "Not Modified")    STATUS_CODE(305, "Use Proxy")    STATUS_CODE(307, "Temporary Redirect")    STATUS_CODE(400, "Bad Request")    STATUS_CODE(401, "Unauthorized")    STATUS_CODE(402, "Payment Required")    STATUS_CODE(403, "Forbidden")    STATUS_CODE(404, "Not Found")    STATUS_CODE(405, "Method Not Allowed")    STATUS_CODE(406, "Not Acceptable")    STATUS_CODE(407, "Proxy Authentication Required")    STATUS_CODE(408, "Request Time-out")    STATUS_CODE(409, "Conflict")    STATUS_CODE(410, "Gone")    STATUS_CODE(411, "Length Required")    STATUS_CODE(412, "Precondition Failed")    STATUS_CODE(413, "Request Entity Too Large")    STATUS_CODE(414, "Request-URI Too Large")    STATUS_CODE(415, "Unsupported Media Type")    STATUS_CODE(416, "Requested Range Not Satisfiable")    STATUS_CODE(417, "Expectation Failed")    STATUS_CODE(418, "I\"m a teapot")              // RFC 2324    STATUS_CODE(422, "Unprocessable Entity")       // RFC 4918    STATUS_CODE(423, "Locked")                     // RFC 4918    STATUS_CODE(424, "Failed Dependency")          // RFC 4918    STATUS_CODE(425, "Unordered Collection")       // RFC 4918    STATUS_CODE(426, "Upgrade Required")           // RFC 2817    STATUS_CODE(500, "Internal Server Error")    STATUS_CODE(501, "Not Implemented")    STATUS_CODE(502, "Bad Gateway")    STATUS_CODE(503, "Service Unavailable")    STATUS_CODE(504, "Gateway Time-out")    STATUS_CODE(505, "HTTP Version not supported")    STATUS_CODE(506, "Variant Also Negotiates")    // RFC 2295    STATUS_CODE(507, "Insufficient Storage")       // RFC 4918    STATUS_CODE(509, "Bandwidth Limit Exceeded")    STATUS_CODE(510, "Not Extended")                // RFC 2774    }    return 0;}static void _prepare_response(struct http_connection *conn);static void _disable_write(struct http_connection *conn);static void _enable_write(struct http_connection *conn);static void _close_socket(struct http_connection * conn);static void _peacefull_close(struct http_connection * conn);static void _send_response_header(struct http_connection *conn);/** http_parser callback*/static int onHttpMessageBegin(http_parser *parser){    struct http_connection * conn = (struct http_connection *)parser->data;    conn->state |= HTTP_MESSAGE_BEGIN;    return 0;}static int onHttpUrl(http_parser *parser, const char *at, size_t length){    struct http_connection *conn = (struct http_connection *)parser->data;    int i= 0;    conn->path = (char*)malloc(length+1);    strncpy(conn->path, at, length);    conn->path[length] = 0;    for(; i < length && at[i] != '?'; i++);    if(i < length)    {        /* got query string */        i++;        if(i < length)        {            int qlen = length - i;            conn->query_string = (char*)malloc(qlen + 1);            strncpy(conn->query_string, at+i, qlen);            conn->query_string[qlen] = 0;        }    }    return 0;}static inline void check_insert_header(http_parser *parser, struct http_connection *conn){    /*     * insert the header we parsed previously     * into the header map     */    if( (conn->cur_header_tag && conn->cur_header_tag[0] != 0) &&            (conn->cur_header_value && conn->cur_header_value[0] != 0))    {        if(!conn->header_tags ||                conn->header_size == conn->header_capacity)        {            conn->header_capacity += 8;            conn->header_tags = (char**)realloc(conn->header_tags,sizeof(char*)*conn->header_capacity);            conn->header_values = (char**)realloc(conn->header_tags, sizeof(char*)*conn->header_capacity);        }        conn->header_tags[conn->header_size] = conn->cur_header_tag;        conn->header_values[conn->header_size++] = conn->cur_header_value;        /*         *  clear header value. this sets up a nice         * feedback loop where the next time         * HeaderValue is called, it can simply append        */        conn->cur_header_tag = 0;        conn->cur_tag_cap = 0;        conn->cur_tag_size = 0;        conn->cur_header_value = 0;        conn->cur_value_cap = 0;        conn->cur_value_size = 0;    }}static void check_dynamic_string(char **str, int *cap, int size, int add_size){    if(!*str || size + add_size >= *cap)    {        *cap = size + add_size + 64;        *str = (char*)realloc(*str, *cap);    }}static int onHttpHeaderField(http_parser *parser, const char *at, size_t length){    struct http_connection * conn = (struct http_connection *)parser->data;    check_insert_header(parser, conn);    check_dynamic_string(&conn->cur_header_tag,                         &conn->cur_tag_cap, conn->cur_tag_size, length);    strncpy(conn->cur_header_tag + conn->cur_tag_size, at, length);    return 0;}static int onHttpHeaderValue(http_parser *parser, const char *at, size_t length){    struct http_connection * conn = (struct http_connection *)parser->data;    check_dynamic_string(&conn->cur_header_value,                         &conn->cur_value_cap, conn->cur_value_size, length);    strncpy(conn->cur_header_value + conn->cur_value_size, at, length);    return 0;}static int onHttpHeadersComplete(http_parser *parser){    printf("server, http_connection, onHttpHeadersComplete\n");    if(parser)    {        struct http_connection * conn = (struct http_connection *)parser->data;        if(conn)        {#ifdef HTTP_SERVER_DEBUG            int i = 0;#endif            check_insert_header(parser, conn);            conn->state |= HTTP_HEADER_COMPLETE;            conn->method = http_method_str((enum http_method)conn->parser.method);            sprintf(conn->version, "%.1d.%.1d", conn->parser.http_major,                    conn->parser.http_minor);        #ifdef HTTP_SERVER_DEBUG            printf("server,http_connection, %d headers\n", conn->header_size);            for(; i < conn->header_size; i++)            {                printf("header key %s value %s\n", conn->header_tags[i], conn->header_values[i]);            }        #endif        }    }    return 0;}static int onHttpBody(http_parser *parser, const char *at, size_t length){    /* TODO: implement */    return 0;}static int onHttpMessageComplete(http_parser *parser){    struct http_connection * conn = (struct http_connection *)parser->data;    if(conn)    {        _prepare_response(conn);;        conn->state |= HTTP_MESSAGE_COMPLETE;        _enable_write(conn);        _send_response_header(conn);        switch(conn->status_code)        {        case 200:        case 206:            break;        default:            _disable_write(conn);            _peacefull_close(conn);            break;        }    }    return 0;}static void _prepare_response(struct http_connection *conn){    char *filename = conn->path;    char *p;    int i = 0;    if(strncmp(conn->method, "GET", 3) != 0)    {        conn->status_code = 403;        return;    }    while(*filename == '/') filename++;    p = filename;    while(*p != '?' && *p != 0) p++;    if(*p == '?') *p = 0;    conn->fp = fopen(filename, "rb");    if(conn->fp)    {        fseek(conn->fp, 0, SEEK_END);        conn->remain = ftell(conn->fp);        fseek(conn->fp, 0, SEEK_SET);        conn->status_code = 200;        printf("open %s OK\n", filename);    }    else    {        conn->status_code = 404;    }}static void _send_response_header(struct http_connection *conn){    char * p = conn->buffer;    p += sprintf(p, "HTTP/1.1 %d %s\r\n",                         conn->status_code,                         _status_string(conn->status_code));    if(conn->status_code == 200)    {        p += sprintf(p, "Content-Length: %d\r\n"                     "Content-Type: application/octet-stream\r\n",                     conn->remain);    }    p += sprintf(p, "\r\n");    //printf("response headers: %s\n", conn->buffer);    bufferevent_write(conn->bev, conn->buffer, p - conn->buffer);}static void _close_socket(struct http_connection * conn){    if(conn && conn->fd != 0)    {        if(conn->bev)        {            bufferevent_free(conn->bev);            conn->bev = 0;        }        evutil_closesocket(conn->fd);        conn->fd = 0;    }}static void _peacefull_close(struct http_connection * conn){    if( evbuffer_get_length(bufferevent_get_output(conn->bev)) == 0)    {        printf("http_connection, all data sent, close connection(%s:%d)\n"                  , conn->peer_address, conn->peer_port);        /* delete this connection */        delete_http_connection(conn);        return;    }    else    {        printf("http_connection, wait bufferevent_socket to flush data\n");    }}static void _event_callback(struct bufferevent *bev, short what, void *ctx){    struct http_connection * conn = (struct http_connection *)ctx;    if( (what & (BEV_EVENT_READING | BEV_EVENT_TIMEOUT)) == (BEV_EVENT_READING | BEV_EVENT_TIMEOUT))    {        /* TODO: check socket alive */    }    else if((what &(BEV_EVENT_WRITING | BEV_EVENT_TIMEOUT)) == (BEV_EVENT_WRITING | BEV_EVENT_TIMEOUT))    {        /* TODO: check socket alive */    }    else if(what & BEV_EVENT_ERROR)    {        /* TODO: error notify */        printf( "http_connection, %s:%d, error - %s\n", conn->peer_address,                   conn->peer_port,                   evutil_socket_error_to_string( evutil_socket_geterror(conn->fd) ) );        _close_socket(conn);    }}static void _disable_write(struct http_connection *conn){    if(conn->write_enabled)    {        bufferevent_disable(conn->bev, EV_WRITE|EV_TIMEOUT);        conn->write_enabled = 0;    }}static void _enable_write(struct http_connection *conn){    if(!conn->write_enabled)    {        bufferevent_enable(conn->bev, EV_WRITE | EV_TIMEOUT);        conn->write_enabled = 1;    }}// default write callbackstatic void _write_callback(struct bufferevent *bev, void * args){    struct http_connection * conn = (struct http_connection *)args;    if(conn->fp)    {        if(feof(conn->fp))        {            printf("http_connection, call peacefull_close via EOF\n");            _peacefull_close(conn);        }        else        {            int to_read = BUFFER_SIZE;            if(to_read > conn->remain) to_read = conn->remain;            conn->data_size = fread(conn->buffer, 1, to_read, conn->fp);            conn->remain -= conn->data_size;#ifdef HTTP_SERVER_DEBUG            printf("http_connection, read %d bytes\n", conn->data_size);#endif            if(conn->data_size)bufferevent_write(bev, conn->buffer, conn->data_size);        }    }    else    {        printf("http_connection, call peacefull_close via fp NULL\n");        _peacefull_close(conn);    }}// default read callbackstatic void _read_callback(struct bufferevent *bev, void * args){    struct http_connection * conn = (struct http_connection *)args;    int n;    while( (n = bufferevent_read(bev, conn->buffer, BUFFER_SIZE)) > 0 )    {        http_parser_execute(&conn->parser, &conn->parser_settings, conn->buffer, n);    }}void delete_http_connection(struct http_connection *conn){    int i = 0;    _disable_write(conn);    _close_socket(conn);    /* free resources */    if(conn->peer_address)free(conn->peer_address);    if(conn->fp)fclose(conn->fp);    if(conn->cur_header_tag)free(conn->cur_header_tag);    if(conn->cur_header_value)free(conn->cur_header_value);    if(conn->path)free(conn->path);    if(conn->query_string)free(conn->query_string);    for(; i < conn->header_size; i++)    {        free(conn->header_tags[i]);        free(conn->header_values[i]);    }    free(conn->header_tags);    free(conn->header_values);    free(conn);}




1 0
原创粉丝点击