]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MAJOR: checks: Implement HTTP check using tcp-check rules
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 15 Apr 2020 09:32:03 +0000 (11:32 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 27 Apr 2020 07:39:38 +0000 (09:39 +0200)
HTTP health-checks are now internally based on tcp-checks. Of course all the
configuration parsing of the "http-check" keyword and the httpchk option has
been rewritten. But the main changes is that now, as for tcp-check ruleset, it
is possible to perform several send/expect sequences into the same
health-checks. Thus the connect rule is now also available from HTTP checks, jst
like set-var, unset-var and comment rules.

Because the request defined by the "option httpchk" line is used for the first
request only, it is now possible to set the method, the uri and the version on a
"http-check send" line.

doc/configuration.txt
include/types/checks.h
include/types/proxy.h
src/cfgparse-listen.c
src/cfgparse.c
src/checks.c
src/haproxy.c
src/session.c

index 937f73aa748d8e15917eefa34c7e02f7eb845a3e..997b2a7b9da1b483549a60a3d0944180f4df665d 100644 (file)
@@ -2653,9 +2653,12 @@ fullconn                                  X          -         X         X
 grace                                     X          X         X         X
 hash-type                                 X          -         X         X
 http-after-response                       -          X         X         X
+http-check connect                        X          -         X         X
 http-check disable-on-404                 X          -         X         X
-http-check expect                         -          -         X         X
+http-check expect                         X          -         X         X
 http-check send-state                     X          -         X         X
+http-check set-var                        X          -         X         X
+http-check unset-var                      X          -         X         X
 http-request                              -          X         X         X
 http-response                             -          X         X         X
 http-reuse                                X          -         X         X
@@ -4386,6 +4389,72 @@ http-after-response unset-var(<var-name>) [ { if | unless } <condition> ]
   Example:
     http-after-response unset-var(sess.last_redir)
 
+http-check connect [params*]
+  Opens a new connection to perform an HTTP health check
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    no    |   yes  |   yes
+
+  Just like tcp-check health checks, it is possible to configure the connection
+  to use to perform HTTP health check. This directive should also be used to
+  describe a scenario involving several request/response exchanges, possibly on
+  different ports or with different servers.
+
+  When there are no TCP port configured on the server line neither server port
+  directive, then the first step of the http-check sequence must be to specify
+  the port with a "http-check connect".
+
+  In an http-check ruleset a 'connect' is required, it is also mandatory to start
+  the ruleset with a 'connect' rule. Purpose is to ensure admin know what they
+  do.
+
+  When a connect must start the ruleset, if may still be preceded by set-var,
+  unset-var or comment rules.
+
+  Parameters :
+    They are optional and can be used to describe how HAProxy should open and
+    use the HTTP connection.
+
+    default      Use default options of the server line to do the health
+                 checks. The server options are used only if not redifined.
+
+    port <expr>  if not set, check port or server port is used.
+                 It tells HAProxy where to open the connection to.
+                 <port> must be a valid TCP port source integer, from 1 to
+                 65535 or an sample-fetch expression.
+
+    addr <ip>    defines the IP address to do the health check.
+
+    send-proxy   send a PROXY protocol string
+
+    via-socks4   enables outgoing health checks using upstream socks4 proxy.
+
+    ssl          opens a ciphered connection
+
+    sni <sni>    specifies the SNI to use to do health checks over SSL.
+
+    alpn <alpn>  defines which protocols to advertise with ALPN. The protocol
+                 list consists in a comma-delimited list of protocol names,
+                 for instance: "h2,http/1.1". If it is not set, the server ALPN
+                 is used.
+
+    linger       cleanly close the connection instead of using a single RST.
+
+    Examples:
+         # check HTTP and HTTPs services on a server.
+         # first open port 80 thanks to server line port directive, then
+         # tcp-check opens port 443, ciphered and run a request on it:
+         option httpchk
+
+         http-check connect
+         http-check send GET / HTTP/1.1 hdr host haproxy.1wt.eu
+         http-check expect rstatus "^[23][0-9]{2}"
+         http-check connect port 443 ssl sni haproxy.1wt.eu
+         http-check send GET / HTTP/1.1 hdr host haproxy.1wt.eu
+         http-check expect rstatus "^[23][0-9]{2}"
+
+         server www 10.0.0.1 check port 80
+
+  See also : "option httpchk", "http-check send", "http-check expect"
 
 http-check disable-on-404
   Enable a maintenance mode upon HTTP/404 response to health-checks
@@ -4408,11 +4477,58 @@ http-check disable-on-404
   See also : "option httpchk", "http-check expect"
 
 
-http-check expect [!] <match> <pattern>
+http-check expect [min-recv <int>]
+                  [ok-status <st>] [error-status <st>] [tout-status <st>]
+                  [on-success <fmt>] [on-error <fmt>] [status-code <expr>]
+                  [!] <match> <pattern>
   Make HTTP health checks consider response contents or specific status codes
   May be used in sections :   defaults | frontend | listen | backend
                                  yes   |    no    |   yes  |   yes
+
   Arguments :
+    min-recv  is optional and can define the minimum amount of data required to
+              evaluate the current expect rule. If the number of received bytes
+              is under this limit, the check will wait for more data. This
+              option can be used to resolve some ambiguous matching rules or to
+              avoid executing costly regex matches on content known to be still
+              incomplete. If an exact string is used, the minimum between the
+              string length and this parameter is used. This parameter is
+              ignored if it is set to -1. If the expect rule does not match,
+              the check will wait for more data. If set to 0, the evaluation
+              result is always conclusive.
+
+    ok-status <st>     is optional and can be used to set the check status if
+                       the expect rule is successfully evaluated and if it is
+                       the last rule in the tcp-check ruleset. "L7OK", "L7OKC",
+                       "L6OK" and "L4OK" are supported and may be used to set,
+                       respectively, HCHK_STATUS_L7OK, HCHK_STATUS_L7OKCD,
+                       HCHK_STATUS_L6OK or HCHK_STATUS_L4OK success status.
+                        By default "L7OK" is used.
+
+    error-status <st>  is optional and can be used to set the check status if
+                       an error occurred during the expect rule evaluation.
+                       "L7RSP", "L7STS", "L6RSP" and "L4CON" are supported and
+                       may be used to set, respectively, HCHK_STATUS_L7RSP,
+                       HCHK_STATUS_L7STS, HCHK_STATUS_L6RSP or HCHK_STATUS_L4CON
+                       error status. By default "L7RSP" is used.
+
+    tout-status <st>   is optional and can be used to set the check status if
+                       a timeout occurred during the expect rule evaluation.
+                       "L7TOUT", "L6TOUT", and "L4TOUT" are supported and may
+                       be used to set, respectively, HCHK_STATUS_L7TOUT,
+                       HCHK_STATUS_L6TOUT or HCHK_STATUS_L4TOUT timeout status.
+                       By default "L7TOUT" is used.
+
+    on-success <fmt>   is optional and can be used to customize the
+                       informational message reported in logs if the expect
+                       rule is successfully evaluated and if it is the last rule
+                       in the tcp-check ruleset. <fmt> is a log-format string.
+
+    on-error <fmt>     is optional and can be used to customize the
+                       informational message reported in logs if an error
+                       occurred during the expect rule evaluation. <fmt> is a
+                       log-format string.
+
     <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 preceded by an
@@ -4471,9 +4587,11 @@ http-check expect [!] <match> <pattern>
   waste some CPU cycles, especially when regular expressions are used, and that
   it is always better to focus the checks on smaller resources.
 
-  Also "http-check expect" doesn't support HTTP keep-alive. Keep in mind that it
-  will automatically append a "Connection: close" header, meaning that this
-  header should not be present in the request provided by "option httpchk".
+  In an http-check ruleset, the last expect rule may be implicit. If no expect
+  rule is specified after the last "http-check send", an implicit expect rule
+  is defined to match on 2xx or 3xx status codes. It means this rule is also
+  defined if there is no "http-check" rule at all, when only "option httpchk"
+  is set.
 
   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.
@@ -4491,23 +4609,41 @@ http-check expect [!] <match> <pattern>
          # check that we have a correct hexadecimal tag before /html
          http-check expect rstring <!--tag:[0-9a-f]*--></html>
 
-  See also : "option httpchk", "http-check disable-on-404"
+  See also : "option httpchk", "http-check connect", "http-check disable-on-404"
+             and "http-check send"
 
 
-http-check send [hdr <name> <value>]* [body <string>]
+http-check send [meth <method>] [uri <uri>] [vsn <version>]
+                [hdr <name> <fmt>]* [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.
+    meth <method>  is the optional HTTP method used with the requests. When not
+                   set, the "OPTIONS" method is used, as it generally requires
+                   low server processing and is easy to filter out from the
+                   logs. Any method may be used, though it is not recommended
+                   to invent non-standard ones.
+
+    uri <uri>      is optional and set the URI referenced in the HTTP requests.
+                   it defaults to " / " which is accessible by default on almost
+                   any server, but may be changed to any other URI. Query
+                   strings are permitted.
+
+    vsn <version>  is the optional HTTP version string. It defaults to
+                   "HTTP/1.0" but some servers might behave incorrectly in HTTP
+                   1.0, so turningit to HTTP/1.1 may sometimes help. Note that
+                   the Host field is mandatory in HTTP/1.1, use "hdr" argument
+                   to add it.
+
+    hdr <name> <fmt>  adds the HTTP header field whose name is specified in
+                      <name> and whose value is defined by <fmt>, which follows
+                      to the log-format rules.
+
+    body <string>  add the body defined by <string> to the request 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
@@ -4518,6 +4654,10 @@ http-check send [hdr <name> <value>]* [body <string>]
   "http-check expect" direcive is defined independently of this directive, just
   like the state header if the directive "http-check send-state" is defined.
 
+  Also "http-check send" doesn't support HTTP keep-alive. Keep in mind that it
+  will automatically append a "Connection: close" header, meaning that this
+  header should not be present in the request provided by "http-check send".
+
   See also : "option httpchk", "http-check send-state" and "http-check expect"
 
 
@@ -4572,6 +4712,50 @@ http-check send-state
   See also : "option httpchk", "http-check disable-on-404"
 
 
+http-check set-var(<var-name>) <expr>
+
+  This operation sets the content of a variable. The variable is declared inline.
+
+  May be used in sections:   defaults | frontend | listen | backend
+                               yes    |    no    |   yes  |   yes
+
+  Arguments:
+    <var-name>  The name of the variable starts with an indication about its
+                scope. The scopes allowed for http-check are:
+                  "proc" : the variable is shared with the whole process.
+                  "sess" : the variable is shared with the tcp-check session.
+                  "check": the variable is declared for the lifetime of the tcp-check.
+                This prefix is followed by a name. The separator is a '.'.
+                The name may only contain characters 'a-z', 'A-Z', '0-9', '.',
+                and '-'.
+
+    <expr>      Is a sample-fetch expression potentially followed by converters.
+
+  Example:
+    http-check set-var(check.port) int(1234)
+
+
+http-check unset-var(<var-name>)
+
+  Free a reference to a variable within its scope.
+
+  May be used in sections:   defaults | frontend | listen | backend
+                               yes    |    no    |   yes  |   yes
+
+  Arguments:
+    <var-name>  The name of the variable starts with an indication about its
+                scope. The scopes allowed for http-check are:
+                  "proc" : the variable is shared with the whole process.
+                  "sess" : the variable is shared with the tcp-check session.
+                  "check": the variable is declared for the lifetime of the tcp-check.
+                This prefix is followed by a name. The separator is a '.'.
+                The name may only contain characters 'a-z', 'A-Z', '0-9', '.',
+                and '-'.
+
+  Example:
+    http-check unset-var(check.port)
+
+
 http-request <action> [options...] [ { if | unless } <condition> ]
   Access control for Layer 7 requests
 
@@ -7181,11 +7365,18 @@ option httpchk <method> <uri> <version>
   considered valid, while all other ones indicate a server failure, including
   the lack of any response.
 
-  The port and interval are specified in the server configuration.
+  Combined with "http-check" directives, it is possible to customize the
+  request sent during the HTTP health checks or the matching rules on the
+  response. It is also possible to configure a send/expect sequence, just like
+  with the directive "tcp-check" for TCP health checks.
+
+  The server configuration is used by default to open connections to perform
+  HTTP health checks. By it is also possible to overwrite server parameters
+  using "http-check connect" rules.
 
-  This option does not necessarily require an HTTP backend, it also works with
-  plain TCP backends. This is particularly useful to check simple scripts bound
-  to some dedicated ports using the inetd daemon.
+  "httpchk" option does not necessarily require an HTTP backend, it also works
+  with 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
index a43efd7c8edaf05b96659311a89da50a06dc85cd..f52e4a4d880ebb5a7fafb9f44db7909daabda8bb 100644 (file)
@@ -232,15 +232,40 @@ enum tcpcheck_send_type {
        TCPCHK_SEND_UNDEF = 0,  /* Send is not parsed. */
        TCPCHK_SEND_STRING,     /* Send an ASCII string. */
        TCPCHK_SEND_BINARY,     /* Send a binary sequence. */
-       TCPCHK_SEND_STRING_LF, /* Send an ASCII log-format string. */
-       TCPCHK_SEND_BINARY_LF, /* Send a binary log-format sequence. */
+       TCPCHK_SEND_STRING_LF,  /* Send an ASCII log-format string. */
+       TCPCHK_SEND_BINARY_LF,  /* Send a binary log-format sequence. */
+       TCPCHK_SEND_HTTP,       /* Send an HTTP request */
 };
 
+struct tcpcheck_http_hdr {
+       struct ist  name;  /* the header name */
+       struct list value; /* the log-format string value */
+       struct list list;  /* header chained list */
+};
+
+#define TCPCHK_SND_HTTP_FL_URI_FMT    0x0001 /* Use a log-format string for the uri */
+#define TCPCHK_SND_HTTP_FL_BODY_FMT   0x0002 /* Use a log-format string for the body */
+#define TCPCHK_SND_HTTP_FROM_OPT      0x0004 /* Send rule coming from "option httpck" directive */
+
 struct tcpcheck_send {
        enum tcpcheck_send_type type;
        union {
                struct ist  data; /* an ASCII string or a binary sequence */
                struct list fmt;  /* an ASCII or hexa log-format string */
+               struct {
+                       unsigned int flags;             /* TCPCHK_SND_HTTP_FL_* */
+                       struct http_meth meth;          /* the HTTP request method */
+                       union {
+                               struct ist  uri;        /* the HTTP request uri is a string  */
+                               struct list uri_fmt;    /* or a log-format string */
+                       };
+                       struct ist vsn;                 /* the HTTP request version string */
+                       struct list hdrs;               /* the HTTP request header list */
+                       union {
+                               struct ist   body;      /* the HTTP request payload is a string */
+                               struct list  body_fmt;  /* or a log-format string */
+                       };
+               } http;           /* Info about the HTTP request to send */
        };
 };
 
@@ -251,12 +276,16 @@ enum tcpcheck_eval_ret {
 };
 
 enum tcpcheck_expect_type {
-       TCPCHK_EXPECT_UNDEF = 0,    /* Match is not used. */
-       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_CUSTOM,       /* Execute a custom function. */
+       TCPCHK_EXPECT_UNDEF = 0,         /* Match is not used. */
+       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_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 */
 };
 
 /* tcp-check expect flags */
@@ -275,11 +304,11 @@ struct tcpcheck_expect {
        };
        struct tcpcheck_rule *head;     /* first expect of a chain. */
        int min_recv;                   /* Minimum amount of data before an expect can be applied. (default: -1, ignored) */
-       struct list onerror_fmt;        /* log-format string to use as comment on error */
-       struct list onsuccess_fmt;      /* log-format string to use as comment on success (if last rule) */
        enum healthcheck_status ok_status;   /* The healthcheck status to use on success (default: L7OKD) */
        enum healthcheck_status err_status;  /* The healthcheck status to use on error (default: L7RSP) */
        enum healthcheck_status tout_status; /* The healthcheck status to use on timeout (default: L7TOUT) */
+       struct list onerror_fmt;        /* log-format string to use as comment on error */
+       struct list onsuccess_fmt;      /* log-format string to use as comment on success (if last rule) */
        struct sample_expr *status_expr; /* sample expr to determine the check status code */
 };
 
@@ -311,10 +340,13 @@ struct tcpcheck_rule {
 
 #define TCPCHK_RULES_NONE           0x00000000
 #define TCPCHK_RULES_UNUSED_TCP_RS  0x00000001 /* An unused tcp-check ruleset exists */
+#define TCPCHK_RULES_UNUSED_HTTP_RS 0x00000002 /* An unused http-check ruleset exists */
+#define TCPCHK_RULES_UNUSED_RS      0x00000003 /* Mask for unused ruleset */
 
 #define TCPCHK_RULES_PGSQL_CHK   0x00000010
 #define TCPCHK_RULES_REDIS_CHK   0x00000020
 #define TCPCHK_RULES_SMTP_CHK    0x00000030
+#define TCPCHK_RULES_HTTP_CHK    0x00000040
 #define TCPCHK_RULES_MYSQL_CHK   0x00000050
 #define TCPCHK_RULES_LDAP_CHK    0x00000060
 #define TCPCHK_RULES_SSL3_CHK    0x00000070
index f9fe7c594669e5926a96c11f9c96a99012a82405..35ca0ad108ce459ff7ca1b5a884335370e310d55 100644 (file)
@@ -156,22 +156,10 @@ enum PR_SRV_STATE_FILE {
 #define PR_O2_SRC_ADDR 0x00100000      /* get the source ip and port for logs */
 
 #define PR_O2_FAKE_KA   0x00200000      /* pretend we do keep-alive with server eventhough we close */
-/* unused : 0x00400000 */
-
-#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> */
-/* unused: 0x08000000 */
+/* unused : 0x00400000..0x80000000 */
 
 /* server health checks */
 #define PR_O2_CHK_NONE  0x00000000      /* no L7 health checks configured (TCP by default) */
-/* unused: 0x10000000..0x30000000 */
-#define PR_O2_HTTP_CHK  0x40000000      /* use HTTP 'OPTIONS' method to check server health */
-/* unused 0x50000000..0x80000000 */
 #define PR_O2_TCPCHK_CHK 0x90000000     /* use TCPCHK check for server health */
 #define PR_O2_EXT_CHK   0xA0000000      /* use external command for server health */
 /* unused: 0xB0000000 to 0xF000000, reserved for health checks */
@@ -418,16 +406,8 @@ struct proxy {
        struct task *task;                      /* the associated task, mandatory to manage rate limiting, stopping and resource shortage, NULL if disabled */
        struct tcpcheck_rules tcpcheck_rules;   /* tcp-check send / expect rules */
        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 */
-       struct my_regex *expect_regex;          /* http-check expected content */
        struct buffer *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 */
index 3a7050e45573f4c46e3e77af34ac2b950d40d31f..438accc8ca73ae1d47ff04a5188738c0cd36ec0c 100644 (file)
@@ -166,7 +166,6 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
 {
        static struct proxy *curproxy = NULL;
        const char *err;
-       char *error;
        int rc;
        unsigned val;
        int err_code = 0;
@@ -285,25 +284,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                        curproxy->redispatch_after = defproxy.redispatch_after;
                        curproxy->max_ka_queue = defproxy.max_ka_queue;
 
-                       if (defproxy.check_req) {
-                               curproxy->check_req = calloc(1, defproxy.check_len);
-                               memcpy(curproxy->check_req, defproxy.check_req, defproxy.check_len);
-                       }
-                       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;
-
-                       curproxy->tcpcheck_rules.flags = (defproxy.tcpcheck_rules.flags & ~TCPCHK_RULES_UNUSED_TCP_RS);
+                       curproxy->tcpcheck_rules.flags = (defproxy.tcpcheck_rules.flags & ~TCPCHK_RULES_UNUSED_RS);
                        curproxy->tcpcheck_rules.list  = defproxy.tcpcheck_rules.list;
                        if (!LIST_ISEMPTY(&defproxy.tcpcheck_rules.preset_vars)) {
                                if (!dup_tcpcheck_vars(&curproxy->tcpcheck_rules.preset_vars,
@@ -315,21 +296,6 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                                }
                        }
 
-                       if (defproxy.expect_str) {
-                               curproxy->expect_str = strdup(defproxy.expect_str);
-                               if (defproxy.expect_regex) {
-                                       /* note: this regex is known to be valid */
-                                       error = NULL;
-                                       if (!(curproxy->expect_regex = regex_comp(defproxy.expect_str, 1, 1, &error))) {
-                                               ha_alert("parsing [%s:%d] : regular expression '%s' : %s\n", file, linenum,
-                                                        defproxy.expect_str, error);
-                                               free(error);
-                                               err_code |= ERR_ALERT | ERR_FATAL;
-                                               goto out;
-                                       }
-                               }
-                       }
-
                        curproxy->ck_opts = defproxy.ck_opts;
                        if (defproxy.cookie_name)
                                curproxy->cookie_name = strdup(defproxy.cookie_name);
@@ -502,7 +468,6 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                        goto out;
                }
 
-               free(defproxy.check_req);
                free(defproxy.check_command);
                free(defproxy.check_path);
                free(defproxy.cookie_name);
@@ -521,9 +486,6 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                defproxy.orgto_hdr_len = 0;
                free(defproxy.server_id_hdr_name);
                defproxy.server_id_hdr_len = 0;
-               free(defproxy.expect_str);
-               regex_free(defproxy.expect_regex);
-               defproxy.expect_regex = NULL;
 
                if (defproxy.conf.logformat_string != default_http_log_format &&
                    defproxy.conf.logformat_string != default_tcp_log_format &&
index ae27434c2ab0562243f9f9cd22cb774f27c8fcf3..e5c9219e117e2d0331c0d148e31d2b70251817d7 100644 (file)
@@ -2504,7 +2504,14 @@ int check_config_validity()
                else if (curproxy->options & PR_O_TRANSP)
                        curproxy->options &= ~(PR_O_DISPATCH | PR_O_HTTP_PROXY);
 
-               if ((curproxy->options2 & PR_O2_CHK_ANY) != PR_O2_HTTP_CHK) {
+               if ((curproxy->tcpcheck_rules.flags & TCPCHK_RULES_UNUSED_HTTP_RS)) {
+                       ha_warning("config : %s '%s' uses http-check rules without 'option httpchk', so the rules are ignored.\n",
+                                  proxy_type_str(curproxy), curproxy->id);
+                       err_code |= ERR_WARN;
+               }
+
+               if ((curproxy->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
+                   (curproxy->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
                        if (curproxy->options & PR_O_DISABLE404) {
                                ha_warning("config : '%s' will be ignored for %s '%s' (requires 'option httpchk').\n",
                                           "disable-on-404", proxy_type_str(curproxy), curproxy->id);
@@ -3097,8 +3104,7 @@ out_uri_auth_compat:
                        }
                }
 
-               if ((curproxy->tcpcheck_rules.flags & TCPCHK_RULES_UNUSED_TCP_RS) &&
-                   (curproxy->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
+               if ((curproxy->tcpcheck_rules.flags & TCPCHK_RULES_UNUSED_TCP_RS)) {
                        ha_warning("config : %s '%s' uses tcp-check rules without 'option tcp-check', so the rules are ignored.\n",
                                   proxy_type_str(curproxy), curproxy->id);
                        err_code |= ERR_WARN;
index 8ff6bc83cbba4503bc748f21628c2377f469d0c2..41927204a37db506d9a7bd6ec944cff848068f08 100644 (file)
@@ -38,6 +38,8 @@
 #include <common/standard.h>
 #include <common/time.h>
 #include <common/hathreads.h>
+#include <common/http.h>
+#include <common/h1.h>
 
 #include <types/global.h>
 #include <types/dns.h>
@@ -67,7 +69,6 @@
 #include <proto/ssl_sock.h>
 #include <proto/sample.h>
 
-static int httpchk_expect(struct server *s, int done);
 static int tcpcheck_get_step_id(struct check *, struct tcpcheck_rule *);
 static int tcpcheck_main(struct check *);
 static void __event_srv_chk_w(struct conn_stream *cs);
@@ -75,6 +76,12 @@ static int wake_srv_chk(struct conn_stream *cs);
 static void __event_srv_chk_r(struct conn_stream *cs);
 
 static int srv_check_healthcheck_port(struct check *chk);
+static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px,
+                                                   struct list *rules, const char *file, int line,
+                                                   char **errmsg);
+static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
+                                                  struct list *rules, unsigned int proto,
+                                                  const char *file, int line, char **errmsg);
 
 /* Global list to share all tcp-checks */
 struct list tcpchecks_list = LIST_HEAD_INIT(tcpchecks_list);
@@ -665,6 +672,18 @@ static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
                                case TCPCHK_EXPECT_REGEX_BINARY:
                                        chunk_appendf(chk, " (expect binary regex)");
                                        break;
+                               case TCPCHK_EXPECT_HTTP_STATUS:
+                                       chunk_appendf(chk, " (expect HTTP status '%.*s')", (unsigned int)istlen(expect->data), expect->data.ptr);
+                                       break;
+                               case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
+                                       chunk_appendf(chk, " (expect HTTP status regex)");
+                                       break;
+                               case TCPCHK_EXPECT_HTTP_BODY:
+                                       chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), expect->data.ptr);
+                                       break;
+                               case TCPCHK_EXPECT_HTTP_REGEX_BODY:
+                                       chunk_appendf(chk, " (expect HTTP body regex)");
+                                       break;
                                case TCPCHK_EXPECT_CUSTOM:
                                        chunk_appendf(chk, " (expect custom function)");
                                        break;
@@ -854,9 +873,7 @@ static void __event_srv_chk_r(struct conn_stream *cs)
 {
        struct connection *conn = cs->conn;
        struct check *check = cs->data;
-       struct server *s = check->server;
        struct task *t = check->task;
-       char *desc;
        int done;
 
        if (unlikely(check->result == CHK_RES_FAILED))
@@ -907,58 +924,14 @@ static void __event_srv_chk_r(struct conn_stream *cs)
        }
 
        /* Run the checks... */
-       switch (check->type) {
-       case PR_O2_HTTP_CHK:
-               if (!done && b_data(&check->bi) < strlen("HTTP/1.0 000\r"))
-                       goto wait_more_data;
-
-               /* Check if the server speaks HTTP 1.X */
-               if ((b_data(&check->bi) < strlen("HTTP/1.0 000\r")) ||
-                   (memcmp(b_head(&check->bi), "HTTP/1.", 7) != 0 ||
-                   (*(b_head(&check->bi) + 12) != ' ' && *(b_head(&check->bi) + 12) != '\r')) ||
-                   !isdigit((unsigned char) *(b_head(&check->bi) + 9)) || !isdigit((unsigned char) *(b_head(&check->bi) + 10)) ||
-                   !isdigit((unsigned char) *(b_head(&check->bi) + 11))) {
-                       cut_crlf(b_head(&check->bi));
-                       set_server_check_status(check, HCHK_STATUS_L7RSP, b_head(&check->bi));
-
-                       goto out_wakeup;
-               }
-
-               check->code = str2uic(b_head(&check->bi) + 9);
-               desc = ltrim(b_head(&check->bi) + 12, ' ');
-
-               if ((s->proxy->options & PR_O_DISABLE404) &&
-                        (s->next_state != SRV_ST_STOPPED) && (check->code == 404)) {
-                       /* 404 may be accepted as "stopping" only if the server was up */
-                       cut_crlf(desc);
-                       set_server_check_status(check, 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 (*(b_head(&check->bi) + 9) == '2' || *(b_head(&check->bi) + 9) == '3') {
-                       cut_crlf(desc);
-                       set_server_check_status(check,  HCHK_STATUS_L7OKD, desc);
-               }
-               else {
-                       cut_crlf(desc);
-                       set_server_check_status(check, HCHK_STATUS_L7STS, desc);
-               }
-               break;
 
-       default:
-               /* good connection is enough for pure TCP check */
-               if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
-                       if (check->use_ssl == 1)
-                               set_server_check_status(check, HCHK_STATUS_L6OK, NULL);
-                       else
-                               set_server_check_status(check, HCHK_STATUS_L4OK, NULL);
-               }
-               break;
-       } /* switch */
+       /* good connection is enough for pure TCP check */
+       if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
+               if (check->use_ssl == 1)
+                       set_server_check_status(check, HCHK_STATUS_L6OK, NULL);
+               else
+                       set_server_check_status(check, HCHK_STATUS_L4OK, NULL);
+       }
 
  out_wakeup:
        /* collect possible new errors */
@@ -1188,47 +1161,6 @@ static int connect_conn_chk(struct task *t)
        if (conn)
                return SF_ERR_INTERNAL;
 
-       /* prepare the check buffer.
-        * This should not be used if check is the secondary agent check
-        * of a server as s->proxy->check_req will relate to the
-        * configuration of the primary check. Similarly, tcp-check uses
-        * its own strings.
-        */
-       if (check->type && check->type != PR_O2_TCPCHK_CHK && !(check->state & CHK_ST_AGENT)) {
-               b_putblk(&check->bo, s->proxy->check_req, s->proxy->check_len);
-
-               /* we want to check if this host replies to HTTP requests
-                * so we'll send the request, and won't wake the checker up now.
-                */
-               if ((check->type) == PR_O2_HTTP_CHK) {
-                       /* 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 */
-               }
-       }
-
        /* for tcp-checks, the initial connection setup is handled separately as
         * it may be sent to a specific port and not to the server's.
         */
@@ -2194,110 +2126,6 @@ static int start_checks()
        return 0;
 }
 
-/*
- * Perform content verification check on data in s->check.buffer 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 THREAD_LOCAL 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, b_head(&s->check.bi) + 9, 3);
-               memcpy(status_msg + strlen(status_msg) - 4, b_head(&s->check.bi) + 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 = regex_exec(s->proxy->expect_regex, status_code);
-
-               /* 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->check, 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 = b_head(&s->check.bi); *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->check, HCHK_STATUS_L7RSP,
-                                               "HTTP content check could not find a response body");
-                       return 1;
-               }
-
-               /* Check that response body is not empty... */
-               if (*contentptr == '\0') {
-                       if (!done)
-                               return 0;
-
-                       set_server_check_status(&s->check, 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 = regex_exec(s->proxy->expect_regex, contentptr);
-
-               /* 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->check, HCHK_STATUS_L7RSP,
-                                                       "HTTP check matched unwanted content");
-                       else
-                               set_server_check_status(&s->check, HCHK_STATUS_L7OKD,
-                                                       "HTTP content check matched");
-               }
-               else {
-                       if (s->proxy->options2 & PR_O2_EXP_INV)
-                               set_server_check_status(&s->check, HCHK_STATUS_L7OKD,
-                                                       "HTTP check did not match unwanted content");
-                       else
-                               set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
-                                                       "HTTP content check did not match");
-               }
-               break;
-       }
-       return 1;
-}
-
 /*
  * return the id of a step in a send/expect session
  */
@@ -2339,6 +2167,8 @@ static void tcpcheck_onerror_message(struct buffer *msg, struct check *check, st
        chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
        switch (rule->expect.type) {
        case TCPCHK_EXPECT_STRING:
+       case TCPCHK_EXPECT_HTTP_STATUS:
+       case TCPCHK_EXPECT_HTTP_BODY:
                chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), rule->expect.data.ptr,
                              tcpcheck_get_step_id(check, rule));
                break;
@@ -2346,6 +2176,8 @@ static void tcpcheck_onerror_message(struct buffer *msg, struct check *check, st
                chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
                break;
        case TCPCHK_EXPECT_REGEX:
+       case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
+       case TCPCHK_EXPECT_HTTP_REGEX_BODY:
                chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
                break;
        case TCPCHK_EXPECT_REGEX_BINARY:
@@ -3082,6 +2914,54 @@ static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcp
                if (parse_binary(b_orig(tmp),  &check->bo.area, (int *)&check->bo.data, NULL) == 0)
                        goto error_lf;
                break;
+       case TCPCHK_SEND_HTTP: {
+               struct ist meth, uri, vsn;
+
+               meth = ((send->http.meth.meth == HTTP_METH_OTHER)
+                       ? ist2(send->http.meth.str.area, send->http.meth.str.data)
+                       : http_known_methods[send->http.meth.meth]);
+               uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt
+               vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
+
+               chunk_istcat(&check->bo, meth);
+               check->bo.area[check->bo.data++] = ' ';
+               chunk_istcat(&check->bo, uri);
+               check->bo.area[check->bo.data++] = ' ';
+               chunk_istcat(&check->bo, vsn);
+               chunk_istcat(&check->bo, ist("\r\n"));
+               chunk_istcat(&check->bo, ist("Connection: close\r\n"));
+               if (isttest(send->http.body)) {
+                       // TODO: handle body_fmt
+                       chunk_appendf(&check->bo, "Content-Length: %s\r\n", ultoa(istlen(send->http.body)));
+               }
+               if (check->proxy->options2 & PR_O2_CHK_SNDST) {
+                       trash.data = httpchk_build_status_header(check->server, b_orig(&trash), b_size(&trash));
+                       chunk_cat(&check->bo, &trash);
+               }
+               if (!LIST_ISEMPTY(&send->http.hdrs)) {
+                       struct tcpcheck_http_hdr *hdr;
+
+                       tmp = alloc_trash_chunk();
+                       if (!tmp)
+                               goto error_lf;
+                       list_for_each_entry(hdr, &send->http.hdrs, list) {
+                               chunk_reset(tmp);
+                                tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
+                               if (!b_data(tmp))
+                                       continue;
+                               chunk_istcat(&check->bo, hdr->name);
+                               check->bo.area[check->bo.data++] = ' ';
+                               chunk_cat(&check->bo, tmp);
+                               chunk_istcat(&check->bo, ist("\r\n"));
+                       }
+               }
+               chunk_istcat(&check->bo, ist("\r\n"));
+               if (isttest(send->http.body)) {
+                       // TODO: handle body_fmt
+                       chunk_istcat(&check->bo, send->http.body);
+               }
+               break;
+       }
        case TCPCHK_SEND_UNDEF:
                /* Should never happen. */
                ret = TCPCHK_EVAL_STOP;
@@ -3113,6 +2993,130 @@ static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcp
 
 }
 
+static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
+{
+       enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
+       struct tcpcheck_expect *expect = &rule->expect;
+       struct buffer *msg = NULL;
+       enum healthcheck_status status;
+       struct ist desc = ist(NULL);
+       char *body;
+       size_t body_len;
+       int match, inverse;
+
+       last_read |= b_full(&check->bi);
+
+       /* Must at least receive the status line (HTTP/1.X XXX.) */
+       if (!last_read && b_data(&check->bi) < 13)
+               goto wait_more_data;
+
+       /* Check if the server speaks HTTP 1.X */
+       if (b_data(&check->bi) < 13 ||
+           memcmp(b_head(&check->bi), "HTTP/1.", 7) != 0 ||
+           (*b_peek(&check->bi, 12) != ' ' && *b_peek(&check->bi, 12) != '\r') ||
+           !isdigit((unsigned char) *b_peek(&check->bi, 9)) || !isdigit((unsigned char) *b_peek(&check->bi, 10)) ||
+           !isdigit((unsigned char) *b_peek(&check->bi, 11))) {
+               status = HCHK_STATUS_L7RSP;
+               desc   = ist2(b_head(&check->bi), my_memcspn(b_head(&check->bi), b_data(&check->bi), "\r\n", 2));
+               goto error;
+       }
+
+       check->code = strl2uic(b_peek(&check->bi, 9), 3);
+
+       if (check->server &&
+           (check->server->proxy->options & PR_O_DISABLE404) &&
+           (check->server->next_state != SRV_ST_STOPPED) &&
+           (check->code == 404)) {
+               /* 404 may be accepted as "stopping" only if the server was up */
+               goto out;
+       }
+
+       inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
+       /* Make GCC happy ; initialize match to a failure state. */
+       match = inverse;
+
+       switch (expect->type) {
+       case TCPCHK_EXPECT_HTTP_STATUS:
+               match = my_memmem(b_peek(&check->bi, 9), 3, expect->data.ptr, istlen(expect->data)) != NULL;
+
+               /* Set status and description in case of error */
+               status = HCHK_STATUS_L7STS;
+               desc  = ist2(b_peek(&check->bi, 12), my_memcspn(b_peek(&check->bi, 12), b_data(&check->bi) - 12, "\r\n", 2));
+               break;
+       case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
+               match = regex_exec2(expect->regex, b_peek(&check->bi, 9), 3);
+
+               /* Set status and description in case of error */
+               status = HCHK_STATUS_L7STS;
+               desc  = ist2(b_peek(&check->bi, 12), my_memcspn(b_peek(&check->bi, 12), b_data(&check->bi) - 12, "\r\n", 2));
+               break;
+       case TCPCHK_EXPECT_HTTP_BODY:
+       case TCPCHK_EXPECT_HTTP_REGEX_BODY:
+               body = (char *)my_memmem(b_head(&check->bi), b_data(&check->bi), "\r\n\r\n", 4);
+               if (!body) {
+                       if (!last_read)
+                               goto wait_more_data;
+
+                       status = HCHK_STATUS_L7RSP;
+                       desc = ist("HTTP content check could not find a response body");
+                       goto error;
+               }
+               body += 4;
+               body_len = b_tail(&check->bi) - body;
+
+               if (!last_read &&
+                   ((expect->type == TCPCHK_EXPECT_HTTP_BODY && body_len < istlen(expect->data)) ||
+                    (expect->min_recv > 0 && body_len < expect->min_recv))) {
+                       ret = TCPCHK_EVAL_WAIT;
+                       goto out;
+               }
+
+               if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
+                       match = my_memmem(body, body_len, expect->data.ptr, istlen(expect->data)) != NULL;
+               else
+                       match = regex_exec2(expect->regex, body, body_len);
+
+               /* Set status and description in case of error */
+               status = HCHK_STATUS_L7RSP;
+               desc = (inverse
+                       ? ist("HTTP check matched unwanted content")
+                       : ist("HTTP content check did not match"));
+               break;
+
+       default:
+               /* should never happen */
+               status = HCHK_STATUS_L7RSP;
+               goto error;
+       }
+
+       /* Wait for more data on mismatch only if no minimum is defined (-1),
+        * otherwise the absence of match is already conclusive.
+        */
+       if (!match && !last_read && (expect->min_recv == -1)) {
+               ret = TCPCHK_EVAL_WAIT;
+               goto out;
+       }
+
+       if (!(match ^ inverse))
+               goto error;
+
+  out:
+       free_trash_chunk(msg);
+       return ret;
+
+  error:
+       ret = TCPCHK_EVAL_STOP;
+       msg = alloc_trash_chunk();
+       if (msg)
+               tcpcheck_onerror_message(msg, check, rule, 0, desc);
+       set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
+       goto out;
+
+  wait_more_data:
+       ret = TCPCHK_EVAL_WAIT;
+       goto out;
+}
+
 /* Evaluate a TCPCHK_ACT_EXPECT rule. It returns 1 to evaluate the next rule, 0
  * to wait and -1 to stop the check.
  */
@@ -3123,6 +3127,8 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct t
        struct buffer *msg = NULL;
        int match, inverse;
 
+       last_read |= b_full(&check->bi);
+
        /* The current expect might need more data than the previous one, check again
         * that the minimum amount data required to match is respected.
         */
@@ -3168,7 +3174,7 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct t
                if (expect->custom)
                        ret = expect->custom(check, rule, last_read);
                goto out;
-       case TCPCHK_EXPECT_UNDEF:
+       default:
                /* Should never happen. */
                ret = TCPCHK_EVAL_STOP;
                goto out;
@@ -3306,6 +3312,7 @@ static int tcpcheck_main(struct check *check)
        list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
                enum tcpcheck_eval_ret eval_ret;
 
+               check->code = 0;
                switch (rule->action) {
                case TCPCHK_ACT_CONNECT:
                        check->current_step = rule;
@@ -3352,10 +3359,6 @@ static int tcpcheck_main(struct check *check)
                                        }
                                }
 
-                               /* buffer full, don't wait for more data */
-                               if (b_full(&check->bi))
-                                       last_read = 1;
-
                                /* Check that response body is not empty... */
                                if (!b_data(&check->bi)) {
                                        if (!last_read)
@@ -3373,7 +3376,10 @@ static int tcpcheck_main(struct check *check)
                                must_read = 0;
                        }
 
-                       eval_ret = tcpcheck_eval_expect(check, rule, last_read);
+                       eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
+                                   ? tcpcheck_eval_expect_http(check, rule, last_read)
+                                   : tcpcheck_eval_expect(check, rule, last_read));
+
                        if (eval_ret == TCPCHK_EVAL_WAIT) {
                                check->current_step = rule->expect.head;
                                conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
@@ -3406,8 +3412,17 @@ static int tcpcheck_main(struct check *check)
                rule = check->current_step;
 
                if (rule->action == TCPCHK_ACT_EXPECT) {
-                       struct buffer *msg = alloc_trash_chunk();
+                       struct buffer *msg;
 
+                       if (check->server &&
+                           (check->server->proxy->options & PR_O_DISABLE404) &&
+                           (check->server->next_state != SRV_ST_STOPPED) &&
+                           (check->code == 404)) {
+                               set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
+                               goto out_end_tcpcheck;
+                       }
+
+                       msg = alloc_trash_chunk();
                        if (msg)
                                tcpcheck_onsuccess_message(msg, check, rule, ist(NULL));
                        set_server_check_status(check, rule->expect.ok_status,
@@ -3477,10 +3492,40 @@ void free_check(struct check *check)
        }
 }
 
-static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
+static void free_tcpcheck_fmt(struct list *fmt)
 {
        struct logformat_node *lf, *lfb;
 
+       list_for_each_entry_safe(lf, lfb, fmt, list) {
+               LIST_DEL(&lf->list);
+               release_sample_expr(lf->expr);
+               free(lf->arg);
+               free(lf);
+       }
+}
+
+static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
+{
+       if (!hdr)
+               return;
+
+       free_tcpcheck_fmt(&hdr->value);
+       free(hdr->name.ptr);
+       free(hdr);
+}
+
+static void free_tcpcheck_http_hdrs(struct list *hdrs)
+{
+       struct tcpcheck_http_hdr *hdr, *bhdr;
+
+       list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
+               LIST_DEL(&hdr->list);
+               free_tcpcheck_http_hdr(hdr);
+       }
+}
+
+static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
+{
        if (!rule)
                return;
 
@@ -3494,38 +3539,40 @@ static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
                        break;
                case TCPCHK_SEND_STRING_LF:
                case TCPCHK_SEND_BINARY_LF:
-                       list_for_each_entry_safe(lf, lfb, &rule->send.fmt, list) {
-                               LIST_DEL(&lf->list);
-                               release_sample_expr(lf->expr);
-                               free(lf->arg);
-                               free(lf);
-                       }
+                       free_tcpcheck_fmt(&rule->send.fmt);
+                       break;
+               case TCPCHK_SEND_HTTP:
+                       free(rule->send.http.meth.str.area);
+                       if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
+                               free(rule->send.http.uri.ptr);
+                       else
+                               free_tcpcheck_fmt(&rule->send.http.uri_fmt);
+                       free(rule->send.http.vsn.ptr);
+                       free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
+                       if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
+                               free(rule->send.http.body.ptr);
+                       else
+                               free_tcpcheck_fmt(&rule->send.http.body_fmt);
                        break;
                case TCPCHK_SEND_UNDEF:
                        break;
                }
                break;
        case TCPCHK_ACT_EXPECT:
-               list_for_each_entry_safe(lf, lfb, &rule->expect.onerror_fmt, list) {
-                       LIST_DEL(&lf->list);
-                       release_sample_expr(lf->expr);
-                       free(lf->arg);
-                       free(lf);
-               }
-               list_for_each_entry_safe(lf, lfb, &rule->expect.onsuccess_fmt, list) {
-                       LIST_DEL(&lf->list);
-                       release_sample_expr(lf->expr);
-                       free(lf->arg);
-                       free(lf);
-               }
+               free_tcpcheck_fmt(&rule->expect.onerror_fmt);
+               free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
                release_sample_expr(rule->expect.status_expr);
                switch (rule->expect.type) {
                case TCPCHK_EXPECT_STRING:
                case TCPCHK_EXPECT_BINARY:
+               case TCPCHK_EXPECT_HTTP_STATUS:
+               case TCPCHK_EXPECT_HTTP_BODY:
                        free(rule->expect.data.ptr);
                        break;
                case TCPCHK_EXPECT_REGEX:
                case TCPCHK_EXPECT_REGEX_BINARY:
+               case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
+               case TCPCHK_EXPECT_HTTP_REGEX_BODY:
                        regex_free(rule->expect.regex);
                        break;
                case TCPCHK_EXPECT_CUSTOM:
@@ -4025,12 +4072,18 @@ REGISTER_POST_CHECK(start_checks);
 static int check_proxy_tcpcheck(struct proxy *px)
 {
        struct tcpcheck_rule *chk, *back;
-       char *comment = NULL;
+       char *comment = NULL, *errmsg = NULL;
        enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
        int ret = 0;
 
-       if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK)
+       if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
+               deinit_proxy_tcpcheck(px);
                goto out;
+       }
+
+       free(px->check_command);
+       free(px->check_path);
+       px->check_command = px->check_path = NULL;
 
        if (!px->tcpcheck_rules.list) {
                ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
@@ -4038,6 +4091,39 @@ static int check_proxy_tcpcheck(struct proxy *px)
                goto out;
        }
 
+       /* HTTP ruleset */
+       if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
+               struct tcpcheck_rule *next;
+
+               /* move remaining send rule from "option httpchk" line to the right place */
+               chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
+               if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
+                       next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
+                       if (next && next->action == TCPCHK_ACT_CONNECT) {
+                               LIST_DEL(&chk->list);
+                               LIST_ADD(&next->list, &chk->list);
+                               chk->index = next->index;
+                       }
+               }
+
+               /* add implicit expect rule if the last one is a send. */
+               chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
+               if (chk && chk->action == TCPCHK_ACT_SEND) {
+                       next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "rstatus", "^[23]", ""},
+                                                    1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
+                                                    px->conf.file, px->conf.line, &errmsg);
+                       if (!next) {
+                               ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
+                                        "(%s).\n", px->id, errmsg);
+                               free(errmsg);
+                               ret |= ERR_ALERT | ERR_FATAL;
+                               goto out;
+                       }
+                       LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
+                       next->index = chk->index;
+               }
+       }
+
        /* If there is no connect rule preceeding all send / expect rules, an
         * implicit one is inserted before all others
         */
@@ -4420,10 +4506,13 @@ static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, st
        int alpn_len = 0;
 
        list_for_each_entry(chk, rules, list) {
-               if (chk->action != TCPCHK_ACT_COMMENT && chk->action != TCPCHK_ACT_ACTION_KW)
+               if (chk->action == TCPCHK_ACT_CONNECT)
                        break;
-       }
-       if (&chk->list != rules && chk->action != TCPCHK_ACT_CONNECT) {
+               if (chk->action == TCPCHK_ACT_COMMENT ||
+                   chk->action == TCPCHK_ACT_ACTION_KW ||
+                   (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
+                       continue;
+
                memprintf(errmsg, "first step MUST also be a 'connect', "
                          "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
                          "when there is a 'connect' step in the tcp-check ruleset");
@@ -4669,6 +4758,7 @@ static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struc
                        goto error;
                }
                break;
+       case TCPCHK_SEND_HTTP:
        case TCPCHK_SEND_UNDEF:
                goto error;
        }
@@ -4681,64 +4771,342 @@ static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struc
        return NULL;
 }
 
-static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
-                                                   const char *file, int line, char **errmsg)
+static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
+                                                     const char *file, int line, char **errmsg)
 {
        struct tcpcheck_rule *chk = NULL;
-       char *comment = NULL;
-
-       if (!*(args[cur_arg+1])) {
-               memprintf(errmsg, "expects a string as argument");
-               goto error;
-       }
-       cur_arg++;
-       comment = strdup(args[cur_arg]);
-       if (!comment) {
-               memprintf(errmsg, "out of memory");
-               goto error;
-       }
-
-       chk = calloc(1, sizeof(*chk));
-       if (!chk) {
-               memprintf(errmsg, "out of memory");
-               goto error;
-       }
-       chk->action  = TCPCHK_ACT_COMMENT;
-       chk->comment = comment;
-       return chk;
-
-  error:
-       free(comment);
-       return NULL;
-}
-
-static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px, struct list *rules,
-                                                  const char *file, int line, char **errmsg)
-{
-       struct tcpcheck_rule *prev_check, *chk = NULL;
-       struct sample_expr *status_expr = NULL;
-       char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
-       enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
-       enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
-       enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
-       enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
-       long min_recv = -1;
-       int inverse = 0, with_capture = 0;
-
-       str = on_success_msg = on_error_msg = comment = pattern = NULL;
-       if (!*(args[cur_arg+1])) {
-               memprintf(errmsg, "expects at least a matching pattern as arguments");
-               goto error;
-       }
+       struct tcpcheck_http_hdr *hdr = NULL;
+        struct http_hdr hdrs[global.tune.max_http_hdr];
+       char *meth = NULL, *uri = NULL, *vsn = NULL;
+       char *body = NULL, *comment = NULL;
+       unsigned int flags = 0;
+       int i = 0;
 
        cur_arg++;
        while (*(args[cur_arg])) {
-               int in_pattern = 0;
-
-         rescan:
-               if (strcmp(args[cur_arg], "min-recv") == 0) {
-                       if (in_pattern) {
-                               memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
+               if (strcmp(args[cur_arg], "meth") == 0) {
+                       if (!*(args[cur_arg+1])) {
+                               memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
+                               goto error;
+                       }
+                       cur_arg++;
+                       meth = args[cur_arg];
+               }
+               else if (strcmp(args[cur_arg], "uri") == 0) {
+                       if (!*(args[cur_arg+1])) {
+                               memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
+                               goto error;
+                       }
+                       cur_arg++;
+                       uri = args[cur_arg];
+                       // TODO: log-format uri
+               }
+               else if (strcmp(args[cur_arg], "vsn") == 0) {
+                       if (!*(args[cur_arg+1])) {
+                               memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
+                               goto error;
+                       }
+                       cur_arg++;
+                       vsn = args[cur_arg];
+               }
+                else if (strcmp(args[cur_arg], "hdr") == 0) {
+                       if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
+                               memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
+                               goto error;
+                       }
+                       hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
+                       hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
+                       i++;
+                       cur_arg += 2;
+               }
+               else if (strcmp(args[cur_arg], "body") == 0) {
+                       if (!*(args[cur_arg+1])) {
+                               memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
+                               goto error;
+                       }
+                       cur_arg++;
+                       body = args[cur_arg];
+                       // TODO: log-format body
+               }
+               else if (strcmp(args[cur_arg], "comment") == 0) {
+                       if (!*(args[cur_arg+1])) {
+                               memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
+                               goto error;
+                       }
+                       cur_arg++;
+                       free(comment);
+                       comment = strdup(args[cur_arg]);
+                       if (!comment) {
+                               memprintf(errmsg, "out of memory");
+                               goto error;
+                       }
+               }
+               else {
+                       memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'hdr' and 'body' but got '%s' as argument.",
+                                 args[cur_arg]);
+                       goto error;
+               }
+               cur_arg++;
+       }
+
+       hdrs[i].n = hdrs[i].v = IST_NULL;
+
+       chk = calloc(1, sizeof(*chk));
+       if (!chk) {
+               memprintf(errmsg, "out of memory");
+               goto error;
+       }
+       chk->action    = TCPCHK_ACT_SEND;
+       chk->comment   = comment; comment = NULL;
+       chk->send.type = TCPCHK_SEND_HTTP;
+       chk->send.http.flags = flags;
+       LIST_INIT(&chk->send.http.hdrs);
+
+       if (meth) {
+               chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
+               chk->send.http.meth.str.area = strdup(meth);
+               chk->send.http.meth.str.data = strlen(meth);
+               if (!chk->send.http.meth.str.area) {
+                       memprintf(errmsg, "out of memory");
+                       goto error;
+               }
+       }
+       if (uri) {
+               chk->send.http.uri = ist2(strdup(uri), strlen(uri));
+               if (!isttest(chk->send.http.uri)) {
+                       memprintf(errmsg, "out of memory");
+                       goto error;
+               }
+       }
+       if (vsn) {
+               chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
+               if (!isttest(chk->send.http.vsn)) {
+                       memprintf(errmsg, "out of memory");
+                       goto error;
+               }
+       }
+       for (i = 0; hdrs[i].n.len; i++) {
+               hdr = calloc(1, sizeof(*hdr));
+               if (!hdr) {
+                       memprintf(errmsg, "out of memory");
+                       goto error;
+               }
+               LIST_INIT(&hdr->value);
+               hdr->name = ist2(strdup(hdrs[i].n.ptr), hdrs[i].n.len);
+               if (!hdr->name.ptr) {
+                       memprintf(errmsg, "out of memory");
+                       goto error;
+               }
+
+               hdrs[i].v.ptr[hdrs[i].v.len] = '\0';
+               if (!parse_logformat_string(hdrs[i].v.ptr, px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
+                       goto error;
+               LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
+               hdr = NULL;
+       }
+
+       if (body) {
+               chk->send.http.body = ist2(strdup(body), strlen(body));
+               if (!isttest(chk->send.http.body)) {
+                       memprintf(errmsg, "out of memory");
+                       goto error;
+               }
+       }
+
+       return chk;
+
+  error:
+       free_tcpcheck_http_hdr(hdr);
+       free_tcpcheck(chk, 0);
+       free(comment);
+       return NULL;
+}
+
+static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
+{
+       struct logformat_node *lf, *lfb;
+       struct tcpcheck_http_hdr *hdr, *bhdr;
+
+
+       if (new->send.http.meth.str.area) {
+               free(old->send.http.meth.str.area);
+               old->send.http.meth.meth = new->send.http.meth.meth;
+               old->send.http.meth.str.area = new->send.http.meth.str.area;
+               old->send.http.meth.str.data = new->send.http.meth.str.data;
+               new->send.http.meth.str = BUF_NULL;
+       }
+
+       if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
+               if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
+                       free(old->send.http.uri.ptr);
+               else
+                       free_tcpcheck_fmt(&old->send.http.uri_fmt);
+               old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
+               old->send.http.uri = new->send.http.uri;
+               new->send.http.uri = IST_NULL;
+       }
+       else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
+               if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
+                       free(old->send.http.uri.ptr);
+               else
+                       free_tcpcheck_fmt(&old->send.http.uri_fmt);
+               old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
+               LIST_INIT(&old->send.http.uri_fmt);
+               list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
+                       LIST_DEL(&lf->list);
+                       LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
+               }
+       }
+
+       if (isttest(new->send.http.vsn)) {
+               free(old->send.http.vsn.ptr);
+               old->send.http.vsn = new->send.http.vsn;
+               new->send.http.vsn = IST_NULL;
+       }
+
+       free_tcpcheck_http_hdrs(&old->send.http.hdrs);
+       list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
+               LIST_DEL(&hdr->list);
+               LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
+       }
+
+       if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
+               if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
+                       free(old->send.http.body.ptr);
+               else
+                       free_tcpcheck_fmt(&old->send.http.body_fmt);
+               old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
+               old->send.http.body = new->send.http.body;
+               new->send.http.body = IST_NULL;
+       }
+       else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
+               if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
+                       free(old->send.http.body.ptr);
+               else
+                       free_tcpcheck_fmt(&old->send.http.body_fmt);
+               old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
+               LIST_INIT(&old->send.http.body_fmt);
+               list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
+                       LIST_DEL(&lf->list);
+                       LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
+               }
+       }
+}
+
+static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
+{
+       struct tcpcheck_rule *r;
+
+       if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
+               r = get_first_tcpcheck_rule(rules);
+               if (r && r->action == TCPCHK_ACT_CONNECT)
+                       r = get_next_tcpcheck_rule(rules, r);
+               if (!r || r->action != TCPCHK_ACT_SEND)
+                       LIST_ADD(rules->list, &chk->list);
+               else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
+                       LIST_DEL(&r->list);
+                       free_tcpcheck(r, 0);
+                       LIST_ADD(rules->list, &chk->list);
+               }
+               else {
+                       tcpcheck_overwrite_send_http_rule(r, chk);
+                       free_tcpcheck(chk, 0);
+               }
+       }
+       else {
+               r = get_last_tcpcheck_rule(rules);
+               if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
+                       /* no error */;
+               else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
+                       memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
+                                 chk->index+1);
+                       return 0;
+               }
+               else if (r->action != TCPCHK_ACT_SEND && chk->action == TCPCHK_ACT_EXPECT) {
+                       memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
+                                 chk->index+1);
+                       return 0;
+               }
+               else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
+                       memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
+                                 chk->index+1);
+                       return 0;
+               }
+
+               if (chk->action == TCPCHK_ACT_SEND) {
+                       r = get_first_tcpcheck_rule(rules);
+                       if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
+                               tcpcheck_overwrite_send_http_rule(r, chk);
+                               free_tcpcheck(chk, 0);
+                               LIST_DEL(&r->list);
+                               r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
+                               chk = r;
+                       }
+               }
+               LIST_ADDQ(rules->list, &chk->list);
+       }
+       return 1;
+}
+
+static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
+                                                   const char *file, int line, char **errmsg)
+{
+       struct tcpcheck_rule *chk = NULL;
+       char *comment = NULL;
+
+       if (!*(args[cur_arg+1])) {
+               memprintf(errmsg, "expects a string as argument");
+               goto error;
+       }
+       cur_arg++;
+       comment = strdup(args[cur_arg]);
+       if (!comment) {
+               memprintf(errmsg, "out of memory");
+               goto error;
+       }
+
+       chk = calloc(1, sizeof(*chk));
+       if (!chk) {
+               memprintf(errmsg, "out of memory");
+               goto error;
+       }
+       chk->action  = TCPCHK_ACT_COMMENT;
+       chk->comment = comment;
+       return chk;
+
+  error:
+       free(comment);
+       return NULL;
+}
+
+static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
+                                                  struct list *rules, unsigned int proto,
+                                                  const char *file, int line, char **errmsg)
+{
+       struct tcpcheck_rule *prev_check, *chk = NULL;
+       struct sample_expr *status_expr = NULL;
+       char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
+       enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
+       enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
+       enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
+       enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
+       long min_recv = -1;
+       int inverse = 0, with_capture = 0;
+
+       str = on_success_msg = on_error_msg = comment = pattern = NULL;
+       if (!*(args[cur_arg+1])) {
+               memprintf(errmsg, "expects at least a matching pattern as arguments");
+               goto error;
+       }
+
+       cur_arg++;
+       while (*(args[cur_arg])) {
+               int in_pattern = 0;
+
+         rescan:
+               if (strcmp(args[cur_arg], "min-recv") == 0) {
+                       if (in_pattern) {
+                               memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
                                goto error;
                        }
                        if (!*(args[cur_arg+1])) {
@@ -4763,15 +5131,47 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str
                                cur_arg++;
                        goto rescan;
                }
-               else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "binary") == 0 ||
-                        strcmp(args[cur_arg], "rstring") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
+               else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
                        if (type != TCPCHK_EXPECT_UNDEF) {
                                memprintf(errmsg, "only on pattern expected");
                                goto error;
                        }
-                       type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING :
-                               ((*(args[cur_arg]) == 'b') ?  TCPCHK_EXPECT_BINARY :
-                                ((*(args[cur_arg]+1) == 's') ? TCPCHK_EXPECT_REGEX : TCPCHK_EXPECT_REGEX_BINARY)));
+                       if (proto != TCPCHK_RULES_HTTP_CHK)
+                               type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX);
+                       else
+                               type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY);
+
+                       if (!*(args[cur_arg+1])) {
+                               memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
+                               goto error;
+                       }
+                       cur_arg++;
+                       pattern = args[cur_arg];
+               }
+               else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
+                       if (proto == TCPCHK_RULES_HTTP_CHK)
+                               goto bad_http_kw;
+                       if (type != TCPCHK_EXPECT_UNDEF) {
+                               memprintf(errmsg, "only on pattern expected");
+                               goto error;
+                       }
+                       type = ((*(args[cur_arg]) == 'b') ?  TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY);
+
+                       if (!*(args[cur_arg+1])) {
+                               memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
+                               goto error;
+                       }
+                       cur_arg++;
+                       pattern = args[cur_arg];
+               }
+               else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
+                       if (proto != TCPCHK_RULES_HTTP_CHK)
+                               goto bad_tcp_kw;
+                       if (type != TCPCHK_EXPECT_UNDEF) {
+                               memprintf(errmsg, "only on pattern expected");
+                               goto error;
+                       }
+                       type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS);
 
                        if (!*(args[cur_arg+1])) {
                                memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
@@ -4943,8 +5343,16 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str
                        cur_arg++;
                }
                else {
-                       memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
-                                 " or comment but got '%s' as argument.", args[cur_arg]);
+                       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]);
+                       }
+                       else {
+                         bad_tcp_kw:
+                               memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
+                                         " or comment but got '%s' as argument.", args[cur_arg]);
+                       }
                        goto error;
                }
 
