]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: checks: Support matching on headers for http-check expect rules
authorChristopher Faulet <cfaulet@haproxy.com>
Tue, 5 May 2020 08:47:36 +0000 (10:47 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Tue, 5 May 2020 09:19:27 +0000 (11:19 +0200)
It is now possible to add http-check expect rules matching HTTP header names and
values. Here is the format of these rules:

  http-check expect header name [ -m <meth> ] <name> [log-format] \
                           [ value [ -m <meth> ] <value> [log-format] [full] ]

the name pattern (name ...) is mandatory but the value pattern (value ...) is
optionnal. If not specified, only the header presence is verified. <meth> is the
matching method, applied on the header name or the header value. Supported
matching methods are:

  * "str" (exact match)
  * "beg" (prefix match)
  * "end" (suffix match)
  * "sub" (substring match)
  * "reg" (regex match)

If not specified, exact matching method is used. If the "log-format" option is
used, the pattern (<name> or <value>) is evaluated as a log-format string. This
option cannot be used with the regex matching method. Finally, by default, the
header value is considered as comma-separated list. Each part may be tested. The
"full" option may be used to test the full header line. Note that matchings are
case insensitive on the header names.

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

index 87cc69163ce2f4796348be6fb25c462b1f8338bb..1092fd5e248a735ed20091c2fd9929160e778112 100644 (file)
@@ -4565,9 +4565,10 @@ http-check expect [min-recv <int>] [comment <msg>]
               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 ('\').
+    <pattern> is the pattern to look for. It may be a string, a regular
+              expression or a more complex pattern with several arguments. If
+              the string 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,
@@ -4590,6 +4591,25 @@ http-check expect [min-recv <int>] [comment <msg>]
                       will be considered invalid if the status code matches.
                       This is mostly used to check for multiple codes.
 
+    header name [ -m <meth> ] <name> [log-format]
+           [ value [ -m <meth> ] <value> [log-format] [full] ] :
+                      test the specified header pattern on the HTTP response
+                      headers. The name pattern is mandatory but the value
+                      pattern is optional. If not specified, only the header
+                      presence is verified. <meth> is the matching method,
+                      applied on the header name or the header value. Supported
+                      matching methods are "str" (exact match), "beg" (prefix
+                      match), "end" (suffix match), "sub" (substring match) or
+                      "reg" (regex match). If not specified, exact matching
+                      method is used. If the "log-format" option is used, the
+                      pattern (<name> or <value>) is evaluated as a log-format
+                      string. This option cannot be used with the regex
+                      matching method. Finally, by default, the header value is
+                      considered as comma-separated list. Each part may be
+                      tested. The "full" option may be used to test the full
+                      header line. Note that matchings are case insensitive on
+                      the header names.
+
     string <string> : test the exact string match in the HTTP response body.
                       A health check response will be considered valid if the
                       response's body contains this exact string. If the
@@ -4631,6 +4651,9 @@ http-check expect [min-recv <int>] [comment <msg>]
          # only accept status 200 as valid
          http-check expect status 200,201,300-310
 
+         # be sure a sessid coookie is set
+         http-check expect header name "set-cookie" value -m beg "sessid="
+
          # consider SQL errors as errors
          http-check expect ! string SQL\ Error
 
index 48092859cfecd8d1cd03f49952ea9c60160ba2be..927dada33be263ab4815227b56e0af0db1970835 100644 (file)
@@ -248,17 +248,34 @@ enum tcpcheck_expect_type {
        TCPCHK_EXPECT_STRING,            /* Matches a string. */
        TCPCHK_EXPECT_REGEX,             /* Matches a regular pattern. */
        TCPCHK_EXPECT_REGEX_BINARY,      /* Matches a regular pattern on a hex-encoded text. */
-       TCPCHK_EXPECT_BINARY,            /* Matches a binary sequence. */
+       TCPCHK_EXPECT_BINARY,            /* Matches a binary sequence on a hex-encoded text. */
        TCPCHK_EXPECT_CUSTOM,            /* Execute a custom function. */
-       TCPCHK_EXPECT_HTTP_STATUS,       /* Matches a string */
-       TCPCHK_EXPECT_HTTP_REGEX_STATUS, /* Matches a regular pattern */
-       TCPCHK_EXPECT_HTTP_BODY,         /* Matches a string */
-       TCPCHK_EXPECT_HTTP_REGEX_BODY,   /* Matches a regular pattern */
+       TCPCHK_EXPECT_HTTP_STATUS,       /* Matches a list of codes on the HTTP status */
+       TCPCHK_EXPECT_HTTP_REGEX_STATUS, /* Matches a regular pattern on the HTTP status */
+       TCPCHK_EXPECT_HTTP_HEADER,       /* Matches on HTTP headers */
+       TCPCHK_EXPECT_HTTP_BODY,         /* Matches a string oa the HTTP payload */
+       TCPCHK_EXPECT_HTTP_REGEX_BODY,   /* Matches a regular pattern on a HTTP payload */
 };
 
 /* tcp-check expect flags */
-#define TCPCHK_EXPT_FL_INV    0x0001 /* Matching is inversed */
-
+#define TCPCHK_EXPT_FL_INV             0x0001 /* Matching is inversed */
+#define TCPCHK_EXPT_FL_HTTP_HNAME_STR  0x0002 /* Exact match on the HTTP header name */
+#define TCPCHK_EXPT_FL_HTTP_HNAME_BEG  0x0004 /* Prefix match on the HTTP header name */
+#define TCPCHK_EXPT_FL_HTTP_HNAME_END  0x0008 /* Suffix match on the HTTP header name */
+#define TCPCHK_EXPT_FL_HTTP_HNAME_SUB  0x0010 /* Substring match on the HTTP header name */
+#define TCPCHK_EXPT_FL_HTTP_HNAME_REG  0x0020 /* Regex match on the HTTP header name */
+#define TCPCHK_EXPT_FL_HTTP_HNAME_FMT  0x0040 /* The HTTP header name is a log-format string */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_NONE  0x0080 /* No match on the HTTP header value */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_STR   0x0100 /* Exact match on the HTTP header value */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_BEG   0x0200 /* Prefix match on the HTTP header value */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_END   0x0400 /* Suffix match on the HTTP header value */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_SUB   0x0800 /* Substring match on the HTTP header value */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_REG   0x1000 /* Regex match on the HTTP header value*/
+#define TCPCHK_EXPT_FL_HTTP_HVAL_FMT   0x2000 /* The HTTP header value is a log-format string */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_FULL  0x4000 /* Match the full header value ( no stop on commas ) */
+
+#define TCPCHK_EXPT_FL_HTTP_HNAME_TYPE 0x003E /* Mask to get matching method on header name */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_TYPE  0x1F00 /* Mask to get matching method on header value */
 struct tcpcheck_expect {
        enum tcpcheck_expect_type type;   /* Type of pattern used for matching. */
        unsigned int flags;               /* TCPCHK_EXPT_FL_* */
@@ -266,6 +283,19 @@ struct tcpcheck_expect {
                struct ist data;             /* Matching a literal string / binary anywhere in the response. */
                struct my_regex *regex;      /* Matching a regex pattern. */
                struct tcpcheck_codes codes; /* Matching a list of codes */
+               struct {
+                       union {
+                               struct ist name;
+                               struct list name_fmt;
+                               struct my_regex *name_re;
+                       };
+                       union {
+                               struct ist value;
+                               struct list value_fmt;
+                               struct my_regex *value_re;
+                       };
+               } hdr;                       /* Matching a header pattern */
+
 
                /* custom function to eval epxect rule */
                enum tcpcheck_eval_ret (*custom)(struct check *, struct tcpcheck_rule *, int);
diff --git a/reg-tests/checks/http-check-expect.vtc b/reg-tests/checks/http-check-expect.vtc
new file mode 100644 (file)
index 0000000..83e4330
--- /dev/null
@@ -0,0 +1,66 @@
+varnishtest "Health-checks: some http-check expect tests"
+feature ignore_unknown_macro
+#REQUIRE_VERSION=2.2
+#REGTEST_TYPE=slow
+# This script tests http-check expect rules.
+
+server s1 {
+    rxreq
+    expect req.method == OPTIONS
+    expect req.url == /
+    expect req.proto == HTTP/1.0
+    txresp -status 202 \
+      -hdr "x-test1: true, next value" \
+      -hdr "x-test2: true, begin-value, value-end, value-sub-string, value-reg-123ABC" \
+      -hdr "x-begin-test: 1" \
+      -hdr "x-test-end: 1" \
+      -hdr "x-sub-test: 1" \
+      -hdr "x-reg-test1: 1" \
+      -hdr "x-hdr-name: x-test1"
+} -start
+
+syslog S1 -level notice {
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Proxy be1 started."
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be1/srv succeeded.*code: 202"
+} -start
+
+haproxy h1 -conf {
+    defaults
+        mode http
+        timeout client 1s
+        timeout server 1s
+        timeout connect 100ms
+        option log-health-checks
+
+    backend be1
+        log ${S1_addr}:${S1_port} len 2048 local0
+        option httpchk
+       http-check expect status 200-399
+
+       http-check expect header name "x-test1"
+       http-check expect header name -m str "X-Test2"
+       http-check expect header name -m beg "X-Begin-"
+       http-check expect header name -m end "-End"
+       http-check expect header name -m sub "-Sub-"
+       http-check expect header name -m reg "^[a-z]+-Reg-[a-z]+[0-9]\$"
+       http-check set-var(check.hdr_name) check.fhdr(x-hdr-name)
+       http-check expect header name -m str "%[var(check.hdr_name)]" log-format
+       http-check expect header name -m str "%[check.fhdr(x-hdr-name)]" log-format
+
+       http-check expect header name "x-test1" value "true, next value" full
+       http-check expect header name "x-test2" value -m str "true"
+       http-check expect header name -m beg "x-test" value -m beg "begin-"
+       http-check expect header name -m beg "x-test" value -m end "-end"
+       http-check expect header name -m beg "x-test" value -m sub "-sub-"
+       http-check expect header name -m beg "x-test" value -m reg "^value-reg-[A-Z0-9]+\$"
+       http-check expect header name -m beg "x-test" value -m reg "value-reg-[A-Z0-9]+" full
+       http-check set-var(check.hdr_value) str(x-test1)
+       http-check expect header name -m beg "x-" value -m str "%[var(check.hdr_value)]" log-format
+       http-check expect header name -m beg "x-" value -m str "%[check.fhdr(x-hdr-name)]" log-format full
+
+        server srv ${s1_addr}:${s1_port} check inter 100ms rise 1 fall 1
+} -start
+
+syslog S1 -wait
index 8d371e652d5c48cf42917541dc7c9a8cba146a3b..a5ddc13ba752abc5506036698fe3042853f79031 100644 (file)
@@ -606,6 +606,9 @@ static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
                                case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
                                        chunk_appendf(chk, " (expect HTTP status regex)");
                                        break;
+                               case TCPCHK_EXPECT_HTTP_HEADER:
+                                       chunk_appendf(chk, " (expect HTTP header pattern)");
+                                       break;
                                case TCPCHK_EXPECT_HTTP_BODY:
                                        chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
                                        break;
@@ -796,6 +799,21 @@ static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
                case TCPCHK_EXPECT_HTTP_REGEX_BODY:
                        regex_free(rule->expect.regex);
                        break;
+               case TCPCHK_EXPECT_HTTP_HEADER:
+                       if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
+                               regex_free(rule->expect.hdr.name_re);
+                       else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
+                               free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
+                       else
+                               istfree(&rule->expect.hdr.name);
+
+                       if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
+                               regex_free(rule->expect.hdr.value_re);
+                       else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
+                               free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
+                       else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
+                               istfree(&rule->expect.hdr.value);
+                       break;
                case TCPCHK_EXPECT_CUSTOM:
                case TCPCHK_EXPECT_UNDEF:
                        break;
@@ -1069,6 +1087,8 @@ static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *ch
        case TCPCHK_EXPECT_CUSTOM:
                chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
                break;
+       case TCPCHK_EXPECT_HTTP_HEADER:
+               chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule));
        case TCPCHK_EXPECT_UNDEF:
                /* Should never happen. */
                return;
@@ -2116,8 +2136,8 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, str
        struct htx_blk *blk;
        enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
        struct tcpcheck_expect *expect = &rule->expect;
-       struct buffer *msg = NULL;
-       enum healthcheck_status status;
+       struct buffer *msg = NULL, *nbuf = NULL, *vbuf = NULL;
+       enum healthcheck_status status = HCHK_STATUS_L7RSP;
        struct ist desc = IST_NULL;
        int i, match, inverse;
 
@@ -2176,6 +2196,110 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, str
                        desc = htx_sl_res_reason(sl);
                break;
 
+       case TCPCHK_EXPECT_HTTP_HEADER: {
+               struct http_hdr_ctx ctx;
+               struct ist npat, vpat, value;
+               int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
+
+               if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
+                       nbuf = alloc_trash_chunk();
+                       if (!nbuf)
+                               goto error;
+                       nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
+                       npat = ist2(b_orig(nbuf), b_data(nbuf));
+               }
+               else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
+                       npat = expect->hdr.name;
+
+               if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
+                       vbuf = alloc_trash_chunk();
+                       if (!vbuf)
+                               goto error;
+                       vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
+                       vpat = ist2(b_orig(vbuf), b_data(vbuf));
+               }
+               else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
+                       vpat = expect->hdr.value;
+
+               match = 0;
+               ctx.blk = NULL;
+               while (1) {
+                       switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
+                       case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
+                               if (!http_find_str_header(htx, npat, &ctx, full))
+                                       goto end_of_match;
+                               break;
+                       case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
+                               if (!http_find_pfx_header(htx, npat, &ctx, full))
+                                       goto end_of_match;
+                               break;
+                       case TCPCHK_EXPT_FL_HTTP_HNAME_END:
+                               if (!http_find_sfx_header(htx, npat, &ctx, full))
+                                       goto end_of_match;
+                               break;
+                       case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
+                               if (!http_find_sub_header(htx, npat, &ctx, full))
+                                       goto end_of_match;
+                               break;
+                       case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
+                               if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
+                                       goto end_of_match;
+                               break;
+                       }
+
+                       if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
+                               match = 1;
+                               goto end_of_match;
+                       }
+
+                       value = ctx.value;
+                       switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
+                       case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
+                               if (isteq(value, vpat)) {
+                                       match = 1;
+                                       goto end_of_match;
+                               }
+                               break;
+                       case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
+                               if (istlen(value) < istlen(vpat))
+                                       break;
+                               value = ist2(istptr(value), istlen(vpat));
+                               if (isteq(value, vpat)) {
+                                       match = 1;
+                                       goto end_of_match;
+                               }
+                               break;
+                       case TCPCHK_EXPT_FL_HTTP_HVAL_END:
+                               if (istlen(value) < istlen(vpat))
+                                       break;
+                               value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat));
+                               if (isteq(value, vpat)) {
+                                       match = 1;
+                                       goto end_of_match;
+                               }
+                               break;
+                       case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
+                               if (isttest(istist(value, vpat))) {
+                                       match = 1;
+                                       goto end_of_match;
+                               }
+                               break;
+                       case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
+                               if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
+                                       match = 1;
+                                       goto end_of_match;
+                               }
+                               break;
+                       }
+               }
+
+         end_of_match:
+               status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7STS);
+               if (LIST_ISEMPTY(&expect->onerror_fmt))
+                       desc = htx_sl_res_reason(sl);
+               break;
+       }
+
        case TCPCHK_EXPECT_HTTP_BODY:
        case TCPCHK_EXPECT_HTTP_REGEX_BODY:
                chunk_reset(&trash);
