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