BUG/MEDIUM: http: correctly report request body timeouts

This is the continuation of previous patch "BUG/MEDIUM: http/session:
disable client-side expiration only after body".

This one takes care of properly reporting the client-side read timeout
when waiting for a body from the client. Since the timeout may happen
before or after the server starts to respond, we have to take care of
the situation in three different ways :
  - if the server does not read our data fast enough, we emit a 504
    if we're waiting for headers, or we simply break the connection
    if headers were already received. We report either sH or sD
    depending on whether we've seen headers or not.

  - if the server has not yet started to respond, but has read all of
    the client's data and we're still waiting for more data from the
    client, we can safely emit a 408 and abort the request ;

  - if the server has already started to respond (thus it's a transfer
    timeout during a bidirectional exchange), then we silently break
    the connection, and only the session flags will indicate in the
    logs that something went wrong with client or server side.

This bug is tagged MEDIUM because it touches very sensible areas, however
its impact is very low. It might be worth performing a careful backport
to 1.4 once it has been confirmed that everything is correct and that it
does not introduce any regression.
This commit is contained in:
Willy Tarreau 2014-05-07 14:24:16 +02:00
parent b1982e27aa
commit b9edf8fbec
1 changed files with 74 additions and 2 deletions

View File

@ -5175,6 +5175,13 @@ int http_request_forward_body(struct session *s, struct channel *req, int an_bit
*/
msg->msg_state = HTTP_MSG_ERROR;
http_resync_states(s);
if (req->flags & CF_READ_TIMEOUT)
goto cli_timeout;
if (req->flags & CF_WRITE_TIMEOUT)
goto srv_timeout;
return 1;
}
@ -5455,6 +5462,68 @@ int http_request_forward_body(struct session *s, struct channel *req, int an_bit
s->flags |= SN_FINST_D;
}
return 0;
cli_timeout:
if (!(s->flags & SN_ERR_MASK))
s->flags |= SN_ERR_CLITO;
if (!(s->flags & SN_FINST_MASK)) {
if (txn->rsp.msg_state < HTTP_MSG_ERROR)
s->flags |= SN_FINST_H;
else
s->flags |= SN_FINST_D;
}
if (txn->status > 0) {
/* Don't send any error message if something was already sent */
stream_int_retnclose(req->prod, NULL);
}
else {
txn->status = 408;
stream_int_retnclose(req->prod, http_error_message(s, HTTP_ERR_408));
}
msg->msg_state = HTTP_MSG_ERROR;
req->analysers = 0;
s->rep->analysers = 0; /* we're in data phase, we want to abort both directions */
session_inc_http_err_ctr(s);
s->fe->fe_counters.failed_req++;
s->be->be_counters.failed_req++;
if (s->listener->counters)
s->listener->counters->failed_req++;
return 0;
srv_timeout:
if (!(s->flags & SN_ERR_MASK))
s->flags |= SN_ERR_SRVTO;
if (!(s->flags & SN_FINST_MASK)) {
if (txn->rsp.msg_state < HTTP_MSG_ERROR)
s->flags |= SN_FINST_H;
else
s->flags |= SN_FINST_D;
}
if (txn->status > 0) {
/* Don't send any error message if something was already sent */
stream_int_retnclose(req->prod, NULL);
}
else {
txn->status = 504;
stream_int_retnclose(req->prod, http_error_message(s, HTTP_ERR_504));
}
msg->msg_state = HTTP_MSG_ERROR;
req->analysers = 0;
s->rep->analysers = 0; /* we're in data phase, we want to abort both directions */
s->be->be_counters.failed_resp++;
if (objt_server(s->target)) {
objt_server(s->target)->counters.failed_resp++;
health_adjust(objt_server(s->target), HANA_STATUS_HTTP_READ_TIMEOUT);
}
return 0;
}
/* This stream analyser waits for a complete HTTP response. It returns 1 if the
@ -5622,8 +5691,11 @@ int http_wait_for_response(struct session *s, struct channel *rep, int an_bit)
return 0;
}
/* read timeout : return a 504 to the client. */
else if (rep->flags & CF_READ_TIMEOUT) {
/* read/write timeout : return a 504 to the client.
* The write timeout may happen when we're uploading POST
* data that the server is not consuming fast enough.
*/
else if (rep->flags & (CF_READ_TIMEOUT|CF_WRITE_TIMEOUT)) {
if (msg->err_pos >= 0)
http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->msg_state, s->fe);
else if (txn->flags & TX_NOT_FIRST)