http: fix "Expect: 100-continue" client side

Instead of sending data always at the beginning of the request wait until the
server will respond with "HTTP/1.1 100 Continue".
Before this patch server do send "HTTP/1.1 100 Continue" but client always send
post data even without waiting server response.

P.S. this patch also touches some not 100% related tab-align issues.

Covered-by: http/data_length_constraints
Covered-by: http/read_on_write_error
This commit is contained in:
Azat Khuzhin 2016-03-11 13:08:28 +03:00
parent 5c2b4c19f1
commit 0b46b39e95
2 changed files with 48 additions and 22 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;
@ -2165,8 +2183,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 +2195,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 +2205,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 +2285,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 +2700,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)
{ {