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
This commit is contained in:
Niels Provos 2006-11-23 06:32:20 +00:00
parent 3882669d3e
commit 942656bb5c
4 changed files with 184 additions and 16 deletions

View File

@ -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 *);

View 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;
};

92
http.c
View File

@ -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.

View 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();
}