mirror of
https://github.com/cuberite/libevent.git
synced 2025-09-12 05:48:51 -04:00
from trunk: correct handling of trailing headers in chunked replies; from Scott Lamb
svn:r892
This commit is contained in:
parent
0690f0f555
commit
efb50876fd
@ -1,6 +1,7 @@
|
||||
Changes in 1.4.6-stable:
|
||||
o evutil.h now includes <stdarg.h> directly
|
||||
o switch all uses of [v]snprintf over to evutil
|
||||
o Correct handling of trailing headers in chunked replies; from Scott Lamb.
|
||||
|
||||
Changes in 1.4.5-stable:
|
||||
o Fix connection keep-alive behavior for HTTP/1.0
|
||||
|
1
evhttp.h
1
evhttp.h
@ -216,7 +216,6 @@ struct {
|
||||
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 */
|
||||
|
||||
|
@ -17,6 +17,13 @@
|
||||
#define HTTP_PREFIX "http://"
|
||||
#define HTTP_DEFAULTPORT 80
|
||||
|
||||
enum message_read_status {
|
||||
ALL_DATA_READ = 1,
|
||||
MORE_DATA_EXPECTED = 0,
|
||||
DATA_CORRUPTED = -1,
|
||||
REQUEST_CANCELED = -2
|
||||
};
|
||||
|
||||
enum evhttp_connection_error {
|
||||
EVCON_HTTP_TIMEOUT,
|
||||
EVCON_HTTP_EOF,
|
||||
@ -30,9 +37,15 @@ struct evhttp_request;
|
||||
/* A stupid connection object - maybe make this a bufferevent later */
|
||||
|
||||
enum evhttp_connection_state {
|
||||
EVCON_DISCONNECTED, /* not currently connected not trying either */
|
||||
EVCON_CONNECTING, /* tries to currently connect */
|
||||
EVCON_CONNECTED /* connection is established */
|
||||
EVCON_DISCONNECTED, /**< not currently connected not trying either*/
|
||||
EVCON_CONNECTING, /**< tries to currently connect */
|
||||
EVCON_IDLE, /**< connection is established */
|
||||
EVCON_READING_FIRSTLINE,/**< reading Request-Line (incoming conn) or
|
||||
**< Status-Line (outgoing conn) */
|
||||
EVCON_READING_HEADERS, /**< reading request/response headers */
|
||||
EVCON_READING_BODY, /**< reading request/response body */
|
||||
EVCON_READING_TRAILER, /**< reading request/response chunked trailer */
|
||||
EVCON_WRITING /**< writing request/response headers/body */
|
||||
};
|
||||
|
||||
struct event_base;
|
||||
@ -124,10 +137,10 @@ void evhttp_get_request(struct evhttp *, int, struct sockaddr *, socklen_t);
|
||||
|
||||
int evhttp_hostportfile(char *, char **, u_short *, char **);
|
||||
|
||||
int evhttp_parse_lines(struct evhttp_request *, struct evbuffer*);
|
||||
int evhttp_parse_firstline(struct evhttp_request *, struct evbuffer*);
|
||||
int evhttp_parse_headers(struct evhttp_request *, struct evbuffer*);
|
||||
|
||||
void evhttp_start_read(struct evhttp_connection *);
|
||||
void evhttp_read_header(int, short, void *);
|
||||
void evhttp_make_header(struct evhttp_connection *, struct evhttp_request *);
|
||||
|
||||
void evhttp_write_buffer(struct evhttp_connection *,
|
||||
|
290
http.c
290
http.c
@ -207,6 +207,10 @@ static void evhttp_connection_start_detectclose(
|
||||
static void evhttp_connection_stop_detectclose(
|
||||
struct evhttp_connection *evcon);
|
||||
static void evhttp_request_dispatch(struct evhttp_connection* evcon);
|
||||
static void evhttp_read_firstline(struct evhttp_connection *evcon,
|
||||
struct evhttp_request *req);
|
||||
static void evhttp_read_header(struct evhttp_connection *evcon,
|
||||
struct evhttp_request *req);
|
||||
|
||||
void evhttp_read(int, short, void *);
|
||||
void evhttp_write(int, short, void *);
|
||||
@ -345,6 +349,24 @@ evhttp_write_buffer(struct evhttp_connection *evcon,
|
||||
evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_WRITE_TIMEOUT);
|
||||
}
|
||||
|
||||
static int
|
||||
evhttp_connected(struct evhttp_connection *evcon)
|
||||
{
|
||||
switch (evcon->state) {
|
||||
case EVCON_DISCONNECTED:
|
||||
case EVCON_CONNECTING:
|
||||
return (0);
|
||||
case EVCON_IDLE:
|
||||
case EVCON_READING_FIRSTLINE:
|
||||
case EVCON_READING_HEADERS:
|
||||
case EVCON_READING_BODY:
|
||||
case EVCON_READING_TRAILER:
|
||||
case EVCON_WRITING:
|
||||
default:
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create the headers need for an HTTP request
|
||||
*/
|
||||
@ -687,23 +709,29 @@ evhttp_write(int fd, short what, void *arg)
|
||||
(*evcon->cb)(evcon, evcon->cb_arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the connection state.
|
||||
* - If this is an outgoing connection, we've just processed the response;
|
||||
* idle or close the connection.
|
||||
* - If this is an incoming connection, we've just processed the request;
|
||||
* respond.
|
||||
*/
|
||||
static void
|
||||
evhttp_connection_done(struct evhttp_connection *evcon)
|
||||
{
|
||||
struct evhttp_request *req = TAILQ_FIRST(&evcon->requests);
|
||||
int con_outgoing = evcon->flags & EVHTTP_CON_OUTGOING;
|
||||
|
||||
/*
|
||||
* if this is an incoming connection, we need to leave the request
|
||||
* on the connection, so that we can reply to it.
|
||||
*/
|
||||
if (con_outgoing) {
|
||||
/* idle or close the connection */
|
||||
int need_close;
|
||||
TAILQ_REMOVE(&evcon->requests, req, next);
|
||||
req->evcon = NULL;
|
||||
|
||||
evcon->state = EVCON_IDLE;
|
||||
|
||||
need_close =
|
||||
evhttp_is_connection_close(req->flags, req->input_headers) ||
|
||||
evhttp_is_connection_close(req->flags, req->input_headers)||
|
||||
evhttp_is_connection_close(req->flags, req->output_headers);
|
||||
|
||||
/* check if we got asked to close the connection */
|
||||
@ -713,10 +741,9 @@ evhttp_connection_done(struct evhttp_connection *evcon)
|
||||
if (TAILQ_FIRST(&evcon->requests) != NULL) {
|
||||
/*
|
||||
* We have more requests; reset the connection
|
||||
* and deal with the next request. xxx: no
|
||||
* persistent connection right now
|
||||
* and deal with the next request.
|
||||
*/
|
||||
if (evcon->state != EVCON_CONNECTED)
|
||||
if (!evhttp_connected(evcon))
|
||||
evhttp_connection_connect(evcon);
|
||||
else
|
||||
evhttp_request_dispatch(evcon);
|
||||
@ -727,6 +754,12 @@ evhttp_connection_done(struct evhttp_connection *evcon)
|
||||
*/
|
||||
evhttp_connection_start_detectclose(evcon);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* incoming connection - we need to leave the request on the
|
||||
* connection so that we can reply to it.
|
||||
*/
|
||||
evcon->state = EVCON_WRITING;
|
||||
}
|
||||
|
||||
/* notify the user of the request */
|
||||
@ -740,12 +773,17 @@ evhttp_connection_done(struct evhttp_connection *evcon)
|
||||
|
||||
/*
|
||||
* Handles reading from a chunked request.
|
||||
* return 1: all data has been read
|
||||
* return 0: more data is expected
|
||||
* return -1: data is corrupted
|
||||
* return ALL_DATA_READ:
|
||||
* all data has been read
|
||||
* return MORE_DATA_EXPECTED:
|
||||
* more data is expected
|
||||
* return DATA_CORRUPTED:
|
||||
* data is corrupted
|
||||
* return REQUEST_CANCLED:
|
||||
* request was canceled by the user calling evhttp_cancel_request
|
||||
*/
|
||||
|
||||
static int
|
||||
static enum message_read_status
|
||||
evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf)
|
||||
{
|
||||
int len;
|
||||
@ -768,18 +806,18 @@ evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf)
|
||||
free(p);
|
||||
if (error) {
|
||||
/* could not get chunk size */
|
||||
return (-1);
|
||||
return (DATA_CORRUPTED);
|
||||
}
|
||||
if (req->ntoread == 0) {
|
||||
/* Last chunk */
|
||||
return (1);
|
||||
return (ALL_DATA_READ);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* don't have enough to complete a chunk; wait for more */
|
||||
if (len < req->ntoread)
|
||||
return (0);
|
||||
return (MORE_DATA_EXPECTED);
|
||||
|
||||
/* Completed chunk */
|
||||
evbuffer_add(req->input_buffer,
|
||||
@ -793,7 +831,28 @@ evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf)
|
||||
}
|
||||
}
|
||||
|
||||
return (0);
|
||||
return (MORE_DATA_EXPECTED);
|
||||
}
|
||||
|
||||
static void
|
||||
evhttp_read_trailer(struct evhttp_connection *evcon, struct evhttp_request *req)
|
||||
{
|
||||
struct evbuffer *buf = evcon->input_buffer;
|
||||
|
||||
switch (evhttp_parse_headers(req, buf)) {
|
||||
case DATA_CORRUPTED:
|
||||
evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER);
|
||||
break;
|
||||
case ALL_DATA_READ:
|
||||
event_del(&evcon->ev);
|
||||
evhttp_connection_done(evcon);
|
||||
break;
|
||||
case MORE_DATA_EXPECTED:
|
||||
default:
|
||||
evhttp_add_event(&evcon->ev, evcon->timeout,
|
||||
HTTP_READ_TIMEOUT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
@ -802,16 +861,24 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req)
|
||||
struct evbuffer *buf = evcon->input_buffer;
|
||||
|
||||
if (req->chunked) {
|
||||
int res = evhttp_handle_chunked_read(req, buf);
|
||||
if (res == 1) {
|
||||
switch (evhttp_handle_chunked_read(req, buf)) {
|
||||
case ALL_DATA_READ:
|
||||
/* finished last chunk */
|
||||
evhttp_connection_done(evcon);
|
||||
evcon->state = EVCON_READING_TRAILER;
|
||||
evhttp_read_trailer(evcon, req);
|
||||
return;
|
||||
} else if (res == -1) {
|
||||
case DATA_CORRUPTED:
|
||||
/* corrupted data */
|
||||
evhttp_connection_fail(evcon,
|
||||
EVCON_HTTP_INVALID_HEADER);
|
||||
return;
|
||||
case REQUEST_CANCELED:
|
||||
/* request canceled */
|
||||
evhttp_request_free(req);
|
||||
return;
|
||||
case MORE_DATA_EXPECTED:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (req->ntoread < 0) {
|
||||
/* Read until connection close. */
|
||||
@ -863,7 +930,28 @@ evhttp_read(int fd, short what, void *arg)
|
||||
evhttp_connection_done(evcon);
|
||||
return;
|
||||
}
|
||||
evhttp_read_body(evcon, req);
|
||||
|
||||
switch (evcon->state) {
|
||||
case EVCON_READING_FIRSTLINE:
|
||||
evhttp_read_firstline(evcon, req);
|
||||
break;
|
||||
case EVCON_READING_HEADERS:
|
||||
evhttp_read_header(evcon, req);
|
||||
break;
|
||||
case EVCON_READING_BODY:
|
||||
evhttp_read_body(evcon, req);
|
||||
break;
|
||||
case EVCON_READING_TRAILER:
|
||||
evhttp_read_trailer(evcon, req);
|
||||
break;
|
||||
case EVCON_DISCONNECTED:
|
||||
case EVCON_CONNECTING:
|
||||
case EVCON_IDLE:
|
||||
case EVCON_WRITING:
|
||||
default:
|
||||
event_errx(1, "%s: illegal connection state %d",
|
||||
__func__, evcon->state);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
@ -873,6 +961,8 @@ evhttp_write_connectioncb(struct evhttp_connection *evcon, void *arg)
|
||||
struct evhttp_request *req = TAILQ_FIRST(&evcon->requests);
|
||||
assert(req != NULL);
|
||||
|
||||
assert(evcon->state == EVCON_WRITING);
|
||||
|
||||
/* We are done writing our header and are now expecting the response */
|
||||
req->kind = EVHTTP_RESPONSE;
|
||||
|
||||
@ -890,7 +980,7 @@ evhttp_connection_free(struct evhttp_connection *evcon)
|
||||
|
||||
/* notify interested parties that this connection is going down */
|
||||
if (evcon->fd != -1) {
|
||||
if (evcon->state == EVCON_CONNECTED && evcon->closecb != NULL)
|
||||
if (evhttp_connected(evcon) && evcon->closecb != NULL)
|
||||
(*evcon->closecb)(evcon, evcon->closecb_arg);
|
||||
}
|
||||
|
||||
@ -954,7 +1044,9 @@ evhttp_request_dispatch(struct evhttp_connection* evcon)
|
||||
evhttp_connection_stop_detectclose(evcon);
|
||||
|
||||
/* we assume that the connection is connected already */
|
||||
assert(evcon->state == EVCON_CONNECTED);
|
||||
assert(evcon->state == EVCON_IDLE);
|
||||
|
||||
evcon->state = EVCON_WRITING;
|
||||
|
||||
/* Create the header from the store arguments */
|
||||
evhttp_make_header(evcon, req);
|
||||
@ -971,16 +1063,13 @@ evhttp_connection_reset(struct evhttp_connection *evcon)
|
||||
|
||||
if (evcon->fd != -1) {
|
||||
/* inform interested parties about connection close */
|
||||
if (evcon->state == EVCON_CONNECTED && evcon->closecb != NULL)
|
||||
if (evhttp_connected(evcon) && evcon->closecb != NULL)
|
||||
(*evcon->closecb)(evcon, evcon->closecb_arg);
|
||||
|
||||
EVUTIL_CLOSESOCKET(evcon->fd);
|
||||
evcon->fd = -1;
|
||||
}
|
||||
evcon->state = EVCON_DISCONNECTED;
|
||||
|
||||
/* remove unneeded flags */
|
||||
evcon->flags &= ~EVHTTP_CON_CLOSEDETECT;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1057,7 +1146,7 @@ evhttp_connectioncb(int fd, short what, void *arg)
|
||||
|
||||
/* Reset the retry count as we were successful in connecting */
|
||||
evcon->retry_cnt = 0;
|
||||
evcon->state = EVCON_CONNECTED;
|
||||
evcon->state = EVCON_IDLE;
|
||||
|
||||
/* try to start requests that have queued up on this connection */
|
||||
evhttp_request_dispatch(evcon);
|
||||
@ -1295,63 +1384,74 @@ evhttp_add_header(struct evkeyvalq *headers,
|
||||
* request object given an event buffer.
|
||||
*
|
||||
* Returns
|
||||
* -1 on error
|
||||
* 0 when we need to read more headers
|
||||
* 1 when all headers have been read.
|
||||
* DATA_CORRUPTED on error
|
||||
* MORE_DATA_EXPECTED when we need to read more headers
|
||||
* ALL_DATA_READ when all headers have been read.
|
||||
*/
|
||||
|
||||
int
|
||||
evhttp_parse_lines(struct evhttp_request *req, struct evbuffer* buffer)
|
||||
enum message_read_status
|
||||
evhttp_parse_firstline(struct evhttp_request *req, struct evbuffer *buffer)
|
||||
{
|
||||
char *line;
|
||||
int done = 0;
|
||||
enum message_read_status status = ALL_DATA_READ;
|
||||
|
||||
line = evbuffer_readline(buffer);
|
||||
if (line == NULL)
|
||||
return (MORE_DATA_EXPECTED);
|
||||
|
||||
switch (req->kind) {
|
||||
case EVHTTP_REQUEST:
|
||||
if (evhttp_parse_request_line(req, line) == -1)
|
||||
status = DATA_CORRUPTED;
|
||||
break;
|
||||
case EVHTTP_RESPONSE:
|
||||
if (evhttp_parse_response_line(req, line) == -1)
|
||||
status = DATA_CORRUPTED;
|
||||
break;
|
||||
default:
|
||||
status = DATA_CORRUPTED;
|
||||
}
|
||||
|
||||
free(line);
|
||||
return (status);
|
||||
}
|
||||
|
||||
enum message_read_status
|
||||
evhttp_parse_headers(struct evhttp_request *req, struct evbuffer* buffer)
|
||||
{
|
||||
char *line;
|
||||
enum message_read_status status = MORE_DATA_EXPECTED;
|
||||
|
||||
struct evkeyvalq* headers = req->input_headers;
|
||||
while ((line = evbuffer_readline(buffer)) != NULL) {
|
||||
while ((line = evbuffer_readline(buffer))
|
||||
!= NULL) {
|
||||
char *skey, *svalue;
|
||||
|
||||
if (*line == '\0') { /* Last header - Done */
|
||||
done = 1;
|
||||
free (line);
|
||||
status = ALL_DATA_READ;
|
||||
free(line);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Processing of header lines */
|
||||
if (req->got_firstline == 0) {
|
||||
switch (req->kind) {
|
||||
case EVHTTP_REQUEST:
|
||||
if (evhttp_parse_request_line(req, line) == -1)
|
||||
goto error;
|
||||
break;
|
||||
case EVHTTP_RESPONSE:
|
||||
if (evhttp_parse_response_line(req, line) == -1)
|
||||
goto error;
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
req->got_firstline = 1;
|
||||
} else {
|
||||
/* Regular header */
|
||||
svalue = line;
|
||||
skey = strsep(&svalue, ":");
|
||||
if (svalue == NULL)
|
||||
goto error;
|
||||
svalue = line;
|
||||
skey = strsep(&svalue, ":");
|
||||
if (svalue == NULL)
|
||||
goto error;
|
||||
|
||||
svalue += strspn(svalue, " ");
|
||||
svalue += strspn(svalue, " ");
|
||||
|
||||
if (evhttp_add_header(headers, skey, svalue) == -1)
|
||||
goto error;
|
||||
}
|
||||
if (evhttp_add_header(headers, skey, svalue) == -1)
|
||||
goto error;
|
||||
|
||||
free (line);
|
||||
free(line);
|
||||
}
|
||||
|
||||
return (done);
|
||||
return (status);
|
||||
|
||||
error:
|
||||
free (line);
|
||||
return (-1);
|
||||
free(line);
|
||||
return (DATA_CORRUPTED);
|
||||
}
|
||||
|
||||
static int
|
||||
@ -1385,7 +1485,7 @@ evhttp_get_body_length(struct evhttp_request *req)
|
||||
}
|
||||
}
|
||||
|
||||
event_debug(("%s: bytes to read: %d (in buffer %d)\n",
|
||||
event_debug(("%s: bytes to read: %lld (in buffer %ld)\n",
|
||||
__func__, req->ntoread,
|
||||
EVBUFFER_LENGTH(req->evcon->input_buffer)));
|
||||
|
||||
@ -1402,6 +1502,7 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req)
|
||||
evhttp_connection_done(evcon);
|
||||
return;
|
||||
}
|
||||
evcon->state = EVCON_READING_BODY;
|
||||
xfer_enc = evhttp_find_header(req->input_headers, "Transfer-Encoding");
|
||||
if (xfer_enc != NULL && strcasecmp(xfer_enc, "chunked") == 0) {
|
||||
req->chunked = 1;
|
||||
@ -1416,38 +1517,42 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req)
|
||||
evhttp_read_body(evcon, req);
|
||||
}
|
||||
|
||||
void
|
||||
evhttp_read_header(int fd, short what, void *arg)
|
||||
static void
|
||||
evhttp_read_firstline(struct evhttp_connection *evcon,
|
||||
struct evhttp_request *req)
|
||||
{
|
||||
struct evhttp_connection *evcon = arg;
|
||||
struct evhttp_request *req = TAILQ_FIRST(&evcon->requests);
|
||||
int n, res;
|
||||
enum message_read_status res;
|
||||
|
||||
if (what == EV_TIMEOUT) {
|
||||
event_debug(("%s: timeout on %d\n", __func__, fd));
|
||||
evhttp_connection_fail(evcon, EVCON_HTTP_TIMEOUT);
|
||||
res = evhttp_parse_firstline(req, evcon->input_buffer);
|
||||
if (res == DATA_CORRUPTED) {
|
||||
/* Error while reading, terminate */
|
||||
event_debug(("%s: bad header lines on %d\n",
|
||||
__func__, evcon->fd));
|
||||
evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER);
|
||||
return;
|
||||
} else if (res == MORE_DATA_EXPECTED) {
|
||||
/* Need more header lines */
|
||||
evhttp_add_event(&evcon->ev,
|
||||
evcon->timeout, HTTP_READ_TIMEOUT);
|
||||
return;
|
||||
}
|
||||
|
||||
n = evbuffer_read(evcon->input_buffer, fd, -1);
|
||||
if (n == 0) {
|
||||
event_debug(("%s: no more data on %d", __func__, fd));
|
||||
evhttp_connection_fail(evcon, EVCON_HTTP_EOF);
|
||||
return;
|
||||
}
|
||||
if (n == -1) {
|
||||
event_debug(("%s: bad read on %d", __func__, fd));
|
||||
evhttp_connection_fail(evcon, EVCON_HTTP_EOF);
|
||||
return;
|
||||
}
|
||||
evhttp_read_header(evcon, req);
|
||||
}
|
||||
|
||||
res = evhttp_parse_lines(req, evcon->input_buffer);
|
||||
if (res == -1) {
|
||||
static void
|
||||
evhttp_read_header(struct evhttp_connection *evcon, struct evhttp_request *req)
|
||||
{
|
||||
enum message_read_status res;
|
||||
int fd = evcon->fd;
|
||||
|
||||
res = evhttp_parse_headers(req, evcon->input_buffer);
|
||||
if (res == DATA_CORRUPTED) {
|
||||
/* Error while reading, terminate */
|
||||
event_debug(("%s: bad header lines on %d\n", __func__, fd));
|
||||
evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER);
|
||||
return;
|
||||
} else if (res == 0) {
|
||||
} else if (res == MORE_DATA_EXPECTED) {
|
||||
/* Need more header lines */
|
||||
evhttp_add_event(&evcon->ev,
|
||||
evcon->timeout, HTTP_READ_TIMEOUT);
|
||||
@ -1640,7 +1745,7 @@ evhttp_make_request(struct evhttp_connection *evcon,
|
||||
TAILQ_INSERT_TAIL(&evcon->requests, req, next);
|
||||
|
||||
/* If the connection object is not connected; make it so */
|
||||
if (evcon->state != EVCON_CONNECTED)
|
||||
if (!evhttp_connected(evcon))
|
||||
return (evhttp_connection_connect(evcon));
|
||||
|
||||
/*
|
||||
@ -1665,10 +1770,11 @@ evhttp_start_read(struct evhttp_connection *evcon)
|
||||
/* Set up an event to read the headers */
|
||||
if (event_initialized(&evcon->ev))
|
||||
event_del(&evcon->ev);
|
||||
event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read_header, evcon);
|
||||
event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read, evcon);
|
||||
EVHTTP_BASE_SET(evcon, &evcon->ev);
|
||||
|
||||
evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_READ_TIMEOUT);
|
||||
evcon->state = EVCON_READING_FIRSTLINE;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -2322,7 +2428,7 @@ evhttp_request_set_chunked_cb(struct evhttp_request *req,
|
||||
const char *
|
||||
evhttp_request_uri(struct evhttp_request *req) {
|
||||
if (req->uri == NULL)
|
||||
event_debug(("%s: request %p has no uri\n", req));
|
||||
event_debug(("%s: request %p has no uri\n", __func__, req));
|
||||
return (req->uri);
|
||||
}
|
||||
|
||||
@ -2351,7 +2457,7 @@ evhttp_get_request_connection(
|
||||
evhttp_connection_set_base(evcon, http->base);
|
||||
|
||||
evcon->flags |= EVHTTP_CON_INCOMING;
|
||||
evcon->state = EVCON_CONNECTED;
|
||||
evcon->state = EVCON_READING_FIRSTLINE;
|
||||
|
||||
evcon->fd = fd;
|
||||
|
||||
|
@ -67,6 +67,7 @@ static struct event_base *base;
|
||||
void http_suite(void);
|
||||
|
||||
void http_basic_cb(struct evhttp_request *req, void *arg);
|
||||
static void http_chunked_cb(struct evhttp_request *req, void *arg);
|
||||
void http_post_cb(struct evhttp_request *req, void *arg);
|
||||
void http_dispatcher_cb(struct evhttp_request *req, void *arg);
|
||||
|
||||
@ -91,6 +92,7 @@ http_setup(short *pport, struct event_base *base)
|
||||
|
||||
/* Register a callback for certain types of requests */
|
||||
evhttp_set_cb(myhttp, "/test", http_basic_cb, NULL);
|
||||
evhttp_set_cb(myhttp, "/chunked", http_chunked_cb, NULL);
|
||||
evhttp_set_cb(myhttp, "/postit", http_post_cb, NULL);
|
||||
evhttp_set_cb(myhttp, "/", http_dispatcher_cb, NULL);
|
||||
|
||||
@ -163,15 +165,23 @@ http_readcb(struct bufferevent *bev, void *arg)
|
||||
if (evbuffer_find(bev->input,
|
||||
(const unsigned char*) what, strlen(what)) != NULL) {
|
||||
struct evhttp_request *req = evhttp_request_new(NULL, NULL);
|
||||
int done;
|
||||
enum message_read_status done;
|
||||
|
||||
req->kind = EVHTTP_RESPONSE;
|
||||
done = evhttp_parse_lines(req, bev->input);
|
||||
done = evhttp_parse_firstline(req, bev->input);
|
||||
if (done != ALL_DATA_READ)
|
||||
goto out;
|
||||
|
||||
done = evhttp_parse_headers(req, bev->input);
|
||||
if (done != ALL_DATA_READ)
|
||||
goto out;
|
||||
|
||||
if (done == 1 &&
|
||||
evhttp_find_header(req->input_headers,
|
||||
"Content-Type") != NULL)
|
||||
test_ok++;
|
||||
|
||||
out:
|
||||
evhttp_request_free(req);
|
||||
bufferevent_disable(bev, EV_READ);
|
||||
if (base)
|
||||
@ -213,6 +223,55 @@ http_basic_cb(struct evhttp_request *req, void *arg)
|
||||
evbuffer_free(evb);
|
||||
}
|
||||
|
||||
static char const* const CHUNKS[] = {
|
||||
"This is funny",
|
||||
"but not hilarious.",
|
||||
"bwv 1052"
|
||||
};
|
||||
|
||||
struct chunk_req_state {
|
||||
struct evhttp_request *req;
|
||||
int i;
|
||||
};
|
||||
|
||||
static void
|
||||
http_chunked_trickle_cb(int fd, short events, void *arg)
|
||||
{
|
||||
struct evbuffer *evb = evbuffer_new();
|
||||
struct chunk_req_state *state = arg;
|
||||
struct timeval when = { 0, 0 };
|
||||
|
||||
evbuffer_add_printf(evb, "%s", CHUNKS[state->i]);
|
||||
evhttp_send_reply_chunk(state->req, evb);
|
||||
evbuffer_free(evb);
|
||||
|
||||
if (++state->i < sizeof(CHUNKS)/sizeof(CHUNKS[0])) {
|
||||
event_once(-1, EV_TIMEOUT,
|
||||
http_chunked_trickle_cb, state, &when);
|
||||
} else {
|
||||
evhttp_send_reply_end(state->req);
|
||||
free(state);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
http_chunked_cb(struct evhttp_request *req, void *arg)
|
||||
{
|
||||
struct timeval when = { 0, 0 };
|
||||
struct chunk_req_state *state = malloc(sizeof(struct chunk_req_state));
|
||||
event_debug(("%s: called\n", __func__));
|
||||
|
||||
memset(state, 0, sizeof(struct chunk_req_state));
|
||||
state->req = req;
|
||||
|
||||
/* generate a chunked reply */
|
||||
evhttp_send_reply_start(req, HTTP_OK, "Everything is fine");
|
||||
|
||||
/* but trickle it across several iterations to ensure we're not
|
||||
* assuming it comes all at once */
|
||||
event_once(-1, EV_TIMEOUT, http_chunked_trickle_cb, state, &when);
|
||||
}
|
||||
|
||||
static void
|
||||
http_basic_test(void)
|
||||
{
|
||||
@ -937,6 +996,229 @@ http_base_test(void)
|
||||
fprintf(stdout, "OK\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* the server is going to reply with chunked data.
|
||||
*/
|
||||
|
||||
static void
|
||||
http_chunked_readcb(struct bufferevent *bev, void *arg)
|
||||
{
|
||||
/* nothing here */
|
||||
}
|
||||
|
||||
static void
|
||||
http_chunked_errorcb(struct bufferevent *bev, short what, void *arg)
|
||||
{
|
||||
if (!test_ok)
|
||||
goto out;
|
||||
|
||||
test_ok = -1;
|
||||
|
||||
if ((what & EVBUFFER_EOF) != 0) {
|
||||
struct evhttp_request *req = evhttp_request_new(NULL, NULL);
|
||||
const char *header;
|
||||
enum message_read_status done;
|
||||
|
||||
req->kind = EVHTTP_RESPONSE;
|
||||
done = evhttp_parse_firstline(req, EVBUFFER_INPUT(bev));
|
||||
if (done != ALL_DATA_READ)
|
||||
goto out;
|
||||
|
||||
done = evhttp_parse_headers(req, EVBUFFER_INPUT(bev));
|
||||
if (done != ALL_DATA_READ)
|
||||
goto out;
|
||||
|
||||
header = evhttp_find_header(req->input_headers, "Transfer-Encoding");
|
||||
if (header == NULL || strcmp(header, "chunked"))
|
||||
goto out;
|
||||
|
||||
header = evhttp_find_header(req->input_headers, "Connection");
|
||||
if (header == NULL || strcmp(header, "close"))
|
||||
goto out;
|
||||
|
||||
header = evbuffer_readline(EVBUFFER_INPUT(bev));
|
||||
if (header == NULL)
|
||||
goto out;
|
||||
/* 13 chars */
|
||||
if (strcmp(header, "d"))
|
||||
goto out;
|
||||
free((char*)header);
|
||||
|
||||
if (strncmp((char *)EVBUFFER_DATA(EVBUFFER_INPUT(bev)),
|
||||
"This is funny", 13))
|
||||
goto out;
|
||||
|
||||
evbuffer_drain(EVBUFFER_INPUT(bev), 13 + 2);
|
||||
|
||||
header = evbuffer_readline(EVBUFFER_INPUT(bev));
|
||||
if (header == NULL)
|
||||
goto out;
|
||||
/* 18 chars */
|
||||
if (strcmp(header, "12"))
|
||||
goto out;
|
||||
free((char *)header);
|
||||
|
||||
if (strncmp((char *)EVBUFFER_DATA(EVBUFFER_INPUT(bev)),
|
||||
"but not hilarious.", 18))
|
||||
goto out;
|
||||
|
||||
evbuffer_drain(EVBUFFER_INPUT(bev), 18 + 2);
|
||||
|
||||
header = evbuffer_readline(EVBUFFER_INPUT(bev));
|
||||
if (header == NULL)
|
||||
goto out;
|
||||
/* 8 chars */
|
||||
if (strcmp(header, "8"))
|
||||
goto out;
|
||||
free((char *)header);
|
||||
|
||||
if (strncmp((char *)EVBUFFER_DATA(EVBUFFER_INPUT(bev)),
|
||||
"bwv 1052.", 8))
|
||||
goto out;
|
||||
|
||||
evbuffer_drain(EVBUFFER_INPUT(bev), 8 + 2);
|
||||
|
||||
header = evbuffer_readline(EVBUFFER_INPUT(bev));
|
||||
if (header == NULL)
|
||||
goto out;
|
||||
/* 0 chars */
|
||||
if (strcmp(header, "0"))
|
||||
goto out;
|
||||
free((char *)header);
|
||||
|
||||
test_ok = 2;
|
||||
}
|
||||
|
||||
out:
|
||||
event_loopexit(NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
http_chunked_writecb(struct bufferevent *bev, void *arg)
|
||||
{
|
||||
if (EVBUFFER_LENGTH(EVBUFFER_OUTPUT(bev)) == 0) {
|
||||
/* enable reading of the reply */
|
||||
bufferevent_enable(bev, EV_READ);
|
||||
test_ok++;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
http_chunked_request_done(struct evhttp_request *req, void *arg)
|
||||
{
|
||||
if (req->response_code != HTTP_OK) {
|
||||
fprintf(stderr, "FAILED\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (evhttp_find_header(req->input_headers,
|
||||
"Transfer-Encoding") == NULL) {
|
||||
fprintf(stderr, "FAILED\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (EVBUFFER_LENGTH(req->input_buffer) != 13 + 18 + 8) {
|
||||
fprintf(stderr, "FAILED\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (strncmp((char *)EVBUFFER_DATA(req->input_buffer),
|
||||
"This is funnybut not hilarious.bwv 1052",
|
||||
13 + 18 + 8)) {
|
||||
fprintf(stderr, "FAILED\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
test_ok = 1;
|
||||
event_loopexit(NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
http_chunked_test(void)
|
||||
{
|
||||
struct bufferevent *bev;
|
||||
int fd;
|
||||
const char *http_request;
|
||||
short port = -1;
|
||||
struct timeval tv_start, tv_end;
|
||||
struct evhttp_connection *evcon = NULL;
|
||||
struct evhttp_request *req = NULL;
|
||||
int i;
|
||||
|
||||
test_ok = 0;
|
||||
fprintf(stdout, "Testing Chunked HTTP Reply: ");
|
||||
|
||||
http = http_setup(&port, NULL);
|
||||
|
||||
fd = http_connect("127.0.0.1", port);
|
||||
|
||||
/* Stupid thing to send a request */
|
||||
bev = bufferevent_new(fd,
|
||||
http_chunked_readcb, http_chunked_writecb,
|
||||
http_chunked_errorcb, NULL);
|
||||
|
||||
http_request =
|
||||
"GET /chunked HTTP/1.1\r\n"
|
||||
"Host: somehost\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
|
||||
bufferevent_write(bev, http_request, strlen(http_request));
|
||||
|
||||
evutil_gettimeofday(&tv_start, NULL);
|
||||
|
||||
event_dispatch();
|
||||
|
||||
evutil_gettimeofday(&tv_end, NULL);
|
||||
evutil_timersub(&tv_end, &tv_start, &tv_end);
|
||||
|
||||
if (tv_end.tv_sec >= 1) {
|
||||
fprintf(stdout, "FAILED (time)\n");
|
||||
exit (1);
|
||||
}
|
||||
|
||||
|
||||
if (test_ok != 2) {
|
||||
fprintf(stdout, "FAILED\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* now try again with the regular connection object */
|
||||
evcon = evhttp_connection_new("127.0.0.1", port);
|
||||
if (evcon == NULL) {
|
||||
fprintf(stdout, "FAILED\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* make two requests to check the keepalive behavior */
|
||||
for (i = 0; i < 2; i++) {
|
||||
test_ok = 0;
|
||||
req = evhttp_request_new(http_chunked_request_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, "/chunked") == -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)
|
||||
{
|
||||
@ -950,4 +1232,6 @@ http_suite(void)
|
||||
http_failure_test();
|
||||
http_highport_test();
|
||||
http_dispatcher_test();
|
||||
|
||||
http_chunked_test();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user