From: Niels Provos Date: Sat, 18 Nov 2006 03:05:26 +0000 (+0000) Subject: make persistent connections work; needs more testing X-Git-Tag: release-2.0.1-alpha~698 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=36212f9df0664c3d9056d18d962ef37acf9a45e3;p=libevent make persistent connections work; needs more testing svn:r261 --- diff --git a/evhttp.h b/evhttp.h index e2cec260..9f7dafe7 100644 --- a/evhttp.h +++ b/evhttp.h @@ -102,7 +102,7 @@ struct evhttp_request { struct evkeyvalq *input_headers; struct evkeyvalq *output_headers; - /* xxx: do we still need these? */ + /* address of the remote host and the port connection came from */ char *remote_host; u_short remote_port; @@ -159,7 +159,7 @@ const char *evhttp_request_uri(struct evhttp_request *req); /* Interfaces for dealing with HTTP headers */ -const char *evhttp_find_header(struct evkeyvalq *, const char *); +const char *evhttp_find_header(const struct evkeyvalq *, const char *); int evhttp_remove_header(struct evkeyvalq *, const char *); int evhttp_add_header(struct evkeyvalq *, const char *, const char *); void evhttp_clear_headers(struct evkeyvalq *); diff --git a/http-internal.h b/http-internal.h index 5843b37c..17015e1e 100644 --- a/http-internal.h +++ b/http-internal.h @@ -30,6 +30,9 @@ enum evhttp_connection_state { }; struct evhttp_connection { + /* we use tailq only if they were created for an http server */ + TAILQ_ENTRY(evhttp_connection) next; + int fd; struct event ev; struct evbuffer *input_buffer; @@ -44,6 +47,9 @@ struct evhttp_connection { enum evhttp_connection_state state; + /* for server connections, the http server they are connected with */ + struct evhttp *http_server; + TAILQ_HEAD(evcon_requestq, evhttp_request) requests; void (*cb)(struct evhttp_connection *, void *); @@ -63,6 +69,7 @@ struct evhttp { struct event bind_ev; TAILQ_HEAD(httpcbq, evhttp_cb) callbacks; + TAILQ_HEAD(evconq, evhttp_connection) connections; void (*gencb)(struct evhttp_request *req, void *); void *gencbarg; @@ -77,8 +84,7 @@ int evhttp_connection_connect(struct evhttp_connection *); /* notifies the current request that it failed; resets connection */ void evhttp_connection_fail(struct evhttp_connection *); -void evhttp_get_request(int, struct sockaddr *, socklen_t, - void (*)(struct evhttp_request *, void *), void *); +void evhttp_get_request(struct evhttp *, int, struct sockaddr *, socklen_t); int evhttp_hostportfile(char *, char **, u_short *, char **); diff --git a/http.c b/http.c index 1ca1d060..e73fd217 100644 --- a/http.c +++ b/http.c @@ -76,6 +76,8 @@ static int make_socket_ai(int (*f)(int, const struct sockaddr *, socklen_t), static int make_socket(int (*)(int, const struct sockaddr *, socklen_t), 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); void evhttp_write(int, short, void *); @@ -191,8 +193,6 @@ evhttp_make_header_request(struct evhttp_connection *evcon, evhttp_remove_header(req->output_headers, "Accept-Encoding"); evhttp_remove_header(req->output_headers, "Proxy-Connection"); - evhttp_remove_header(req->output_headers, "Connection"); - evhttp_add_header(req->output_headers, "Connection", "close"); req->minor = 0; /* Generate request line */ @@ -211,6 +211,13 @@ evhttp_make_header_request(struct evhttp_connection *evcon, } } +static int +evhttp_is_connection_close(struct evkeyvalq* headers) +{ + const char *connection = evhttp_find_header(headers, "Connection"); + return (connection != NULL && strcasecmp(connection, "close") == 0); +} + /* * Create the headers needed for an HTTP reply */ @@ -229,7 +236,21 @@ evhttp_make_header_response(struct evhttp_connection *evcon, evhttp_add_header(req->output_headers, "Content-Type", "text/html; charset=ISO-8859-1"); } - if (evhttp_find_header(req->output_headers, "Connection") == NULL) { + + /* + * we need to add the content length if the user did not give it, + * this is required for persistent connections to work. + */ + if (evhttp_find_header(req->output_headers, "Content-Length") == NULL){ + static char len[12]; + snprintf(len, sizeof(len), "%ld", + EVBUFFER_LENGTH(req->output_buffer)); + evhttp_add_header(req->output_headers, "Content-Length", len); + } + + /* if the request asked for a close, we send a close, too */ + if (evhttp_is_connection_close(req->input_headers)) { + evhttp_remove_header(req->output_headers, "Connection"); evhttp_add_header(req->output_headers, "Connection", "close"); } } @@ -405,16 +426,13 @@ evhttp_connection_done(struct evhttp_connection *evcon) * on the connection, so that we can reply to it. */ if (evcon->flags & EVHTTP_CON_OUTGOING) { - struct evkeyvalq *headers = req->input_headers; - const char *connection; - TAILQ_REMOVE(&evcon->requests, req, next); req->evcon = NULL; /* check if we got asked to close the connection */ - connection = evhttp_find_header(headers, "Connection"); - if (connection != NULL && strcasecmp(connection, "close") == 0) + if (evhttp_is_connection_close(req->input_headers) || + evhttp_is_connection_close(req->output_headers)) evhttp_connection_reset(evcon); if (TAILQ_FIRST(&evcon->requests) != NULL) { @@ -460,7 +478,7 @@ evhttp_read(int fd, short what, void *arg) } n = evbuffer_read(req->input_buffer, fd, req->ntoread); - event_debug(("%s: got %d on %d\n", __func__, n, req->fd)); + event_debug(("%s: got %d on %d\n", __func__, n, fd)); if (n == -1) { event_warn("%s: evbuffer_read", __func__); @@ -502,6 +520,11 @@ evhttp_write_connectioncb(struct evhttp_connection *evcon, void *arg) void evhttp_connection_free(struct evhttp_connection *evcon) { + if (evcon->http_server != NULL) { + struct evhttp *http = evcon->http_server; + TAILQ_REMOVE(&http->connections, evcon, next); + } + if (event_initialized(&evcon->ev)) event_del(&evcon->ev); @@ -717,7 +740,7 @@ evhttp_parse_request_line(struct evhttp_request *req, char *line) } const char * -evhttp_find_header(struct evkeyvalq *headers, const char *key) +evhttp_find_header(const struct evkeyvalq *headers, const char *key) { struct evkeyval *header; @@ -898,7 +921,8 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req) req->ntoread = atoi(content_length); event_debug(("%s: bytes to read: %d (in buffer %d)\n", - __func__, req->ntoread, EVBUFFER_LENGTH(evcon->buffer))); + __func__, req->ntoread, + EVBUFFER_LENGTH(evcon->input_buffer))); if (req->ntoread > 0) req->ntoread -= EVBUFFER_LENGTH(evcon->input_buffer); @@ -1125,29 +1149,28 @@ evhttp_start_read(struct evhttp_connection *evcon) event_add(&evcon->ev, &tv); } -static int -evhttp_is_connection_close(struct evkeyval* headers) -{ - const char *connection = evhttp_find_header(headers, "Connection"); - return (connection != NULL && strcasecmp(connection, "close") == 0); -} - void evhttp_send_done(struct evhttp_connection *evcon, void *arg) { struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); TAILQ_REMOVE(&evcon->requests, req, next); - if (req->flags & EVHTTP_REQ_OWN_CONNECTION) { - if (!evhttp_is_connection_close(req->input_headers) && - !evhttp_is_connection_close(req->output_headers)) { - event_warnx("%s: persistent connection not supported", - __func__); - } - evhttp_connection_free(evcon); - } - + int need_close = evhttp_is_connection_close(req->input_headers) || + evhttp_is_connection_close(req->output_headers); + 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 */ + if (evhttp_associate_new_request_with_connection(evcon) == -1) + evhttp_connection_free(evcon); } /* @@ -1342,8 +1365,7 @@ accept_socket(int fd, short what, void *arg) return; } - evhttp_get_request(nfd, (struct sockaddr *)&ss, addrlen, - evhttp_handle_request, http); + evhttp_get_request(http, nfd, (struct sockaddr *)&ss, addrlen); } static int @@ -1384,6 +1406,7 @@ evhttp_start(const char *address, u_short port) } TAILQ_INIT(&http->callbacks); + TAILQ_INIT(&http->connections); if (bind_socket(http, address, port) == -1) { free(http); @@ -1397,12 +1420,18 @@ void evhttp_free(struct evhttp* http) { struct evhttp_cb *http_cb; + struct evhttp_connection *evcon; int fd = http->bind_ev.ev_fd; /* Remove the accepting part */ event_del(&http->bind_ev); close(fd); + while ((evcon = TAILQ_FIRST(&http->connections)) != NULL) { + /* evhttp_connection_free removes the connection */ + evhttp_connection_free(evcon); + } + while ((http_cb = TAILQ_FIRST(&http->callbacks)) != NULL) { TAILQ_REMOVE(&http->callbacks, http_cb, next); free(http_cb->what); @@ -1528,12 +1557,11 @@ evhttp_request_uri(struct evhttp_request *req) { * The callback is executed once the whole request has been read. */ -void -evhttp_get_request(int fd, struct sockaddr *sa, socklen_t salen, - void (*cb)(struct evhttp_request *, void *), void *arg) +static struct evhttp_connection* +evhttp_get_request_connection( + int fd, struct sockaddr *sa, socklen_t salen) { struct evhttp_connection *evcon; - struct evhttp_request *req; char *hostname, *portname; name_from_addr(sa, salen, &hostname, &portname); @@ -1542,17 +1570,23 @@ evhttp_get_request(int fd, struct sockaddr *sa, socklen_t salen, /* we need a connection object to put the http request on */ if ((evcon = evhttp_connection_new(hostname, atoi(portname))) == NULL) - return; + return (NULL); evcon->flags |= EVHTTP_CON_INCOMING; evcon->state = EVCON_CONNECTED; - if ((req = evhttp_request_new(cb, arg)) == NULL) { - evhttp_connection_free(evcon); - return; - } - evcon->fd = fd; + return (evcon); +} + +static int +evhttp_associate_new_request_with_connection(struct evhttp_connection *evcon) +{ + struct evhttp *http = evcon->http_server; + struct evhttp_request *req; + if ((req = evhttp_request_new(evhttp_handle_request, http)) == NULL) + return (-1); + req->evcon = evcon; /* the request ends up owning the connection */ req->flags |= EVHTTP_REQ_OWN_CONNECTION; @@ -1560,11 +1594,34 @@ evhttp_get_request(int fd, struct sockaddr *sa, socklen_t salen, req->kind = EVHTTP_REQUEST; - if ((req->remote_host = strdup(hostname)) == NULL) + if ((req->remote_host = strdup(evcon->address)) == NULL) event_err(1, "%s: strdup", __func__); - req->remote_port = atoi(portname); + req->remote_port = evcon->port; evhttp_start_read(evcon); + + return (0); +} + +void +evhttp_get_request(struct evhttp *http, int fd, + struct sockaddr *sa, socklen_t salen) +{ + struct evhttp_connection *evcon; + + evcon = evhttp_get_request_connection(fd, sa, salen); + if (evcon == NULL) + return; + + /* + * if we want to accept more than one request on a connection, + * we need to know which http server it belongs to. + */ + evcon->http_server = http; + TAILQ_INSERT_TAIL(&http->connections, evcon, next); + + if (evhttp_associate_new_request_with_connection(evcon) == -1) + evhttp_connection_free(evcon); } diff --git a/test/regress_http.c b/test/regress_http.c index 53e8bc42..d58c2ae1 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -194,7 +194,8 @@ http_basic_test(void) http_request = "GET /test HTTP/1.1\r\n" - "Host: somehost \r\n" + "Host: somehost\r\n" + "Connection: close\r\n" "\r\n"; bufferevent_write(bev, http_request, strlen(http_request));