diff --git a/ChangeLog b/ChangeLog index 72c84980..2f97370c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -97,7 +97,8 @@ Changes in current version: o deprecate timeout_* event functions by moving them to event_compat.h o Move windows gettimeofday replacement into a new evutil_gettimeofday(). o Make configure script work on IRIX. - + o provide a method for canceling ongoing http requests. + Changes in 1.4.0: o allow \r or \n individually to separate HTTP headers instead of the standard "\r\n"; from Charles Kerr. diff --git a/http-internal.h b/http-internal.h index 1ce58f84..d282206e 100644 --- a/http-internal.h +++ b/http-internal.h @@ -23,7 +23,8 @@ enum evhttp_connection_error { EVCON_HTTP_TIMEOUT, EVCON_HTTP_EOF, EVCON_HTTP_INVALID_HEADER, - EVCON_HTTP_BUFFER_ERROR + EVCON_HTTP_BUFFER_ERROR, + EVCON_HTTP_REQUEST_CANCEL }; struct evbuffer; diff --git a/http.c b/http.c index b8927235..47a48f90 100644 --- a/http.c +++ b/http.c @@ -483,6 +483,7 @@ evhttp_connection_incoming_fail(struct evhttp_request *req, return (-1); case EVCON_HTTP_INVALID_HEADER: case EVCON_HTTP_BUFFER_ERROR: + case EVCON_HTTP_REQUEST_CANCEL: default: /* xxx: probably should just error on default */ /* the callback looks at the uri to determine errors */ if (req->uri) { @@ -525,14 +526,23 @@ evhttp_connection_fail(struct evhttp_connection *evcon, return; } - /* save the callback for later; the cb might free our object */ - cb = req->cb; - cb_arg = req->cb_arg; + /* when the request was canceled, the callback is not executed */ + if (error != EVCON_HTTP_REQUEST_CANCEL) { + /* save the callback for later; the cb might free our object */ + cb = req->cb; + cb_arg = req->cb_arg; + } else { + cb = NULL; + cb_arg = NULL; + } TAILQ_REMOVE(&evcon->requests, req, next); evhttp_request_free(req); - /* xxx: maybe we should fail all requests??? */ + /* do not fail all requests; the next request is going to get + * send over a new connection. when a user cancels a request, + * all other pending requests should be processed as normal + */ /* reset the connection */ evhttp_connection_reset(evcon); @@ -1574,6 +1584,32 @@ evhttp_make_request(struct evhttp_connection *evcon, return (0); } +void +evhttp_cancel_request(struct evhttp_request *req) +{ + struct evhttp_connection *evcon = req->evcon; + if (evcon != NULL) { + /* We need to remove it from the connection */ + if (TAILQ_FIRST(&evcon->requests) == req) { + /* it's currently being worked on, so reset + * the connection. + */ + evhttp_connection_fail(evcon, + EVCON_HTTP_REQUEST_CANCEL); + + /* connection fail freed the request */ + return; + } else { + /* otherwise, we can just remove it from the + * queue + */ + TAILQ_REMOVE(&evcon->requests, req, next); + } + } + + evhttp_request_free(req); +} + /* * Reads data from file descriptor into request structure * Request structure needs to be set up correctly. diff --git a/include/event2/http.h b/include/event2/http.h index 9d0c1d3d..c8826f8e 100644 --- a/include/event2/http.h +++ b/include/event2/http.h @@ -274,11 +274,37 @@ void evhttp_connection_set_closecb(struct evhttp_connection *evcon, void evhttp_connection_get_peer(struct evhttp_connection *evcon, char **address, ev_uint16_t *port); -/** The connection gets ownership of the request */ +/** + Make an HTTP request over the specified connection. + + The connection gets ownership of the request. + + @param evcon the evhttp_connection object over which to send the request + @param req the previously created and configured request object + @param type the request type EVHTTP_REQ_GET, EVHTTP_REQ_POST, etc. + @param uri the URI associated with the request + @return 0 on success, -1 on failure + @see evhttp_cancel_request() +*/ int evhttp_make_request(struct evhttp_connection *evcon, struct evhttp_request *req, enum evhttp_cmd_type type, const char *uri); +/** + Cancels a pending HTTP request. + + Cancels an ongoing HTTP request. The callback associated with this request + is not executed and the request object is freed. If the request is + currently being processed, e.g. it is ongoing, the corresponding + evhttp_connection object is going to get reset. + + A request cannot be canceled if its callback has executed already. + + @param req the evhttp_request to cancel; req becomes invalid after this call. +*/ +void evhttp_cancel_request(struct evhttp_request *req); + + /** Returns the request URI */ const char *evhttp_request_get_uri(struct evhttp_request *req); /** Returns the input headers */ diff --git a/test/regress_http.c b/test/regress_http.c index 019bb852..09f8b4e2 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -66,12 +66,13 @@ static struct event_base *base; void http_suite(void); -void http_basic_cb(struct evhttp_request *req, void *arg); -void http_chunked_cb(struct evhttp_request *req, void *arg); -void http_post_cb(struct evhttp_request *req, void *arg); -void http_put_cb(struct evhttp_request *req, void *arg); -void http_delete_cb(struct evhttp_request *req, void *arg); -void http_dispatcher_cb(struct evhttp_request *req, void *arg); +static void http_basic_cb(struct evhttp_request *req, void *arg); +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_delete_cb(struct evhttp_request *req, void *arg); +static void http_delay_cb(struct evhttp_request *req, void *arg); +static void http_dispatcher_cb(struct evhttp_request *req, void *arg); static struct evhttp * http_setup(short *pport, struct event_base *base) @@ -98,6 +99,7 @@ http_setup(short *pport, struct event_base *base) evhttp_set_cb(myhttp, "/postit", http_post_cb, NULL); evhttp_set_cb(myhttp, "/putit", http_put_cb, NULL); evhttp_set_cb(myhttp, "/deleteit", http_delete_cb, NULL); + evhttp_set_cb(myhttp, "/delay", http_delay_cb, NULL); evhttp_set_cb(myhttp, "/", http_dispatcher_cb, NULL); *pport = port; @@ -207,7 +209,7 @@ http_errorcb(struct bufferevent *bev, short what, void *arg) event_loopexit(NULL); } -void +static void http_basic_cb(struct evhttp_request *req, void *arg) { struct evbuffer *evb = evbuffer_new(); @@ -222,7 +224,7 @@ http_basic_cb(struct evhttp_request *req, void *arg) evbuffer_free(evb); } -void +static void http_chunked_cb(struct evhttp_request *req, void *arg) { struct evbuffer *evb = evbuffer_new(); @@ -322,11 +324,32 @@ http_basic_test(void) fprintf(stdout, "OK\n"); } +static void +http_delay_reply(evutil_socket_t fd, short what, void *arg) +{ + struct evhttp_request *req = arg; + + evhttp_send_reply(req, HTTP_OK, "Everything is fine", NULL); + + ++test_ok; +} + +static void +http_delay_cb(struct evhttp_request *req, void *arg) +{ + struct timeval tv; + timerclear(&tv); + tv.tv_sec = 0; + tv.tv_usec = 200 * 1000; + + event_once(-1, EV_TIMEOUT, http_delay_reply, req, &tv); +} + /* * HTTP DELETE test, just piggyback on the basic test */ -void +static void http_delete_cb(struct evhttp_request *req, void *arg) { struct evbuffer *evb = evbuffer_new(); @@ -485,6 +508,120 @@ http_connection_test(int persistent) fprintf(stdout, "OK\n"); } +static void +http_request_never_call(struct evhttp_request *req, void *arg) +{ + fprintf(stdout, "FAILED\n"); + exit(1); +} + +static void +http_do_cancel(evutil_socket_t fd, short what, void *arg) +{ + struct evhttp_request *req = arg; + struct timeval tv; + timerclear(&tv); + tv.tv_sec = 0; + tv.tv_usec = 500 * 1000; + + evhttp_cancel_request(req); + + event_loopexit(&tv); + + ++test_ok; +} + +static void +http_cancel_test(void) +{ + short port = -1; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct timeval tv; + + test_ok = 0; + fprintf(stdout, "Testing Request Cancelation: "); + + http = http_setup(&port, NULL); + + evcon = evhttp_connection_new("127.0.0.1", port); + if (evcon == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(http_request_never_call, NULL); + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/delay") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + timerclear(&tv); + tv.tv_sec = 0; + tv.tv_usec = 100 * 1000; + + event_once(-1, EV_TIMEOUT, http_do_cancel, req, &tv); + + event_dispatch(); + + if (test_ok != 2) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* try to make another request over the same connection */ + test_ok = 0; + + req = evhttp_request_new(http_request_done, NULL); + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_dispatch(); + + /* make another request: request empty reply */ + test_ok = 0; + + req = evhttp_request_new(http_request_empty_done, NULL); + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Empty", "itis"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_dispatch(); + + if (test_ok != 1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + evhttp_connection_free(evcon); + evhttp_free(http); + + fprintf(stdout, "OK\n"); +} + static void http_request_done(struct evhttp_request *req, void *arg) { @@ -1811,6 +1948,7 @@ http_suite(void) http_base_test(); http_bad_header_test(); http_basic_test(); + http_cancel_test(); http_connection_test(0 /* not-persistent */); http_connection_test(1 /* persistent */); http_virtual_host_test();