]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: http: add the ability to redefine http-err-codes and http-fail-codes
authorWilly Tarreau <w@1wt.eu>
Thu, 11 Jan 2024 11:06:49 +0000 (12:06 +0100)
committerWilly Tarreau <w@1wt.eu>
Thu, 11 Jan 2024 14:10:08 +0000 (15:10 +0100)
The new global keywords "http-err-codes" and "http-fail-codes" allow to
redefine which HTTP status codes indicate a client-induced error or a
server error, as tracked by stick-table counters. This is only done
globally, though everything was done so that it could easily be extended
to a per-proxy mechanism if there was a real need for this (but it would
eat quite more RAM then).

A simple reg-test was added (http-err-fail.vtc).

doc/configuration.txt
reg-tests/http-rules/http-err-fail.vtc [new file with mode: 0644]
src/http.c

index ece3919f0d8ff1ff7d3907d5776cf872ec3f0b31..18da6a52b143c7269457b8885648e1e3af367617 100644 (file)
@@ -1919,6 +1919,40 @@ hard-stop-after <time>
 
   See also: grace
 
+http-err-codes [+-]<range>[,...] [...]
+  Replace, reduce or extend the list of status codes that define an error as
+  considered by the termination codes and the "http_err_cnt" counter in stick
+  tables. The default range for errors is 400 to 499, but in certain contexts
+  some users prefer to exclude specific codes, especially when tracking client
+  errors (e.g. 404 on systems with dynamically generated contents). See also
+  "http-fail-codes" and "http_err_cnt".
+
+  A range specified without '+' nor '-' redefines the existing range to the new
+  one. A range starting with '+' extends the existing range to also include the
+  specified one, which may or may not overlap with the existing one. A range
+  starting with '-' removes the specified range from the existing one. A range
+  consists in a number from 100 to 599, optionally followed by "-" followed by
+  another number greater than or equal to the first one to indicate the high
+  boundary of the range. Multiple ranges may be delimited by commas for a same
+  add/del/ replace operation.
+
+  Example:
+     http-err-codes 400,402-444,446-480,490   # sets exactly these codes
+     http-err-codes 400-499 -450 +500         # sets 400 to 500 except 450
+     http-err-codes -450-459                  # removes 450 to 459 from range
+     http-err-codes +501,505                  # adds 501 and 505 to range
+
+http-fail-codes [+-]<range>[,...] [...]
+  Replace, reduce or extend the list of status codes that define a failure as
+  considered by the termination codes and the "http_fail_cnt" counter in stick
+  tables. The default range for failures is 500 to 599 except 501 and 505 which
+  can be triggered by clients, and normally indicate a failure from the server
+  to process the request. Some users prefer to exclude certain codes in certain
+  contexts where it is known they're not relevant, such as 500 in certain SOAP
+  environments as it doesn't translate a server fault there. The syntax is
+  exactly the same as for http-err-codes above. See also "http-err-codes" and
+  "http_fail_cnt".
+
 httpclient.resolvers.disabled <on|off>
   Disable the DNS resolution of the httpclient. Prevent the creation of the
   "default" resolvers section.
diff --git a/reg-tests/http-rules/http-err-fail.vtc b/reg-tests/http-rules/http-err-fail.vtc
new file mode 100644 (file)
index 0000000..6d8f9ea
--- /dev/null
@@ -0,0 +1,84 @@
+varnishtest "test for http-err-codes/http-fail-codes redefinitions"
+
+feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(3.0-dev1)'"
+feature ignore_unknown_macro
+
+server s2 {
+       rxreq
+       txresp -status 220
+} -start
+
+server s3 {
+       rxreq
+       txresp -status 300
+} -start
+
+server s4 {
+       rxreq
+       txresp -status 400
+} -start
+
+server s5 {
+       rxreq
+       txresp -status 555
+} -start
+
+
+haproxy h1 -conf {
+    global
+       http-err-codes  220 +300-499 -300-399  # only 220, 400-499 remain
+       http-fail-codes -550-580 +555,599,556-566
+
+    defaults
+       mode http
+       timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+       timeout client  "${HAPROXY_TEST_TIMEOUT-5s}"
+       timeout server  "${HAPROXY_TEST_TIMEOUT-5s}"
+       option socket-stats
+
+    frontend fe
+       bind "fd@${fe}"
+       http-request track-sc0 path
+       http-after-response add-header x-table err=%[sc0_http_err_cnt],fail=%[sc0_http_fail_cnt]
+       stick-table type string size 100 store http_err_cnt,http_fail_cnt
+       default_backend be
+
+    backend be
+       use-server s2 if { path -m beg /2 }
+       use-server s3 if { path -m beg /3 }
+       use-server s4 if { path -m beg /4 }
+       use-server s5 if { path -m beg /5 }
+
+       server s2 ${s2_addr}:${s2_port}
+       server s3 ${s3_addr}:${s3_port}
+       server s4 ${s4_addr}:${s4_port}
+       server s5 ${s5_addr}:${s5_port}
+} -start
+
+client c2 -connect ${h1_fe_sock} {
+       txreq -url "/2"
+       rxresp
+       expect resp.status == 220
+       expect resp.http.x-table ~ "err=1,fail=0"
+} -run
+
+client c3 -connect ${h1_fe_sock} {
+       txreq -url "/3"
+       rxresp
+       expect resp.status == 300
+       expect resp.http.x-table ~ "err=0,fail=0"
+} -run
+
+client c4 -connect ${h1_fe_sock} {
+       txreq -url "/4"
+       rxresp
+       expect resp.status == 400
+       expect resp.http.x-table ~ "err=1,fail=0"
+} -run
+
+client c5 -connect ${h1_fe_sock} {
+       txreq -url "/5"
+       rxresp
+       expect resp.status == 555
+       expect resp.http.x-table ~ "err=0,fail=1"
+} -run
index 82da7bb132dbaadb8ee1ce0b9a254afa17d197d1..8c94dfb3644202d26e565ec240a9c4875939da9e 100644 (file)
@@ -12,6 +12,7 @@
 
 #include <ctype.h>
 #include <haproxy/api.h>