@@ -5009,6 +5417,8 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str
 
        switch (chk->expect.type) {
        case TCPCHK_EXPECT_STRING:
+       case TCPCHK_EXPECT_HTTP_STATUS:
+       case TCPCHK_EXPECT_HTTP_BODY:
                chk->expect.data = ist2(strdup(pattern), strlen(pattern));
                if (!chk->expect.data.ptr) {
                        memprintf(errmsg, "out of memory");
@@ -5022,6 +5432,8 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str
                }
        case TCPCHK_EXPECT_REGEX:
        case TCPCHK_EXPECT_REGEX_BINARY:
+       case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
+       case TCPCHK_EXPECT_HTTP_REGEX_BODY:
                chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg);
                if (!chk->expect.regex)
                        goto error;
@@ -5098,7 +5510,7 @@ static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
        else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
                chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
        else if (strcmp(args[cur_arg], "expect") == 0)
-               chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
+               chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
        else if (strcmp(args[cur_arg], "comment") == 0)
                chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
        else {
@@ -5148,7 +5560,9 @@ static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
                                 struct proxy *defpx, const char *file, int line,
                                 char **errmsg)
 {
-       int cur_arg, ret = 0;
+       struct tcpcheck_ruleset *rs = NULL;
+       struct tcpcheck_rule *chk = NULL;
+       int index, cur_arg, ret = 0;
 
        if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
                ret = 1;
@@ -5159,156 +5573,89 @@ static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
                curpx->options |= PR_O_DISABLE404;
                if (too_many_args(1, args, errmsg, NULL))
                        goto error;
+               goto out;
        }
        else if (strcmp(args[cur_arg], "send-state") == 0) {
                /* enable emission of the apparent state of a server in HTTP checks */
                curpx->options2 |= PR_O2_CHK_SNDST;
                if (too_many_args(1, args, errmsg, NULL))
                        goto error;
+               goto out;
        }
