mirror of
https://github.com/cuberite/libevent.git
synced 2025-09-09 04:19:10 -04:00
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:
parent
3882669d3e
commit
942656bb5c
4
evhttp.h
4
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 *);
|
||||
|
@ -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
92
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.
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user