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 | -
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 :
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"
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);
--- /dev/null
+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
* 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)
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:
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]) {
}
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;
&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;
*
* 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;
}
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;