-       else if (strcmp(args[cur_arg], "send") == 0) {
-               free(curpx->check_hdrs);
-               free(curpx->check_body);
-               curpx->check_hdrs = curpx->check_body = NULL;
-               curpx->check_hdrs_len = curpx->check_body_len = 0;
 
-               cur_arg++;
-               while (*(args[cur_arg])) {
-                       if (strcmp(args[cur_arg], "hdr") == 0) {
-                               int hdr_len;
-                               if (!*(args[cur_arg+1]) || !*(args[cur_arg+2])) {
-                                       memprintf(errmsg, "'%s %s' : %s expects a name and a value as parameter.",
-                                                 args[0], args[1], args[cur_arg]);
-                                       goto error;
-                               }
-
-                               cur_arg++;
-                               hdr_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 4;
-                               curpx->check_hdrs = my_realloc2(curpx->check_hdrs, curpx->check_hdrs_len+hdr_len+1);
-                               if (curpx->check_hdrs == NULL) {
-                                       memprintf(errmsg, "out of memory.");
-                                       goto error;
-                               }
-                               snprintf(curpx->check_hdrs + curpx->check_hdrs_len, hdr_len+1, "%s: %s\r\n", args[cur_arg], args[cur_arg+1]);
-                               curpx->check_hdrs_len += hdr_len;
+       /* Deduce the ruleset name from the proxy info */
+       chunk_printf(&trash, "*http-check-%s_%s-%d",
+                    ((curpx == defpx) ? "defaults" : curpx->id),
+                    curpx->conf.file, curpx->conf.line);
 
-                               cur_arg++;
-                       }
-                       else if (strcmp(args[cur_arg], "body") == 0) {
-                               if (!*(args[cur_arg+1])) {
-                                       memprintf(errmsg, "'%s %s' : %s expects a string as parameter.",
-                                                 args[0], args[1], args[cur_arg]);
-                                       goto error;
-                               }
-                               cur_arg++;
-                               free(curpx->check_body);
-                               curpx->check_body = strdup(args[cur_arg]);
-                               curpx->check_body_len = strlen(args[cur_arg]);
-                               if (curpx->check_body == NULL) {
-                                       memprintf(errmsg, "out of memory.");
-                                       goto error;
-                               }
-                       }
-                       else {
-                               memprintf(errmsg, "'%s %s' only supports 'hdr' and 'body', found '%s'.",
-                                        args[0], args[1], args[cur_arg]);
-                               goto error;
-                       }
-                       cur_arg++;
+       rs = tcpcheck_ruleset_lookup(b_orig(&trash));
+       if (rs == NULL) {
+               rs = tcpcheck_ruleset_create(b_orig(&trash));
+               if (rs == NULL) {
+                       memprintf(errmsg, "out of memory.\n");
+                       goto error;
                }
        }
-       else if (strcmp(args[cur_arg], "expect") == 0) {
-               const char *ptr_arg;
 
-               if (curpx->options2 & PR_O2_EXP_TYPE) {
-                       memprintf(errmsg, "'%s %s' already specified.", args[0], args[1]);
-                       goto error;
-               }
+       index = 0;
+       if (!LIST_ISEMPTY(&rs->rules)) {
+               chk = LIST_PREV(&rs->rules, typeof(chk), list);
+               if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
+                       index = chk->index + 1;
+       }
 
-               cur_arg++;
+       if (strcmp(args[cur_arg], "connect") == 0)
+               chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
+       else if (strcmp(args[cur_arg], "send") == 0)
+               chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
+       else if (strcmp(args[cur_arg], "expect") == 0)
+               chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
+                                           file, line, errmsg);
+       else if (strcmp(args[cur_arg], "comment") == 0)
+               chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
+       else {
+               struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
 
-               /* consider exclamation marks, sole or at the beginning of a word */
-               while (*(ptr_arg = args[cur_arg])) {
-                       while (*ptr_arg == '!') {
-                               curpx->options2 ^= PR_O2_EXP_INV;
-                               ptr_arg++;
-                       }
-                       if (*ptr_arg)
-                               break;
-                       cur_arg++;
+               if (!kw) {
+                       action_kw_tcp_check_build_list(&trash);
+                       memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
+                                 " 'send', 'expect'%s%s. but got '%s'",
+                                 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
+                       goto error;
                }
+               chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
+       }
 
-               /* 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])) {
-                               memprintf(errmsg, "'%s %s %s' expects <string> as an argument.",
-                                         args[0], args[1], ptr_arg);
-                               goto error;
-                       }
-                       curpx->options2 |= PR_O2_EXP_STS;
-                       free(curpx->expect_str);
-                       curpx->expect_str = strdup(args[cur_arg+1]);
-               }
-               else if (strcmp(ptr_arg, "string") == 0) {
-                       if (!*(args[cur_arg+1])) {
-                               memprintf(errmsg, "'%s %s %s' expects <string> as an argument.",
-                                         args[0], args[1], ptr_arg);
-                               goto error;
-                       }
-                       curpx->options2 |= PR_O2_EXP_STR;
-                       free(curpx->expect_str);
-                       curpx->expect_str = strdup(args[cur_arg+1]);
-               }
-               else if (strcmp(ptr_arg, "rstatus") == 0) {
-                       if (!*(args[cur_arg+1])) {
-                               memprintf(errmsg, "'%s %s %s' expects <regex> as an argument.",
-                                         args[0], args[1], ptr_arg);
-                               goto error;
-                       }
-                       curpx->options2 |= PR_O2_EXP_RSTS;
-                       free(curpx->expect_str);
-                       regex_free(curpx->expect_regex);
-                       curpx->expect_str = strdup(args[cur_arg+1]);
-                       if (!(curpx->expect_regex = regex_comp(args[cur_arg+1], 1, 1, errmsg))) {
-                               memprintf(errmsg, "'%s %s %s' : regular expression '%s': %s.",
-                                         args[0], args[1], ptr_arg, args[cur_arg+1], *errmsg);
-                               goto error;
-                       }
-               }
-               else if (strcmp(ptr_arg, "rstring") == 0) {
-                       if (!*(args[cur_arg+1])) {
-                               memprintf(errmsg, "'%s %s %s' expects <regex> as an argument.",
-                                         args[0], args[1], ptr_arg);
-                               goto error;
-                       }
-                       curpx->options2 |= PR_O2_EXP_RSTR;
-                       free(curpx->expect_str);
-                       regex_free(curpx->expect_regex);
-                       curpx->expect_str = strdup(args[cur_arg+1]);
-                       if (!(curpx->expect_regex = regex_comp(args[cur_arg+1], 1, 1, errmsg))) {
-                               memprintf(errmsg, "'%s %s %s' : regular expression '%s': %s.",
-                                         args[0], args[1], ptr_arg, args[cur_arg + 1], *errmsg);
-                               goto error;
-                       }
-               }
-               else {
-                       memprintf(errmsg, "'%s %s' only supports [!] 'status', 'string', 'rstatus', 'rstring', found '%s'.",
-                                 args[0], args[1], ptr_arg);
+       if (!chk) {
+               memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
+               goto error;
+       }
+       ret = (*errmsg != NULL); /* Handle warning */
+
+       chk->index = index;
+       if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
+           (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
+               /* Use this ruleset if the proxy already has http-check enabled */
+               curpx->tcpcheck_rules.list = &rs->rules;
+               curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
+               if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
+                       memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
+                       curpx->tcpcheck_rules.list = NULL;
                        goto error;
                }
        }
        else {
-               memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send', 'send-state' and 'expect'. but got '%s'.",
-                         args[0], args[1]);
-               goto error;
+               /* mark this ruleset as unused for now */
+               curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
+               LIST_ADDQ(&rs->rules, &chk->list);
        }
 
-       ret = (*errmsg != NULL); /* Handle warning */
+  out:
        return ret;
 
   error:
+       free_tcpcheck(chk, 0);
+       tcpcheck_ruleset_release(rs);
        return -1;
 }
 
