correct handling of trailing headers in chunked replies; from Scott Lamb.

svn:r887
This commit is contained in:
Niels Provos 2008-06-26 00:40:57 +00:00
parent 21f76156b1
commit 9998c0cbc8
5 changed files with 280 additions and 129 deletions

View File

@ -111,7 +111,8 @@ Changes in current version:
o Rename INPUT and OUTPUT to EVRPC_INPUT and EVRPC_OUTPUT. Retain INPUT/OUTPUT aliases on on-win32 platforms for backwards compatibility.
o Do not use SO_REUSEADDR when connecting
o Support 64-bit integers in RPC structs
o Correct handling of trailing headers in chunked replies; from Scott Lamb.
Changes in 1.4.0:
o allow \r or \n individually to separate HTTP headers instead of the standard "\r\n"; from Charles Kerr.
o demote most http warnings to debug messages

View File

@ -19,6 +19,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,
@ -34,9 +41,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;
@ -60,7 +73,6 @@ struct evhttp_connection {
#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 */
#define EVHTTP_CON_GOTHEADERS 0x0008 /* done reading headers */
int timeout; /* timeout in seconds for events */
int retry_cnt; /* retry count */
@ -134,10 +146,10 @@ void evhttp_connection_fail(struct evhttp_connection *,
void evhttp_get_request(struct evhttp *, evutil_socket_t, struct sockaddr *, socklen_t);
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(evutil_socket_t, short, void *);
void evhttp_make_header(struct evhttp_connection *, struct evhttp_request *);
void evhttp_write_buffer(struct evhttp_connection *,

272
http.c
View File

@ -207,10 +207,13 @@ 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);
/* callbacks for bufferevent */
static void evhttp_read_cb(struct bufferevent *, void *);
static void evhttp_read_header_cb(struct bufferevent *bufev, void *arg);
static void evhttp_write_cb(struct bufferevent *, void *);
static void evhttp_error_cb(struct bufferevent *bufev, short what, void *arg);
static int evhttp_decode_uri_internal(const char *uri, size_t length,
@ -351,6 +354,24 @@ evhttp_write_buffer(struct evhttp_connection *evcon,
bufferevent_enable(evcon->bufev, EV_WRITE);
}
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
*/
@ -621,23 +642,29 @@ evhttp_write_cb(struct bufferevent *bufev, 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 */
@ -647,10 +674,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);
@ -661,6 +687,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 */
@ -686,14 +718,7 @@ evhttp_connection_done(struct evhttp_connection *evcon)
* request was canceled by the user calling evhttp_cancel_request
*/
enum chunked_read_status {
ALL_DATA_READ = 1,
MORE_DATA_EXPECTED = 0,
DATA_CORRUPTED = -1,
REQUEST_CANCELED = -2
};
static enum chunked_read_status
static enum message_read_status
evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf)
{
int len;
@ -747,6 +772,26 @@ evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf)
return (MORE_DATA_EXPECTED);
}
static void
evhttp_read_trailer(struct evhttp_connection *evcon, struct evhttp_request *req)
{
struct evbuffer *buf = bufferevent_get_input(evcon->bufev);
switch (evhttp_parse_headers(req, buf)) {
case DATA_CORRUPTED:
evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER);
break;
case ALL_DATA_READ:
bufferevent_disable(evcon->bufev, EV_READ);
evhttp_connection_done(evcon);
break;
case MORE_DATA_EXPECTED:
default:
bufferevent_enable(evcon->bufev, EV_READ);
break;
}
}
static void
evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req)
{
@ -756,8 +801,8 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req)
switch (evhttp_handle_chunked_read(req, buf)) {
case ALL_DATA_READ:
/* finished last chunk */
bufferevent_disable(evcon->bufev, EV_READ);
evhttp_connection_done(evcon);
evcon->state = EVCON_READING_TRAILER;
evhttp_read_trailer(evcon, req);
return;
case DATA_CORRUPTED:
/* corrupted data */
@ -803,11 +848,6 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req)
}
/* Read more! */
bufferevent_setcb(evcon->bufev,
evhttp_read_cb,
evhttp_write_cb,
evhttp_error_cb,
evcon);
bufferevent_enable(evcon->bufev, EV_READ);
}
@ -821,8 +861,33 @@ evhttp_read_cb(struct bufferevent *bufev, void *arg)
struct evhttp_connection *evcon = arg;
struct evhttp_request *req = TAILQ_FIRST(&evcon->requests);
evhttp_read_body(evcon, req);
/* note the request may have been freed in evhttp_read_body */
switch (evcon->state) {
case EVCON_READING_FIRSTLINE:
evhttp_read_firstline(evcon, req);
/* note the request may have been freed in
* evhttp_read_body */
break;
case EVCON_READING_HEADERS:
evhttp_read_header(evcon, req);
/* note the request may have been freed in
* evhttp_read_body */
break;
case EVCON_READING_BODY:
evhttp_read_body(evcon, req);
/* note the request may have been freed in
* evhttp_read_body */
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
@ -832,6 +897,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;
@ -849,7 +916,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);
}
@ -910,7 +977,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);
@ -926,16 +995,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|EVHTTP_CON_GOTHEADERS);
}
static void
@ -1004,6 +1070,7 @@ static void
evhttp_error_cb(struct bufferevent *bufev, short what, void *arg)
{
struct evhttp_connection *evcon = arg;
struct evhttp_request *req = TAILQ_FIRST(&evcon->requests);
switch (evcon->state) {
case EVCON_CONNECTING:
@ -1016,9 +1083,9 @@ evhttp_error_cb(struct bufferevent *bufev, short what, void *arg)
}
break;
case EVCON_CONNECTED:
if (what == (EVBUFFER_READ|EVBUFFER_EOF) &&
(evcon->flags & EVHTTP_CON_GOTHEADERS)) {
case EVCON_READING_BODY:
if (!req->chunked && req->ntoread < 0
&& what == (EVBUFFER_READ|EVBUFFER_EOF)) {
/* EOF on read can be benign */
evhttp_connection_done(evcon);
return;
@ -1026,6 +1093,11 @@ evhttp_error_cb(struct bufferevent *bufev, short what, void *arg)
break;
case EVCON_DISCONNECTED:
case EVCON_IDLE:
case EVCON_READING_FIRSTLINE:
case EVCON_READING_HEADERS:
case EVCON_READING_TRAILER:
case EVCON_WRITING:
default:
break;
}
@ -1071,12 +1143,11 @@ evhttp_connection_cb(struct bufferevent *bufev, void *arg)
/* Reset the retry count as we were successful in connecting */
evcon->retry_cnt = 0;
evcon->state = EVCON_CONNECTED;
evcon->flags &= ~EVHTTP_CON_GOTHEADERS;
evcon->state = EVCON_IDLE;
/* reset the bufferevent cbs */
bufferevent_setcb(evcon->bufev,
evhttp_read_header_cb,
evhttp_read_cb,
evhttp_write_cb,
evhttp_error_cb,
evcon);
@ -1309,63 +1380,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_readln(buffer, NULL, EVBUFFER_EOL_CRLF);
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;
}
mm_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_readln(buffer, NULL, EVBUFFER_EOL_CRLF)) != NULL) {
while ((line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_CRLF))
!= NULL) {
char *skey, *svalue;
if (*line == '\0') { /* Last header - Done */
done = 1;
status = ALL_DATA_READ;
mm_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;
mm_free(line);
}
return (done);
return (status);
error:
mm_free(line);
return (-1);
return (DATA_CORRUPTED);
}
static int
@ -1417,6 +1499,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;
@ -1433,20 +1516,39 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req)
}
static void
evhttp_read_header_cb(struct bufferevent *bufev, void *arg)
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 res;
int fd = evcon->fd;
enum message_read_status res;
res = evhttp_parse_lines(req, bufferevent_get_input(evcon->bufev));
if (res == -1) {
res = evhttp_parse_firstline(req, bufferevent_get_input(evcon->bufev));
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 */
return;
}
evhttp_read_header(evcon, req);
}
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, bufferevent_get_input(evcon->bufev));
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 == MORE_DATA_EXPECTED) {
/* Need more header lines */
return;
}
@ -1454,9 +1556,6 @@ evhttp_read_header_cb(struct bufferevent *bufev, void *arg)
/* Disable reading for now */
bufferevent_disable(evcon->bufev, EV_READ);
/* we got all headers */
evcon->flags |= EVHTTP_CON_GOTHEADERS;
/* Done reading headers, do the real work */
switch (req->kind) {
case EVHTTP_REQUEST:
@ -1531,7 +1630,7 @@ evhttp_connection_base_new(struct event_base *base,
}
if ((evcon->bufev = bufferevent_new(-1,
evhttp_read_header_cb,
evhttp_read_cb,
evhttp_write_cb,
evhttp_error_cb, evcon)) == NULL) {
event_warn("%s: bufferevent_new failed", __func__);
@ -1674,7 +1773,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));
/*
@ -1725,6 +1824,7 @@ evhttp_start_read(struct evhttp_connection *evcon)
/* Set up an event to read the headers */
bufferevent_disable(evcon->bufev, EV_WRITE);
bufferevent_enable(evcon->bufev, EV_READ);
evcon->state = EVCON_READING_FIRSTLINE;
}
static void
@ -2560,7 +2660,7 @@ evhttp_get_request_connection(
return (NULL);
evcon->flags |= EVHTTP_CON_INCOMING;
evcon->state = EVCON_CONNECTED;
evcon->state = EVCON_READING_FIRSTLINE;
evcon->fd = fd;

View File

@ -93,7 +93,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 */

View File

@ -181,15 +181,23 @@ http_readcb(struct bufferevent *bev, void *arg)
if (evbuffer_find(EVBUFFER_INPUT(bev),
(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, EVBUFFER_INPUT(bev));
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;
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)
@ -231,31 +239,53 @@ 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(evutil_socket_t 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, 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 evbuffer *evb = evbuffer_new();
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");
/* first chunk */
evbuffer_add_printf(evb, "This is funny");
evhttp_send_reply_chunk(req, evb);
/* second chunk */
evbuffer_add_printf(evb, "but not hilarious.");
evhttp_send_reply_chunk(req, evb);
/* third and last chunk */
evbuffer_add_printf(evb, "bwv 1052");
evhttp_send_reply_chunk(req, evb);
/* finish request */
evhttp_send_reply_end(req);
evbuffer_free(evb);
/* 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
@ -1555,12 +1585,15 @@ http_chunked_errorcb(struct bufferevent *bev, short what, void *arg)
if ((what & EVBUFFER_EOF) != 0) {
struct evhttp_request *req = evhttp_request_new(NULL, NULL);
const char *header;
int done;
enum message_read_status done;
req->kind = EVHTTP_RESPONSE;
done = evhttp_parse_lines(req, EVBUFFER_INPUT(bev));
done = evhttp_parse_firstline(req, EVBUFFER_INPUT(bev));
if (done != ALL_DATA_READ)
goto out;
if (done != 1)
done = evhttp_parse_headers(req, EVBUFFER_INPUT(bev));
if (done != ALL_DATA_READ)
goto out;
header = evhttp_find_header(req->input_headers, "Transfer-Encoding");
@ -1678,6 +1711,7 @@ http_chunked_test(void)
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: ");
@ -1723,23 +1757,28 @@ http_chunked_test(void)
fprintf(stdout, "FAILED\n");
exit(1);
}
req = evhttp_request_new(http_chunked_request_done, NULL);
/* Add the information that we care about */
evhttp_add_header(req->output_headers, "Host", "somehost");
/* 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);
/* 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);
}
/* Add the information that we care about */
evhttp_add_header(req->output_headers, "Host", "somehost");
event_dispatch();
/* 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);
}
if (test_ok != 1) {
fprintf(stdout, "FAILED\n");
exit(1);
event_dispatch();
if (test_ok != 1) {
fprintf(stdout, "FAILED\n");
exit(1);
}
}
evhttp_connection_free(evcon);