@@ -2237,6 +2361,8 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, str
                goto error;
 
   out:
+       free_trash_chunk(nbuf);
+       free_trash_chunk(vbuf);
        free_trash_chunk(msg);
        return ret;
 
@@ -3969,15 +4095,16 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str
 {
        struct tcpcheck_rule *prev_check, *chk = NULL;
        struct sample_expr *status_expr = NULL;
-       char *on_success_msg, *on_error_msg, *comment, *pattern;
+       char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
        enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
        enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
        enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
        enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
+       unsigned int flags = 0;
        long min_recv = -1;
        int inverse = 0;
 
-       on_success_msg = on_error_msg = comment = pattern = NULL;
+       on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
        if (!*(args[cur_arg+1])) {
                memprintf(errmsg, "expects at least a matching pattern as arguments");
                goto error;
@@ -4075,6 +4202,116 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str
                        }
                        type = TCPCHK_EXPECT_CUSTOM;
                }
+               else if (strcmp(args[cur_arg], "header") == 0) {
+                       int orig_arg = cur_arg;
+
+                       if (proto != TCPCHK_RULES_HTTP_CHK)
+                               goto bad_tcp_kw;
+                       if (type != TCPCHK_EXPECT_UNDEF) {
+                               memprintf(errmsg, "only on pattern expected");
+                               goto error;
+                       }
+                       type = TCPCHK_EXPECT_HTTP_HEADER;
+
+                       /* Parse the name pattern, mandatory */
+                       if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) || strcmp(args[cur_arg+1], "name") != 0) {
+                               memprintf(errmsg, "'%s' expects at the keyword name as first argument followed by a pattern",
+                                         args[orig_arg]);
+                               goto error;
+                       }
+                       cur_arg += 2;
+                       if (strcmp(args[cur_arg], "-m") == 0) {
+                               if  (!*(args[cur_arg+1])) {
+                                       memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
+                                                 args[orig_arg], args[cur_arg]);
+                                       goto error;
+                               }
+                               if (strcmp(args[cur_arg+1], "str") == 0)
+                                       flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
+                               else if (strcmp(args[cur_arg+1], "beg") == 0)
+                                       flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
+                               else if (strcmp(args[cur_arg+1], "end") == 0)
+                                       flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
+                               else if (strcmp(args[cur_arg+1], "sub") == 0)
+                                       flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
+                               else if (strcmp(args[cur_arg+1], "reg") == 0)
+                                       flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
+                               else {
+                                       memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
+                                                 args[orig_arg], args[cur_arg], args[cur_arg+1]);
+                                       goto error;
+                               }
+                               cur_arg += 2;
+                       }
+                       else
+                               flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
+                       npat = args[cur_arg];
+
+                       if (!(*args[cur_arg+1])) {
+                               flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
+                               goto next;
+                       }
+
+                       if (strcmp(args[cur_arg+1], "log-format") == 0) {
+                               if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
+                                       memprintf(errmsg, "'%s': '%s' cannot be used with a regex matching pattern",
+                                                 args[orig_arg], args[cur_arg+1]);
+                                       goto error;
+                               }
+                               flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
+                               cur_arg++;
+                       }
+
+                       if (!(*args[cur_arg+1]) || strcmp(args[cur_arg+1], "value") != 0) {
+                               flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
+                               goto next;
+                       }
+
+                       /* Parse the value pattern, optionnal */
+                       cur_arg += 2;
+                       if (strcmp(args[cur_arg], "-m") == 0) {
+                               if  (!*(args[cur_arg+1])) {
+                                       memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
+                                                 args[orig_arg], args[cur_arg]);
+                                       goto error;
+                               }
+                               if (strcmp(args[cur_arg+1], "str") == 0)
+                                       flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
+                               else if (strcmp(args[cur_arg+1], "beg") == 0)
+                                       flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
+                               else if (strcmp(args[cur_arg+1], "end") == 0)
+                                       flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
+                               else if (strcmp(args[cur_arg+1], "sub") == 0)
+                                       flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
+                               else if (strcmp(args[cur_arg+1], "reg") == 0)
+                                       flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
+                               else {
+                                       memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
+                                                 args[orig_arg], args[cur_arg], args[cur_arg+1]);
+                                       goto error;
+                               }
+                               cur_arg += 2;
+                       }
+                       else
+                               flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
+                       vpat = args[cur_arg];
+
+                       while (*args[cur_arg+1]) {
+                               if (strcmp(args[cur_arg+1], "log-format") == 0) {
+                                       if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
+                                               memprintf(errmsg, "'%s': '%s' cannot be used with a regex matching pattern",
+                                                         args[orig_arg], args[cur_arg+1]);
+                                               goto error;
+                                       }
+                                       flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
+                               }
+                               else if (strcmp(args[cur_arg+1], "full") == 0)
+                                       flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
+                               else
+                                       break;
+                               cur_arg++;
+                       }
+               }
                else if (strcmp(args[cur_arg], "comment") == 0) {
                        if (in_pattern) {
                                memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
@@ -4220,7 +4457,7 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str
                        if (proto == TCPCHK_RULES_HTTP_CHK) {
                          bad_http_kw:
                                memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
-                                         " or comment but got '%s' as argument.", args[cur_arg]);
+                                         "[!]header or comment but got '%s' as argument.", args[cur_arg]);
                        }
                        else {
                          bad_tcp_kw:
@@ -4229,7 +4466,7 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str
                        }
                        goto error;
                }
