]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: checks: Add a way to send custom headers and payload during http chekcs
authorChristopher Faulet <cfaulet@haproxy.com>
Thu, 9 Apr 2020 06:44:06 +0000 (08:44 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 27 Apr 2020 07:39:37 +0000 (09:39 +0200)
The 'http-check send' directive have been added to add headers and optionnaly a
payload to the request sent during HTTP healthchecks. The request line may be
customized by the "option httpchk" directive but there was not official way to
add extra headers. An old trick consisted to hide these headers at the end of
the version string, on the "option httpchk" line. And it was impossible to add
an extra payload with an "http-check expect" directive because of the
"Connection: close" header appended to the request (See issue #16 for details).

So to make things official and fully support payload additions, the "http-check
send" directive have been added :

    option httpchk POST /status HTTP/1.1

    http-check send hdr Content-Type "application/json;charset=UTF-8" \
        hdr X-test-1 value1 hdr X-test-2 value2 \
        body "{id: 1, field: \"value\"}"

When a payload is defined, the Content-Length header is automatically added. So
chunk-encoded requests are not supported yet. For now, there is no special
validity checks on the extra headers.

This patch is inspired by Kiran Gavali's work. It should fix the issue #16 and
as far as possible, it may be backported, at least as far as 1.8.

doc/configuration.txt
include/types/proxy.h
reg-tests/checks/http-check-send.vtc [new file with mode: 0644]
src/cfgparse-listen.c
src/checks.c
src/haproxy.c

index 676d5a07559e455dcca12f481578cd64b62336bc..3f247b6d88d0970d6553e662b684763097f2a20c 100644 (file)
@@ -4491,6 +4491,33 @@ http-check expect [!] <match> <pattern>
   See also : "option httpchk", "http-check disable-on-404"
 
 
+http-check send [hdr <name> <value>]* [body <string>]
+  Add a possible list of headers and/or a body to the request sent during HTTP
+  health checks.
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    no    |   yes  |   yes
+  Arguments :
+    hdr <name> <value>   adds the HTTP header field whose name is specified in
+                         <name> and whose value is defined by <value> to the
+                         request sent during HTTP health checks.
+
+    body <string>        add the body defined by <string> to the request sent
+                         sent during HTTP health checks. If defined, the
+                         "Content-Length" header is thus automatically added
+                         to the request.
+
+  In addition to the request line defined by the "option httpchk" directive,
+  this one is the valid way to add some headers and optionally a body to the
+  request sent during HTTP health checks. If a body is defined, the associate
+  "Content-Length" header is automatically added. The old trick consisting to
+  add headers after the version string on the "option httpchk" line is now
+  deprecated. Note also the "Connection: close" header is still added if a
+  "http-check expect" direcive is defined independently of this directive, just
+  like the state header if the directive "http-check send-state" is defined.
+
+  See also : "option httpchk", "http-check send-state" and "http-check expect"
+
+
 http-check send-state
   Enable emission of a state header with HTTP health checks
   May be used in sections :   defaults | frontend | listen | backend
@@ -7143,8 +7170,7 @@ option httpchk <method> <uri> <version>
     <version> is the optional HTTP version string. It defaults to "HTTP/1.0"
               but some servers might behave incorrectly in HTTP 1.0, so turning
               it to HTTP/1.1 may sometimes help. Note that the Host field is
-              mandatory in HTTP/1.1, and as a trick, it is possible to pass it
-              after "\r\n" following the version string.
+              mandatory in HTTP/1.1, use "http-check send" directive to add it.
 
   By default, server health checks only consist in trying to establish a TCP
   connection. When "option httpchk" is specified, a complete HTTP request is
@@ -7158,12 +7184,18 @@ option httpchk <method> <uri> <version>
   plain TCP backends. This is particularly useful to check simple scripts bound
   to some dedicated ports using the inetd daemon.
 
+  Note : For a while, there was no way to add headers or body in the request
+         used for HTTP health checks. So a workaround was to hide it at the end
+         of the version string with a "\r\n" after the version. It is now
+        deprecated. The directive "http-check send" must be used instead.
+
   Examples :
       # Relay HTTPS traffic to Apache instance and check service availability
       # using HTTP request "OPTIONS * HTTP/1.1" on port 80.
       backend https_relay
           mode tcp
-          option httpchk OPTIONS * HTTP/1.1\r\nHost:\ www
+          option httpchk OPTIONS * HTTP/1.1
+          http-check send hdr Host www
           server apache1 192.168.1.1:443 check port 80
 
   See also : "option ssl-hello-chk", "option smtpchk", "option mysql-check",
index 84ac8d058dbe16ed7ccaf913f7c0aff7a18e6c00..9666909e56bfc3388ef8fb2e333c092d31baddd5 100644 (file)
@@ -426,6 +426,10 @@ struct proxy {
        int grace;                              /* grace time after stop request */
        int check_len;                          /* Length of the HTTP or SSL3 request */
        char *check_req;                        /* HTTP or SSL request to use for PR_O_HTTP_CHK|PR_O_SSL3_CHK */
+       int check_body_len;                     /* Length of the request body for HTTP checks */
+       char *check_hdrs;                       /* Request headers for HTTP cheks */
+       int check_hdrs_len;                     /* Length of the headers for HTTP checks */
+       char *check_body;                       /* Request body for HTTP cheks */
        char *check_command;                    /* Command to use for external agent checks */
        char *check_path;                       /* PATH environment to use for external agent checks */
        char *expect_str;                       /* http-check expected content : string or text version of the regex */
diff --git a/reg-tests/checks/http-check-send.vtc b/reg-tests/checks/http-check-send.vtc
new file mode 100644 (file)
index 0000000..7e2e37b
--- /dev/null
@@ -0,0 +1,150 @@
+varnishtest "Health-checks: http-check send test"
+
+feature ignore_unknown_macro
+
+# This script tests HTTP health-checks and more particularly the "http-check
+# send" directive.
+
+server s1 {
+    rxreq
+    expect req.method == OPTIONS
+    expect req.url == /
+    expect req.proto == HTTP/1.0
+    txresp
+} -start
+
+server s2 {
+    rxreq
+    expect req.method == GET
+    expect req.url == /test
+    expect req.proto == HTTP/1.1
+    txresp
+} -start
+
+server s3 {
+    rxreq
+    expect req.method == OPTIONS
+    expect req.url == /
+    expect req.proto == HTTP/1.0
+    expect req.http.hdr == <undef>
+    expect req.http.host == <undef>
+    expect req.http.x-test == <undef>
+    expect req.http.content-length == <undef>
+    expect req.bodylen == 0
+    txresp
+} -start
+
+server s4 {
+    rxreq
+    expect req.method == GET
+    expect req.url == /status
+    expect req.proto == HTTP/1.1
+    expect req.http.hdr == <undef>
+    expect req.http.host == "my-www-host"
+    expect req.http.x-test == true
+    expect req.http.content-length == 4
+    expect req.bodylen == 4
+    expect req.body == "test"
+    txresp
+} -start
+
+server s5 {
+    rxreq
+    expect req.method == GET
+    expect req.url == /status
+    expect req.proto == HTTP/1.1
+    expect req.http.hdr == <undef>
+    expect req.http.host == "other-www-host"
+    expect req.http.x-test == <undef>
+    expect req.http.x-new-test == true
+    expect req.http.content-length == 10
+    expect req.bodylen == 10
+    expect req.body == "other test"
+    txresp
+} -start
+
+
+syslog S1 -level notice {
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Proxy be1 started."
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be1/srv succeeded.*code: 200"
+} -start
+
+syslog S2 -level notice {
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Proxy be2 started."
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be2/srv succeeded.*code: 200"
+} -start
+
+syslog S3 -level notice {
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Proxy be3 started."
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be3/srv succeeded.*code: 200"
+} -start
+
+syslog S4 -level notice {
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Proxy be4 started."
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be4/srv succeeded.*code: 200"
+} -start
+
+syslog S5 -level notice {
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Proxy be5 started."
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be5/srv succeeded.*code: 200"
+} -start
+
+
+haproxy h1 -conf {
+    defaults
+        mode http
+        timeout client 1s
+        timeout server 1s
+        timeout connect 200ms
+        option httpchk
+        option log-health-checks
+
+    backend be1
+        log ${S1_addr}:${S1_port} len 2048 local0
+        server srv ${s1_addr}:${s1_port} check inter 200ms rise 1 fall 1
+
+    backend be2
+        log ${S2_addr}:${S2_port} len 2048 local0
+        option httpchk GET /test HTTP/1.1
+        server srv ${s2_addr}:${s2_port} check inter 200ms rise 1 fall 1
+
+    defaults
+        mode http
+        timeout client 1s
+        timeout server 1s
+        timeout connect 200ms
+        option httpchk GET /status HTTP/1.1\r\nHdr:\ must-be-removed
+        option log-health-checks
+        http-check send hdr Host "my-www-host" hdr X-test true body "test"
+
+    backend be3
+        option httpchk
+        log ${S3_addr}:${S3_port} len 2048 local0
+        server srv ${s3_addr}:${s3_port} check inter 200ms rise 1 fall 1
+
+    backend be4
+        log ${S4_addr}:${S4_port} len 2048 local0
+        server srv ${s4_addr}:${s4_port} check inter 200ms rise 1 fall 1
+
+    backend be5
+        log ${S5_addr}:${S5_port} len 2058 local0
+        http-check send hdr Host "other-www-host" hdr X-New-Test true body "other test"
+        server srv ${s5_addr}:${s5_port} check inter 200ms rise 1 fall 1
+
+} -start
+
+syslog S1 -wait
+syslog S2 -wait
+syslog S3 -wait
+syslog S4 -wait
+syslog S5 -wait
index 7ebd2d5134722a613bd838b43bfd8f668909837c..fbefdd5aa2fbbd9f4d68ca8b3a921d44c30f1f04 100644 (file)
@@ -291,6 +291,18 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                        }
                        curproxy->check_len = defproxy.check_len;
 
+                       if (defproxy.check_hdrs) {
+                               curproxy->check_hdrs = calloc(1, defproxy.check_hdrs_len);
+                               memcpy(curproxy->check_hdrs, defproxy.check_hdrs, defproxy.check_hdrs_len);
+                       }
+                       curproxy->check_hdrs_len = defproxy.check_hdrs_len;
+
+                       if (defproxy.check_body) {
+                               curproxy->check_body = calloc(1, defproxy.check_body_len);
+                               memcpy(curproxy->check_body, defproxy.check_body, defproxy.check_body_len);
+                       }
+                       curproxy->check_body_len = defproxy.check_body_len;
+
                        if (defproxy.expect_str) {
                                curproxy->expect_str = strdup(defproxy.expect_str);
                                if (defproxy.expect_regex) {
@@ -2309,7 +2321,10 @@ stats_error_parsing:
 
                        /* use HTTP request to check servers' health */
                        free(curproxy->check_req);
-                       curproxy->check_req = NULL;
+                       free(curproxy->check_hdrs);
+                       free(curproxy->check_body);
+                       curproxy->check_req = curproxy->check_hdrs = curproxy->check_body = NULL;
+                       curproxy->check_len = curproxy->check_hdrs_len = curproxy->check_body_len = 0;
                        curproxy->options2 &= ~PR_O2_CHK_ANY;
                        curproxy->options2 |= PR_O2_HTTP_CHK;
                        if (!*args[2]) { /* no argument */
@@ -2320,16 +2335,49 @@ stats_error_parsing:
                                curproxy->check_req = malloc(reqlen);
                                curproxy->check_len = snprintf(curproxy->check_req, reqlen,
                                                               "OPTIONS %s HTTP/1.0\r\n", args[2]); /* URI to use */
-                       } else { /* more arguments : METHOD URI [HTTP_VER] */
-                               int reqlen = strlen(args[2]) + strlen(args[3]) + 3 + strlen("\r\n");
-                               if (*args[4])
-                                       reqlen += strlen(args[4]);
-                               else
-                                       reqlen += strlen("HTTP/1.0");
+                       } else if (!*args[4]) { /* two arguments : METHOD URI */
+                               int reqlen = strlen(args[2]) + strlen(args[3]) + strlen(" HTTP/1.0\r\n") + 1;
 
                                curproxy->check_req = malloc(reqlen);
                                curproxy->check_len = snprintf(curproxy->check_req, reqlen,
-                                                              "%s %s %s\r\n", args[2], args[3], *args[4]?args[4]:"HTTP/1.0");
+                                                              "%s %s HTTP/1.0\r\n", args[2], args[3]);
+                       } else { /* 3 arguments : METHOD URI HTTP_VER */
+                               char *vsn = args[4];
+                               char *hdrs = strstr(vsn, "\r\n");
+                               char *body = strstr(vsn, "\r\n\r\n");
+
+                               if (hdrs || body) {
+                                       ha_warning("parsing [%s:%d]: '%s %s' : hiding headers or body at the end of the version string is deprecated."
+                                                  " Please, consider to use 'http-check send' directive instead.\n",
+                                                  file, linenum, args[0], args[1]);
+                                       err_code |= ERR_WARN;
+                               }
+
+                               if (hdrs == body)
+                                       hdrs = NULL;
+                               if (hdrs) {
+                                       *hdrs = '\0';
+                                       hdrs += 2;
+                               }
+                               if (body) {
+                                       *body = '\0';
+                                       body += 4;
+                               }
+
+                               curproxy->check_len = strlen(args[2]) + strlen(args[3]) + strlen(vsn) + 4;
+                               curproxy->check_req = malloc(curproxy->check_len+1);
+                               snprintf(curproxy->check_req, curproxy->check_len+1, "%s %s %s\r\n", args[2], args[3], vsn);
+
+                               if (hdrs) {
+                                       curproxy->check_hdrs_len = strlen(hdrs) + 2;
+                                       curproxy->check_hdrs = malloc(curproxy->check_hdrs_len+1);
+                                       snprintf(curproxy->check_hdrs, curproxy->check_hdrs_len+1, "%s\r\n", hdrs);
+                               }
+
+                               if (body) {
+                                       curproxy->check_body_len = strlen(body);
+                                       curproxy->check_body = strdup(body);
+                               }
                        }
                        if (alertif_too_many_args_idx(3, 1, file, linenum, args, &err_code))
                                goto out;
@@ -2811,6 +2859,63 @@ stats_error_parsing:
                        if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code))
                                goto out;
                }
