diff --git a/doc/configuration.txt b/doc/configuration.txt index 676d5a075..3f247b6d8 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -4491,6 +4491,33 @@ http-check expect [!] See also : "option httpchk", "http-check disable-on-404" +http-check send [hdr ]* [body ] + 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 adds the HTTP header field whose name is specified in + and whose value is defined by to the + request sent during HTTP health checks. + + body add the body defined by 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 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 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", diff --git a/include/types/proxy.h b/include/types/proxy.h index 84ac8d058..9666909e5 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -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 index 000000000..7e2e37b2c --- /dev/null +++ b/reg-tests/checks/http-check-send.vtc @@ -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 == + expect req.http.host == + expect req.http.x-test == + expect req.http.content-length == + 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 == + 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 == + expect req.http.host == "other-www-host" + expect req.http.x-test == + 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 diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index 7ebd2d513..fbefdd5aa 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -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 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) /* 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 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) 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 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) 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; diff --git a/src/checks.c b/src/checks.c index e7a889f16..22a2c501b 100644 --- a/src/checks.c +++ b/src/checks.c @@ -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 */ } } diff --git a/src/haproxy.c b/src/haproxy.c index f641a8a14..4af56fbc5 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -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);