Libevhtp

From 탱이의 잡동사니
Jump to: navigation, search

Overview

libevhtp 는 libevent 를 이용한 HTTP API 라이브러리이다. libevhtp 를 사용하면 손쉽게 REST 인터페이스를 구현할 수 있다.

홈페이지: https://github.com/ellzey/libevhtp

Usage

libevhtp 라이브러리는 내부적으로 libevent 을 사용한다. 따라서 프로그램 컴파일 시, libevhtp 뿐만 아니라 libevent 도 같이 링크를 걸어주어야 한다.

$ gcc -o main main.c -levent -levhtp

하지만 ssl 관련 설정을 사용한다면, 다음의 오류 메시지가 나타날 수 있다.

$ gcc -o main main.c -levent -levhtp -lpthread -lssl -lcrypto
//usr/local/lib/libevhtp.a(evhtp.c.o): In function `_evhtp_connection_accept':
evhtp.c:(.text+0x8d7): undefined reference to `bufferevent_openssl_socket_new'
collect2: error: ld returned 1 exit status

이럴 땐 다음과 같이 libevent_openssl 라이브러리를 추가해주면 된다.

$ gcc -o main main.c -levent -levhtp -lpthread -lssl -lcrypto -levent_openssl

Types

evhtp_t

typedef struct evhtp_s            evhtp_t;
 
/**
 * @brief main structure containing all configuration information
 */
struct evhtp_s {
    evhtp_t  * parent;           /**< only when this is a vhost */
    evbase_t * evbase;           /**< the initialized event_base */
    evserv_t * server;           /**< the libevent listener struct */
    char     * server_name;      /**< the name included in Host: responses */
    void     * arg;              /**< user-defined evhtp_t specific arguments */
    int        bev_flags;        /**< bufferevent flags to use on bufferevent_*_socket_new() */
    uint64_t   max_body_size;
    uint64_t   max_keepalive_requests;
    int        disable_100_cont; /**< if set, evhtp will not respond to Expect: 100-continue */
 
#ifndef EVHTP_DISABLE_SSL
    evhtp_ssl_ctx_t * ssl_ctx;   /**< if ssl enabled, this is the servers CTX */
    evhtp_ssl_cfg_t * ssl_cfg;
#endif
 
#ifndef EVHTP_DISABLE_EVTHR
    evthr_pool_t * thr_pool;     /**< connection threadpool */
#endif
 
#ifndef EVHTP_DISABLE_EVTHR
    pthread_mutex_t    * lock;   /**< parent lock for add/del cbs in threads */
    evhtp_thread_init_cb thread_init_cb;
    void               * thread_init_cbarg;
#endif
    evhtp_callbacks_t * callbacks;
    evhtp_defaults_t    defaults;
 
    struct timeval recv_timeo;
    struct timeval send_timeo;
 
    TAILQ_HEAD(, evhtp_alias_s) aliases;
    TAILQ_HEAD(, evhtp_s) vhosts;
    TAILQ_ENTRY(evhtp_s) next_vhost;
};

evhtp_callback_t

typedef struct evhtp_callback_s   evhtp_callback_t;
 
/**
 * @brief structure containing a single callback and configuration
 *
 * The definition structure which is used within the evhtp_callbacks_t
 * structure. This holds information about what should execute for either
 * a single or regex path.
 *
 * For example, if you registered a callback to be executed on a request
 * for "/herp/derp", your defined callback will be executed.
 *
 * Optionally you can set callback-specific hooks just like per-connection
 * hooks using the same rules.
 *
 */
struct evhtp_callback_s {
    evhtp_callback_type type;           /**< the type of callback (regex|path) */
    evhtp_callback_cb   cb;             /**< the actual callback function */
    unsigned int        hash;           /**< the full hash generated integer */
    void              * cbarg;          /**< user-defind arguments passed to the cb */
    evhtp_hooks_t     * hooks;          /**< per-callback hooks */
 
    union {
        char * path;
        char * glob;
#ifndef EVHTP_DISABLE_REGEX
        regex_t * regex;
#endif
    } val;
 
    TAILQ_ENTRY(evhtp_callback_s) next;
};

evhtp_request_t

HTTP request 요청시 모든 정보가 저장되는 구조체이다.

typedef struct evhtp_request_s    evhtp_request_t;
 
/**
 * @brief a structure containing all information for a http request.
 */
struct evhtp_request_s {
    evhtp_t            * htp;         /**< the parent evhtp_t structure */
    evhtp_connection_t * conn;        /**< the associated connection */
    evhtp_hooks_t      * hooks;       /**< request specific hooks */
    evhtp_uri_t        * uri;         /**< request URI information */
    evbuf_t            * buffer_in;   /**< buffer containing data from client */
    evbuf_t            * buffer_out;  /**< buffer containing data to client */
    evhtp_headers_t    * headers_in;  /**< headers from client */
    evhtp_headers_t    * headers_out; /**< headers to client */
    evhtp_proto          proto;       /**< HTTP protocol used */
    htp_method           method;      /**< HTTP method used */
    evhtp_res            status;      /**< The HTTP response code or other error conditions */
    int                  keepalive;   /**< set to 1 if the connection is keep-alive */
    int                  finished;    /**< set to 1 if the request is fully processed */
    int                  chunked;     /**< set to 1 if the request is chunked */
 
    evhtp_callback_cb cb;             /**< the function to call when fully processed */
    void            * cbarg;          /**< argument which is passed to the cb function */
    int               error;
 
    TAILQ_ENTRY(evhtp_request_s) next;
};

evhtp_connection_t

struct evhtp_connection_s {
    evhtp_t                    * htp;
    evbase_t                   * evbase;
    evbev_t                    * bev;
    evthr_t                    * thread;
    evhtp_ssl_t                * ssl;
    evhtp_hooks_t              * hooks;
    htparser                   * parser;
    event_t                    * resume_ev;
    struct sockaddr            * saddr;
    struct timeval               recv_timeo;    /**< conn read timeouts (overrides global) */
    struct timeval               send_timeo;    /**< conn write timeouts (overrides global) */
    evutil_socket_t              sock;
    uint8_t                      error;
    uint8_t                      owner;         /**< set to 1 if this structure owns the bufferevent */
    uint8_t                      vhost_via_sni; /**< set to 1 if the vhost was found via SSL SNI */
    evhtp_request_t            * request;       /**< the request currently being processed */
    uint64_t                     max_body_size;
    uint64_t                     body_bytes_read;
    uint64_t                     num_requests;
    evhtp_type                   type;          /**< server or client */
    char                         paused;
    char                         free_connection;
    struct ev_token_bucket_cfg * ratelimit_cfg; /**< connection-specific ratelimiting configuration. */
 
    TAILQ_HEAD(, evhtp_request_s) pending;      /**< client pending data */
};
 
typedef struct evhtp_connection_s evhtp_connection_t;


evhtp_hooks_t

struct evhtp_hooks_s {
    evhtp_hook_headers_start_cb   on_headers_start;
    evhtp_hook_header_cb          on_header;
    evhtp_hook_headers_cb         on_headers;
    evhtp_hook_path_cb            on_path;
    evhtp_hook_read_cb            on_read;
    evhtp_hook_request_fini_cb    on_request_fini;
    evhtp_hook_connection_fini_cb on_connection_fini;
    evhtp_hook_err_cb             on_error;
    evhtp_hook_chunk_new_cb       on_new_chunk;
    evhtp_hook_chunk_fini_cb      on_chunk_fini;
    evhtp_hook_chunks_fini_cb     on_chunks_fini;
    evhtp_hook_hostname_cb        on_hostname;
    evhtp_hook_write_cb           on_write;
 
    void * on_headers_start_arg;
    void * on_header_arg;
    void * on_headers_arg;
    void * on_path_arg;
    void * on_read_arg;
    void * on_request_fini_arg;
    void * on_connection_fini_arg;
    void * on_error_arg;
    void * on_new_chunk_arg;
    void * on_chunk_fini_arg;
    void * on_chunks_fini_arg;
    void * on_hostname_arg;
    void * on_write_arg;
};
 
typedef struct evhtp_hooks_s      evhtp_hooks_t;

evhtp_uri_t

/**
 * @brief a generic container representing an entire URI strucutre
 */
struct evhtp_uri_s {
    evhtp_authority_t * authority;
    evhtp_path_t      * path;
    unsigned char     * fragment;     /**< data after '#' in uri */
    unsigned char     * query_raw;    /**< the unparsed query arguments */
    evhtp_query_t     * query;        /**< list of k/v for query arguments */
    htp_scheme          scheme;       /**< set if a scheme is found */
};
 
typedef struct evhtp_uri_s        evhtp_uri_t;

evbuf_t

typedef struct evbuffer           evbuf_t;

evhtp_headers_t

#define evhtp_headers_t evhtp_kvs_t
typedef struct evhtp_kvs_s        evhtp_kvs_t;
 
/**
 * @brief a generic key/value structure
 */
struct evhtp_kv_s {
    char * key;
    char * val;
 
    size_t klen;
    size_t vlen;
 
    char k_heaped; /**< set to 1 if the key can be free()'d */
    char v_heaped; /**< set to 1 if the val can be free()'d */
 
    TAILQ_ENTRY(evhtp_kv_s) next;
};
 
TAILQ_HEAD(evhtp_kvs_s, evhtp_kv_s);

htp_method

http request 요청 타입

typedef enum htp_method      htp_method;
 
enum htp_method {
    htp_method_GET = 0,
    htp_method_HEAD,
    htp_method_POST,
    htp_method_PUT,
    htp_method_DELETE,
    htp_method_MKCOL,
    htp_method_COPY,
    htp_method_MOVE,
    htp_method_OPTIONS,
    htp_method_PROPFIND,
    htp_method_PROPPATCH,
    htp_method_LOCK,
    htp_method_UNLOCK,
    htp_method_TRACE,
    htp_method_CONNECT, /* RFC 2616 */
    htp_method_PATCH,   /* RFC 5789 */
    htp_method_UNKNOWN,
};

Functions

생성/삭제

/**
 * @brief creates a new evhtp_t instance
 *
 * @param evbase the initialized event base
 * @param arg user-defined argument which is evhtp_t specific
 *
 * @return a new evhtp_t structure or NULL on error
 */
evhtp_t * evhtp_new(evbase_t * evbase, void * arg);
void      evhtp_free(evhtp_t * evhtp);

evhtp 인스턴스 생성/삭제 함수

콜백 관련

  • Interface
/**
 * @brief sets a callback to be executed on a specific path
 *
 * @param htp the initialized evhtp_t
 * @param path the path to match
 * @param cb the function to be executed
 * @param arg user-defined argument passed to the callback
 *
 * @return evhtp_callback_t * on success, NULL on error.
 */
evhtp_callback_t * evhtp_set_cb(evhtp_t * htp, const char * path, evhtp_callback_cb cb, void * arg);
 
/**
 * @brief sets a callback to be executed based on a regex pattern
 *
 * @param htp the initialized evhtp_t
 * @param pattern a POSIX compat regular expression
 * @param cb the function to be executed
 * @param arg user-defined argument passed to the callback
 *
 * @return evhtp_callback_t * on success, NULL on error
 */
#ifndef EVHTP_DISABLE_REGEX
evhtp_callback_t * evhtp_set_regex_cb(evhtp_t * htp, const char * pattern, evhtp_callback_cb cb, void * arg);
#endif
 
/**
 * @brief sets a callback to to be executed on simple glob/wildcard patterns
 *        this is useful if the app does not care about what was matched, but
 *        just that it matched. This is technically faster than regex.
 *
 * @param htp
 * @param pattern wildcard pattern, the '*' can be set at either or both the front or end.
 * @param cb
 * @param arg
 *
 * @return
 */
evhtp_callback_t * evhtp_set_glob_cb(evhtp_t * htp, const char * pattern, evhtp_callback_cb cb, void * arg);

지정된 경로가 호출되었을 때 실행 될 콜백 함수를 지정한다. evhtp_set_regex_cb() 함수는 정규 표현식 경로를 지원하지만, libevhtp 빌드시, 정규 표현식 옵션이 설정되어 있어야 한다. evhtp_set_glob_cb() 함수는 globbing 을 지원한다.

  • Example
void testcb(evhtp_request_t *req, void *a)
{
    const char *str = a;
 
    evbuffer_add_printf(req->buffer_out, "%s", str);
    evhtp_send_reply(req, EVHTP_RES_OK);
}
 
evhtp_t *htp        = evhtp_new(evbase, NULL);
 
evhtp_callback_t *cb1 = evhtp_set_cb(htp, "/simple/", testcb, "simple");
evhtp_callback_t *cb2 = evhtp_set_regex_cb(htp, "^(/anything/).*", testcb, "simple");
evhtp_callback_t *cb3 = evhtp_set_glob_cb(htp, "/glob/", testcb, "simple");

connection hook

  • Interface
/**
 * @brief sets a callback hook for either a connection or a path/regex .
 *
 * A user may set a variety of hooks either per-connection, or per-callback.
 * This allows the developer to hook into various parts of the request processing
 * cycle.
 *
 * a per-connection hook can be set at any time, but it is recommended to set these
 * during either a pre-accept phase, or post-accept phase. This allows a developer
 * to set hooks before any other hooks are called.
 *
 * a per-callback hook works differently. In this mode a developer can setup a set
 * of hooks prior to starting the event loop for specific callbacks. For example
 * if you wanted to hook something ONLY for a callback set by evhtp_set_cb or
 * evhtp_set_regex_cb this is the method of doing so.
 *
 * per-callback example:
 *
 * evhtp_callback_t * cb = evhtp_set_regex_cb(htp, "/anything/(.*)", default_cb, NULL);
 *
 * evhtp_set_hook(&cb->hooks, evhtp_hook_on_headers, anything_headers_cb, NULL);
 *
 * evhtp_set_hook(&cb->hooks, evhtp_hook_on_fini, anything_fini_cb, NULL);
 *
 * With the above example, once libevhtp has determined that it has a user-defined
 * callback for /anything/.*; anything_headers_cb will be executed after all headers
 * have been parsed, and anything_fini_cb will be executed before the request is
 * free()'d.
 *
 * The same logic applies to per-connection hooks, but it should be noted that if
 * a per-callback hook is set, the per-connection hook will be ignored.
 *
 * @param hooks double pointer to the evhtp_hooks_t structure
 * @param type the hook type
 * @param cb the callback to be executed.
 * @param arg optional argument which is passed when the callback is executed
 *
 * @return 0 on success, -1 on error (if hooks is NULL, it is allocated)
 */
int evhtp_set_hook(evhtp_hooks_t ** hooks, evhtp_hook_type type, evhtp_hook cb, void * arg);
 
/**
 * @brief remove a specific hook from being called.
 *
 * @param hooks
 * @param type
 *
 * @return
 */
int evhtp_unset_hook(evhtp_hooks_t ** hooks, evhtp_hook_type type);
 
/**
 * @brief removes all hooks.
 *
 * @param hooks
 *
 * @return
 */
int evhtp_unset_all_hooks(evhtp_hooks_t ** hooks);
 
/**
 * @brief sets a callback which is called if no other callbacks are matched
 *
 * @param htp the initialized evhtp_t
 * @param cb  the function to be executed
 * @param arg user-defined argument passed to the callback
 */
void evhtp_set_gencb(evhtp_t * htp, evhtp_callback_cb cb, void * arg);
void evhtp_set_pre_accept_cb(evhtp_t * htp, evhtp_pre_accept_cb, void * arg);
void evhtp_set_post_accept_cb(evhtp_t * htp, evhtp_post_accept_cb, void * arg);
 
/**
 * @brief types associated with where a developer can hook into
 *        during the request processing cycle.
 */
enum evhtp_hook_type {
    evhtp_hook_on_header,       /**< type which defines to hook after one header has been parsed */
    evhtp_hook_on_headers,      /**< type which defines to hook after all headers have been parsed */
    evhtp_hook_on_path,         /**< type which defines to hook once a path has been parsed */
    evhtp_hook_on_read,         /**< type which defines to hook whenever the parser recieves data in a body */
    evhtp_hook_on_request_fini, /**< type which defines to hook before the request is free'd */
    evhtp_hook_on_connection_fini,
    evhtp_hook_on_new_chunk,
    evhtp_hook_on_chunk_complete,
    evhtp_hook_on_chunks_complete,
    evhtp_hook_on_headers_start,
    evhtp_hook_on_error,        /**< type which defines to hook whenever an error occurs */
    evhtp_hook_on_hostname,
    evhtp_hook_on_write
};

각각의 HTTP 연결마다 호출되는 Hook 콜백 함수를 설정한다. evhtp_set_gencb(), evhtp_set_pre_accept_cb(), evhtp_set_post_accept_cb() 함수들은 default callback 함수들을 지정한다.

Listen 소켓 설정/해제

  • Interface
/**
 * @brief bind to a socket, optionally with specific protocol support
 *        formatting. The addr can be defined as one of the following:
 *          ipv6:<ipv6addr> for binding to an IPv6 address.
 *          unix:<named pipe> for binding to a unix named socket
 *          ipv4:<ipv4addr> for binding to an ipv4 address
 *        Otherwise the addr is assumed to be ipv4.
 *
 * @param htp
 * @param addr
 * @param port
 * @param backlog
 *
 * @return
 */
int evhtp_bind_socket(evhtp_t * htp, const char * addr, uint16_t port, int backlog);
 
/**
 * @brief stops the listening socket.
 *
 * @param htp
 */
void evhtp_unbind_socket(evhtp_t * htp);

Listen 주소를 설정/해제한다. 주소 설정 시, 자동으로 주소 타입을 파싱해서 알맞은 소켓 타입으로 생성한 후, listen 한다. "0.0.0.0" 설정시, localhost 주소를 listen 한다.

  • Example
evhtp_bind_socket(htp, "0.0.0.0", 8081, 1024);
 
evhtp_unbind_socket(htp);

evhtp_send_reply

HTTP Request 를 보낸 클라이언트로 응답값을 전송한다.

void evhtp_send_reply(evhtp_request_t * request, evhtp_res code);
void evhtp_send_reply_start(evhtp_request_t * request, evhtp_res code);
void evhtp_send_reply_body(evhtp_request_t * request, evbuf_t * buf);
void evhtp_send_reply_end(evhtp_request_t * request);

사용가능한 응답값은 다음과 같다.

#define EVHTP_RES_ERROR         0
#define EVHTP_RES_PAUSE         1
#define EVHTP_RES_FATAL         2
#define EVHTP_RES_USER          3
#define EVHTP_RES_DATA_TOO_LONG 4
#define EVHTP_RES_OK            200
 
#define EVHTP_RES_100           100
#define EVHTP_RES_CONTINUE      100
#define EVHTP_RES_SWITCH_PROTO  101
#define EVHTP_RES_PROCESSING    102
#define EVHTP_RES_URI_TOOLONG   122
 
#define EVHTP_RES_200           200
#define EVHTP_RES_CREATED       201
#define EVHTP_RES_ACCEPTED      202
#define EVHTP_RES_NAUTHINFO     203
#define EVHTP_RES_NOCONTENT     204
#define EVHTP_RES_RSTCONTENT    205
#define EVHTP_RES_PARTIAL       206
#define EVHTP_RES_MSTATUS       207
#define EVHTP_RES_IMUSED        226
 
#define EVHTP_RES_300           300
#define EVHTP_RES_MCHOICE       300
#define EVHTP_RES_MOVEDPERM     301
#define EVHTP_RES_FOUND         302
#define EVHTP_RES_SEEOTHER      303
#define EVHTP_RES_NOTMOD        304
#define EVHTP_RES_USEPROXY      305
#define EVHTP_RES_SWITCHPROXY   306
#define EVHTP_RES_TMPREDIR      307
 
#define EVHTP_RES_400           400
#define EVHTP_RES_BADREQ        400
#define EVHTP_RES_UNAUTH        401
#define EVHTP_RES_PAYREQ        402
#define EVHTP_RES_FORBIDDEN     403
#define EVHTP_RES_NOTFOUND      404
#define EVHTP_RES_METHNALLOWED  405
#define EVHTP_RES_NACCEPTABLE   406
#define EVHTP_RES_PROXYAUTHREQ  407
#define EVHTP_RES_TIMEOUT       408
#define EVHTP_RES_CONFLICT      409
#define EVHTP_RES_GONE          410
#define EVHTP_RES_LENREQ        411
#define EVHTP_RES_PRECONDFAIL   412
#define EVHTP_RES_ENTOOLARGE    413
#define EVHTP_RES_URITOOLARGE   414
#define EVHTP_RES_UNSUPPORTED   415
#define EVHTP_RES_RANGENOTSC    416
#define EVHTP_RES_EXPECTFAIL    417
#define EVHTP_RES_IAMATEAPOT    418
 
#define EVHTP_RES_500           500
#define EVHTP_RES_SERVERR       500
#define EVHTP_RES_NOTIMPL       501
#define EVHTP_RES_BADGATEWAY    502
#define EVHTP_RES_SERVUNAVAIL   503
#define EVHTP_RES_GWTIMEOUT     504
#define EVHTP_RES_VERNSUPPORT   505
#define EVHTP_RES_BWEXEED       509


evhtp_request_get_method

HTTP request 의 요청 타입을 리턴한다.

htp_method evhtp_request_get_method(evhtp_request_t * r) {
    return htparser_get_method(r->conn->parser);
}

Samples

test_basic

  • test_basic.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <evhtp.h>
 
void testcb(evhtp_request_t *req, void *a)
{
    const char *str = a;
 
    evbuffer_add_printf(req->buffer_out, "%s", str);
    evhtp_send_reply(req, EVHTP_RES_OK);
}
 
int main(int argc, char **argv)
{
    evbase_t *evbase    = event_base_new();
    evhtp_t *htp        = evhtp_new(evbase, NULL);
 
    evhtp_set_cb(htp, "/simple/", testcb, "simple");
    evhtp_set_cb(htp, "/1/ping", testcb, "one");
    evhtp_set_cb(htp, "/1/ping.json", testcb, "two");
#ifndef EVHTP_DISABLE_EVTHR
    evhtp_use_threads(htp, NULL, 4, NULL);
#endif
    evhtp_bind_socket(htp, "0.0.0.0", 8081, 1024);
 
    event_base_loop(evbase, 0);
 
    evhtp_unbind_socket(htp);
    evhtp_free(htp);
    event_base_free(evbase);
 
    return 0;
}

References