From: Thomas Bernard Date: Fri, 8 Jan 2016 21:36:20 +0000 (-0800) Subject: Added http method extending X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=8dcb94a4ca0999bdada6baa2a986f4c00a922060;p=libevent Added http method extending User can define his own response method by calling evhttp_set_ext_method_cmp() on the struct http, or evhttp_connection_set_ext_method_cmp() on the connection. We expose a new stucture `evhttp_ext_method` which is passed to the callback if it's set. So any field can be modified, with some exceptions (in evhttp_method_): If the cmp function is set, it has the ability to modify method, and flags. Other fields will be ignored. Flags returned are OR'd with the current flags. Based on changes to the #282 from: Mark Ellzey --- diff --git a/http-internal.h b/http-internal.h index 72016a01..3bf16b5b 100644 --- a/http-internal.h +++ b/http-internal.h @@ -105,6 +105,8 @@ struct evhttp_connection { struct event_base *base; struct evdns_base *dns_base; int ai_family; + + evhttp_ext_method_cb ext_method_cmp; }; /* A callback for an http server */ @@ -175,6 +177,8 @@ struct evhttp { void *newreqcbarg; struct event_base *base; + + evhttp_ext_method_cb ext_method_cmp; }; /* XXX most of these functions could be static. */ diff --git a/http.c b/http.c index 00267828..ecb12bbf 100644 --- a/http.c +++ b/http.c @@ -192,13 +192,15 @@ static void evhttp_get_request(struct evhttp *, evutil_socket_t, struct sockaddr static void evhttp_write_buffer(struct evhttp_connection *, void (*)(struct evhttp_connection *, void *), void *); static void evhttp_make_header(struct evhttp_connection *, struct evhttp_request *); +static int evhttp_method_may_have_body_(struct evhttp_connection *, enum evhttp_cmd_type); /* callbacks for bufferevent */ static void evhttp_read_cb(struct bufferevent *, void *); static void evhttp_write_cb(struct bufferevent *, void *); static void evhttp_error_cb(struct bufferevent *bufev, short what, void *arg); -static int evhttp_find_vhost(struct evhttp *http, struct evhttp **outhttp, - const char *hostname); +static int evhttp_find_vhost(struct evhttp *http, struct evhttp **outhttp, const char *hostname); +static const char *evhttp_method_(struct evhttp_connection *evcon, + enum evhttp_cmd_type type, ev_uint16_t *flags); #ifndef EVENT__HAVE_STRSEP /* strsep replacement for platforms that lack it. Only works if @@ -296,12 +298,15 @@ evhttp_htmlescape(const char *html) } /** Given an evhttp_cmd_type, returns a constant string containing the - * equivalent HTTP command, or NULL if the evhttp_command_type is + * equivalent HTTP command, or NULL if the evhttp_cmd_type is * unrecognized. */ static const char * -evhttp_method(enum evhttp_cmd_type type) +evhttp_method_(struct evhttp_connection *evcon, + enum evhttp_cmd_type type, ev_uint16_t *flags) { - const char *method; + struct evhttp_ext_method ext_method; + const char *method = NULL; + ev_uint16_t tmp_flags = EVHTTP_METHOD_HAS_BODY; switch (type) { case EVHTTP_REQ_GET: @@ -312,6 +317,7 @@ evhttp_method(enum evhttp_cmd_type type) break; case EVHTTP_REQ_HEAD: method = "HEAD"; + tmp_flags &= ~EVHTTP_METHOD_HAS_BODY; break; case EVHTTP_REQ_PUT: method = "PUT"; @@ -324,6 +330,7 @@ evhttp_method(enum evhttp_cmd_type type) break; case EVHTTP_REQ_TRACE: method = "TRACE"; + tmp_flags &= ~EVHTTP_METHOD_HAS_BODY; break; case EVHTTP_REQ_CONNECT: method = "CONNECT"; @@ -353,10 +360,41 @@ evhttp_method(enum evhttp_cmd_type type) method = "MOVE"; break; default: - method = NULL; + /* setup the structure to allow for the cmp. + * + * if the cmp function is set, it has the ability to + * modify method and flags. Other fields will be + * ignored. + * + * NOTE: the flags returned are OR'd with the current + * flags. + */ + tmp_flags = 0; + ext_method.method = NULL; + ext_method.type = type; + ext_method.flags = tmp_flags; + + if (evcon->ext_method_cmp != NULL && + evcon->ext_method_cmp(&ext_method) == 0) { + + if (ext_method.type != type) { + event_debug(("%s: callback modified type from %u to %u, not allowed", + __func__, type, ext_method.type)); + return NULL; + } + + method = ext_method.method; + tmp_flags |= ext_method.flags; + } + break; } + event_debug(("%s: type=%04x => '%s' flags=%04x", + __func__, (int)type, method, tmp_flags)); + + if (flags) + *flags = tmp_flags; return (method); } @@ -452,11 +490,12 @@ evhttp_make_header_request(struct evhttp_connection *evcon, struct evhttp_request *req) { const char *method; + ev_uint16_t flags; evhttp_remove_header(req->output_headers, "Proxy-Connection"); /* Generate request line */ - if (!(method = evhttp_method(req->type))) { + if (!(method = evhttp_method_(evcon, req->type, &flags))) { method = "NULL"; } @@ -464,9 +503,12 @@ evhttp_make_header_request(struct evhttp_connection *evcon, "%s %s HTTP/%d.%d\r\n", method, req->uri, req->major, req->minor); - /* Add the content length on a post or put request if missing */ - if ((req->type == EVHTTP_REQ_POST || req->type == EVHTTP_REQ_PUT) && - evhttp_find_header(req->output_headers, "Content-Length") == NULL){ + /* Add the content length on a request if missing + * Always add it for POST and PUT requests as clients expect it */ + if ((flags & EVHTTP_METHOD_HAS_BODY) && + (evbuffer_get_length(req->output_buffer) > 0 || + req->type == EVHTTP_REQ_POST || req->type == EVHTTP_REQ_PUT) && + evhttp_find_header(req->output_headers, "Content-Length") == NULL) { char size[22]; evutil_snprintf(size, sizeof(size), EV_SIZE_FMT, EV_SIZE_ARG(evbuffer_get_length(req->output_buffer))); @@ -1940,11 +1982,30 @@ evhttp_parse_request_line(struct evhttp_request *req, char *line, size_t len) break; } /* switch */ + if (!type) { + /* check extended methods, we only care about the + * type set by the cmp function if the cmp function + * returns a 0 value. + */ + struct evhttp_ext_method ext_method; + + ext_method.method = method; + ext_method.type = 0; + + if (req->evcon->ext_method_cmp && + req->evcon->ext_method_cmp(&ext_method) == 0) { + /* TODO: make sure the other fields in ext_method are + * not changed by the callback. + */ + type = ext_method.type; + } + } + if (!type) { event_debug(("%s: bad method %s on request %p from %s", - __func__, method, req, req->remote_host)); - /* No error yet; we'll give a better error later when we see that - * req->type is unsupported in evhttp_handle_request(). */ + __func__, method, req, req->remote_host)); + /* No error yet; we'll give a better error later when + * we see that req->type is unsupported. */ } req->type = type; @@ -2278,31 +2339,11 @@ evhttp_get_body_length(struct evhttp_request *req) } static int -evhttp_method_may_have_body(enum evhttp_cmd_type type) +evhttp_method_may_have_body_(struct evhttp_connection *evcon, enum evhttp_cmd_type type) { - switch (type) { - case EVHTTP_REQ_POST: - case EVHTTP_REQ_PUT: - case EVHTTP_REQ_PATCH: - case EVHTTP_REQ_PROPFIND: - case EVHTTP_REQ_PROPPATCH: - case EVHTTP_REQ_MKCOL: - case EVHTTP_REQ_LOCK: - case EVHTTP_REQ_UNLOCK: - case EVHTTP_REQ_COPY: - case EVHTTP_REQ_MOVE: - - case EVHTTP_REQ_GET: - case EVHTTP_REQ_DELETE: - case EVHTTP_REQ_OPTIONS: - case EVHTTP_REQ_CONNECT: - return 1; - - case EVHTTP_REQ_TRACE: - case EVHTTP_REQ_HEAD: - default: - return 0; - } + ev_uint16_t flags; + evhttp_method_(evcon, type, &flags); + return (flags & EVHTTP_METHOD_HAS_BODY) ? 1 : 0; } static void @@ -2312,7 +2353,7 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req) /* If this is a request without a body, then we are done */ if (req->kind == EVHTTP_REQUEST && - !evhttp_method_may_have_body(req->type)) { + !evhttp_method_may_have_body_(evcon, req->type)) { evhttp_connection_done(evcon); return; } @@ -2575,6 +2616,13 @@ int evhttp_connection_set_flags(struct evhttp_connection *evcon, return 0; } +void +evhttp_connection_set_ext_method_cmp(struct evhttp_connection *evcon, + evhttp_ext_method_cb cmp) +{ + evcon->ext_method_cmp = cmp; +} + void evhttp_connection_set_base(struct evhttp_connection *evcon, struct event_base *base) @@ -4040,6 +4088,12 @@ evhttp_set_allowed_methods(struct evhttp* http, ev_uint32_t methods) http->allowed_methods = methods; } +void +evhttp_set_ext_method_cmp(struct evhttp *http, evhttp_ext_method_cb cmp) +{ + http->ext_method_cmp = cmp; +} + int evhttp_set_cb(struct evhttp *http, const char *uri, void (*cb)(struct evhttp_request *, void *), void *cbarg) @@ -4476,6 +4530,7 @@ evhttp_get_request(struct evhttp *http, evutil_socket_t fd, * we need to know which http server it belongs to. */ evcon->http_server = http; + evcon->ext_method_cmp = http->ext_method_cmp; TAILQ_INSERT_TAIL(&http->connections, evcon, next); if (evhttp_associate_new_request_with_connection(evcon) == -1) diff --git a/include/event2/http.h b/include/event2/http.h index c9ec1175..c5cd7bd0 100644 --- a/include/event2/http.h +++ b/include/event2/http.h @@ -73,6 +73,7 @@ struct evkeyvalq; struct evhttp_bound_socket; struct evconnlistener; struct evdns_base; +struct evhttp_ext_method; /** * Create a new HTTP server. @@ -248,6 +249,26 @@ void evhttp_set_default_content_type(struct evhttp *http, EVENT2_EXPORT_SYMBOL void evhttp_set_allowed_methods(struct evhttp* http, ev_uint32_t methods); +typedef int (*evhttp_ext_method_cb)(struct evhttp_ext_method *); +/** + Sets the callback function which allows HTTP extended methods + to be supported by this server. + + The callback should : + - if method field is NULL : set method field according to type field + - else : set type and flags fields according to method string + - return 0 for success (known method / type) + - return -1 for error (unknown method / type) + + evhttp_set_allowed_methods still needs to be called. + + @param http the http server on which to add support to the methods + @param cmp the extended method callback + @see evhttp_ext_method +*/ +EVENT2_EXPORT_SYMBOL +void evhttp_set_ext_method_cmp(struct evhttp *http, evhttp_ext_method_cb cmp); + /** Set a callback for a specified URI @@ -552,6 +573,23 @@ enum evhttp_cmd_type { EVHTTP_REQ_MOVE = 1 << 15, }; +#define EVHTTP_REQ_MAX EVHTTP_REQ_MOVE + +/** + * @brief stucture that is passed to (and modified by) the + * extended method callback function + * + * @see evhttp_set_ext_method_cmp + * @see evhttp_connection_set_ext_method_cmp + */ +struct evhttp_ext_method { + const char *method; + ev_uint32_t type; /* @see enum evhttp_cmd_type */ + ev_uint16_t flags; /* Available flag : EVHTTP_METHOD_HAS_BODY */ +}; + +#define EVHTTP_METHOD_HAS_BODY 0x0001 + /** a request object can represent either a request or a reply */ enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE }; @@ -742,6 +780,15 @@ void evhttp_request_own(struct evhttp_request *req); EVENT2_EXPORT_SYMBOL int evhttp_request_is_owned(struct evhttp_request *req); +/** + * Sets extended method cmp callback for this http connection. + * + * @see evhttp_set_ext_method_cmp + */ +EVENT2_EXPORT_SYMBOL +void evhttp_connection_set_ext_method_cmp(struct evhttp_connection *evcon, + evhttp_ext_method_cb cmp); + /** * Returns the connection object associated with the request or NULL * diff --git a/test/regress_http.c b/test/regress_http.c index fdffee01..226a9b64 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -71,6 +71,29 @@ static struct event_base *exit_base; static char const BASIC_REQUEST_BODY[] = "This is funny"; +/* defines an extended HTTP method "CUSTOM" + * without body */ +#define EVHTTP_REQ_CUSTOM ((EVHTTP_REQ_MAX) << 1) + +static int ext_method_cb(struct evhttp_ext_method *p) +{ + if (p == NULL) + return -1; + if (p->method) { + if (strcmp(p->method, "CUSTOM") == 0) { + p->type = EVHTTP_REQ_CUSTOM; + p->flags = 0; /*EVHTTP_METHOD_HAS_BODY*/ + return 0; + } + } else { + if (p->type == EVHTTP_REQ_CUSTOM) { + p->method = "CUSTOM"; + return 0; + } + } + return -1; +} + static void http_basic_cb(struct evhttp_request *req, void *arg); static void http_timeout_cb(struct evhttp_request *req, void *arg); static void http_large_cb(struct evhttp_request *req, void *arg); @@ -78,6 +101,7 @@ static void http_chunked_cb(struct evhttp_request *req, void *arg); static void http_post_cb(struct evhttp_request *req, void *arg); static void http_put_cb(struct evhttp_request *req, void *arg); static void http_genmethod_cb(struct evhttp_request *req, void *arg); +static void http_custom_cb(struct evhttp_request *req, void *arg); static void http_delay_cb(struct evhttp_request *req, void *arg); static void http_large_delay_cb(struct evhttp_request *req, void *arg); static void http_badreq_cb(struct evhttp_request *req, void *arg); @@ -148,6 +172,9 @@ http_setup_gencb(ev_uint16_t *pport, struct event_base *base, int mask, evhttp_set_gencb(myhttp, cb, cbarg); + /* add support for extended HTTP methods */ + evhttp_set_ext_method_cmp(myhttp, ext_method_cb); + /* Register a callback for certain types of requests */ evhttp_set_cb(myhttp, "/test", http_basic_cb, myhttp); evhttp_set_cb(myhttp, "/test nonconformant", http_basic_cb, myhttp); @@ -165,6 +192,7 @@ http_setup_gencb(ev_uint16_t *pport, struct event_base *base, int mask, evhttp_set_cb(myhttp, "/unlockit", http_genmethod_cb, base); evhttp_set_cb(myhttp, "/copyit", http_genmethod_cb, base); evhttp_set_cb(myhttp, "/moveit", http_genmethod_cb, base); + evhttp_set_cb(myhttp, "/custom", http_custom_cb, base); evhttp_set_cb(myhttp, "/delay", http_delay_cb, base); evhttp_set_cb(myhttp, "/largedelay", http_large_delay_cb, base); evhttp_set_cb(myhttp, "/badrequest", http_badreq_cb, base); @@ -872,6 +900,78 @@ http_genmethod_test(void *arg, enum evhttp_cmd_type method, const char *name, co evutil_closesocket(fd); } +/* + * HTTP CUSTOM test, just piggyback on the basic test + */ +static void +http_custom_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + int empty = evhttp_find_header(evhttp_request_get_input_headers(req), "Empty") != NULL; + + /* Expecting a CUSTOM request */ + if (evhttp_request_get_command(req) != EVHTTP_REQ_CUSTOM) { + fprintf(stdout, "FAILED (custom type)\n"); + exit(1); + } + + TT_BLATHER(("%s: called\n", __func__)); + evbuffer_add_printf(evb, BASIC_REQUEST_BODY); + + /* allow sending of an empty reply */ + evhttp_send_reply(req, HTTP_OK, "Everything is fine", + !empty ? evb : NULL); + + evbuffer_free(evb); +} + +static void +http_custom_test(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev; + evutil_socket_t fd = -1; + const char *http_request; + ev_uint16_t port = 0; + struct evhttp *http; + + test_ok = 0; + + http = http_setup(&port, data->base, 0); + /* Allow custom */ + evhttp_set_allowed_methods(http, EVHTTP_REQ_CUSTOM); + + tt_assert(http); + fd = http_connect("127.0.0.1", port); + tt_int_op(fd, >=, 0); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, data->base); + + http_request = + "CUSTOM /custom HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + bufferevent_free(bev); + evutil_closesocket(fd); + fd = -1; + + evhttp_free(http); + + tt_int_op(test_ok, ==, 2); + end: + if (fd >= 0) + evutil_closesocket(fd); +} + static void http_delete_test(void *arg) { @@ -1024,10 +1124,10 @@ static void http_allowed_methods_test(void *arg) { struct basic_test_data *data = arg; - struct bufferevent *bev1, *bev2, *bev3; - evutil_socket_t fd1=-1, fd2=-1, fd3=-1; + struct bufferevent *bev1, *bev2, *bev3, *bev4; + evutil_socket_t fd1=-1, fd2=-1, fd3=-1, fd4=-1; const char *http_request; - char *result1=NULL, *result2=NULL, *result3=NULL; + char *result1=NULL, *result2=NULL, *result3=NULL, *result4=NULL; ev_uint16_t port = 0; struct evhttp *http = http_setup(&port, data->base, 0); @@ -1037,8 +1137,8 @@ http_allowed_methods_test(void *arg) fd1 = http_connect("127.0.0.1", port); tt_assert(fd1 != EVUTIL_INVALID_SOCKET); - /* GET is out; PATCH is in. */ - evhttp_set_allowed_methods(http, EVHTTP_REQ_PATCH); + /* GET is out; PATCH & CUSTOM are in. */ + evhttp_set_allowed_methods(http, EVHTTP_REQ_PATCH | EVHTTP_REQ_CUSTOM); /* Stupid thing to send a request */ bev1 = bufferevent_socket_new(data->base, fd1, 0); @@ -1092,9 +1192,28 @@ http_allowed_methods_test(void *arg) event_base_dispatch(data->base); + fd4 = http_connect("127.0.0.1", port); + tt_int_op(fd4, >=, 0); + + bev4 = bufferevent_socket_new(data->base, fd4, 0); + bufferevent_enable(bev4, EV_READ|EV_WRITE); + bufferevent_setcb(bev4, NULL, NULL, + http_allowed_methods_eventcb, &result4); + + http_request = + "CUSTOM /test HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev4, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + bufferevent_free(bev1); bufferevent_free(bev2); bufferevent_free(bev3); + bufferevent_free(bev4); evhttp_free(http); @@ -1110,6 +1229,10 @@ http_allowed_methods_test(void *arg) tt_assert(result3); tt_assert(!strncmp(result3, "HTTP/1.1 501 ", strlen("HTTP/1.1 501 "))); + /* Custom method (and allowed) */ + tt_assert(result4); + tt_assert(!strncmp(result4, "HTTP/1.1 200 ", strlen("HTTP/1.1 200 "))); + end: if (result1) free(result1); @@ -1117,12 +1240,16 @@ http_allowed_methods_test(void *arg) free(result2); if (result3) free(result3); + if (result4) + free(result4); if (fd1 >= 0) evutil_closesocket(fd1); if (fd2 >= 0) evutil_closesocket(fd2); if (fd3 >= 0) evutil_closesocket(fd3); + if (fd4 >= 0) + evutil_closesocket(fd4); } static void http_request_no_action_done(struct evhttp_request *, void *); @@ -1153,6 +1280,8 @@ http_connection_test_(struct basic_test_data *data, int persistent, } tt_assert(http); + evhttp_set_allowed_methods(http, EVHTTP_REQ_GET | EVHTTP_REQ_CUSTOM); + if (ssl) { #ifdef EVENT__HAVE_OPENSSL SSL *ssl = SSL_new(get_ssl_ctx()); @@ -1177,6 +1306,9 @@ http_connection_test_(struct basic_test_data *data, int persistent, tt_assert(evhttp_connection_get_server(evcon) == NULL); + /* add support for CUSTOM method */ + evhttp_connection_set_ext_method_cmp(evcon, ext_method_cb); + /* * At this point, we want to schedule a request to the HTTP * server using our make request method. @@ -1233,6 +1365,21 @@ http_connection_test_(struct basic_test_data *data, int persistent, event_base_dispatch(data->base); + /* make a CUSTOM request */ + test_ok = 0; + + req = evhttp_request_new(http_request_empty_done, data->base); + + /* our CUSTOM method doesn't have Body */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Empty", "itis"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_CUSTOM, "/test") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + end: if (evcon) evhttp_connection_free(evcon); @@ -5080,6 +5227,7 @@ struct testcase_t http_testcases[] = { HTTP(unlock), HTTP(copy), HTTP(move), + HTTP(custom), HTTP(allowed_methods), HTTP(failure), HTTP(connection), diff --git a/whatsnew-2.2.txt b/whatsnew-2.2.txt index c13d22fc..c89eee59 100644 --- a/whatsnew-2.2.txt +++ b/whatsnew-2.2.txt @@ -68,3 +68,33 @@ heavy computation. The watcher API is defined in . A concrete example of how watchers can help monitor server performance is available in "sample/watch-timing.c". + +* Support for custom HTTP methods + +Libevent HTTP code now supports defining custom HTTP methods. It is done +through a callback: + + #define EVHTTP_REQ_CUSTOM ((EVHTTP_REQ_MAX) << 1) + static int ext_method_cb(struct evhttp_ext_method *p) + { + if (p == NULL) + return -1; + if (p->method) { + if (strcmp(p->method, "CUSTOM") == 0) { + p->type = EVHTTP_REQ_CUSTOM; + p->flags = 0; /*EVHTTP_METHOD_HAS_BODY*/ + return 0; + } + } else { + if (p->type == EVHTTP_REQ_CUSTOM) { + p->method = "CUSTOM"; + return 0; + } + } + } + +And to register this callback with http server you can use: + evhttp_set_ext_method_cmp(http, ext_method_cb); + +Or registering callback with one client only: + evhttp_connection_set_ext_method_cmp(evcon, ext_method_cb);