grace X X X X
hash-type X - X X
http-check disable-on-404 X - X X
+http-check expect - - X X
http-check send-state X - X X
http-request - X X X
id - X X X
generate an alert, just a notice. If the server responds 2xx or 3xx again, it
will immediately be reinserted into the farm. The status on the stats page
reports "NOLB" for a server in this mode. It is important to note that this
- option only works in conjunction with the "httpchk" option.
+ option only works in conjunction with the "httpchk" option. If this option
+ is used with "http-check expect", then it has precedence over it so that 404
+ responses will still be considered as soft-stop.
- See also : "option httpchk"
+ See also : "option httpchk", "http-check expect"
+
+
+http-check expect [!] <match> <pattern>
+ Make HTTP health checks consider reponse contents or specific status codes
+ May be used in sections : defaults | frontend | listen | backend
+ no | no | yes | yes
+ Arguments :
+ <match> is a keyword indicating how to look for a specific pattern in the
+ response. The keyword may be one of "status", "rstatus",
+ "string", or "rstring". The keyword may be preceeded by an
+ exclamation mark ("!") to negate the match. Spaces are allowed
+ between the exclamation mark and the keyword. See below for more
+ details on the supported keywords.
+
+ <pattern> is the pattern to look for. It may be a string or a regular
+ expression. If the pattern contains spaces, they must be escaped
+ with the usual backslash ('\').
+
+ By default, "option httpchk" considers that response statuses 2xx and 3xx
+ are valid, and that others are invalid. When "http-check expect" is used,
+ it defines what is considered valid or invalid. Only one "http-check"
+ statement is supported in a backend. If a server fails to respond or times
+ out, the check obviously fails. The available matches are :
+
+ status <string> : test the exact string match for the HTTP status code.
+ A health check respose will be considered valid if the
+ response's status code is exactly this string. If the
+ "status" keyword is prefixed with "!", then the response
+ will be considered invalid if the status code matches.
+
+ rstatus <regex> : test a regular expression for the HTTP status code.
+ A health check respose will be considered valid if the
+ response's status code matches the expression. If the
+ "rstatus" keyword is prefixed with "!", then the response
+ will be considered invalid if the status code matches.
+ This is mostly used to check for multiple codes.
+
+ string <string> : test the exact string match in the HTTP response body.
+ A health check respose will be considered valid if the
+ response's body contains this exact string. If the
+ "string" keyword is prefixed with "!", then the response
+ will be considered invalid if the body contains this
+ string. This can be used to look for a mandatory word at
+ the end of a dynamic page, or to detect a failure when a
+ specific error appears on the check page (eg: a stack
+ trace).
+
+ rstring <regex> : test a regular expression on the HTTP response body.
+ A health check respose will be considered valid if the
+ response's body matches this expression. If the "rstring"
+ keyword is prefixed with "!", then the response will be
+ considered invalid if the body matches the expression.
+ This can be used to look for a mandatory word at the end
+ of a dynamic page, or to detect a failure when a specific
+ error appears on the check page (eg: a stack trace).
+
+ It is important to note that the responses will be limited to a certain size
+ defined by the global "tune.chksize" option, which defaults to 16384 bytes.
+ Thus, too large responses may not contain the mandatory pattern when using
+ "string" or "rstring". If a large response is absolutely required, it is
+ possible to change the default max size by setting the global variable.
+ However, it is worth keeping in mind that parsing very large responses can
+ waste some CPU cycles, especially when regular expressions are used, and that
+ it is always better to focus the checks on smaller resources.
+
+ Last, if "http-check expect" is combined with "http-check disable-on-404",
+ then this last one has precedence when the server responds with 404.
+
+ Examples :
+ # only accept status 200 as valid
+ http-request expect status 200
+
+ # consider SQL errors as errors
+ http-request expect ! string SQL\ Error
+
+ # consider status 5xx only as errors
+ http-request expect ! rstatus ^5
+
+ # check that we have a correct hexadecimal tag before /html
+ http-request expect rstring <!--tag:[0-9a-f]*</html>
+
+ See also : "option httpchk", "http-check disable-on-404"
http-check send-state
#define PR_O2_SSL3_CHK 0x00100000 /* use SSLv3 CLIENT_HELLO packets for server health */
#define PR_O2_FAKE_KA 0x00200000 /* pretend we do keep-alive with server eventhough we close */
#define PR_O2_LDAP_CHK 0x00400000 /* use LDAP check for server health */
+
+#define PR_O2_EXP_NONE 0x00000000 /* http-check : no expect rule */
+#define PR_O2_EXP_STS 0x00800000 /* http-check expect status */
+#define PR_O2_EXP_RSTS 0x01000000 /* http-check expect rstatus */
+#define PR_O2_EXP_STR 0x01800000 /* http-check expect string */
+#define PR_O2_EXP_RSTR 0x02000000 /* http-check expect rstring */
+#define PR_O2_EXP_TYPE 0x03800000 /* mask for http-check expect type */
+#define PR_O2_EXP_INV 0x04000000 /* http-check expect !<rule> */
/* end of proxy->options2 */
/* bits for sticking rules */
int grace; /* grace time after stop request */
char *check_req; /* HTTP or SSL request to use for PR_O_HTTP_CHK|PR_O_SSL3_CHK */
int check_len; /* Length of the HTTP or SSL3 request */
+ char *expect_str; /* http-check expected content */
+ regex_t *expect_regex; /* http-check expected content */
struct chunk errmsg[HTTP_ERR_SIZE]; /* default or customized error messages for known errors */
int uuid; /* universally unique proxy ID, used for SNMP */
unsigned int backlog; /* force the frontend's listen backlog */
/* enable emission of the apparent state of a server in HTTP checks */
curproxy->options2 |= PR_O2_CHK_SNDST;
}
+ else if (strcmp(args[1], "expect") == 0) {
+ const char *ptr_arg;
+ int cur_arg;
+
+ if (curproxy->options2 & PR_O2_EXP_TYPE) {
+ Alert("parsing [%s:%d] : '%s %s' already specified.\n", file, linenum, args[0], args[1]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ cur_arg = 2;
+ /* consider exclamation marks, sole or at the beginning of a word */
+ while (*(ptr_arg = args[cur_arg])) {
+ while (*ptr_arg == '!') {
+ curproxy->options2 ^= PR_O2_EXP_INV;
+ ptr_arg++;
+ }
+ if (*ptr_arg)
+ break;
+ cur_arg++;
+ }
+ /* now ptr_arg points to the beginning of a word past any possible
+ * exclamation mark, and cur_arg is the argument which holds this word.
+ */
+ if (strcmp(ptr_arg, "status") == 0) {
+ if (!*(args[cur_arg + 1])) {
+ Alert("parsing [%s:%d] : '%s %s %s' expects <string> as an argument.\n",
+ file, linenum, args[0], args[1], ptr_arg);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ curproxy->options2 |= PR_O2_EXP_STS;
+ curproxy->expect_str = strdup(args[cur_arg + 1]);
+ }
+ else if (strcmp(ptr_arg, "string") == 0) {
+ if (!*(args[cur_arg + 1])) {
+ Alert("parsing [%s:%d] : '%s %s %s' expects <string> as an argument.\n",
+ file, linenum, args[0], args[1], ptr_arg);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ curproxy->options2 |= PR_O2_EXP_STR;
+ curproxy->expect_str = strdup(args[cur_arg + 1]);
+ }
+ else if (strcmp(ptr_arg, "rstatus") == 0) {
+ if (!*(args[cur_arg + 1])) {
+ Alert("parsing [%s:%d] : '%s %s %s' expects <regex> as an argument.\n",
+ file, linenum, args[0], args[1], ptr_arg);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ curproxy->options2 |= PR_O2_EXP_RSTS;
+ curproxy->expect_regex = calloc(1, sizeof(regex_t));
+ if (regcomp(curproxy->expect_regex, args[cur_arg + 1], REG_EXTENDED) != 0) {
+ Alert("parsing [%s:%d] : '%s %s %s' : bad regular expression '%s'.\n",
+ file, linenum, args[0], args[1], ptr_arg, args[cur_arg + 1]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ }
+ else if (strcmp(ptr_arg, "rstring") == 0) {
+ if (!*(args[cur_arg + 1])) {
+ Alert("parsing [%s:%d] : '%s %s %s' expects <regex> as an argument.\n",
+ file, linenum, args[0], args[1], ptr_arg);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ curproxy->options2 |= PR_O2_EXP_RSTR;
+ curproxy->expect_regex = calloc(1, sizeof(regex_t));
+ if (regcomp(curproxy->expect_regex, args[cur_arg + 1], REG_EXTENDED) != 0) {
+ Alert("parsing [%s:%d] : '%s %s %s' : bad regular expression '%s'.\n",
+ file, linenum, args[0], args[1], ptr_arg, args[cur_arg + 1]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ }
+ else {
+ Alert("parsing [%s:%d] : '%s %s' only supports [!] 'status', 'string', 'rstatus', 'rstring', found '%s'.\n",
+ file, linenum, args[0], args[1], ptr_arg);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ }
else {
- Alert("parsing [%s:%d] : '%s' only supports 'disable-on-404'.\n", file, linenum, args[0]);
+ Alert("parsing [%s:%d] : '%s' only supports 'disable-on-404', 'expect' .\n", file, linenum, args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
#include <proto/server.h>
#include <proto/task.h>
+static int httpchk_expect(struct server *s, int done);
+
const struct check_status check_statuses[HCHK_STATUS_SIZE] = {
[HCHK_STATUS_UNKNOWN] = { SRV_CHK_UNKNOWN, "UNK", "Unknown" },
[HCHK_STATUS_INI] = { SRV_CHK_UNKNOWN, "INI", "Initializing" },
}
s->check_code = str2uic(s->check_data + 9);
-
desc = ltrim(s->check_data + 12, ' ');
- /* check the reply : HTTP/1.X 2xx and 3xx are OK */
- if (*(s->check_data + 9) == '2' || *(s->check_data + 9) == '3') {
- cut_crlf(desc);
- set_server_check_status(s, HCHK_STATUS_L7OKD, desc);
- }
- else if ((s->proxy->options & PR_O_DISABLE404) &&
- (s->state & SRV_RUNNING) &&
- (s->check_code == 404)) {
+ if ((s->proxy->options & PR_O_DISABLE404) &&
+ (s->state & SRV_RUNNING) && (s->check_code == 404)) {
/* 404 may be accepted as "stopping" only if the server was up */
cut_crlf(desc);
set_server_check_status(s, HCHK_STATUS_L7OKCD, desc);
}
+ else if (s->proxy->options2 & PR_O2_EXP_TYPE) {
+ /* Run content verification check... We know we have at least 13 chars */
+ if (!httpchk_expect(s, done))
+ goto wait_more_data;
+ }
+ /* check the reply : HTTP/1.X 2xx and 3xx are OK */
+ else if (*(s->check_data + 9) == '2' || *(s->check_data + 9) == '3') {
+ cut_crlf(desc);
+ set_server_check_status(s, HCHK_STATUS_L7OKD, desc);
+ }
else {
cut_crlf(desc);
set_server_check_status(s, HCHK_STATUS_L7STS, desc);
return 0;
}
+/*
+ * Perform content verification check on data in s->check_data buffer.
+ * The buffer MUST be terminated by a null byte before calling this function.
+ * Sets server status appropriately. The caller is responsible for ensuring
+ * that the buffer contains at least 13 characters. If <done> is zero, we may
+ * return 0 to indicate that data is required to decide of a match.
+ */
+static int httpchk_expect(struct server *s, int done)
+{
+ static char status_msg[] = "HTTP status check returned code <000>";
+ char status_code[] = "000";
+ char *contentptr;
+ int crlf;
+ int ret;
+
+ switch (s->proxy->options2 & PR_O2_EXP_TYPE) {
+ case PR_O2_EXP_STS:
+ case PR_O2_EXP_RSTS:
+ memcpy(status_code, s->check_data + 9, 3);
+ memcpy(status_msg + strlen(status_msg) - 4, s->check_data + 9, 3);
+
+ if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STS)
+ ret = strncmp(s->proxy->expect_str, status_code, 3) == 0;
+ else
+ ret = regexec(s->proxy->expect_regex, status_code, MAX_MATCH, pmatch, 0) == 0;
+
+ /* we necessarily have the response, so there are no partial failures */
+ if (s->proxy->options2 & PR_O2_EXP_INV)
+ ret = !ret;
+
+ set_server_check_status(s, ret ? HCHK_STATUS_L7OKD : HCHK_STATUS_L7STS, status_msg);
+ break;
+
+ case PR_O2_EXP_STR:
+ case PR_O2_EXP_RSTR:
+ /* very simple response parser: ignore CR and only count consecutive LFs,
+ * stop with contentptr pointing to first char after the double CRLF or
+ * to '\0' if crlf < 2.
+ */
+ crlf = 0;
+ for (contentptr = s->check_data; *contentptr; contentptr++) {
+ if (crlf >= 2)
+ break;
+ if (*contentptr == '\r')
+ continue;
+ else if (*contentptr == '\n')
+ crlf++;
+ else
+ crlf = 0;
+ }
+
+ /* Check that response contains a body... */
+ if (crlf < 2) {
+ if (!done)
+ return 0;
+
+ set_server_check_status(s, HCHK_STATUS_L7RSP,
+ "HTTP content check could not find a response body");
+ return 1;
+ }
+
+ /* Check that response body is not empty... */
+ if (*contentptr == '\0') {
+ set_server_check_status(s, HCHK_STATUS_L7RSP,
+ "HTTP content check found empty response body");
+ return 1;
+ }
+
+ /* Check the response content against the supplied string
+ * or regex... */
+ if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STR)
+ ret = strstr(contentptr, s->proxy->expect_str) != NULL;
+ else
+ ret = regexec(s->proxy->expect_regex, contentptr, MAX_MATCH, pmatch, 0) == 0;
+
+ /* if we don't match, we may need to wait more */
+ if (!ret && !done)
+ return 0;
+
+ if (ret) {
+ /* content matched */
+ if (s->proxy->options2 & PR_O2_EXP_INV)
+ set_server_check_status(s, HCHK_STATUS_L7RSP,
+ "HTTP check matched unwanted content");
+ else
+ set_server_check_status(s, HCHK_STATUS_L7OKD,
+ "HTTP content check matched");
+ }
+ else {
+ if (s->proxy->options2 & PR_O2_EXP_INV)
+ set_server_check_status(s, HCHK_STATUS_L7OKD,
+ "HTTP check did not match unwanted content");
+ else
+ set_server_check_status(s, HCHK_STATUS_L7RSP,
+ "HTTP content check did not match");
+ }
+ break;
+ }
+ return 1;
+}
+
/*
* Local variables:
* c-indent-level: 8