]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: support for http-request set-timeout client
authorVladimir Vdovin <deliran@verdict.gg>
Wed, 27 Sep 2023 14:43:07 +0000 (17:43 +0300)
committerChristopher Faulet <cfaulet@haproxy.com>
Thu, 28 Sep 2023 06:49:22 +0000 (08:49 +0200)
Added set-timeout for frontend side of session, so it can be used to set
custom per-client timeouts if needed. Added cur_client_timeout to fetch
client timeout samples.

doc/configuration.txt
include/haproxy/action-t.h
include/haproxy/action.h
reg-tests/http-set-timeout/set_timeout.vtc
src/action.c
src/http_act.c
src/stream.c

index 37f62da33bcd001db4de069185f15ae97104ead7..e98f571f36ec1fcec388c6be9356237414a273f3 100644 (file)
@@ -7010,7 +7010,7 @@ http-request <action> [options...] [ { if | unless } <condition> ]
     - set-query <fmt>
     - set-src <expr>
     - set-src-port <expr>
-    - set-timeout { server | tunnel } { <timeout> | <expr> }
+    - set-timeout { client | server | tunnel } { <timeout> | <expr> }
     - set-tos <tos>
     - set-uri <fmt>
     - set-var(<var-name>[,<cond>...]) <expr>
@@ -7925,10 +7925,10 @@ http-request set-src-port <expr> [ { if | unless } <condition> ]
   the address family supports a port, otherwise it forces the source address to
   IPv4 "0.0.0.0" before rewriting the port.
 
-http-request set-timeout { server | tunnel } { <timeout> | <expr> }
+http-request set-timeout { client | server | tunnel } { <timeout> | <expr> }
                                        [ { if | unless } <condition> ]
 
-  This action overrides the specified "server" or "tunnel" timeout for the
+  This action overrides the specified "client", "server" or "tunnel" timeout for the
   current stream only. The timeout can be specified in millisecond or with any
   other unit if the number is suffixed by the unit as explained at the top of
   this document. It is also possible to write an expression which must returns
@@ -7936,8 +7936,8 @@ http-request set-timeout { server | tunnel } { <timeout> | <expr> }
 
   Note that the server/tunnel timeouts are only relevant on the backend side
   and thus this rule is only available for the proxies with backend
-  capabilities. Also the timeout value must be non-null to obtain the expected
-  results.
+  capabilities. As well as client timeout is only relevant for frontend side.
+  Also the timeout value must be non-null to obtain the expected results.
 
   Example:
     http-request set-timeout tunnel 5s
@@ -20098,6 +20098,11 @@ cur_tunnel_timeout : integer
   In the default case, this will be equal to be_tunnel_timeout unless a
   "set-timeout" rule has been applied. See also "be_tunnel_timeout".
 
+cur_client_timeout : integer
+  Returns the currently applied client timeout in millisecond for the stream.
+  In the default case, this will be equal to fe_client_timeout unless a
+  "set-timeout" rule has been applied. See also "fe_client_timeout".
+
 dst : ip
   This is the destination IP address of the connection on the client side,
   which is the address the client connected to. Any tcp/http rules may alter
@@ -20353,7 +20358,7 @@ fe_name : string
 
 fe_client_timeout : integer
   Returns the configuration value in millisecond for the client timeout of the
-  current frontend.
+  current frontend. This timeout can be overwritten by a "set-timeout" rule.
 
 res.timer.data : integer
   this is the total transfer time of the response payload till the last byte
