]> granicus.if.org Git - libevent/commitdiff
persistent connections are somewhat complicated; detect on the client side if the
authorNiels Provos <provos@gmail.com>
Thu, 23 Nov 2006 06:32:20 +0000 (06:32 +0000)
committerNiels Provos <provos@gmail.com>
Thu, 23 Nov 2006 06:32:20 +0000 (06:32 +0000)
server closes a persistent connection.  previously, we would have failed the next
request on that connection.  provide test case.

svn:r277

evhttp.h
http-internal.h
http.c
test/regress_http.c

index cb5330aebf039603653bb689dbfca80f24900c93..229259d1190f85d21c0084d40ba3700cecc5a304 100644 (file)
--- a/evhttp.h
+++ b/evhttp.h
@@ -77,6 +77,10 @@ void evhttp_set_cb(struct evhttp *, const char *,
 void evhttp_set_gencb(struct evhttp *,
     void (*)(struct evhttp_request *, void *), void *);
 
+void evhttp_set_timeout(struct evhttp *, int timeout_in_secs);
+
+/* Request/Response functionality */
+
 void evhttp_send_error(struct evhttp_request *, int, const char *);
 void evhttp_send_reply(struct evhttp_request *, int, const char *,
     struct evbuffer *);
index f95a2090ddca335420e6e68f175e2ce28703c4f5..a7cefbf8f2f9d890821e436c45eff527e844b26c 100644 (file)
@@ -50,6 +50,7 @@ struct evhttp_connection {
        int flags;
 #define EVHTTP_CON_INCOMING    0x0001  /* only one request on it ever */
 #define EVHTTP_CON_OUTGOING    0x0002  /* multiple requests possible */
+#define EVHTTP_CON_CLOSEDETECT  0x0004  /* detecting if persistent close */
 
        int timeout;                    /* timeout in seconds for events */
        
@@ -82,6 +83,8 @@ struct evhttp {
        TAILQ_HEAD(httpcbq, evhttp_cb) callbacks;
         struct evconq connections;
 
+        int timeout;
+
        void (*gencb)(struct evhttp_request *req, void *);
        void *gencbarg;
 };
diff --git a/http.c b/http.c
index 1711eec8a0206727f0f40620f5e52d5ef485bc45..d8e134faeea26e994630093156ea4155d026346c 100644 (file)
--- a/http.c
+++ b/http.c
@@ -117,6 +117,11 @@ static int make_socket(int should_bind, const char *, short);
 static void name_from_addr(struct sockaddr *, socklen_t, char **, char **);
 static int evhttp_associate_new_request_with_connection(
        struct evhttp_connection *evcon);
+static void evhttp_connection_start_detectclose(
+       struct evhttp_connection *evcon);
+static void evhttp_connection_stop_detectclose(
+       struct evhttp_connection *evcon);
+static void evhttp_request_dispatch(struct evhttp_connection* evcon);
 
 void evhttp_write(int, short, void *);
 
@@ -517,10 +522,12 @@ evhttp_connection_done(struct evhttp_connection *evcon)
                TAILQ_REMOVE(&evcon->requests, req, next);
                req->evcon = NULL;
 
+               int need_close = 
+                   evhttp_is_connection_close(req->input_headers) ||
+                   evhttp_is_connection_close(req->output_headers);
 
                /* check if we got asked to close the connection */
-               if (evhttp_is_connection_close(req->input_headers) ||
-                   evhttp_is_connection_close(req->output_headers))
+               if (need_close)
                        evhttp_connection_reset(evcon);
 
                if (TAILQ_FIRST(&evcon->requests) != NULL) {
@@ -529,7 +536,16 @@ evhttp_connection_done(struct evhttp_connection *evcon)
                         * and deal with the next request.  xxx: no
                         * persistent connection right now
                         */
-                       evhttp_connection_connect(evcon);
+                       if (evcon->state != EVCON_CONNECTED)
+                               evhttp_connection_connect(evcon);
+                       else
+                               evhttp_request_dispatch(evcon);
+               } else if (!need_close) {
+                       /*
+                        * The connection is going to be persistent, but we
+                        * need to detect if the other side closes it.
+                        */
+                       evhttp_connection_start_detectclose(evcon);
                }
        }
 
@@ -628,7 +644,7 @@ evhttp_connection_free(struct evhttp_connection *evcon)
        free(evcon);
 }
 