@@ -5413,9 +5760,6 @@ int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, str
        rules->list  = NULL;
        rules->flags = 0;
 
-       free(curpx->check_req);
-       curpx->check_req = NULL;
-
        rules->list = &rs->rules;
 
   out:
@@ -5476,7 +5820,7 @@ int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, s
                                               "on-error", "%[check.payload(),cut_crlf]",
                                               "on-success", "Redis server is ok",
                                               ""},
-                                   1, curpx, &rs->rules, file, line, &errmsg);
+                                   1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
        if (!chk) {
                ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
                goto error;
@@ -5577,7 +5921,7 @@ int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx,
                                                "min-recv", "5", "ok-status", "L6OK",
                                                "error-status", "L6RSP", "tout-status", "L6TOUT",
                                                ""},
-                                   1, curpx, &rs->rules, file, line, &errmsg);
+                                   1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
        if (!chk) {
                ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
                goto error;
@@ -5677,7 +6021,7 @@ int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struc
                                               "error-status", "L7RSP",
                                               "on-error", "%[check.payload(),cut_crlf]",
                                               ""},
-                                   1, curpx, &rs->rules, file, line, &errmsg);
+                                   1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
        if (!chk) {
                ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
                goto error;
@@ -5691,7 +6035,7 @@ int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struc
                                               "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
                                               "status-code", "check.payload(0,3)",
                                               ""},