index 7fafd612a4a68fcdaf76623f43431b0a4cfdf7e3..f77bdce5f441035a2c57fc1d8460e53040e588be 100644 (file)
@@ -99,6 +99,7 @@ enum act_name {
 enum act_timeout_name {
        ACT_TIMEOUT_SERVER,
        ACT_TIMEOUT_TUNNEL,
+       ACT_TIMEOUT_CLIENT,
 };
 
 enum act_normalize_uri {
index 8a35664f408b8e1c0f98da37df7aa842bdedf4ce..73d672e47ec07302c1884be90741ba81c96ebfae 100644 (file)
@@ -102,10 +102,8 @@ int check_trk_action(struct act_rule *rule, struct proxy *px, char **err);
  */
 int check_capture(struct act_rule *rule, struct proxy *px, char **err);
 
-int cfg_parse_rule_set_timeout(const char **args, int idx, int *out_timeout,
-                               enum act_timeout_name *name,
-                               struct sample_expr **expr, char **err,
-                               const char *file, int line, struct arg_list *al);
+int cfg_parse_rule_set_timeout(const char **args, int idx, struct act_rule *rule,
+                              struct proxy *px, char **err);
 
 static inline void release_timeout_action(struct act_rule *rule)
 {
index ebaa6a3b4feb396454a207532db6cd7bf8fdc3b2..6fa0a35f115d2e49e65a74c26e3a57c63a16d551 100644 (file)
@@ -4,7 +4,7 @@ feature ignore_unknown_macro
 
 #REQUIRE_VERSION=2.4
 
-server srv_h1 -repeat 3 {
+server srv_h1 -repeat 5 {
     rxreq
     txresp
 } -start
@@ -24,6 +24,16 @@ syslog Slog3 -level info {
     expect ~ "^.*timeout: 5000 3000.*$"
 } -start
 
+syslog Slog4 -level info {
+    recv
+    expect ~ "^.*timeout: 5000 5000.*$"
+} -start
+
+syslog Slog5 -level info {
+    recv
+    expect ~ "^.*timeout: 5000 3000.*$"
+} -start
+
 haproxy hap -conf {
     defaults
         timeout connect 5s
@@ -46,6 +56,14 @@ haproxy hap -conf {
         http-request set-timeout server 5s
        server srv_h1 ${srv_h1_addr}:${srv_h1_port}
 
+    listen li3
+        mode http
+        bind "fd@${li3}"
+        log-format "timeout: %[fe_client_timeout] %[cur_client_timeout]"
+        log ${Slog4_addr}:${Slog4_port} len 2048 local0 debug err
+        http-request set-timeout client 5s
+       server srv_h1 ${srv_h1_addr}:${srv_h1_port}
+
     frontend fe1
         mode http
         bind "fd@${fe1}"
@@ -57,6 +75,18 @@ haproxy hap -conf {
        mode http
        http-request set-timeout server int(3),mul(1000)
        server srv_h1 ${srv_h1_addr}:${srv_h1_port}
+
+    frontend fe2
+        mode http
+        bind "fd@${fe2}"
+        log-format "timeout: %[fe_client_timeout] %[cur_client_timeout]"
+        log ${Slog5_addr}:${Slog5_port} len 2048 local0 debug err
+        http-request set-timeout client int(3),mul(1000)
+        default_backend be2
+
+    backend be2
+       mode http
+       server srv_h1 ${srv_h1_addr}:${srv_h1_port}
 } -start
 
 client c1 -connect ${hap_li1_sock} {
@@ -77,6 +107,20 @@ client c3 -connect ${hap_fe1_sock} {
     expect resp.status == 200
 } -run
 
+client c4 -connect ${hap_li3_sock} {
+    txreq
+    rxresp
+    expect resp.status == 200
+} -run
+
+client c5 -connect ${hap_fe2_sock} {
+    txreq
+    rxresp
+    expect resp.status == 200
+} -run
+
 syslog Slog1 -wait
 syslog Slog2 -wait
 syslog Slog3 -wait
+syslog Slog4 -wait
+syslog Slog5 -wait
index 9d3bfe4b900fa77ede8b4d5d98e54c4f94289d98..47f5f8607a1e85e9f865c24ff813ee25dd839a0e 100644 (file)
@@ -174,35 +174,49 @@ int act_resolution_error_cb(struct resolv_requester *requester, int error_code)
 }
 
 /* Parse a set-timeout rule statement. It first checks if the timeout name is
- * valid and returns it in <name>. Then the timeout is parsed as a plain value
- * and * returned in <out_timeout>. If there is a parsing error, the value is
- * reparsed as an expression and returned in <expr>.
+ * valid and proxy is capable of handling it, and returns it in <rule->arg.timeout.type>.
+ * Then the timeout is parsed as a plain value and * returned in <rule->arg.timeout.value>.
+ * If there is a parsing error, the value is reparsed as an expression and
+ * returned in <rule->arg.timeout.expr>.
  *
  * Returns -1 if the name is invalid or neither a time or an expression can be
  * parsed, or if the timeout value is 0.
  */
-int cfg_parse_rule_set_timeout(const char **args, int idx, int *out_timeout,
-                               enum act_timeout_name *name,
-                               struct sample_expr **expr, char **err,
-                               const char *file, int line, struct arg_list *al)
+int cfg_parse_rule_set_timeout(const char **args, int idx, struct act_rule *rule,
+                              struct proxy *px, char **err)
 {
        const char *res;
        const char *timeout_name = args[idx++];
 
        if (strcmp(timeout_name, "server") == 0) {
-               *name = ACT_TIMEOUT_SERVER;
+               if (!(px->cap & PR_CAP_BE)) {
+                       memprintf(err, "'%s' has no backend capability", px->id);
+                       return -1;
+               }
+               rule->arg.timeout.type = ACT_TIMEOUT_SERVER;
        }
        else if (strcmp(timeout_name, "tunnel") == 0) {
-               *name = ACT_TIMEOUT_TUNNEL;
+               if (!(px->cap & PR_CAP_BE)) {
+                       memprintf(err, "'%s' has no backend capability", px->id);
+                       return -1;
+               }
+               rule->arg.timeout.type = ACT_TIMEOUT_TUNNEL;
+       }
+       else if (strcmp(timeout_name, "client") == 0) {
+               if (!(px->cap & PR_CAP_FE)) {
+                       memprintf(err, "'%s' has no frontend capability", px->id);
+                       return -1;
+               }
+               rule->arg.timeout.type = ACT_TIMEOUT_CLIENT;
        }
        else {
                memprintf(err,
-                         "'set-timeout' rule supports 'server'/'tunnel' (got '%s')",
+                         "'set-timeout' rule supports 'server'/'tunnel'/'client' (got '%s')",
                          timeout_name);
                return -1;
        }
 
-       res = parse_time_err(args[idx], (unsigned int *)out_timeout, TIME_UNIT_MS);
+       res = parse_time_err(args[idx], (unsigned int *)&rule->arg.timeout.value, TIME_UNIT_MS);
        if (res == PARSE_TIME_OVER) {
                memprintf(err, "timer overflow in argument '%s' to rule 'set-timeout %s' (maximum value is 2147483647 ms or ~24.8 days)",
                          args[idx], timeout_name);
@@ -215,14 +229,15 @@ int cfg_parse_rule_set_timeout(const char **args, int idx, int *out_timeout,
        }
        /* res not NULL, parsing error */
        else if (res) {
-               *expr = sample_parse_expr((char **)args, &idx, file, line, err, al, NULL);
-               if (!*expr) {
+               rule->arg.timeout.expr = sample_parse_expr((char **)args, &idx, px->conf.args.file,
+                                                          px->conf.args.line, err, &px->conf.args, NULL);
+               if (!rule->arg.timeout.expr) {
                        memprintf(err, "unexpected character '%c' in rule 'set-timeout %s'", *res, timeout_name);
                        return -1;
                }
        }
        /* res NULL, parsing ok but value is 0 */
-       else if (!(*out_timeout)) {
+       else if (!(rule->arg.timeout.value)) {
                memprintf(err, "null value is not valid for a 'set-timeout %s' rule",
                          timeout_name);
                return -1;
index d168cf5e034c433857d8518c23c00f3203182733..1878b03dd92d0a15671aeaf4b59328605943a466 100644 (file)
@@ -2192,18 +2192,7 @@ static enum act_parse_ret parse_http_set_timeout(const char **args,
                return ACT_RET_PRS_ERR;
        }
 
-       if (!(px->cap & PR_CAP_BE)) {
-               memprintf(err, "proxy '%s' has no backend capability", px->id);
-               return ACT_RET_PRS_ERR;
-       }
-
-       if (cfg_parse_rule_set_timeout(args, cur_arg,
-                                      &rule->arg.timeout.value,
-                                      &rule->arg.timeout.type,
-                                      &rule->arg.timeout.expr,
-                                      err,
-                                      px->conf.args.file,
-                                      px->conf.args.line, &px->conf.args) == -1) {
+       if (cfg_parse_rule_set_timeout(args, cur_arg, rule, px, err) == -1) {
                return ACT_RET_PRS_ERR;
        }
 
index 45b0c56d9849cdfb0f3dbcf05f1a2e4433960b72..d59d28f59f083bf9e1af2dc5b4f2d2a86a032a18 100644 (file)
@@ -867,6 +867,10 @@ void stream_retnclose(struct stream *s, const struct buffer *msg)
 int stream_set_timeout(struct stream *s, enum act_timeout_name name, int timeout)
 {
        switch (name) {
+       case ACT_TIMEOUT_CLIENT:
+               s->scf->ioto = timeout;
+               return 1;
+
        case ACT_TIMEOUT_SERVER:
                s->scb->ioto = timeout;
                return 1;
@@ -3939,6 +3943,17 @@ static struct action_kw_list stream_http_after_res_actions =  { ILH, {
 
 INITCALL1(STG_REGISTER, http_after_res_keywords_register, &stream_http_after_res_actions);
 
+static int smp_fetch_cur_client_timeout(const struct arg *args, struct sample *smp, const char *km, void *private)
+{
+       smp->flags = SMP_F_VOL_TXN;
+       smp->data.type = SMP_T_SINT;
+       if (!smp->strm)
+               return 0;
+
+       smp->data.u.sint = TICKS_TO_MS(smp->strm->scf->ioto);
+       return 1;
+}
+
 static int smp_fetch_cur_server_timeout(const struct arg *args, struct sample *smp, const char *km, void *private)
 {
        smp->flags = SMP_F_VOL_TXN;
@@ -3989,6 +4004,7 @@ static int smp_fetch_last_rule_line(const struct arg *args, struct sample *smp,
  * Please take care of keeping this list alphabetically sorted.
  */
 static struct sample_fetch_kw_list smp_kws = {ILH, {
+       { "cur_client_timeout", smp_fetch_cur_client_timeout, 0, NULL, SMP_T_SINT, SMP_USE_FTEND, },
        { "cur_server_timeout", smp_fetch_cur_server_timeout, 0, NULL, SMP_T_SINT, SMP_USE_BKEND, },
        { "cur_tunnel_timeout", smp_fetch_cur_tunnel_timeout, 0, NULL, SMP_T_SINT, SMP_USE_BKEND, },
        { "last_rule_file",     smp_fetch_last_rule_file,     0, NULL, SMP_T_STR,  SMP_USE_INTRN, },