-
+         next:
                cur_arg++;
        }
 
@@ -4244,7 +4481,7 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str
        chk->comment = comment; comment = NULL;
        chk->expect.type = type;
        chk->expect.min_recv = min_recv;
-       chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
+       chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
        chk->expect.ok_status = ok_st;
        chk->expect.err_status = err_st;
        chk->expect.tout_status = tout_st;
@@ -4329,6 +4566,63 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str
                if (!chk->expect.regex)
                        goto error;
                break;
+       case TCPCHK_EXPECT_HTTP_HEADER:
+               if (!npat) {
+                       memprintf(errmsg, "unexpected error, undefined header name pattern");
+                       goto error;
+               }
+               if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
+                       chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
+                       if (!chk->expect.hdr.name_re)
+                               goto error;
+               }
+               else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
+                       px->conf.args.ctx = ARGC_SRV;
+                       LIST_INIT(&chk->expect.hdr.name_fmt);
+                       if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
+                               memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
+                               goto error;
+                       }
+               }
+               else {
+                       chk->expect.hdr.name = ist2(strdup(npat), strlen(npat));
+                       if (!isttest(chk->expect.hdr.name)) {
+                               memprintf(errmsg, "out of memory");
+                               goto error;
+                       }
+               }
+
+               if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
+                       chk->expect.hdr.value = IST_NULL;
+                       break;
+               }
+
+               if (!vpat) {
+                       memprintf(errmsg, "unexpected error, undefined header value pattern");
+                       goto error;
+               }
+               else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
+                       chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
+                       if (!chk->expect.hdr.value_re)
+                               goto error;
+               }
+               else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
+                       px->conf.args.ctx = ARGC_SRV;
+                       LIST_INIT(&chk->expect.hdr.value_fmt);
+                       if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
+                               memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
+                               goto error;
+                       }
+               }
+               else {
+                       chk->expect.hdr.value = ist2(strdup(vpat), strlen(vpat));
+                       if (!isttest(chk->expect.hdr.value)) {
+                               memprintf(errmsg, "out of memory");
+                               goto error;
+                       }
+               }
+
+               break;
        case TCPCHK_EXPECT_CUSTOM:
                chk->expect.custom = NULL; /* Must be defined by the caller ! */
                break;