From Scott Lamb:

* Allow the user to set the Content-Length: then stream a reply.
  This is useful for large requests of a known size. Added unit test.

* Don't send a response body on HEAD requests, 1xx status codes, 204
  status codes, or 304 status codes, as described in RFC 2616 section
  4.3. (Doing otherwise causes problems - in particular, if a 304 has a
  chunked body (even an empty one), Safari 3.1.1 issues and then fails
  the next request on the connection with the non-sequitur error message
  "Too many HTTP redirects"!)

* Specify a default Content-Type: when a response body is required, not
  when we have data in the response buffer by the time we make the
  header. (I.e., do this on evhttp_send_reply_start() for consistency.)

* Don't expect a body in response to HEAD requests.



svn:r898
This commit is contained in:
Niels Provos 2008-07-02 06:08:16 +00:00
parent 409236a77d
commit df97fca9ca
3 changed files with 105 additions and 22 deletions

View File

@ -115,6 +115,7 @@ Changes in current version:
o Support multi-line HTTP headers; based on a patch from Moshe Litvin o Support multi-line HTTP headers; based on a patch from Moshe Litvin
o Reject negative Content-Length headers; anonymous bug report o Reject negative Content-Length headers; anonymous bug report
o Detect CLOCK_MONOTONIC at runtime for evdns; anonymous bug report o Detect CLOCK_MONOTONIC at runtime for evdns; anonymous bug report
o Various HTTP correctness fixes from Scott Lamb
Changes in 1.4.0: 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 allow \r or \n individually to separate HTTP headers instead of the standard "\r\n"; from Charles Kerr.

38
http.c
View File