+#include <haproxy/cfgparse.h>
 #include <haproxy/http.h>
 #include <haproxy/tools.h>
 
@@ -1483,3 +1484,100 @@ static void _http_init()
        http_status_del_range(http_fail_status_codes, 505, 505);
 }
 INITCALL0(STG_INIT, _http_init);
+
+/*
+ * registered keywords below
+ */
+
+/* parses a global "http-err-codes" and "http-fail-codes" directive. */
+static int http_parse_http_err_fail_codes(char **args, int section_type, struct proxy *curpx,
+                                         const struct proxy *defpx, const char *file, int line,
+                                         char **err)
+{
+       const char *cmd = args[0];
+       const char *p, *b, *e;
+       int op, low, high;
+       long *bitfield;
+       int ret = -1;
+
+       if (strcmp(cmd, "http-err-codes") == 0)
+               bitfield = http_err_status_codes;
+       else if (strcmp(cmd, "http-fail-codes") == 0)
+               bitfield = http_fail_status_codes;
+       else
+               ABORT_NOW();
+
+       if (!*args[1]) {
+               memprintf(err, "Missing status codes range for '%s'.", cmd);
+               goto end;
+       }
+
+       /* operation: <0 = remove, 0 = replace, >0 = add. The operation is only
+        * reset for each new arg so that we can do +200,300,400 without
+        * changing the operation.
+        */
+       for (; *(p = *(++args)); ) {
+               switch (*p) {
+               case '+': op =  1; p++; break;
+               case '-': op = -1; p++; break;
+               default:  op =  0; break;
+               }
+
+               if (!*p)
+                       goto inval;
+
+               while (1) {
+                       b = p;
+                       e = p + strlen(p);
+                       low = read_uint(&p, e);
+                       if (b == e || p == b)
+                               goto inval;
+
+                       high = low;
+                       if (*p == '-') {
+                               p++;
+                               b = p;
+                               high = read_uint(&p, e);
+                               if (b == e || p == b || (*p && *p != ','))
+                                       goto inval;
+                       }
+                       else if (*p && *p != ',')
+                               goto inval;
+
+                       if (high < low || low < 100 || high > 599) {
+                               memprintf(err, "Invalid status codes range '%s' in '%s'.\n"
+                                         " Codes must be between 100 and 599 and ranges in ascending order.",
+                                         *args, cmd);
+                               goto end;
+                       }
+
+                       if (!op)
+                               memset(bitfield, 0, sizeof(http_err_status_codes));
+                       if (op >= 0)
+                               http_status_add_range(bitfield, low, high);
+                       if (op < 0)
+                               http_status_del_range(bitfield, low, high);
+
+                       if (!*p)
+                               break;
+                       /* skip ',' */
+                       p++;
+               }
+       }
+       ret = 0;
+ end:
+       return ret;
+ inval:
+       memprintf(err, "Invalid status codes range '%s' in '%s' at position %lu. Ranges must be in the form [+-]{low[-{high}]}[,...].",
+                 *args, cmd, (ulong)(p - *args));
+       goto end;
+
+}
+
+static struct cfg_kw_list cfg_kws = {{ },{
+       { CFG_GLOBAL, "http-err-codes",      http_parse_http_err_fail_codes },
+       { CFG_GLOBAL, "http-fail-codes",     http_parse_http_err_fail_codes },
+       { /* END */ }
+}};
+
+INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);