| #include <stdio.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdint.h> |
| #include <errno.h> |
| #include <signal.h> |
| #include <inttypes.h> |
| #include <event2/event.h> |
| |
| #include "../evhtp-internal.h" |
| #include "../evhtp.h" |
| |
| |
| #ifndef EVHTP_DISABLE_EVTHR |
| int use_threads = 0; |
| int num_threads = 0; |
| #endif |
| char * bind_addr = "0.0.0.0"; |
| uint16_t bind_port = 8081; |
| char * ext_body = NULL; |
| char * ssl_pem = NULL; |
| char * ssl_ca = NULL; |
| char * ssl_capath = NULL; |
| size_t bw_limit = 0; |
| uint64_t max_keepalives = 0; |
| int backlog = 1024; |
| |
| struct pauser { |
| event_t * timer_ev; |
| evhtp_request_t * request; |
| struct timeval * tv; |
| }; |
| |
| /* pause testing */ |
| static void |
| resume_request_timer(evutil_socket_t sock, short which, void * arg) { |
| struct pauser * pause = (struct pauser *)arg; |
| |
| printf("resume_request_timer(%p) timer_ev = %p\n", pause->request->conn, pause->timer_ev); |
| fflush(stdout); |
| |
| evhtp_request_resume(pause->request); |
| } |
| |
| static evhtp_res |
| pause_cb(evhtp_request_t * request, evhtp_header_t * header, void * arg) { |
| struct pauser * pause = (struct pauser *)arg; |
| int s = rand() % 1000000; |
| |
| printf("pause_cb(%p) pause == %p, timer_ev = %p\n", |
| request->conn, pause, pause->timer_ev); |
| printf("pause_cb(%p) k=%s, v=%s timer_ev = %p\n", request->conn, |
| header->key, header->val, pause->timer_ev); |
| printf("pause_cb(%p) setting to %ld usec sleep timer_ev = %p\n", |
| request->conn, (long int)s, pause->timer_ev); |
| |
| pause->tv->tv_sec = 0; |
| pause->tv->tv_usec = s; |
| |
| if (evtimer_pending(pause->timer_ev, NULL)) { |
| evtimer_del(pause->timer_ev); |
| } |
| |
| evtimer_add(pause->timer_ev, pause->tv); |
| |
| return EVHTP_RES_PAUSE; |
| } |
| |
| static evhtp_res |
| pause_connection_fini(evhtp_connection_t * connection, void * arg) { |
| printf("pause_connection_fini(%p)\n", connection); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| static evhtp_res |
| pause_request_fini(evhtp_request_t * request, void * arg) { |
| struct pauser * pause = (struct pauser *)arg; |
| |
| printf("pause_request_fini() req=%p, c=%p\n", request, request->conn); |
| event_free(pause->timer_ev); |
| |
| free(pause->tv); |
| free(pause); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| static evhtp_res |
| pause_init_cb(evhtp_request_t * req, evhtp_path_t * path, void * arg) { |
| evbase_t * evbase = req->conn->evbase; |
| struct pauser * pause = calloc(sizeof(struct pauser), 1); |
| |
| pause->tv = calloc(sizeof(struct timeval), 1); |
| |
| pause->timer_ev = evtimer_new(evbase, resume_request_timer, pause); |
| pause->request = req; |
| |
| evhtp_set_hook(&req->hooks, evhtp_hook_on_header, pause_cb, pause); |
| evhtp_set_hook(&req->hooks, evhtp_hook_on_request_fini, pause_request_fini, pause); |
| evhtp_set_hook(&req->conn->hooks, evhtp_hook_on_connection_fini, pause_connection_fini, NULL); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| static void |
| test_pause_cb(evhtp_request_t * request, void * arg) { |
| printf("test_pause_cb(%p)\n", request->conn); |
| evhtp_send_reply(request, EVHTP_RES_OK); |
| } |
| |
| static void |
| _owned_readcb(evbev_t * bev, void * arg) { |
| /* echo the input back to the client */ |
| bufferevent_write_buffer(bev, bufferevent_get_input(bev)); |
| } |
| |
| static void |
| _owned_eventcb(evbev_t * bev, short events, void * arg) { |
| bufferevent_free(bev); |
| } |
| |
| static void |
| test_ownership(evhtp_request_t * request, void * arg) { |
| evhtp_connection_t * conn = evhtp_request_get_connection(request); |
| evbev_t * bev = evhtp_connection_take_ownership(conn); |
| |
| bufferevent_enable(bev, EV_READ); |
| bufferevent_setcb(bev, |
| _owned_readcb, NULL, |
| _owned_eventcb, NULL); |
| } |
| |
| #ifndef EVHTP_DISABLE_REGEX |
| static void |
| test_regex(evhtp_request_t * req, void * arg) { |
| evbuffer_add_printf(req->buffer_out, |
| "start = '%s', end = '%s\n", |
| req->uri->path->match_start, |
| req->uri->path->match_end); |
| |
| evhtp_send_reply(req, EVHTP_RES_OK); |
| } |
| |
| static void |
| dynamic_cb(evhtp_request_t * r, void * arg) { |
| const char * name = arg; |
| |
| evbuffer_add_printf(r->buffer_out, "dynamic_cb = %s\n", name); |
| evhtp_send_reply(r, EVHTP_RES_OK); |
| } |
| |
| static void |
| create_callback(evhtp_request_t * r, void * arg) { |
| char * uri; |
| char * nuri; |
| size_t urilen; |
| |
| uri = r->uri->path->match_start; |
| urilen = strlen(uri); |
| |
| if (urilen == 0) { |
| return evhtp_send_reply(r, EVHTP_RES_BADREQ); |
| } |
| |
| nuri = calloc(urilen + 2, 1); |
| |
| snprintf(nuri, urilen + 2, "/%s", uri); |
| evhtp_set_cb(r->htp, nuri, dynamic_cb, nuri); |
| |
| evhtp_send_reply(r, EVHTP_RES_OK); |
| } |
| |
| #endif |
| |
| static void |
| test_foo_cb(evhtp_request_t * req, void * arg ) { |
| evbuffer_add_reference(req->buffer_out, |
| "test_foo_cb\n", 12, NULL, NULL); |
| |
| evhtp_send_reply(req, EVHTP_RES_OK); |
| } |
| |
| static void |
| test_500_cb(evhtp_request_t * req, void * arg ) { |
| evbuffer_add_reference(req->buffer_out, |
| "test_500_cb\n", 12, NULL, NULL); |
| |
| evhtp_send_reply(req, EVHTP_RES_SERVERR); |
| } |
| |
| static void |
| test_max_body(evhtp_request_t * req, void * arg) { |
| evbuffer_add_reference(req->buffer_out, |
| "test_max_body\n", 14, NULL, NULL); |
| |
| evhtp_send_reply(req, EVHTP_RES_OK); |
| } |
| |
| const char * chunk_strings[] = { |
| "I give you the light of Eärendil,\n", |
| "our most beloved star.\n", |
| "May it be a light for you in dark places,\n", |
| "when all other lights go out.\n", |
| NULL |
| }; |
| |
| static void |
| test_chunking(evhtp_request_t * req, void * arg) { |
| const char * chunk_str; |
| evbuf_t * buf; |
| int i = 0; |
| |
| buf = evbuffer_new(); |
| |
| evhtp_send_reply_chunk_start(req, EVHTP_RES_OK); |
| |
| while ((chunk_str = chunk_strings[i++]) != NULL) { |
| evbuffer_add(buf, chunk_str, strlen(chunk_str)); |
| |
| evhtp_send_reply_chunk(req, buf); |
| |
| evbuffer_drain(buf, -1); |
| } |
| |
| evhtp_send_reply_chunk_end(req); |
| evbuffer_free(buf); |
| } |
| |
| static void |
| test_bar_cb(evhtp_request_t * req, void * arg) { |
| evhtp_send_reply(req, EVHTP_RES_OK); |
| } |
| |
| static void |
| test_glob_cb(evhtp_request_t * req, void * arg) { |
| evbuffer_add(req->buffer_out, "test_glob_cb\n", 13); |
| evhtp_send_reply(req, EVHTP_RES_OK); |
| } |
| |
| static void |
| test_default_cb(evhtp_request_t * req, void * arg) { |
| evbuffer_add_reference(req->buffer_out, |
| "test_default_cb\n", 16, NULL, NULL); |
| |
| |
| evhtp_send_reply(req, EVHTP_RES_OK); |
| } |
| |
| static evhtp_res |
| print_kv(evhtp_request_t * req, evhtp_header_t * hdr, void * arg) { |
| evbuffer_add_printf(req->buffer_out, |
| "print_kv() key = '%s', val = '%s'\n", |
| hdr->key, hdr->val); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| static int |
| output_header(evhtp_header_t * header, void * arg) { |
| evbuf_t * buf = arg; |
| |
| evbuffer_add_printf(buf, "print_kvs() key = '%s', val = '%s'\n", |
| header->key, header->val); |
| return 0; |
| } |
| |
| static evhtp_res |
| print_kvs(evhtp_request_t * req, evhtp_headers_t * hdrs, void * arg ) { |
| evhtp_headers_for_each(hdrs, output_header, req->buffer_out); |
| return EVHTP_RES_OK; |
| } |
| |
| static evhtp_res |
| print_path(evhtp_request_t * req, evhtp_path_t * path, void * arg) { |
| if (ext_body) { |
| evbuffer_add_printf(req->buffer_out, "ext_body: '%s'\n", ext_body); |
| } |
| |
| evbuffer_add_printf(req->buffer_out, |
| "print_path() full = '%s'\n" |
| " path = '%s'\n" |
| " file = '%s'\n" |
| " match start = '%s'\n" |
| " match_end = '%s'\n" |
| " methno = '%d'\n", |
| path->full, path->path, path->file, |
| path->match_start, path->match_end, |
| evhtp_request_get_method(req)); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| static evhtp_res |
| print_data(evhtp_request_t * req, evbuf_t * buf, void * arg) { |
| #ifndef NDEBUG |
| evbuffer_add_printf(req->buffer_out, |
| "got %zu bytes of data\n", |
| evbuffer_get_length(buf)); |
| /* printf("%.*s", (int)evbuffer_get_length(buf), (char *)evbuffer_pullup(buf, evbuffer_get_length(buf))); */ |
| #endif |
| evbuffer_drain(buf, -1); |
| return EVHTP_RES_OK; |
| } |
| |
| static evhtp_res |
| print_new_chunk_len(evhtp_request_t * req, uint64_t len, void * arg) { |
| evbuffer_add_printf(req->buffer_out, "started new chunk, %" PRId64 "u bytes\n", len); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| static evhtp_res |
| print_chunk_complete(evhtp_request_t * req, void * arg) { |
| evbuffer_add_printf(req->buffer_out, "ended a single chunk\n"); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| static evhtp_res |
| print_chunks_complete(evhtp_request_t * req, void * arg) { |
| evbuffer_add_printf(req->buffer_out, "all chunks read\n"); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| #ifndef EVHTP_DISABLE_REGEX |
| static evhtp_res |
| test_regex_hdrs_cb(evhtp_request_t * req, evhtp_headers_t * hdrs, void * arg ) { |
| return EVHTP_RES_OK; |
| } |
| |
| #endif |
| |
| static evhtp_res |
| set_max_body(evhtp_request_t * req, evhtp_headers_t * hdrs, void * arg) { |
| evhtp_request_set_max_body_size(req, 1024); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| static evhtp_res |
| test_pre_accept(evhtp_connection_t * c, void * arg) { |
| uint16_t port = *(uint16_t *)arg; |
| |
| if (port > 10000) { |
| return EVHTP_RES_ERROR; |
| } |
| |
| return EVHTP_RES_OK; |
| } |
| |
| static evhtp_res |
| test_fini(evhtp_request_t * r, void * arg) { |
| struct ev_token_bucket_cfg * tcfg = arg; |
| |
| if (tcfg) { |
| ev_token_bucket_cfg_free(tcfg); |
| } |
| |
| return EVHTP_RES_OK; |
| } |
| |
| #if 0 |
| static evhtp_res |
| print_hostname(evhtp_request_t * r, const char * host, void * arg) { |
| printf("%s\n", host); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| #endif |
| |
| static evhtp_res |
| set_my_connection_handlers(evhtp_connection_t * conn, void * arg) { |
| struct timeval tick; |
| struct ev_token_bucket_cfg * tcfg = NULL; |
| |
| evhtp_set_hook(&conn->hooks, evhtp_hook_on_header, print_kv, "foo"); |
| evhtp_set_hook(&conn->hooks, evhtp_hook_on_headers, print_kvs, "bar"); |
| evhtp_set_hook(&conn->hooks, evhtp_hook_on_path, print_path, "baz"); |
| evhtp_set_hook(&conn->hooks, evhtp_hook_on_read, print_data, "derp"); |
| evhtp_set_hook(&conn->hooks, evhtp_hook_on_new_chunk, print_new_chunk_len, NULL); |
| evhtp_set_hook(&conn->hooks, evhtp_hook_on_chunk_complete, print_chunk_complete, NULL); |
| evhtp_set_hook(&conn->hooks, evhtp_hook_on_chunks_complete, print_chunks_complete, NULL); |
| /* evhtp_set_hook(&conn->hooks, evhtp_hook_on_hostname, print_hostname, NULL); */ |
| |
| if (bw_limit > 0) { |
| tick.tv_sec = 0; |
| tick.tv_usec = 500 * 100; |
| |
| tcfg = ev_token_bucket_cfg_new(bw_limit, bw_limit, bw_limit, bw_limit, &tick); |
| |
| bufferevent_set_rate_limit(conn->bev, tcfg); |
| } |
| |
| evhtp_set_hook(&conn->hooks, evhtp_hook_on_request_fini, test_fini, tcfg); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| #ifndef EVHTP_DISABLE_SSL |
| static int |
| dummy_ssl_verify_callback(int ok, X509_STORE_CTX * x509_store) { |
| return 1; |
| } |
| |
| static int |
| dummy_check_issued_cb(X509_STORE_CTX * ctx, X509 * x, X509 * issuer) { |
| return 1; |
| } |
| |
| #endif |
| |
| const char * optstr = "htn:a:p:r:s:c:C:l:N:m:b:"; |
| |
| const char * help = |
| "Options: \n" |
| " -h : This help text\n" |
| #ifndef EVHTP_DISABLE_EVTHR |
| " -t : Run requests in a thread (default: off)\n" |
| " -n <int> : Number of threads (default: 0 if -t is off, 4 if -t is on)\n" |
| #endif |
| #ifndef EVHTP_DISABLE_SSL |
| " -s <pem> : Enable SSL and PEM (default: NULL)\n" |
| " -c <ca> : CA cert file (default: NULL)\n" |
| " -C <path>: CA Path (default: NULL)\n" |
| #endif |
| " -l <int> : Max bandwidth (in bytes) (default: NULL)\n" |
| " -r <str> : Document root (default: .)\n" |
| " -N <str> : Add this string to body. (default: NULL)\n" |
| " -a <str> : Bind Address (default: 0.0.0.0)\n" |
| " -p <int> : Bind Port (default: 8081)\n" |
| " -m <int> : Max keepalive requests (default: 0)\n"; |
| |
| |
| int |
| parse_args(int argc, char ** argv) { |
| extern char * optarg; |
| extern int optind; |
| extern int opterr; |
| extern int optopt; |
| int c; |
| |
| while ((c = getopt(argc, argv, optstr)) != -1) { |
| switch (c) { |
| case 'h': |
| printf("Usage: %s [opts]\n%s", argv[0], help); |
| return -1; |
| case 'N': |
| ext_body = strdup(optarg); |
| break; |
| case 'a': |
| bind_addr = strdup(optarg); |
| break; |
| case 'p': |
| bind_port = atoi(optarg); |
| break; |
| #ifndef EVHTP_DISABLE_EVTHR |
| case 't': |
| use_threads = 1; |
| break; |
| case 'n': |
| num_threads = atoi(optarg); |
| break; |
| #endif |
| #ifndef EVHTP_DISABLE_SSL |
| case 's': |
| ssl_pem = strdup(optarg); |
| break; |
| case 'c': |
| ssl_ca = strdup(optarg); |
| break; |
| case 'C': |
| ssl_capath = strdup(optarg); |
| break; |
| #endif |
| case 'l': |
| bw_limit = atoll(optarg); |
| break; |
| case 'm': |
| max_keepalives = atoll(optarg); |
| break; |
| case 'b': |
| backlog = atoll(optarg); |
| break; |
| default: |
| printf("Unknown opt %s\n", optarg); |
| return -1; |
| } /* switch */ |
| } |
| |
| #ifndef EVHTP_DISABLE_EVTHR |
| if (use_threads && num_threads == 0) { |
| num_threads = 4; |
| } |
| #endif |
| |
| return 0; |
| } /* parse_args */ |
| |
| static void |
| sigint(int sig, short why, void * data) { |
| event_base_loopexit(data, NULL); |
| } |
| |
| int |
| main(int argc, char ** argv) { |
| struct event * ev_sigint; |
| evbase_t * evbase = NULL; |
| evhtp_t * htp = NULL; |
| evhtp_callback_t * cb_1 = NULL; |
| evhtp_callback_t * cb_2 = NULL; |
| evhtp_callback_t * cb_3 = NULL; |
| evhtp_callback_t * cb_4 = NULL; |
| evhtp_callback_t * cb_5 = NULL; |
| |
| #ifndef EVHTP_DISABLE_REGEX |
| evhtp_callback_t * cb_6 = NULL; |
| #endif |
| evhtp_callback_t * cb_7 = NULL; |
| #ifndef EVHTP_DISABLE_REGEX |
| evhtp_callback_t * cb_8 = NULL; |
| #endif |
| evhtp_callback_t * cb_9 = NULL; |
| evhtp_callback_t * cb_10 = NULL; |
| evhtp_callback_t * cb_11 = NULL; |
| evhtp_callback_t * cb_12 = NULL; |
| |
| if (parse_args(argc, argv) < 0) { |
| exit(1); |
| } |
| |
| srand((unsigned)time(NULL)); |
| |
| evbase = event_base_new(); |
| htp = evhtp_new(evbase, NULL); |
| |
| evhtp_set_parser_flags(htp, EVHTP_PARSE_QUERY_FLAG_LENIENT); |
| evhtp_set_max_keepalive_requests(htp, max_keepalives); |
| |
| /* htp->enable_nodelay = 1; */ |
| /* htp->enable_defer_accept = 1; */ |
| htp->enable_reuseport = 1; |
| |
| cb_1 = evhtp_set_cb(htp, "/ref", test_default_cb, "fjdkls"); |
| evhtp_assert(cb_1 != NULL); |
| |
| cb_2 = evhtp_set_cb(htp, "/foo", test_foo_cb, "bar"); |
| evhtp_assert(cb_2 != NULL); |
| |
| cb_3 = evhtp_set_cb(htp, "/foo/", test_foo_cb, "bar"); |
| evhtp_assert(cb_3 != NULL); |
| |
| cb_4 = evhtp_set_cb(htp, "/bar", test_bar_cb, "baz"); |
| evhtp_assert(cb_4 != NULL); |
| |
| cb_5 = evhtp_set_cb(htp, "/500", test_500_cb, "500"); |
| evhtp_assert(cb_5 != NULL); |
| |
| #ifndef EVHTP_DISABLE_REGEX |
| cb_6 = evhtp_set_regex_cb(htp, "^(/anything/).*", test_regex, NULL); |
| evhtp_assert(cb_6 != NULL); |
| #endif |
| cb_7 = evhtp_set_cb(htp, "/pause", test_pause_cb, NULL); |
| evhtp_assert(cb_7 != NULL); |
| #ifndef EVHTP_DISABLE_REGEX |
| cb_8 = evhtp_set_regex_cb(htp, "^/create/(.*)", create_callback, NULL); |
| evhtp_assert(cb_8 != NULL); |
| #endif |
| cb_9 = evhtp_set_glob_cb(htp, "*/glob/*", test_glob_cb, NULL); |
| evhtp_assert(cb_9 != NULL); |
| |
| cb_10 = evhtp_set_cb(htp, "/max_body_size", test_max_body, NULL); |
| evhtp_assert(cb_10 != NULL); |
| |
| /* set a callback to test out chunking API */ |
| cb_11 = evhtp_set_cb(htp, "/chunkme", test_chunking, NULL); |
| evhtp_assert(cb_11 != NULL); |
| |
| /* set a callback which takes ownership of the underlying bufferevent and |
| * just starts echoing things |
| */ |
| cb_12 = evhtp_set_cb(htp, "/ownme", test_ownership, NULL); |
| evhtp_assert(cb_12 != NULL); |
| |
| /* set a callback to pause on each header for cb_7 */ |
| evhtp_set_hook(&cb_7->hooks, evhtp_hook_on_path, pause_init_cb, NULL); |
| |
| /* set a callback to set hooks specifically for the cb_6 callback */ |
| #ifndef EVHTP_DISABLE_REGEX |
| evhtp_set_hook(&cb_6->hooks, evhtp_hook_on_headers, test_regex_hdrs_cb, NULL); |
| #endif |
| |
| evhtp_set_hook(&cb_10->hooks, evhtp_hook_on_headers, set_max_body, NULL); |
| |
| /* set a default request handler */ |
| evhtp_set_gencb(htp, test_default_cb, "foobarbaz"); |
| |
| /* set a callback invoked before a connection is accepted */ |
| evhtp_set_pre_accept_cb(htp, test_pre_accept, &bind_port); |
| |
| /* set a callback to set per-connection hooks (via a post_accept cb) */ |
| evhtp_set_post_accept_cb(htp, set_my_connection_handlers, NULL); |
| |
| #ifndef EVHTP_DISABLE_SSL |
| if (ssl_pem != NULL) { |
| evhtp_ssl_cfg_t scfg = { |
| .pemfile = ssl_pem, |
| .privfile = ssl_pem, |
| .cafile = ssl_ca, |
| .capath = ssl_capath, |
| .ciphers = "RC4+RSA:HIGH:+MEDIUM:+LOW", |
| .ssl_opts = SSL_OP_NO_SSLv2, |
| .ssl_ctx_timeout = 60 * 60 * 48, |
| .verify_peer = SSL_VERIFY_PEER, |
| .verify_depth = 42, |
| .x509_verify_cb = dummy_ssl_verify_callback, |
| .x509_chk_issued_cb = dummy_check_issued_cb, |
| .scache_type = evhtp_ssl_scache_type_internal, |
| .scache_size = 1024, |
| .scache_timeout = 1024, |
| .scache_init = NULL, |
| .scache_add = NULL, |
| .scache_get = NULL, |
| .scache_del = NULL, |
| }; |
| |
| evhtp_ssl_init(htp, &scfg); |
| #ifndef EVHTP_DISABLE_EVTHR |
| if (use_threads) { |
| #define OPENSSL_THREAD_DEFINES |
| #include <openssl/opensslconf.h> |
| #if defined(OPENSSL_THREADS) |
| #else |
| fprintf(stderr, "Your version of OpenSSL does not support threading!\n"); |
| exit(-1); |
| #endif |
| } |
| #endif |
| } |
| #endif |
| |
| #ifndef EVHTP_DISABLE_EVTHR |
| if (use_threads) { |
| evhtp_use_threads(htp, NULL, num_threads, NULL); |
| } |
| #endif |
| |
| if (evhtp_bind_socket(htp, bind_addr, bind_port, backlog) < 0) { |
| fprintf(stderr, "Could not bind socket: %s\n", strerror(errno)); |
| exit(-1); |
| } |
| |
| ev_sigint = evsignal_new(evbase, SIGINT, sigint, evbase); |
| evsignal_add(ev_sigint, NULL); |
| |
| event_base_loop(evbase, 0); |
| |
| event_free(ev_sigint); |
| evhtp_unbind_socket(htp); |
| |
| evhtp_free(htp); |
| event_base_free(evbase); |
| |
| return 0; |
| } /* main */ |
| |