From: Niels Provos Date: Thu, 23 Nov 2006 06:32:20 +0000 (+0000) Subject: persistent connections are somewhat complicated; detect on the client side if the X-Git-Tag: release-2.0.1-alpha~682 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=942656bb5cd89b131ad119e395a770e6a1488e9b;p=libevent persistent connections are somewhat complicated; detect on the client side if the server closes a persistent connection. previously, we would have failed the next request on that connection. provide test case. svn:r277 --- diff --git a/evhttp.h b/evhttp.h index cb5330ae..229259d1 100644 --- 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 *); diff --git a/http-internal.h b/http-internal.h index f95a2090..a7cefbf8 100644 --- a/http-internal.h +++ b/http-internal.h @@ -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 1711eec8..d8e134fa 100644 --- 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. diff --git a/test/regress_http.c b/test/regress_http.c index d5166fbb..c651bda5 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -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(); }