]> granicus.if.org Git - libevent/commitdiff
allow cancelation of user initiated http requests; this will allow cancelation of...
authorNiels Provos <provos@gmail.com>
Mon, 12 May 2008 03:12:09 +0000 (03:12 +0000)
committerNiels Provos <provos@gmail.com>
Mon, 12 May 2008 03:12:09 +0000 (03:12 +0000)
svn:r812

ChangeLog
http-internal.h
http.c
include/event2/http.h
test/regress_http.c

index 72c849808cd6767f1cfdb5bb09ff49e7c56af3b1..2f97370cc2fa0f34627770aa1d3e93023ca7d890 100644 (file)
--- 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.
index 1ce58f84bff2e2387b1144b0f0243eeb5b676b80..d282206e2b6f43ff4325f6e0de55e891a3d3cf31 100644 (file)
@@ -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 b8927235d1b23a8d2f0e63b4979a57951b50d53f..47a48f90d223e81bb58b2480a966438065f5c54f 100644 (file)
--- 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.
index 9d0c1d3d2861a96e53d419256edd1e4c5ffb4f63..c8826f8ea43e5d4cb9ccac335fd1cd44fab058e0 100644 (file)
@@ -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 */
index 019bb852912cf5a240ec49b37d92473908a13550..09f8b4e2f1a1df11cc4e8681485aeaea8dc8d18e 100644 (file)
@@ -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();