Libevhtp
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
이벤트 베이스
<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 */
- 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;
}; </source>
이벤트 콜백
<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;
- ifndef EVHTP_DISABLE_REGEX
regex_t * regex;
- 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>
- 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 */
- 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); </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>
- 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
</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>
- 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;
} </source>
References
<references />