BUG/MEDIUM: haterm: Properly handle client timeout

No client timeout was set with haterm. It could be an issue with
unresponsive clients. So the I/O timeout of the SC is initialized to the
frontend client timeout when the hstream is created. Then a read activity is
reported when data are received. This read activity is used to set an
expiration date on the hstream task and test it when the hstream is woken up
with TASK_WOKEN_TIMER reason.

When a client timeout is detected, the hstream try to send a 408 and report
an error.
This commit is contained in:
Christopher Faulet 2026-05-05 18:13:21 +02:00
parent c0f137b704
commit b6503f70e2

View File

@ -281,6 +281,7 @@ static int hstream_htx_buf_rcv(struct connection *conn, struct hstream *hs)
end_recv:
if (cur_read) {
hs->req_body = ((hs->req_body < cur_read) ? 0 : hs->req_body - cur_read);
sc_ep_report_read_activity(hs->sc);
}
if (((conn->flags & CO_FL_ERROR) || sc_ep_test(hs->sc, SE_FL_ERROR))) {
@ -879,6 +880,23 @@ static inline int hstream_sl_hdrs_htx_buf_snd(struct hstream *hs,
return ret;
}
/* Send an error to the client, if possible */
static inline void hstream_send_error(struct hstream *hs, struct connection *conn, struct buffer *errmsg)
{
/* Do nothing is the response headers were already sent */
if (hs->flags & HS_ST_HTTP_RESP_SL_SENT)
return;
if (!hstream_get_buf(hs, &hs->res)) {
TRACE_ERROR("could not allocate response buffer", HS_EV_HSTRM_RESP, hs);
return;
}
b_set_data(&hs->res, b_data(errmsg));
memcpy(b_orig(&hs->res), b_head(errmsg), b_data(errmsg));
hstream_htx_buf_snd(conn, hs);
}
/* Must be called before sending to determine if the body request must be
* drained asap before sending. Return 1 if this is the case, 0 if not.
* This is the case by default before sending the response except if
@ -929,6 +947,16 @@ static struct task *process_hstream(struct task *t, void *context, unsigned int
TRACE_STATE("waiting before responding", HS_EV_HSTRM_IO_CB, hs);
goto leave;
}
if (state & TASK_WOKEN_TIMER) {
int exp = (tick_isset(sc_ep_lra(hs->sc)) ? tick_add_ifset(sc_ep_lra(hs->sc), hs->sc->ioto) : TICK_ETERNITY);
if (tick_is_expired(exp, now_ms)) {
TRACE_ERROR("connection timed out", HS_EV_PROCESS_HSTRM, hs);
hs->flags |= HS_ST_CONN_ERROR;
hstream_send_error(hs, conn, &http_err_chunks[HTTP_ERR_408]);
goto out;
}
}
if (!(hs->flags & HS_ST_HTTP_GOT_HDRS)) {
struct htx *htx = htx_from_buf(&hs->req);
@ -1076,6 +1104,14 @@ static struct task *process_hstream(struct task *t, void *context, unsigned int
task_destroy(t);
t = NULL;
}
else {
t->expire = (tick_is_expired(t->expire, now_ms) ? TICK_ETERNITY : t->expire);
if (!(hs->flags & HS_ST_HTTP_EOM_RCVD)) {
int exp = (tick_isset(sc_ep_lra(hs->sc)) ? tick_add_ifset(sc_ep_lra(hs->sc), hs->sc->ioto) : TICK_ETERNITY);
t->expire = tick_first(t->expire, exp);
}
}
leave:
TRACE_LEAVE(HS_EV_PROCESS_HSTRM, hs);
@ -1109,6 +1145,7 @@ void *hstream_new(struct session *sess, struct stconn *sc, struct buffer *input)
hs->obj_type = OBJ_TYPE_HATERM;
hs->sess = sess;
hs->sc = sc;
hs->sc->ioto = sess->fe->timeout.client;
hs->task = t;
hs->req = BUF_NULL;
hs->res = BUF_NULL;