MINOR: http-act: Add 'no-fail' option on 'wait-for-body' action

With this option, the "wait-for-body" action does not return an error at the
end of the timeout but continues the message analysis with the body received
so far, instead of responding with a 408 "Request Timeout" error to the
client on the request side or a 504 "Gateway Timeout" error on the response
side. The remaining body bytes, if any, are streamed to the server or to the
client once received.
This commit is contained in:
Christopher Faulet committed 2026-11-02 21:25:24 +01:00
1 parent 3359100f56
commit 92f34653b4
5 files changed
+97 -10

No files matched your search

+7 -1
View File
@@ -17865,7 +17865,7 @@ use-service <service-name>
http-request use-service prometheus-exporter if { path /metrics }
wait-for-body time <time> [ at-least <bytes> ] [use-large-buffer]
wait-for-body time <time> [ at-least <bytes> ] [use-large-buffer] [no-fail]
Usable in: QUIC Ini| TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft
- | - | - | - | - | X | X | -
@@ -17889,6 +17889,11 @@ wait-for-body time <time> [ at-least <bytes> ] [use-large-buffer]
one is to small to store the message body. To be used, "tune.bufsize.large"
global option must be defined.
"no-fail" option may be set to not return an error to the client at the end
of the <time> timeout but to continue the message analysis with the body
received so far. The remaining body bytes, if any, are forwarded once
received.
This action may be used as a replacement for "option http-buffer-request".
Arguments :
@@ -17902,6 +17907,7 @@ wait-for-body time <time> [ at-least <bytes> ] [use-large-buffer]
Example:
http-request wait-for-body time 1s at-least 1k if METH_POST
http-response wait-for-body time 1s no-fail if { status eq 200 }
See also : "option http-buffer-request" and "tune.bufsize.large"
+1 -1
View File
@@ -49,7 +49,7 @@ int http_req_replace_stline(int action, const char *replace, int len,
int http_res_set_status(unsigned int status, struct ist reason, struct stream *s);
void http_check_request_for_cacheability(struct stream *s, struct channel *req);
void http_check_response_for_cacheability(struct stream *s, struct channel *res);
enum rule_result http_wait_for_msg_body(struct stream *s, struct channel *chn, unsigned int time, unsigned int bytes, unsigned int large_buffer);
enum rule_result http_wait_for_msg_body(struct stream *s, struct channel *chn, unsigned int time, unsigned int bytes, unsigned int large_buffer, unsigned int no_fail);
void http_perform_server_redirect(struct stream *s, struct stconn *sc);
void http_server_error(struct stream *s, struct stconn *sc, int err, int finst, struct http_reply *msg);
void http_reply_and_close(struct stream *s, short status, struct http_reply *msg);
@@ -0,0 +1,72 @@
varnishtest "A test for the no-fail option of the wait-for-body HTTP action"
feature ignore_unknown_macro
#REGTEST_TYPE=slow
server s1 {
rxreq
expect req.bodylen == 1000
txresp
} -start
server s2 {
rxreq
send "HTTP/1.1 200 OK\r\n"
send "Content-Length: 1000\r\n\r\n"
delay 0.15
send_n 13 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=====================================\n"
} -start
haproxy h1 -conf {
global
.if feature(THREAD)
thread-groups 1
.endif
defaults
mode http
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
frontend fe1
bind "fd@${fe1}"
http-request wait-for-body time 50ms no-fail
default_backend be1
backend be1
server srv1 ${s1_addr}:${s1_port}
frontend fe2
bind "fd@${fe2}"
http-response wait-for-body time 50ms no-fail
default_backend be2
backend be2
server srv1 ${s2_addr}:${s2_port}
} -start
# The wait-for-body timeout expires with only part of the request body
# received. Thanks to the no-fail option, the request is processed and
# forwarded to the server anyway. The remaining body bytes are streamed to
# the server once received.
client c1 -connect ${h1_fe1_sock} {
send "POST / HTTP/1.1\r\n"
send "Content-Length: 1000\r\n\r\n"
send "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=====================================\n"
delay 0.15
send_n 12 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=====================================\n"
rxresp
expect resp.status == 200
} -run
# Same test on the response side. The wait-for-body timeout expires with
# only part of the response body received, but the response is processed
# and forwarded to the client anyway. The remaining body bytes are streamed
# to the client once received.
client c2 -connect ${h1_fe2_sock} {
txreq
rxresp
expect resp.status == 200
expect resp.bodylen == 1000
} -run
+11 -5
View File
@@ -2708,7 +2708,8 @@ static enum act_parse_ret parse_http_return(const char **args, int *orig_arg, st
* Otherwise ACT_RET_YIELD is returned to wait for more data. ACT_RET_INV is
* returned if a parsing error is raised by lower level and ACT_RET_ERR if an
* internal error occurred. Finally ACT_RET_ABRT is returned when a timeout
* occurred.
* occurred, unless the 'no-fail' option is set, in which case ACT_RET_CONT is
* returned to continue the analysis.
*/
static enum act_return http_action_wait_for_body(struct act_rule *rule, struct proxy *px,
struct session *sess, struct stream *s, int flags)
@@ -2717,8 +2718,9 @@ static enum act_return http_action_wait_for_body(struct act_rule *rule, struct p
unsigned int time = (uintptr_t)rule->arg.act.p[0];
unsigned int bytes = (uintptr_t)rule->arg.act.p[1];
unsigned int large_buffer = (uintptr_t)rule->arg.act.p[2];
unsigned int no_fail = (uintptr_t)rule->arg.act.p[3];
switch (http_wait_for_msg_body(s, chn, time, bytes, large_buffer)) {
switch (http_wait_for_msg_body(s, chn, time, bytes, large_buffer, no_fail)) {
case HTTP_RULE_RES_CONT:
return ACT_RET_CONT;
case HTTP_RULE_RES_YIELD:
@@ -2756,18 +2758,19 @@ static enum act_parse_ret parse_http_wait_for_body(const char **args, int *orig_
struct act_rule *rule, char **err)
{
int cur_arg;
unsigned int time, bytes, large_buffer;
unsigned int time, bytes, large_buffer, no_fail;
const char *res;
cur_arg = *orig_arg;
if (!*args[cur_arg]) {
memprintf(err, "expects time <time> [ at-least <bytes> ]");
memprintf(err, "expects time <time> [ at-least <bytes> ] [use-large-buffer] [no-fail]");
return ACT_RET_PRS_ERR;
}
time = UINT_MAX; /* To be sure it is set */
bytes = 0; /* Default value, wait all the body */
large_buffer = 0; /* Don't use large buffers by default */
no_fail = 0; /* Report an error on timeout by default */
while (*(args[cur_arg])) {
if (strcmp(args[cur_arg], "time") == 0) {
if (!*args[cur_arg + 1]) {
@@ -2803,19 +2806,22 @@ static enum act_parse_ret parse_http_wait_for_body(const char **args, int *orig_
}
else if (strcmp(args[cur_arg], "use-large-buffer") == 0)
large_buffer = 1;
else if (strcmp(args[cur_arg], "no-fail") == 0)
no_fail = 1;
else
break;
cur_arg++;
}
if (time == UINT_MAX) {
memprintf(err, "expects time <time> [ at-least <bytes> ]");
memprintf(err, "expects time <time> [ at-least <bytes> ] [use-large-buffer] [no-fail]");
return ACT_RET_PRS_ERR;
}
rule->arg.act.p[0] = (void *)(uintptr_t)time;
rule->arg.act.p[1] = (void *)(uintptr_t)bytes;
rule->arg.act.p[2] = (void *)(uintptr_t)large_buffer;
rule->arg.act.p[3] = (void *)(uintptr_t)no_fail;
*orig_arg = cur_arg;
+6 -3
View File
@@ -849,7 +849,7 @@ int http_wait_for_request_body(struct stream *s, struct channel *req, int an_bit
&s->txn.http->req);
switch (http_wait_for_msg_body(s, req, s->be->timeout.httpreq, 0, 0)) {
switch (http_wait_for_msg_body(s, req, s->be->timeout.httpreq, 0, 0, 0)) {
case HTTP_RULE_RES_CONT:
s->waiting_entity.type = STRM_ENTITY_NONE;
s->waiting_entity.ptr = NULL;
@@ -4353,10 +4353,11 @@ static int http_handle_stats(struct stream *s, struct channel *req, struct proxy
*
* If a timeout occurred, this function is responsible to emit the right response
* to the client, depending on the channel (408 on request side, 504 on response
* side). All other errors must be handled by the caller.
* side), unless <no_fail> is set. All other errors must be handled by the caller.
*/
enum rule_result http_wait_for_msg_body(struct stream *s, struct channel *chn,
unsigned int time, unsigned int bytes, unsigned int large_buffer)
unsigned int time, unsigned int bytes,
unsigned int large_buffer, unsigned int no_fail)
{
struct session *sess = s->sess;
struct http_txn *txn = s->txn.http;
@@ -4417,6 +4418,8 @@ enum rule_result http_wait_for_msg_body(struct stream *s, struct channel *chn,
}
if ((chn->flags & CF_READ_TIMEOUT) || tick_is_expired(chn->analyse_exp, now_ms)) {
if (no_fail)
goto end; /* on timeout, stop waiting and continue the analysis */
if (!(chn->flags & CF_ISRESP))
goto abort_req;
goto abort_res;