mirror of
https://github.com/haproxy/haproxy.git
synced 2026-11-06 13:05:33 +00:00
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:
1 parent
3359100f56
commit
92f34653b4
5 files changed
+97
-10
No files matched your search
@@ -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"
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user