-                                   1, curpx, &rs->rules, file, line, &errmsg);
+                                   1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
        if (!chk) {
                ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
                goto error;
@@ -5715,7 +6059,7 @@ int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struc
                                               "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
                                               "status-code", "check.payload(0,3)",
                                               ""},
-                                   1, curpx, &rs->rules, file, line, &errmsg);
+                                   1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
        if (!chk) {
                ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
                goto error;
@@ -5848,7 +6192,7 @@ int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, s
                                               "error-status", "L7RSP",
                                               "on-error", "%[check.payload(6,0)]",
                                               ""},
-                                   1, curpx, &rs->rules, file, line, &errmsg);
+                                   1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
        if (!chk) {
                ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
                goto error;
@@ -5862,7 +6206,7 @@ int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, s
                                               "on-success", "PostgreSQL server is ok",
                                               "on-error",   "PostgreSQL unknown error",
                                               ""},
-                                   1, curpx, &rs->rules, file, line, &errmsg);
+                                   1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
        if (!chk) {
                ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
                goto error;
@@ -6075,7 +6419,7 @@ int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, s
        }
 
        chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
-                                   1, curpx, &rs->rules, file, line, &errmsg);
+                                   1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
        if (!chk) {
                ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
                goto error;
@@ -6086,7 +6430,7 @@ int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, s
 
        if (mysql_req) {
                chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
-                                           1, curpx, &rs->rules, file, line, &errmsg);
+                                           1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
                if (!chk) {
                        ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
                        goto error;
@@ -6163,7 +6507,7 @@ int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, st
                                               "min-recv", "14",
                                               "on-error", "Not LDAPv3 protocol",
                                               ""},
-                                   1, curpx, &rs->rules, file, line, &errmsg);
+                                   1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
        if (!chk) {
                ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
                goto error;
@@ -6172,7 +6516,7 @@ int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, st
        LIST_ADDQ(&rs->rules, &chk->list);
 
        chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
-                                   1, curpx, &rs->rules, file, line, &errmsg);
+                                   1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
        if (!chk) {
                ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
                goto error;
@@ -6249,7 +6593,7 @@ int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, st
        LIST_ADDQ(&rs->rules, &chk->list);
 
        chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
-                                   1, curpx, &rs->rules, file, line, &errmsg);
+                                   1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
        if (!chk) {
                ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
                goto error;
@@ -6275,10 +6619,140 @@ int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, st
        goto out;
 }
 
