Merge branch 'http-client-fix-expect-100-continue-v6'

This patch set fixes and covers http client and "Expect: 100-continue"
functionality (plus increase coverage under some related options, to avoid
further regressions).

* http-client-fix-expect-100-continue-v6:
  http: fix leaking of response_code_line
  http: fix "Expect: 100-continue" client side
  test/http: separate coverage for EVHTTP_CON_READ_ON_WRITE_ERROR
  test/http: cover "Expect: 100-continue" client-server interaction
  test/http: *lingering tests shouldn't have "Expect: 100-continue"
This commit is contained in:
Azat Khuzhin 2016-03-11 20:59:36 +03:00
commit f15cbd5276
3 changed files with 106 additions and 28 deletions

View File

@ -190,6 +190,7 @@ enum message_read_status evhttp_parse_firstline_(struct evhttp_request *, struct
enum message_read_status evhttp_parse_headers_(struct evhttp_request *, struct evbuffer*); enum message_read_status evhttp_parse_headers_(struct evhttp_request *, struct evbuffer*);
void evhttp_start_read_(struct evhttp_connection *); void evhttp_start_read_(struct evhttp_connection *);
void evhttp_start_write_(struct evhttp_connection *);
/* response sending HTML the data in the buffer */ /* response sending HTML the data in the buffer */
void evhttp_response_code_(struct evhttp_request *, int, const char *); void evhttp_response_code_(struct evhttp_request *, int, const char *);

69
http.c
View File

@ -580,6 +580,23 @@ evhttp_make_header_response(struct evhttp_connection *evcon,
} }
} }
enum expect { NO, CONTINUE, OTHER };
static enum expect evhttp_have_expect(struct evhttp_request *req, int input)
{
const char *expect;
struct evkeyvalq *h = input ? req->input_headers : req->output_headers;
if (!req->kind == EVHTTP_REQUEST || !REQ_VERSION_ATLEAST(req, 1, 1))
return NO;
expect = evhttp_find_header(h, "Expect");
if (!expect)
return NO;
return !evutil_ascii_strcasecmp(expect, "100-continue") ? CONTINUE : OTHER;
}
/** Generate all headers appropriate for sending the http request in req (or /** Generate all headers appropriate for sending the http request in req (or
* the response, if we're sending a response), and write them to evcon's * the response, if we're sending a response), and write them to evcon's
* bufferevent. Also writes all data from req->output_buffer */ * bufferevent. Also writes all data from req->output_buffer */
@ -605,14 +622,12 @@ evhttp_make_header(struct evhttp_connection *evcon, struct evhttp_request *req)
} }
evbuffer_add(output, "\r\n", 2); evbuffer_add(output, "\r\n", 2);
if (evbuffer_get_length(req->output_buffer) > 0) { if (evhttp_have_expect(req, 0) != CONTINUE &&
evbuffer_get_length(req->output_buffer)) {
/* /*
* For a request, we add the POST data, for a reply, this * For a request, we add the POST data, for a reply, this
* is the regular data. * is the regular data.
*/ */
/* XXX We might want to support waiting (a limited amount of
time) for a continue status line from the server before
sending POST/PUT message bodies. */
evbuffer_add_buffer(output, req->output_buffer); evbuffer_add_buffer(output, req->output_buffer);
} }
} }
@ -1173,12 +1188,15 @@ evhttp_write_connectioncb(struct evhttp_connection *evcon, void *arg)
{ {
/* This is after writing the request to the server */ /* This is after writing the request to the server */
struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); struct evhttp_request *req = TAILQ_FIRST(&evcon->requests);
struct evbuffer *output = bufferevent_get_output(evcon->bufev);
EVUTIL_ASSERT(req != NULL); EVUTIL_ASSERT(req != NULL);
EVUTIL_ASSERT(evcon->state == EVCON_WRITING); EVUTIL_ASSERT(evcon->state == EVCON_WRITING);
/* We need to wait until we've written all of our output data before we can continue */ /* We need to wait until we've written all of our output data before we can
if (evbuffer_get_length(bufferevent_get_output(evcon->bufev)) > 0) { return; } * continue */
if (evbuffer_get_length(output) > 0)
return;
/* We are done writing our header and are now expecting the response */ /* We are done writing our header and are now expecting the response */
req->kind = EVHTTP_RESPONSE; req->kind = EVHTTP_RESPONSE;
@ -1649,6 +1667,8 @@ evhttp_parse_response_line(struct evhttp_request *req, char *line)
return (-1); return (-1);
} }
if (req->response_code_line != NULL)
mm_free(req->response_code_line);
if ((req->response_code_line = mm_strdup(readable)) == NULL) { if ((req->response_code_line = mm_strdup(readable)) == NULL) {
event_warn("%s: strdup", __func__); event_warn("%s: strdup", __func__);
return (-1); return (-1);
@ -2165,8 +2185,7 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req)
req->ntoread = -1; req->ntoread = -1;
} else { } else {
if (evhttp_get_body_length(req) == -1) { if (evhttp_get_body_length(req) == -1) {
evhttp_connection_fail_(evcon, evhttp_connection_fail_(evcon, EVREQ_HTTP_INVALID_HEADER);
EVREQ_HTTP_INVALID_HEADER);
return; return;
} }
if (req->kind == EVHTTP_REQUEST && req->ntoread < 1) { if (req->kind == EVHTTP_REQUEST && req->ntoread < 1) {
@ -2178,12 +2197,8 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req)
} }
/* Should we send a 100 Continue status line? */ /* Should we send a 100 Continue status line? */
if (req->kind == EVHTTP_REQUEST && REQ_VERSION_ATLEAST(req, 1, 1)) { switch (evhttp_have_expect(req, 1)) {
const char *expect; case CONTINUE:
expect = evhttp_find_header(req->input_headers, "Expect");
if (expect) {
if (!evutil_ascii_strcasecmp(expect, "100-continue")) {
/* XXX It would be nice to do some sanity /* XXX It would be nice to do some sanity
checking here. Does the resource exist? checking here. Does the resource exist?
Should the resource accept post requests? If Should the resource accept post requests? If
@ -2192,19 +2207,19 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req)
send their message body. */ send their message body. */
if (req->ntoread > 0) { if (req->ntoread > 0) {
/* ntoread is ev_int64_t, max_body_size is ev_uint64_t */ /* ntoread is ev_int64_t, max_body_size is ev_uint64_t */
if ((req->evcon->max_body_size <= EV_INT64_MAX) && (ev_uint64_t)req->ntoread > req->evcon->max_body_size) { if ((req->evcon->max_body_size <= EV_INT64_MAX) &&
(ev_uint64_t)req->ntoread > req->evcon->max_body_size) {
evhttp_lingering_fail(evcon, req); evhttp_lingering_fail(evcon, req);
return; return;
} }
} }
if (!evbuffer_get_length(bufferevent_get_input(evcon->bufev))) if (!evbuffer_get_length(bufferevent_get_input(evcon->bufev)))
evhttp_send_continue(evcon, req); evhttp_send_continue(evcon, req);
} else { break;
evhttp_send_error(req, HTTP_EXPECTATIONFAILED, case OTHER:
NULL); evhttp_send_error(req, HTTP_EXPECTATIONFAILED, NULL);
return; return;
} case NO: break;
}
} }
evhttp_read_body(evcon, req); evhttp_read_body(evcon, req);
@ -2272,7 +2287,9 @@ evhttp_read_header(struct evhttp_connection *evcon,
case EVHTTP_RESPONSE: case EVHTTP_RESPONSE:
/* Start over if we got a 100 Continue response. */ /* Start over if we got a 100 Continue response. */
if (req->response_code == 100) { if (req->response_code == 100) {
evhttp_start_read_(evcon); struct evbuffer *output = bufferevent_get_output(evcon->bufev);
evbuffer_add_buffer(output, req->output_buffer);
evhttp_start_write_(evcon);
return; return;
} }
if (!evhttp_response_needs_body(req)) { if (!evhttp_response_needs_body(req)) {
@ -2685,6 +2702,16 @@ evhttp_start_read_(struct evhttp_connection *evcon)
} }
} }
void
evhttp_start_write_(struct evhttp_connection *evcon)
{
bufferevent_disable(evcon->bufev, EV_WRITE);
bufferevent_enable(evcon->bufev, EV_READ);
evcon->state = EVCON_WRITING;
evhttp_write_buffer(evcon, evhttp_write_connectioncb, NULL);
}
static void static void
evhttp_send_done(struct evhttp_connection *evcon, void *arg) evhttp_send_done(struct evhttp_connection *evcon, void *arg)
{ {

View File

@ -3768,7 +3768,6 @@ http_data_length_constraints_test_done(struct evhttp_request *req, void *arg)
end: end:
event_base_loopexit(arg, NULL); event_base_loopexit(arg, NULL);
} }
static void static void
http_large_entity_test_done(struct evhttp_request *req, void *arg) http_large_entity_test_done(struct evhttp_request *req, void *arg)
{ {
@ -3777,23 +3776,47 @@ http_large_entity_test_done(struct evhttp_request *req, void *arg)
end: end:
event_base_loopexit(arg, NULL); event_base_loopexit(arg, NULL);
} }
static void
http_failed_request_done(struct evhttp_request *req, void *arg)
{
tt_assert(!req);
end:
event_base_loopexit(arg, NULL);
}
static void
http_expectation_failed_done(struct evhttp_request *req, void *arg)
{
tt_assert(req);
tt_int_op(evhttp_request_get_response_code(req), ==, HTTP_EXPECTATIONFAILED);
end:
event_base_loopexit(arg, NULL);
}
static void static void
http_data_length_constraints_test(void *arg) http_data_length_constraints_test_impl(void *arg, int read_on_write_error)
{ {
struct basic_test_data *data = arg; struct basic_test_data *data = arg;
ev_uint16_t port = 0; ev_uint16_t port = 0;
struct evhttp_connection *evcon = NULL; struct evhttp_connection *evcon = NULL;
struct evhttp_request *req = NULL; struct evhttp_request *req = NULL;
char *long_str = NULL; char *long_str = NULL;
size_t size = (1<<20) * 3; const size_t continue_size = 1<<20;
const size_t size = (1<<20) * 3;
void (*cb)(struct evhttp_request *, void *);
test_ok = 0; test_ok = 0;
cb = http_failed_request_done;
if (read_on_write_error)
cb = http_data_length_constraints_test_done;
http = http_setup(&port, data->base, 0); http = http_setup(&port, data->base, 0);
tt_assert(continue_size < size);
evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port);
tt_assert(evcon); tt_assert(evcon);
if (read_on_write_error)
tt_assert(!evhttp_connection_set_flags(evcon, EVHTTP_CON_READ_ON_WRITE_ERROR)); tt_assert(!evhttp_connection_set_flags(evcon, EVHTTP_CON_READ_ON_WRITE_ERROR));
/* also bind to local host */ /* also bind to local host */
@ -3830,8 +3853,10 @@ http_data_length_constraints_test(void *arg)
} }
event_base_dispatch(data->base); event_base_dispatch(data->base);
if (read_on_write_error)
cb = http_large_entity_test_done;
evhttp_set_max_body_size(http, size - 2); evhttp_set_max_body_size(http, size - 2);
req = evhttp_request_new(http_large_entity_test_done, data->base); req = evhttp_request_new(cb, data->base);
evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost");
evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str); evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str);
if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) { if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) {
@ -3848,6 +3873,27 @@ http_data_length_constraints_test(void *arg)
} }
event_base_dispatch(data->base); event_base_dispatch(data->base);
req = evhttp_request_new(http_dispatcher_test_done, data->base);
evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost");
evhttp_add_header(evhttp_request_get_output_headers(req), "Expect", "100-continue");
long_str[continue_size] = '\0';
evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str);
if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) {
tt_abort_msg("Couldn't make request");
}
event_base_dispatch(data->base);
if (read_on_write_error)
cb = http_expectation_failed_done;
req = evhttp_request_new(cb, data->base);
evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost");
evhttp_add_header(evhttp_request_get_output_headers(req), "Expect", "101-continue");
evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str);
if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) {
tt_abort_msg("Couldn't make request");
}
event_base_dispatch(data->base);
test_ok = 1; test_ok = 1;
end: end:
if (evcon) if (evcon)
@ -3857,6 +3903,10 @@ http_data_length_constraints_test(void *arg)
if (long_str) if (long_str)
free(long_str); free(long_str);
} }
static void http_data_length_constraints_test(void *arg)
{ http_data_length_constraints_test_impl(arg, 0); }
static void http_read_on_write_error_test(void *arg)
{ http_data_length_constraints_test_impl(arg, 1); }
static void static void
http_large_entity_non_lingering_test_done(struct evhttp_request *req, void *arg) http_large_entity_non_lingering_test_done(struct evhttp_request *req, void *arg)
@ -3903,7 +3953,6 @@ http_lingering_close_test_impl(void *arg, int lingering)
req = evhttp_request_new(cb, data->base); req = evhttp_request_new(cb, data->base);
tt_assert(req); tt_assert(req);
evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost");
evhttp_add_header(evhttp_request_get_output_headers(req), "Expect", "100-continue");
evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str); evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str);
if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) { if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) {
tt_abort_msg("Couldn't make request"); tt_abort_msg("Couldn't make request");
@ -4367,6 +4416,7 @@ struct testcase_t http_testcases[] = {
TT_ISOLATED|TT_OFF_BY_DEFAULT, &basic_setup, NULL }, TT_ISOLATED|TT_OFF_BY_DEFAULT, &basic_setup, NULL },
HTTP(data_length_constraints), HTTP(data_length_constraints),
HTTP(read_on_write_error),
HTTP(non_lingering_close), HTTP(non_lingering_close),
HTTP(lingering_close), HTTP(lingering_close),