Libevhtp

From 탱이의 잡동사니
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Overview

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

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

Usage

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

<source lang=bash> $ gcc -o main main.c -levent -levhtp </source>

하지만 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

<source lang=c> 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 */
  1. ifndef EVHTP_DISABLE_SSL
   evhtp_ssl_ctx_t * ssl_ctx;   /**< if ssl enabled, this is the servers CTX */
   evhtp_ssl_cfg_t * ssl_cfg;
  1. endif
  1. ifndef EVHTP_DISABLE_EVTHR
   evthr_pool_t * thr_pool;     /**< connection threadpool */
  1. endif
  1. 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;
  1. 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;

}; </source>

evhtp_callback_t

<source lang=c> 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;
  1. ifndef EVHTP_DISABLE_REGEX
       regex_t * regex;
  1. endif
   } val;
   TAILQ_ENTRY(evhtp_callback_s) next;

}; </source>

evhtp_request_t

HTTP request 요청시 모든 정보가 저장되는 구조체이다. <source lang=c> 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;

}; </source>

evhtp_connection_t

<source lang=c> 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; </source>


evhtp_hooks_t

<source lang=c> 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; </source>

evhtp_uri_t

<source lang=c> /**

* @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; </source>

evbuf_t

<source lang=c> typedef struct evbuffer evbuf_t; </source>

evhtp_headers_t

<source lang=c>

  1. 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); </source>

htp_method

http request 요청 타입 <source lang=c> 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,

}; </source>

Functions

생성/삭제

<source lang=c> /**

* @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); </source> evhtp 인스턴스 생성/삭제 함수

콜백 관련

  • Interface

<source lang=c> /**

* @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
*/
  1. ifndef EVHTP_DISABLE_REGEX

evhtp_callback_t * evhtp_set_regex_cb(evhtp_t * htp, const char * pattern, evhtp_callback_cb cb, void * arg);

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

  • Example

<source lang=c> 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"); </source>

connection hook

  • Interface

<source lang=c> /**

* @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

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

Listen 소켓 설정/해제

  • Interface

<source lang=c> /**

* @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); </source> Listen 주소를 설정/해제한다. 주소 설정 시, 자동으로 주소 타입을 파싱해서 알맞은 소켓 타입으로 생성한 후, listen 한다. "0.0.0.0" 설정시, localhost 주소를 listen 한다.

  • Example

<source lang=c> evhtp_bind_socket(htp, "0.0.0.0", 8081, 1024);

evhtp_unbind_socket(htp); </source>

evhtp_send_reply

HTTP Request 를 보낸 클라이언트로 응답값을 전송한다. <source lang=c> 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); </source>

사용가능한 응답값은 다음과 같다. <source lang=c>

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

</source>


evhtp_request_get_method

HTTP request 의 요청 타입을 리턴한다. <source lang=c> htp_method evhtp_request_get_method(evhtp_request_t * r) {

   return htparser_get_method(r->conn->parser);

} </source>

Samples

test_basic

  • test_basic.c

<source lang=c>

  1. include <stdio.h>
  2. include <stdlib.h>
  3. include <string.h>
  4. include <stdint.h>
  5. include <errno.h>
  6. 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");
  1. ifndef EVHTP_DISABLE_EVTHR
   evhtp_use_threads(htp, NULL, 4, NULL);
  1. 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;

} </source>

References

<references />