diff options
-rw-r--r-- | evdns.c | 2 | ||||
-rw-r--r-- | evhttp.h | 46 | ||||
-rw-r--r-- | http-internal.h | 49 | ||||
-rw-r--r-- | http.c | 137 | ||||
-rw-r--r-- | test/regress_http.c | 23 |
5 files changed, 174 insertions, 83 deletions
@@ -75,7 +75,9 @@ #include <string.h> #include <fcntl.h> #include <sys/time.h> +#ifdef HAVE_STDINT_H #include <stdint.h> +#endif #include <stdlib.h> #include <string.h> #include <errno.h> @@ -83,6 +83,50 @@ void evhttp_send_reply(struct evhttp_request *, int, const char *, /* Interfaces for making requests */ enum evhttp_cmd_type { EVHTTP_REQ_GET, EVHTTP_REQ_POST, EVHTTP_REQ_HEAD }; +enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE }; + +/* + * the request structure that a server receives. + * WARNING: expect this structure to change. I will try to provide + * reasonable accessors. + */ +struct evhttp_request { + TAILQ_ENTRY(evhttp_request) next; + + /* the connection object that this request belongs to */ + struct evhttp_connection *evcon; + int flags; +#define EVHTTP_REQ_OWN_CONNECTION 0x0001 + + struct evkeyvalq *input_headers; + struct evkeyvalq *output_headers; + + /* address of the remote host and the port connection came from */ + char *remote_host; + u_short remote_port; + + enum evhttp_request_kind kind; + enum evhttp_cmd_type type; + + char *uri; /* uri after HTTP request was parsed */ + + char major; /* HTTP Major number */ + char minor; /* HTTP Minor number */ + + int got_firstline; + int response_code; /* HTTP Response code */ + char *response_code_line; /* Readable response */ + + struct evbuffer *input_buffer; /* read data */ + int ntoread; + + struct evbuffer *output_buffer; /* outgoing post or data */ + + /* Callback */ + void (*cb)(struct evhttp_request *, void *); + void *cb_arg; +}; + /* * Creates a new request object that needs to be filled in with the request * parameters. The callback is executed when the request completed or an @@ -114,7 +158,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 f5ad2861..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,51 +47,15 @@ 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 *); void *cb_arg; }; -enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE }; - -struct evhttp_request { - TAILQ_ENTRY(evhttp_request) next; - - /* the connection object that this request belongs to */ - struct evhttp_connection *evcon; - int flags; -#define EVHTTP_REQ_OWN_CONNECTION 0x0001 - - struct evkeyvalq *input_headers; - struct evkeyvalq *output_headers; - - /* xxx: do we still need these? */ - char *remote_host; - u_short remote_port; - - enum evhttp_request_kind kind; - enum evhttp_cmd_type type; - - char *uri; /* uri after HTTP request was parsed */ - - char major; /* HTTP Major number */ - char minor; /* HTTP Minor number */ - - int got_firstline; - int response_code; /* HTTP Response code */ - char *response_code_line; /* Readable response */ - - struct evbuffer *input_buffer; /* read data */ - int ntoread; - - struct evbuffer *output_buffer; /* outgoing post or data */ - - /* Callback */ - void (*cb)(struct evhttp_request *, void *); - void *cb_arg; -}; - struct evhttp_cb { TAILQ_ENTRY(evhttp_cb) next; @@ -102,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; @@ -116,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 **); @@ -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); @@ -1128,20 +1152,26 @@ evhttp_start_read(struct evhttp_connection *evcon) void evhttp_send_done(struct evhttp_connection *evcon, void *arg) { + int need_close; struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); TAILQ_REMOVE(&evcon->requests, req, next); - if (req->flags & EVHTTP_REQ_OWN_CONNECTION) { - const char *connection = - evhttp_find_header(req->output_headers, "Connection"); - if (connection == NULL || strcasecmp(connection, "close")) { - event_warnx("%s: persistent connection not supported", - __func__); - } - evhttp_connection_free(evcon); - } - + 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); } /* @@ -1160,6 +1190,9 @@ evhttp_send_error(struct evhttp_request *req, int error, const char *reason) struct evbuffer *buf = evbuffer_new(); + /* close the connection on error */ + evhttp_add_header(req->output_headers, "Connection", "close"); + evhttp_response_code(req, error, reason); evbuffer_add_printf(buf, fmt, error, reason); @@ -1336,8 +1369,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 @@ -1378,6 +1410,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); @@ -1391,12 +1424,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); @@ -1522,12 +1561,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); @@ -1536,17 +1574,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; @@ -1554,11 +1598,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 cad764ce..2127d49a 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -163,7 +163,7 @@ http_errorcb(struct bufferevent *bev, short what, void *arg) void http_basic_cb(struct evhttp_request *req, void *arg) { - event_debug((stderr, "%s: called\n", __func__)); + event_debug(("%s: called\n", __func__)); struct evbuffer *evb = evbuffer_new(); evbuffer_add_printf(evb, "This is funny"); @@ -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)); @@ -217,14 +218,15 @@ http_basic_test(void) void http_request_done(struct evhttp_request *, void *); void -http_connection_test(void) +http_connection_test(int persistent) { short port = -1; struct evhttp_connection *evcon = NULL; struct evhttp_request *req = NULL; test_ok = 0; - fprintf(stdout, "Testing Basic HTTP Connection: "); + fprintf(stdout, "Testing Request Connection Pipeline %s: ", + persistent ? "(persistent)" : ""); http = http_setup(&port); @@ -265,6 +267,13 @@ http_connection_test(void) /* Add the information that we care about */ evhttp_add_header(req->output_headers, "Host", "somehost"); + /* + * if our connections are not supposed to be persistent; request + * a close from the server. + */ + if (!persistent) + evhttp_add_header(req->output_headers, "Connection", "close"); + /* We give ownership of the request to the connection */ if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { fprintf(stdout, "FAILED\n"); @@ -371,7 +380,7 @@ http_post_test(void) void http_post_cb(struct evhttp_request *req, void *arg) { - event_debug((stderr, "%s: called\n", __func__)); + event_debug(("%s: called\n", __func__)); /* Yes, we are expecting a post request */ if (req->type != EVHTTP_REQ_POST) { @@ -438,6 +447,7 @@ http_failure_readcb(struct bufferevent *bev, void *arg) const char *what = "400 Bad Request"; if (evbuffer_find(bev->input, what, strlen(what)) != NULL) { test_ok = 2; + bufferevent_disable(bev, EV_READ); event_loopexit(NULL); } } @@ -488,7 +498,8 @@ void http_suite(void) { http_basic_test(); - http_connection_test(); + http_connection_test(0 /* not-persistent */); + http_connection_test(1 /* persistent */); http_post_test(); http_failure_test(); } |