@ -326,6 +326,21 @@ evhttp_method(enum evhttp_cmd_type type)
return (method); return (method);
} }
/**
* Determines if a response should have a body.
* Follows the rules in RFC 2616 section 4.3.
* @return 1 if the response MUST have a body;
* 0 if the response MUST NOT have a body.
*/
static int
evhttp_response_needs_body(struct evhttp_request *req)
{
return (req->response_code != HTTP_NOCONTENT &&
req->response_code != HTTP_NOTMODIFIED &&
(req->response_code < 100 || req->response_code >= 200) &&
req->type != EVHTTP_REQ_HEAD);
}
static void static void
evhttp_add_event(struct event *ev, int timeout, int default_timeout) evhttp_add_event(struct event *ev, int timeout, int default_timeout)
{ {
@ -482,7 +497,8 @@ evhttp_make_header_response(struct evhttp_connection *evcon,
evhttp_add_header(req->output_headers, evhttp_add_header(req->output_headers,
"Connection", "keep-alive"); "Connection", "keep-alive");
if (req->minor == 1 || is_keepalive) { if ((req->minor == 1 || is_keepalive) &&
evhttp_response_needs_body(req)) {
/* /*
* we need to add the content length if the * we need to add the content length if the
* user did not give it, this is required for * user did not give it, this is required for
@ -495,7 +511,7 @@ evhttp_make_header_response(struct evhttp_connection *evcon,
} }
/* Potentially add headers for unidentified content. */ /* Potentially add headers for unidentified content. */
if (EVBUFFER_LENGTH(req->output_buffer)) { if (evhttp_response_needs_body(req)) {
if (evhttp_find_header(req->output_headers, if (evhttp_find_header(req->output_headers,
"Content-Type") == NULL) { "Content-Type") == NULL) {
evhttp_add_header(req->output_headers, evhttp_add_header(req->output_headers,
@ -1601,9 +1617,7 @@ evhttp_read_header(struct evhttp_connection *evcon,
break; break;
case EVHTTP_RESPONSE: case EVHTTP_RESPONSE:
if (req->response_code == HTTP_NOCONTENT || if (!evhttp_response_needs_body(req)) {
req->response_code == HTTP_NOTMODIFIED ||
(req->response_code >= 100 && req->response_code < 200)) {
event_debug(("%s: skipping body for code %d\n", event_debug(("%s: skipping body for code %d\n",
__func__, req->response_code)); __func__, req->response_code));
evhttp_connection_done(evcon); evhttp_connection_done(evcon);
@ -1957,8 +1971,14 @@ evhttp_send_reply_start(struct evhttp_request *req, int code,
/* set up to watch for client close */ /* set up to watch for client close */
evhttp_connection_start_detectclose(req->evcon); evhttp_connection_start_detectclose(req->evcon);
evhttp_response_code(req, code, reason); evhttp_response_code(req, code, reason);
if (req->major == 1 && req->minor == 1) { if (evhttp_find_header(req->output_headers, "Content-Length") == NULL &&
/* use chunked encoding for HTTP/1.1 */ req->major == 1 && req->minor == 1 &&
evhttp_response_needs_body(req)) {
/*
* prefer HTTP/1.1 chunked encoding to closing the connection;
* note RFC 2616 section 4.4 forbids it with Content-Length:
* and it's not necessary then anyway.
*/
evhttp_add_header(req->output_headers, "Transfer-Encoding", evhttp_add_header(req->output_headers, "Transfer-Encoding",
"chunked"); "chunked");
req->chunked = 1; req->chunked = 1;
@ -1971,6 +1991,10 @@ void
evhttp_send_reply_chunk(struct evhttp_request *req, struct evbuffer *databuf) evhttp_send_reply_chunk(struct evhttp_request *req, struct evbuffer *databuf)
{ {
struct evbuffer *output = bufferevent_get_output(req->evcon->bufev); struct evbuffer *output = bufferevent_get_output(req->evcon->bufev);
if (EVBUFFER_LENGTH(databuf) == 0)
return;
if (!evhttp_response_needs_body(req))
return;
if (req->chunked) { if (req->chunked) {
evbuffer_add_printf(output, "%x\r\n", evbuffer_add_printf(output, "%x\r\n",
(unsigned)EVBUFFER_LENGTH(databuf)); (unsigned)EVBUFFER_LENGTH(databuf));

View File

@ -64,6 +64,8 @@ static struct evhttp *http;
/* set if a test needs to call loopexit on a base */ /* set if a test needs to call loopexit on a base */
static struct event_base *base; static struct event_base *base;
static char const BASIC_REQUEST_BODY[] = "This is funny";
void http_suite(void); void http_suite(void);
static void http_basic_cb(struct evhttp_request *req, void *arg); static void http_basic_cb(struct evhttp_request *req, void *arg);
@ -96,6 +98,7 @@ http_setup(short *pport, struct event_base *base)
/* Register a callback for certain types of requests */ /* Register a callback for certain types of requests */
evhttp_set_cb(myhttp, "/test", http_basic_cb, NULL); evhttp_set_cb(myhttp, "/test", http_basic_cb, NULL);
evhttp_set_cb(myhttp, "/chunked", http_chunked_cb, NULL); evhttp_set_cb(myhttp, "/chunked", http_chunked_cb, NULL);
evhttp_set_cb(myhttp, "/streamed", http_chunked_cb, NULL);
evhttp_set_cb(myhttp, "/postit", http_post_cb, NULL); evhttp_set_cb(myhttp, "/postit", http_post_cb, NULL);
evhttp_set_cb(myhttp, "/putit", http_put_cb, NULL); evhttp_set_cb(myhttp, "/putit", http_put_cb, NULL);
evhttp_set_cb(myhttp, "/deleteit", http_delete_cb, NULL); evhttp_set_cb(myhttp, "/deleteit", http_delete_cb, NULL);
@ -174,7 +177,7 @@ http_connect(const char *address, u_short port)
static void static void
http_readcb(struct bufferevent *bev, void *arg) http_readcb(struct bufferevent *bev, void *arg)
{ {
const char *what = "This is funny"; const char *what = BASIC_REQUEST_BODY;
event_debug(("%s: %s\n", __func__, EVBUFFER_DATA(EVBUFFER_INPUT(bev)))); event_debug(("%s: %s\n", __func__, EVBUFFER_DATA(EVBUFFER_INPUT(bev))));
@ -230,7 +233,7 @@ http_basic_cb(struct evhttp_request *req, void *arg)
struct evbuffer *evb = evbuffer_new(); struct evbuffer *evb = evbuffer_new();
int empty = evhttp_find_header(req->input_headers, "Empty") != NULL; int empty = evhttp_find_header(req->input_headers, "Empty") != NULL;
event_debug(("%s: called\n", __func__)); event_debug(("%s: called\n", __func__));
evbuffer_add_printf(evb, "This is funny"); evbuffer_add_printf(evb, BASIC_REQUEST_BODY);
/* For multi-line headers test */ /* For multi-line headers test */
{ {
@ -297,7 +300,11 @@ http_chunked_cb(struct evhttp_request *req, void *arg)
memset(state, 0, sizeof(struct chunk_req_state)); memset(state, 0, sizeof(struct chunk_req_state));
state->req = req; state->req = req;
/* generate a chunked reply */ if (strcmp(evhttp_request_uri(req), "/streamed") == 0) {
evhttp_add_header(req->output_headers, "Content-Length", "39");
}
/* generate a chunked/streamed reply */
evhttp_send_reply_start(req, HTTP_OK, "Everything is fine"); evhttp_send_reply_start(req, HTTP_OK, "Everything is fine");
/* but trickle it across several iterations to ensure we're not /* but trickle it across several iterations to ensure we're not
@ -416,7 +423,7 @@ http_delete_cb(struct evhttp_request *req, void *arg)
} }
event_debug(("%s: called\n", __func__)); event_debug(("%s: called\n", __func__));
evbuffer_add_printf(evb, "This is funny"); evbuffer_add_printf(evb, BASIC_REQUEST_BODY);
/* allow sending of an empty reply */ /* allow sending of an empty reply */
evhttp_send_reply(req, HTTP_OK, "Everything is fine", evhttp_send_reply(req, HTTP_OK, "Everything is fine",
@ -494,7 +501,7 @@ http_connection_test(int persistent)
* server using our make request method. * server using our make request method.
*/ */
req = evhttp_request_new(http_request_done, NULL); req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
/* Add the information that we care about */ /* Add the information that we care about */
evhttp_add_header(req->output_headers, "Host", "somehost"); evhttp_add_header(req->output_headers, "Host", "somehost");
@ -515,7 +522,7 @@ http_connection_test(int persistent)
/* try to make another request over the same connection */ /* try to make another request over the same connection */
test_ok = 0; test_ok = 0;
req = evhttp_request_new(http_request_done, NULL); req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
/* Add the information that we care about */ /* Add the information that we care about */
evhttp_add_header(req->output_headers, "Host", "somehost"); evhttp_add_header(req->output_headers, "Host", "somehost");
@ -636,7 +643,7 @@ http_cancel_test(void)
/* try to make another request over the same connection */ /* try to make another request over the same connection */
test_ok = 0; test_ok = 0;
req = evhttp_request_new(http_request_done, NULL); req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
/* Add the information that we care about */ /* Add the information that we care about */
evhttp_add_header(req->output_headers, "Host", "somehost"); evhttp_add_header(req->output_headers, "Host", "somehost");
@ -679,7 +686,7 @@ http_cancel_test(void)
static void static void
http_request_done(struct evhttp_request *req, void *arg) http_request_done(struct evhttp_request *req, void *arg)
{ {
const char *what = "This is funny"; const char *what = arg;
if (req->response_code != HTTP_OK) { if (req->response_code != HTTP_OK) {
fprintf(stderr, "FAILED\n"); fprintf(stderr, "FAILED\n");
@ -776,7 +783,7 @@ http_virtual_host_test(void)
test_ok = 0; test_ok = 0;
/* make a request with the right host and expect a response */ /* make a request with the right host and expect a response */
req = evhttp_request_new(http_request_done, NULL); req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
/* Add the information that we care about */ /* Add the information that we care about */
evhttp_add_header(req->output_headers, "Host", "foo.com"); evhttp_add_header(req->output_headers, "Host", "foo.com");
@ -798,7 +805,7 @@ http_virtual_host_test(void)
test_ok = 0; test_ok = 0;
/* make a request with the right host and expect a response */ /* make a request with the right host and expect a response */
req = evhttp_request_new(http_request_done, NULL); req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
/* Add the information that we care about */ /* Add the information that we care about */
evhttp_add_header(req->output_headers, "Host", "bar.magic.foo.com"); evhttp_add_header(req->output_headers, "Host", "bar.magic.foo.com");
@ -1046,7 +1053,7 @@ http_post_cb(struct evhttp_request *req, void *arg)
} }
evb = evbuffer_new(); evb = evbuffer_new();
evbuffer_add_printf(evb, "This is funny"); evbuffer_add_printf(evb, BASIC_REQUEST_BODY);
evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb);
@ -1056,7 +1063,7 @@ http_post_cb(struct evhttp_request *req, void *arg)
void void
http_postrequest_done(struct evhttp_request *req, void *arg) http_postrequest_done(struct evhttp_request *req, void *arg)
{ {
const char *what = "This is funny"; const char *what = BASIC_REQUEST_BODY;
if (req == NULL) { if (req == NULL) {
fprintf(stderr, "FAILED (timeout)\n"); fprintf(stderr, "FAILED (timeout)\n");
@ -1719,7 +1726,7 @@ http_chunked_request_done(struct evhttp_request *req, void *arg)
} }
static void static void
http_chunked_test(void) http_chunk_out_test(void)
{ {
struct bufferevent *bev; struct bufferevent *bev;
int fd; int fd;
@ -1804,6 +1811,55 @@ http_chunked_test(void)
fprintf(stdout, "OK\n"); fprintf(stdout, "OK\n");
} }
static void
http_stream_out_test(void)
{
short port = -1;
struct evhttp_connection *evcon = NULL;
struct evhttp_request *req = NULL;
test_ok = 0;
printf("Tests streaming responses out: ");
http = http_setup(&port, NULL);
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(http_request_done,
(void *)"This is funnybut not hilarious.bwv 1052");
/* 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, "/streamed")
== -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");
}
static void static void
http_stream_in_chunk(struct evhttp_request *req, void *arg) http_stream_in_chunk(struct evhttp_request *req, void *arg)
{ {
@ -1885,7 +1941,8 @@ http_stream_in_test(void)
"This is funnybut not hilarious.bwv 1052"); "This is funnybut not hilarious.bwv 1052");
printf("Testing streamed reading of non-chunked response: "); printf("Testing streamed reading of non-chunked response: ");
_http_stream_in_test("/test", 13, "This is funny"); _http_stream_in_test("/test", strlen(BASIC_REQUEST_BODY),
BASIC_REQUEST_BODY);
} }
static void static void
@ -2273,7 +2330,8 @@ http_suite(void)
http_incomplete_test(0 /* use_timeout */); http_incomplete_test(0 /* use_timeout */);
http_incomplete_test(1 /* use_timeout */); http_incomplete_test(1 /* use_timeout */);
http_chunked_test(); http_chunk_out_test();
http_stream_out_test();
http_stream_in_test(); http_stream_in_test();
http_stream_in_cancel_test(); http_stream_in_cancel_test();