+               else if (strcmp(args[1], "send") == 0) {
+                       int cur_arg = 2;
+
+                       free(curproxy->check_hdrs);
+                       free(curproxy->check_body);
+                       curproxy->check_hdrs = curproxy->check_body = NULL;
+                       curproxy->check_hdrs_len = curproxy->check_body_len = 0;
+                       while (*(args[cur_arg])) {
+                               if (strcmp(args[cur_arg], "hdr") == 0) {
+                                       int hdr_len;
+                                       if (!*(args[cur_arg+1]) || !*(args[cur_arg+2])) {
+                                               ha_alert("parsing [%s:%d] : '%s %s' : %s expects a name and a value as parameter.\n",
+                                                        file, linenum, args[0], args[1], args[cur_arg]);
+                                               err_code |= ERR_ALERT | ERR_FATAL;
+                                               goto out;
+                                       }
+
+                                       cur_arg++;
+                                       hdr_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 4;
+                                       curproxy->check_hdrs = my_realloc2(curproxy->check_hdrs, curproxy->check_hdrs_len+hdr_len+1);
+                                       if (curproxy->check_hdrs == NULL) {
+                                               ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum);
+                                               err_code |= ERR_ALERT | ERR_FATAL;
+                                               goto out;
+                                       }
+                                       snprintf(curproxy->check_hdrs + curproxy->check_hdrs_len, hdr_len+1, "%s: %s\r\n", args[cur_arg], args[cur_arg+1]);
+                                       curproxy->check_hdrs_len += hdr_len;
+
+                                       cur_arg++;
+                               }
+                               else if (strcmp(args[cur_arg], "body") == 0) {
+                                       if (!*(args[cur_arg+1])) {
+                                               ha_alert("parsing [%s:%d] : '%s %s' : %s expects a string as parameter.\n",
+                                                        file, linenum, args[0], args[1], args[cur_arg]);
+                                               err_code |= ERR_ALERT | ERR_FATAL;
+                                               goto out;
+                                       }
+                                       cur_arg++;
+                                       free(curproxy->check_body);
+                                       curproxy->check_body = strdup(args[cur_arg]);
+                                       curproxy->check_body_len = strlen(args[cur_arg]);
+                                       if (curproxy->check_body == NULL) {
+                                               ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum);
+                                               err_code |= ERR_ALERT | ERR_FATAL;
+                                               goto out;
+                                       }
+                               }
+                               else {
+                                       ha_alert("parsing [%s:%d] : '%s %s' only supports 'hdr' and 'body', found '%s'.\n",
+                                                file, linenum, args[0], args[1], args[cur_arg]);
+                                       err_code |= ERR_ALERT | ERR_FATAL;
+                                       goto out;
+                               }
+                               cur_arg++;
+                       }
+
+               }
                else if (strcmp(args[1], "expect") == 0) {
                        const char *ptr_arg;
                        int cur_arg;
index e7a889f161b3ca2277fffebe9277be1b873ecf6d..22a2c501bccf13b05037a81daff884d3cce9e466 100644 (file)
@@ -1599,13 +1599,30 @@ static int connect_conn_chk(struct task *t)
                        memcpy(b_head(&check->bo) + 11, &gmt_time, 4);
                }
                else if ((check->type) == PR_O2_HTTP_CHK) {
-                       if (s->proxy->options2 & PR_O2_CHK_SNDST)
-                               b_putblk(&check->bo, trash.area,
-                                        httpchk_build_status_header(s, trash.area, trash.size));
                        /* prevent HTTP keep-alive when "http-check expect" is used */
                        if (s->proxy->options2 & PR_O2_EXP_TYPE)
                                b_putist(&check->bo, ist("Connection: close\r\n"));
+
+                       /* If there is a body, add its content-length */
+                       if (s->proxy->check_body_len)
+                               chunk_appendf(&check->bo, "Content-Length: %s\r\n", ultoa(s->proxy->check_body_len));
+
+                       /* Add configured headers */
+                       if (s->proxy->check_hdrs)
+                               b_putblk(&check->bo, s->proxy->check_hdrs, s->proxy->check_hdrs_len);
+
+                       /* Add send-state header */
+                       if (s->proxy->options2 & PR_O2_CHK_SNDST)
+                               b_putblk(&check->bo, trash.area,
+                                        httpchk_build_status_header(s, trash.area, trash.size));
+
+                       /* end-of-header */
                        b_putist(&check->bo, ist("\r\n"));
+
+                       /* Add the body */
+                       if (s->proxy->check_body)
+                               b_putblk(&check->bo, s->proxy->check_body, s->proxy->check_body_len);
+
                        *b_tail(&check->bo) = '\0'; /* to make gdb output easier to read */
                }
        }
index f641a8a14de635311cb9a725f426265661b875ea..4af56fbc55b0575aa95ab959d7eec9d7a812d0e0 100644 (file)
@@ -2527,6 +2527,8 @@ void deinit(void)
                free(p->conf.file);
                free(p->id);
                free(p->check_req);
+               free(p->check_hdrs);
+               free(p->check_body);
                free(p->cookie_name);
                free(p->cookie_domain);
                free(p->cookie_attrs);