-void
+static void
 evhttp_request_dispatch(struct evhttp_connection* evcon)
 {
        struct evhttp_request *req = TAILQ_FIRST(&evcon->requests);
@@ -637,6 +653,9 @@ evhttp_request_dispatch(struct evhttp_connection* evcon)
        if (req == NULL)
                return;
 
+       /* delete possible close detection events */
+       evhttp_connection_stop_detectclose(evcon);
+       
        /* we assume that the connection is connected already */
        assert(evcon->state = EVCON_CONNECTED);
 
@@ -658,13 +677,42 @@ evhttp_connection_reset(struct evhttp_connection *evcon)
                evcon->fd = -1;
        }
        evcon->state = EVCON_DISCONNECTED;
+
+       /* remove unneeded flags */
+       evcon->flags &= ~EVHTTP_CON_CLOSEDETECT;
+}
+
+static void
+evhttp_detect_close_cb(int fd, short what, void *arg)
+{
+       struct evhttp_connection *evcon = arg;
+       evhttp_connection_reset(evcon);
+}
+
+static void
+evhttp_connection_start_detectclose(struct evhttp_connection *evcon)
+{
+       assert((evcon->flags & EVHTTP_REQ_OWN_CONNECTION) == 0);
+       evcon->flags |= EVHTTP_CON_CLOSEDETECT;
+       
+       event_del(&evcon->ev);
+       event_set(&evcon->ev, evcon->fd, EV_READ,
+           evhttp_detect_close_cb, evcon);
+       event_add(&evcon->ev, NULL);
+}
+
+static void
+evhttp_connection_stop_detectclose(struct evhttp_connection *evcon)
+{
+       evcon->flags &= ~EVHTTP_CON_CLOSEDETECT;
+       event_del(&evcon->ev);
 }
 
 /*
  * Call back for asynchronous connection attempt.
  */
 