+
+struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
+{
+       struct tcpcheck_rule *chk = NULL;
+       struct tcpcheck_http_hdr *hdr = NULL;
+       char *meth = NULL, *uri = NULL, *vsn = NULL;
+       char *hdrs, *body;
+
+       hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
+       body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
+       if (hdrs == body)
+               hdrs = NULL;
+       if (hdrs) {
+               *hdrs = '\0';
+               hdrs +=2;
+       }
+       if (body) {
+               *body = '\0';
+               body += 4;
+       }
+       if (hdrs || body) {
+               memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
+                         " Please, consider to use 'http-check send' directive instead.");
+       }
+
+       chk = calloc(1, sizeof(*chk));
+       if (!chk) {
+               memprintf(errmsg, "out of memory");
+               goto error;
+       }
+       chk->action    = TCPCHK_ACT_SEND;
+       chk->send.type = TCPCHK_SEND_HTTP;
+       chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
+       chk->send.http.meth.meth = HTTP_METH_OPTIONS;
+       LIST_INIT(&chk->send.http.hdrs);
+
+       /* Copy the method, uri and version */
+       if (*args[cur_arg]) {
+               if (!*args[cur_arg+1])
+                       uri = args[cur_arg];
+               else
+                       meth = args[cur_arg];
+       }
+       if (*args[cur_arg+1])
+               uri = args[cur_arg+1];
+       if (*args[cur_arg+2])
+               vsn = args[cur_arg+2];
+
+       if (meth) {
+               chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
+               chk->send.http.meth.str.area = strdup(meth);
+               chk->send.http.meth.str.data = strlen(meth);
+               if (!chk->send.http.meth.str.area) {
+                       memprintf(errmsg, "out of memory");
+                       goto error;
+               }
+       }
+       if (uri) {
+               chk->send.http.uri = ist2(strdup(uri), strlen(uri));
+               if (!chk->send.http.uri.ptr) {
+                       memprintf(errmsg, "out of memory");
+                       goto error;
+               }
+       }
+       if (vsn) {
+               chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
+               if (!chk->send.http.vsn.ptr) {
+                       memprintf(errmsg, "out of memory");
+                       goto error;
+               }
+       }
+
+       /* Copy the header */
+       if (hdrs) {
+               struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
+               struct h1m h1m;
+               int i, ret;
+
+               /* Build and parse the request */
+               chunk_printf(&trash, "%s\r\n\r\n", hdrs);
+
+               h1m.flags = H1_MF_HDRS_ONLY;
+               ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
+                                            tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
+                                            &h1m, NULL);
+               if (ret <= 0) {
+                       memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
+                       goto error;
+               }
+
+               for (i = 0; tmp_hdrs[i].n.len; i++) {
+                       hdr = calloc(1, sizeof(*hdr));
+                       if (!hdr) {
+                               memprintf(errmsg, "out of memory");
+                               goto error;
+                       }
+                       LIST_INIT(&hdr->value);
+                       hdr->name = ist2(strdup(tmp_hdrs[i].n.ptr), tmp_hdrs[i].n.len);
+                       if (!hdr->name.ptr) {
+                               memprintf(errmsg, "out of memory");
+                               goto error;
+                       }
+
+                       tmp_hdrs[i].v.ptr[tmp_hdrs[i].v.len] = '\0';
+                       if (!parse_logformat_string(tmp_hdrs[i].v.ptr, px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
+                               goto error;
+                       LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
+               }
+       }
+
+       /* Copy the body */
+       if (body) {
+               chk->send.http.body = ist2(strdup(body), strlen(body));
+               if (!chk->send.http.body.ptr) {
+                       memprintf(errmsg, "out of memory");
+                       goto error;
+               }
+       }
+
+       return chk;
+
+  error:
+       free_tcpcheck_http_hdr(hdr);
+       free_tcpcheck(chk, 0);
+       return NULL;
+}
+
 int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
                            const char *file, int line)
 {
-       static const char *http_req =  "OPTIONS / HTTP/1.0\r\n";
+       struct tcpcheck_ruleset *rs = NULL;
+       struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
+       struct tcpcheck_rule *chk;
+       char *errmsg = NULL;
        int err_code = 0;
 
        if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
@@ -6287,73 +6761,55 @@ int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struc
        if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
                goto out;
 
-       /* use HTTP request to check servers' health */
-       free(curpx->check_req);
-       free(curpx->check_hdrs);
-       free(curpx->check_body);
+       chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
+       if (!chk) {
+               ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
+               goto error;
+       }
+       if (errmsg) {
+               ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
+               err_code |= ERR_WARN;
+               free(errmsg);
+               errmsg = NULL;
+       }
 
-       curpx->check_req = curpx->check_hdrs = curpx->check_body = NULL;
-       curpx->check_len = curpx->check_hdrs_len = curpx->check_body_len = 0;
+  no_request:
        curpx->options2 &= ~PR_O2_CHK_ANY;
-       curpx->options2 |= PR_O2_HTTP_CHK;
+       curpx->options2 |= PR_O2_TCPCHK_CHK;
 
-       cur_arg += 2;
-       if (!*args[cur_arg]) { /* no argument */
-               curpx->check_req = strdup(http_req); /* default request */
-               curpx->check_len = strlen(http_req);
-       }
-       else if (!*args[cur_arg+1]) { /* one argument : URI */
-               curpx->check_len = strlen(args[cur_arg]) + strlen("OPTIONS  HTTP/1.0\r\n");
-               curpx->check_req = malloc(curpx->check_len+1);
-               curpx->check_len = snprintf(curpx->check_req, curpx->check_len+1,
-                                           "OPTIONS %s HTTP/1.0\r\n", args[cur_arg]);
-       }
-       else if (!*args[cur_arg+2]) { /* two arguments : METHOD URI */
-               curpx->check_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + strlen(" HTTP/1.0\r\n") + 1;
-               curpx->check_req = malloc(curpx->check_len+1);
-               curpx->check_len = snprintf(curpx->check_req, curpx->check_len+1,
-                                           "%s %s HTTP/1.0\r\n", args[cur_arg], args[cur_arg+1]);
-       }
-       else { /* 3 arguments : METHOD URI HTTP_VER */
-               char *hdrs = strstr(args[cur_arg+2], "\r\n");
-               char *body = strstr(args[cur_arg+2], "\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, line, 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;
-               }
-
-               curpx->check_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + strlen(args[cur_arg+2]) + 4;
-               curpx->check_req = malloc(curpx->check_len+1);
-               snprintf(curpx->check_req, curpx->check_len+1, "%s %s %s\r\n",
-                        args[cur_arg], args[cur_arg+1], args[cur_arg+2]);
-               if (hdrs) {
-                       curpx->check_hdrs_len = strlen(hdrs) + 2;
-                       curpx->check_hdrs = malloc(curpx->check_hdrs_len+1);
-                       snprintf(curpx->check_hdrs, curpx->check_hdrs_len+1, "%s\r\n", hdrs);
-               }
-               if (body) {
-                       curpx->check_body_len = strlen(body);
-                       curpx->check_body = strdup(body);
+       free_tcpcheck_vars(&rules->preset_vars);
+       rules->list = NULL;
+       rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
+
+       /* Deduce the ruleset name from the proxy info */
+       chunk_printf(&trash, "*http-check-%s_%s-%d",
+                    ((curpx == defpx) ? "defaults" : curpx->id),
+                    curpx->conf.file, curpx->conf.line);
+
+       rs = tcpcheck_ruleset_lookup(b_orig(&trash));
+       if (rs == NULL) {
+               rs = tcpcheck_ruleset_create(b_orig(&trash));
+               if (rs == NULL) {
+                       ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
+                       goto error;
                }
        }
+
+       rules->list = &rs->rules;
+       rules->flags |= TCPCHK_RULES_HTTP_CHK;
+       if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
+               ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
+               rules->list = NULL;
+               goto error;
+       }
+
   out:
+       free(errmsg);
        return err_code;
 
   error:
+       tcpcheck_ruleset_release(rs);
+       free_tcpcheck(chk, 0);
        err_code |= ERR_ALERT | ERR_FATAL;
        goto out;
 }
@@ -6363,8 +6819,6 @@ int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx
 {
        int err_code = 0;
 
-       free(curpx->check_req);
-       curpx->check_req = NULL;
        curpx->options2 &= ~PR_O2_CHK_ANY;
        curpx->options2 |= PR_O2_EXT_CHK;
        if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
@@ -6487,7 +6941,8 @@ static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx,
        LIST_ADDQ(&rs->rules, &chk->list);
 
        chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
-                                   1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
+                                   1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
+                                   srv->conf.file, srv->conf.line, errmsg);
        if (!chk) {
                memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
                goto error;
index 982f7b94b75869918489f691a599a2bd256fb241..e8b2b3ff8b1f83ac4acb2eca6c4c886dcdec0cd0 100644 (file)
@@ -2526,9 +2526,6 @@ void deinit(void)
        while (p) {
                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);
index 0c8e344fcffec61c2cd39ee1ab365fec8ec696de..74b029e798ea76aeff5bd20912187b48a1c24c22 100644 (file)
@@ -216,7 +216,8 @@ int session_accept_fd(struct listener *l, int cfd, struct sockaddr_storage *addr
                if (l->proto->drain)
                        l->proto->drain(cfd);
                if (p->mode == PR_MODE_HTTP ||
-                   (p->mode == PR_MODE_HEALTH && (p->options2 & PR_O2_CHK_ANY) == PR_O2_HTTP_CHK))
+                   (p->mode == PR_MODE_HEALTH && (p->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
+                    (p->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK))
                        send(cfd, "HTTP/1.0 200 OK\r\n\r\n", 19, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_MORE);
                else if (p->mode == PR_MODE_HEALTH)
                        send(cfd, "OK\n", 3, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_MORE);