-void
+static void
 evhttp_connectioncb(int fd, short what, void *arg)
 {
        struct evhttp_connection *evcon = arg;
@@ -923,10 +971,10 @@ evhttp_parse_lines(struct evhttp_request *req, struct evbuffer* buffer)
        int done = 0;
 
        struct evkeyvalq* headers = req->input_headers;
-       while ((endp = evbuffer_find(buffer, "\r\n", 2)) != NULL) {
+       while ((endp = evbuffer_find(buffer, (u_char *)"\r\n", 2)) != NULL) {
                char *skey, *svalue;
 
-               if (strncmp(EVBUFFER_DATA(buffer), "\r\n", 2) == 0) {
+               if (strncmp((char *)EVBUFFER_DATA(buffer), "\r\n", 2) == 0) {
                        evbuffer_drain(buffer, 2);
                        /* Last header - Done */
                        done = 1;
@@ -942,11 +990,13 @@ evhttp_parse_lines(struct evhttp_request *req, struct evbuffer* buffer)
                if (req->got_firstline == 0) {
                        switch (req->kind) {
                        case EVHTTP_REQUEST:
-                               if (evhttp_parse_request_line(req, EVBUFFER_DATA(buffer)) == -1)
+                               if (evhttp_parse_request_line(
+                                           req, EVBUFFER_DATA(buffer)) == -1)
                                        return (-1);
                                break;
                        case EVHTTP_RESPONSE:
-                               if (evhttp_parse_response_line(req, EVBUFFER_DATA(buffer)) == -1)
+                               if (evhttp_parse_response_line(
+                                           req, EVBUFFER_DATA(buffer)) == -1)
                                        return (-1);
                                break;
                        default:
@@ -955,7 +1005,7 @@ evhttp_parse_lines(struct evhttp_request *req, struct evbuffer* buffer)
                        req->got_firstline = 1;
                } else {
                        /* Regular header */
-                       svalue = EVBUFFER_DATA(buffer);
+                       svalue = (char *)EVBUFFER_DATA(buffer);
                        skey = strsep(&svalue, ":");
                        if (svalue == NULL)
                                return (-1);
@@ -1028,7 +1078,7 @@ evhttp_read_header(int fd, short what, void *arg)
        int n, res;
 
        if (what == EV_TIMEOUT) {
-               event_warnx("%s: timeout on %d\n", __func__, fd);
+               event_debug(("%s: timeout on %d\n", __func__, fd));
                evhttp_connection_fail(evcon, EVCON_HTTP_TIMEOUT);
                return;
        }
@@ -1194,7 +1244,7 @@ evhttp_make_request(struct evhttp_connection *evcon,
        
        assert(req->evcon == NULL);
        req->evcon = evcon;
-       assert(!(req->flags && EVHTTP_REQ_OWN_CONNECTION));
+       assert(!(req->flags & EVHTTP_REQ_OWN_CONNECTION));
        
        TAILQ_INSERT_TAIL(&evcon->requests, req, next);
 
@@ -1239,17 +1289,15 @@ evhttp_send_done(struct evhttp_connection *evcon, void *arg)
        need_close = evhttp_is_connection_close(req->input_headers) ||
            evhttp_is_connection_close(req->output_headers);
 
+       assert(req->flags & EVHTTP_REQ_OWN_CONNECTION);
        evhttp_request_free(req);
 
-       if ((req->flags & EVHTTP_REQ_OWN_CONNECTION) == 0)
-               return;
-       
        if (need_close) {
                evhttp_connection_free(evcon);
                return;
        } 
 
-       /* we have a persistent connection; try to accept another request */
+       /* we have a persistent connection; try to accept another request. */
        if (evhttp_associate_new_request_with_connection(evcon) == -1)
                evhttp_connection_free(evcon);
 }
@@ -1489,6 +1537,8 @@ evhttp_start(const char *address, u_short port)
                return (NULL);
        }
 
+       http->timeout = -1;
+
        TAILQ_INIT(&http->callbacks);
        TAILQ_INIT(&http->connections);
 
@@ -1525,6 +1575,12 @@ evhttp_free(struct evhttp* http)
        free(http);
 }
 
+void
+evhttp_set_timeout(struct evhttp* http, int timeout_in_secs)
+{
+       http->timeout = timeout_in_secs;
+}
+
 void
 evhttp_set_cb(struct evhttp *http, const char *uri,
     void (*cb)(struct evhttp_request *, void *), void *cbarg)
@@ -1697,6 +1753,10 @@ evhttp_get_request(struct evhttp *http, int fd,
        if (evcon == NULL)
                return;
 
+       /* the timeout can be used by the server to close idle connections */
+       if (http->timeout != -1)
+               evhttp_connection_set_timeout(evcon, http->timeout);
+
        /* 
         * if we want to accept more than one request on a connection,
         * we need to know which http server it belongs to.
index d5166fbb150d43f6041b42f31202d11b2a2c1b9c..c651bda53c33e07195e3bd20275479f0853e1fd5 100644 (file)
@@ -493,6 +493,106 @@ http_failure_test(void)
        fprintf(stdout, "OK\n");
 }
 
+static void
+close_detect_done(struct evhttp_request *req, void *arg)
+{
+       if (req == NULL || req->response_code != HTTP_OK) {
+       
+               fprintf(stderr, "FAILED\n");
+               exit(1);
+       }
+
+       test_ok = 1;
+       event_loopexit(NULL);
+}
+
+static void
+close_detect_launch(int fd, short what, void *arg)
+{
+       struct evhttp_connection *evcon = arg;
+       struct evhttp_request *req;
+
+       req = evhttp_request_new(close_detect_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);
+       }
+}
+
+static void
+close_detect_cb(struct evhttp_request *req, void *arg)
+{
+       struct evhttp_connection *evcon = arg;
+       struct timeval tv;
+
+       if (req->response_code != HTTP_OK) {
+       
+               fprintf(stderr, "FAILED\n");
+               exit(1);
+       }
+
+       timerclear(&tv);
+       tv.tv_sec = 3;   /* longer than the http time out */
+
+       /* launch a new request on the persistent connection in 6 seconds */
+       event_once(-1, EV_TIMEOUT, close_detect_launch, evcon, &tv);
+}
+
+
+void
+http_close_detection()
+{
+       short port = -1;
+       struct evhttp_connection *evcon = NULL;
+       struct evhttp_request *req = NULL;
+       
+       test_ok = 0;
+       fprintf(stdout, "Testing Connection Close Detection: ");
+
+       http = http_setup(&port);
+
+       /* 2 second timeout */
+       evhttp_set_timeout(http, 2);
+
+       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(close_detect_cb, evcon);
+
+       /* 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();
+
+       if (test_ok != 1) {
+               fprintf(stdout, "FAILED\n");
+               exit(1);
+       }
+
+       evhttp_connection_free(evcon);
+       evhttp_free(http);
+       
+       fprintf(stdout, "OK\n");
+}
 
 void
 http_suite(void)
@@ -500,6 +600,7 @@ http_suite(void)
        http_basic_test();
        http_connection_test(0 /* not-persistent */);
        http_connection_test(1 /* persistent */);
+       http_close_detection();
        http_post_test();
        http_failure_test();
 }