]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MAJOR] implement parameter hashing for POST requests
authormatt.farnsworth@nokia.com <matt.farnsworth@nokia.com>
Mon, 14 Apr 2008 18:47:37 +0000 (20:47 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 15 Apr 2008 13:30:41 +0000 (15:30 +0200)
This patch extends the "url_param" load balancing method by introducing
the "check_post" option. Using this option enables analysis of the beginning
of POST requests to search for the specified URL parameter.

The patch also fixes a few minor typos in comments that were discovered
during code review.

doc/configuration.txt
doc/haproxy-en.txt
include/proto/proto_http.h
include/types/backend.h
include/types/proto_http.h
include/types/proxy.h
src/backend.c
src/client.c
src/ev_poll.c
src/proto_http.c

index 7093c78d407c7427b41b2bf340970a13e69c08ab..7073a0266e4308edc24eacbf329524e6567f58ec 100644 (file)
@@ -696,6 +696,7 @@ backlog <conns>
 
 
 balance <algorithm> [ <arguments> ]
+balance url_param <param> [check_post [<max_wait>]]
   Define the load balancing algorithm to be used in a backend.
   May be used in sections :   defaults | frontend | listen | backend
                                  yes   |    no    |   yes  |   yes
@@ -745,22 +746,47 @@ balance <algorithm> [ <arguments> ]
                   effect.
 
       url_param   The URL parameter specified in argument will be looked up in
-                  the query string of each HTTP request. If it is found
-                  followed by an equal sign ('=') and a value, then the value
-                  is hashed and divided by the total weight of the running
-                  servers. The result designates which server will receive the
-                  request. This is used to track user identifiers in requests
-                  and ensure that a same user ID will always be sent to the
-                  same server as long as no server goes up or down. If no value
-                  is found or if the parameter is not found, then a round robin
-                  algorithm is applied. Note that this algorithm may only be
-                  used in an HTTP backend. This algorithm is static, which
-                  means that changing a server's weight on the fly will have no
-                  effect.
+                  the query string of each HTTP GET request.
+
+                  If the modifier "check_post" is used, then an HTTP POST
+                 request entity will be searched for the parameter argument,
+                 when the question mark indicating a query string ('?') is not
+                 present in the URL. Optionally, specify a number of octets to
+                 wait for before attempting to search the message body. If the
+                 entity can not be searched, then round robin is used for each
+                 request. For instance, if your clients always send the LB
+                 parameter in the first 128 bytes, then specify that. The
+                 default is 48. The entity data will not be scanned until the
+                 required number of octets have arrived at the gateway, this
+                 is the minimum of: (default/max_wait, Content-Length or first
+                 chunk length). If Content-Length is missing or zero, it does
+                 not need to wait for more data than the client promised to
+                 send. When Content-Length is present and larger than
+                 <max_wait>, then waiting is limited to <max_wait> and it is
+                 assumed that this will be enough data to search for the
+                 presence of the parameter. In the unlikely event that
+                 Transfer-Encoding: chunked is used, only the first chunk is
+                 scanned. Parameter values separated by a chunk boundary, may
+                 be randomly balanced if at all.
+
+                  If the parameter is found followed by an equal sign ('=') and
+                  a value, then the value is hashed and divided by the total
+                  weight of the running servers. The result designates which
+                  server will receive the request.
+
+                  This is used to track user identifiers in requests and ensure
+                  that a same user ID will always be sent to the same server as
+                  long as no server goes up or down. If no value is found or if
+                  the parameter is not found, then a round robin algorithm is
+                  applied. Note that this algorithm may only be used in an HTTP
+                  backend. This algorithm is static, which means that changing a
+                  server's weight on the fly will have no effect.
 
     <arguments> is an optional list of arguments which may be needed by some
                 algorithms. Right now, only the "url_param" algorithm supports
-                a mandatory argument.
+                an optional argument.
+
+                balance url_param <param> [check_post [<max_wait>]]
 
   The definition of the load balancing algorithm is mandatory for a backend
   and limited to one per backend.
@@ -768,6 +794,39 @@ balance <algorithm> [ <arguments> ]
   Examples :
         balance roundrobin
         balance url_param userid
+        balance url_param session_id check_post 64
+
+  Note: the following caveats and limitations on using the "check_post"
+  extension with "url_param" must be considered :
+
+    - all POST requests are eligable for consideration, because there is no way
+      to determine if the parameters will be found in the body or entity which
+      may contain binary data. Therefore another method may be required to
+      restrict consideration of POST requests that have no URL parameters in
+      the body. (see acl reqideny http_end)
+
+    - using a <max_wait> value larger than the request buffer size does not
+      make sense and is useless. The buffer size is set at build time, and
+      defaults to 16 kB.
+
+    - Content-Encoding is not supported, the parameter search will probably
+      fail; and load balancing will fall back to Round Robin.
+
+    - Expect: 100-continue is not supported, load balancing will fall back to
+      Round Robin.
+
+    - Transfer-Encoding (RFC2616 3.6.1) is only supported in the first chunk.
+      If the entire parameter value is not present in the first chunk, the
+      selection of server is undefined (actually, defined by how little
+      actually appeared in the first chunk).
+
+    - This feature does not support generation of a 100, 411 or 501 response.
+
+    - In some cases, requesting "check_post" MAY attempt to scan the entire
+      contents of a message body. Scaning normally terminates when linear
+      white space or control characters are found, indicating the end of what
+      might be a URL parameter list. This is probably not a concern with SGML
+      type message bodies.
 
   See also : "dispatch", "cookie", "appsession", "transparent" and "http_proxy".
 
index 20cadd8976fd5c648abb8b8dbd6c3ac24cf12953..503e021a440e14f497dee3ee675f685398c2bc96 100644 (file)
@@ -999,6 +999,48 @@ Some of these applications may not be able to use a cookie for whatever reason,
 but may be able to look for a parameter passed in the URL. If the parameter is
 missing from the URL, then the 'round robin' method applies.
 
+A modifier may be added to specify that parameters in POST requests may be
+found in the messsage body if the URL lacks a '?' separator character.
+A wait limit may also be applied, if no limit is requested then
+the default value is 48 octets, the minimum is 3.  HAProxy may wait, until 48
+octets are received. If Content-Length is missing, or zero it need not
+wait for more data then the client promissed to send. When Content-Length is
+present, and more than <max_wait>; then waiting is limited and it is assumed this
+will be enough data to search for the presence of a parameter.  If
+Transfer-Encoding: chunked is used (unlikely), then the length of the first chunk
+is the maximum number of bytes to wait for.
+
+balance url_param <param> [check_post [<max_wait>]]
+
+Caveats for using the check_post extension:
+
+  - all POST requests are eligable for consideration, because there is
+    no way to determine if the parameters will be found in the body or
+    entity which may contain binary data. Therefore another method may be
+    required to restrict consideration of POST requests that have no URL
+    parameters in the body. (see acl reqideny http_end)
+
+Limitations on inspecting the entity body of a POST:
+
+  - Content-Encoding is not supported, the parameter search will probably fail;
+    and load balancing will fall back to Round Robin.
+
+  - Expect: 100-continue is not supported, load balancing will fall back to
+    Round Robin.
+
+  - Transfer-Encoding(RFC2616 3.6.1) is only supported in the first chunk. If
+    the entire parameter value is not present in the first chunk, the selection
+    of server is undefined (actually, defined by how little actually appeared in
+    the first chunk).
+
+  - This feature does not support generation of a 100, 411 or 501 response.
+
+  - In some cases, requesting check_post MAY attempt to scan the entire contents
+    of a message body.  Scaning normally terminates when linear white space or
+    control characters are found, indicating the end of what might be a URL parameter
+    list.  This is probably not a concern with SGML type message bodies.
+
+
 Example :
 ---------
 
index 523471d1b25d6622e90591ce6c17b00ba5e99fb9..68b3f11ee69121a5dee64885a9df11e3939e0da0 100644 (file)
@@ -81,6 +81,9 @@ void manage_server_side_cookies(struct session *t, struct buffer *rtr);
 void check_response_for_cacheability(struct session *t, struct buffer *rtr);
 int stats_check_uri_auth(struct session *t, struct proxy *backend);
 void init_proto_http();
+int http_find_header2(const char *name, int len,
+                     const char *sol, struct hdr_idx *idx,
+                     struct hdr_ctx *ctx);
 
 #endif /* _PROTO_PROTO_HTTP_H */
 
index 2d627229b233ac09bae00dc8dd3ecee9bdb5526c..6d576b8dbfac080e8c6466d486ccb98586cc2fc0 100644 (file)
@@ -1,6 +1,6 @@
 /*
   include/types/backend.h
-  This file rassembles definitions for backends
+  This file assembles definitions for backends
 
   Copyright (C) 2000-2008 Willy Tarreau - w@1wt.eu
   
@@ -46,7 +46,7 @@
 
 /* various constants */
 
-/* The scale factor between user weight an effective weight allows smooth
+/* The scale factor between user weight and effective weight allows smooth
  * weight modulation even with small weights (eg: 1). It should not be too high
  * though because it limits the number of servers in FWRR mode in order to
  * prevent any integer overflow. The max number of servers per backend is
index 50d12d6de03a0728345cb9840dbf1d2018739ffd..885fbc63411a368c5b5a676f93e9c08420874fcf 100644 (file)
 #include <types/buffers.h>
 #include <types/hdr_idx.h>
 
-/*
- * FIXME: break this into HTTP state and TCP socket state.
- * See server.h for the other end.
- */
-
-/* different possible states for the client side */
-#define CL_STHEADERS   0
-#define CL_STDATA      1
-#define CL_STSHUTR     2
-#define CL_STSHUTW     3
-#define CL_STCLOSE     4
-
 /*
  * FIXME: break this into HTTP state and TCP socket state.
  * See client.h for the other end.
 
 /* different possible states for the server side */
 #define SV_STIDLE      0
-#define SV_STCONN      1
-#define SV_STHEADERS   2
-#define SV_STDATA      3
-#define SV_STSHUTR     4
-#define SV_STSHUTW     5
-#define SV_STCLOSE     6
+#define SV_STANALYZE   1  /* this server state is set by the client to study the body for server assignment */
+#define SV_STCONN      2
+#define SV_STHEADERS   3
+#define SV_STDATA      4
+#define SV_STSHUTR     5
+#define SV_STSHUTW     6
+#define SV_STCLOSE     7
 
 /*
  * Transaction flags moved from session
@@ -204,27 +193,28 @@ typedef enum {
  *                             which marks the end of the line (LF or CRLF).
  */
 struct http_msg {
-       unsigned int msg_state;         /* where we are in the current message parsing */
-       char *sol;                      /* start of line, also start of message when fully parsed */
-       char *eol;                      /* end of line */
-       unsigned int som;               /* Start Of Message, relative to buffer */
-       unsigned int col, sov;          /* current header: colon, start of value */
-       unsigned int eoh;               /* End Of Headers, relative to buffer */
-       char **cap;                     /* array of captured headers (may be NULL) */
-       union {                         /* useful start line pointers, relative to buffer */
+       unsigned int msg_state;                /* where we are in the current message parsing */
+       char *sol;                             /* start of line, also start of message when fully parsed */
+       char *eol;                             /* end of line */
+       unsigned int som;                      /* Start Of Message, relative to buffer */
+       unsigned int col, sov;                 /* current header: colon, start of value */
+       unsigned int eoh;                      /* End Of Headers, relative to buffer */
+       char **cap;                            /* array of captured headers (may be NULL) */
+       union {                                /* useful start line pointers, relative to buffer */
                struct {
-                       int l;          /* request line length (not including CR) */
-                       int m_l;        /* METHOD length (method starts at ->som) */
-                       int u, u_l;     /* URI, length */
-                       int v, v_l;     /* VERSION, length */
-               } rq;                   /* request line : field, length */
+                       int l;                 /* request line length (not including CR) */
+                       int m_l;               /* METHOD length (method starts at ->som) */
+                       int u, u_l;            /* URI, length */
+                       int v, v_l;            /* VERSION, length */
+               } rq;                          /* request line : field, length */
                struct {
-                       int l;          /* status line length (not including CR) */
-                       int v_l;        /* VERSION length (version starts at ->som) */
-                       int c, c_l;     /* CODE, length */
-                       int r, r_l;     /* REASON, length */
-               } st;                   /* status line : field, length */
-       } sl;                           /* start line */
+                       int l;                 /* status line length (not including CR) */
+                       int v_l;               /* VERSION length (version starts at ->som) */
+                       int c, c_l;            /* CODE, length */
+                       int r, r_l;            /* REASON, length */
+               } st;                          /* status line : field, length */
+       } sl;                                  /* start line */
+       unsigned long long hdr_content_len;    /* cache for parsed header value */
 };
 
 /* This is an HTTP transaction. It contains both a request message and a
index 98baf539d26e497f8a95ba95b15f7c5ebf00a2c2..091be57e2eb2e45b511d2ab851e2f9638152727f 100644 (file)
@@ -165,6 +165,7 @@ struct proxy {
        int  cookie_len;                        /* strlen(cookie_name), computed only once */
        char *url_param_name;                   /* name of the URL parameter used for hashing */
        int  url_param_len;                     /* strlen(url_param_name), computed only once */
+       unsigned url_param_post_limit;          /* if checking POST body for URI parameter, max body to wait for */
        char *appsession_name;                  /* name of the cookie to look for */
        int  appsession_name_len;               /* strlen(appsession_name), computed only once */
        int  appsession_len;                    /* length of the appsession cookie value to be used */
index 436a4d9b328761ed15a7d943d51e91f4d7ee730d..38f81d284f0bd80e8657e654c4505b7c1da71212 100644 (file)
@@ -16,6 +16,7 @@
 #include <stdlib.h>
 #include <syslog.h>
 #include <string.h>
+#include <ctype.h>
 
 #include <common/compat.h>
 #include <common/config.h>
@@ -1122,42 +1123,41 @@ static struct server *fwlc_get_next_server(struct proxy *p, struct server *srvto
  * are shared but cookies are not usable. If the parameter is not found, NULL
  * is returned. If any server is found, it will be returned. If no valid server
  * is found, NULL is returned.
- *
  */
 struct server *get_server_ph(struct proxy *px, const char *uri, int uri_len)
 {
        unsigned long hash = 0;
-       char *p;
+       const char *p;
+       const char *params;
        int plen;
 
+       /* when tot_weight is 0 then so is srv_count */
        if (px->lbprm.tot_weight == 0)
                return NULL;
 
+       if ((p = memchr(uri, '?', uri_len)) == NULL)
+               return NULL;
+
        if (px->lbprm.map.state & PR_MAP_RECALC)
                recalc_server_map(px);
 
-       p = memchr(uri, '?', uri_len);
-       if (!p)
-               return NULL;
        p++;
 
        uri_len -= (p - uri);
        plen = px->url_param_len;
-
-       if (uri_len <= plen)
-               return NULL;
+       params = p;
 
        while (uri_len > plen) {
                /* Look for the parameter name followed by an equal symbol */
-               if (p[plen] == '=') {
-                       /* skip the equal symbol */
-                       uri = p;
-                       p += plen + 1;
-                       uri_len -= plen + 1;
-                       if (memcmp(uri, px->url_param_name, plen) == 0) {
-                               /* OK, we have the parameter here at <uri>, and
+               if (params[plen] == '=') {
+                       if (memcmp(params, px->url_param_name, plen) == 0) {
+                               /* OK, we have the parameter here at <params>, and
                                 * the value after the equal sign, at <p>
+                                * skip the equal symbol
                                 */
+                               p += plen + 1;
+                               uri_len -= plen + 1;
+
                                while (uri_len && *p != '&') {
                                        hash = *p + (hash << 6) + (hash << 16) - hash;
                                        uri_len--;
@@ -1166,18 +1166,116 @@ struct server *get_server_ph(struct proxy *px, const char *uri, int uri_len)
                                return px->lbprm.map.srv[hash % px->lbprm.tot_weight];
                        }
                }
+               /* skip to next parameter */
+               p = memchr(params, '&', uri_len);
+               if (!p)
+                       return NULL;
+               p++;
+               uri_len -= (p - params);
+               params = p;
+       }
+       return NULL;
+}
 
+/*
+ * this does the same as the previous server_ph, but check the body contents
+ */
+struct server *get_server_ph_post(struct session *s)
+{
+       unsigned long    hash = 0;
+       struct http_txn *txn  = &s->txn;
+       struct buffer   *req  = s->req;
+       struct http_msg *msg  = &txn->req;
+       struct proxy    *px   = s->be;
+       unsigned int     plen = px->url_param_len;
+
+       /* tot_weight appears to mean srv_count */
+       if (px->lbprm.tot_weight == 0)
+               return NULL;
+
+        unsigned long body = msg->sol[msg->eoh] == '\r' ? msg->eoh + 2 : msg->eoh + 1;
+        unsigned long len  = req->total - body;
+        const char *params = req->data + body;
+
+       if ( len == 0 )
+               return NULL;
+
+       if (px->lbprm.map.state & PR_MAP_RECALC)
+               recalc_server_map(px);
+
+       struct hdr_ctx ctx;
+       ctx.idx = 0;
+
+       /* if the message is chunked, we skip the chunk size, but use the value as len */
+       http_find_header2("Transfer-Encoding", 17, msg->sol, &txn->hdr_idx, &ctx);
+       if ( ctx.idx && strncasecmp(ctx.line+ctx.val,"chunked",ctx.vlen)==0) {
+               unsigned int chunk = 0;
+               while ( params < req->rlim && !HTTP_IS_CRLF(*params)) {
+                       char c = *params;
+                       if (ishex(c)) {
+                               unsigned int hex = toupper(c) - '0';
+                               if ( hex > 9 )
+                                       hex -= 'A' - '9' - 1;
+                               chunk = (chunk << 4) | hex;
+                       }
+                       else
+                               return NULL;
+                       params++;
+                       len--;
+               }
+               /* spec says we get CRLF */
+               if (HTTP_IS_CRLF(*params) && HTTP_IS_CRLF(params[1]))
+                       params += 2;
+               else
+                       return NULL;
+               /* ok we have some encoded length, just inspect the first chunk */
+               len = chunk;
+       }
+
+       const char   *p = params;
+
+       while (len > plen) {
+               /* Look for the parameter name followed by an equal symbol */
+               if (params[plen] == '=') {
+                       if (memcmp(params, px->url_param_name, plen) == 0) {
+                               /* OK, we have the parameter here at <params>, and
+                                * the value after the equal sign, at <p>
+                                * skip the equal symbol
+                                */
+                               p += plen + 1;
+                               len -= plen + 1;
+
+                               while (len && *p != '&') {
+                                       if (unlikely(!HTTP_IS_TOKEN(*p))) {
+                                       /* if in a POST, body must be URI encoded or its not a URI.
+                                        * Do not interprete any possible binary data as a parameter.
+                                        */
+                                               if (likely(HTTP_IS_LWS(*p))) /* eol, uncertain uri len */
+                                                       break;
+                                               return NULL;                 /* oh, no; this is not uri-encoded.
+                                                                             * This body does not contain parameters.
+                                                                             */
+                                       }
+                                       hash = *p + (hash << 6) + (hash << 16) - hash;
+                                       len--;
+                                       p++;
+                                       /* should we break if vlen exceeds limit? */
+                               }
+                               return px->lbprm.map.srv[hash % px->lbprm.tot_weight];
+                       }
+               }
                /* skip to next parameter */
-               uri = p;
-               p = memchr(uri, '&', uri_len);
+               p = memchr(params, '&', len);
                if (!p)
                        return NULL;
                p++;
-               uri_len -= (p - uri);
+               len -= (p - params);
+               params = p;
        }
        return NULL;
 }
 
+
 /*
  * This function marks the session as 'assigned' in direct or dispatch modes,
  * or tries to assign one in balance mode, according to the algorithm. It does
@@ -1254,9 +1352,15 @@ int assign_server(struct session *s)
                                break;
                        case BE_LB_ALGO_PH:
                                /* URL Parameter hashing */
-                               s->srv = get_server_ph(s->be,
-                                                      s->txn.req.sol + s->txn.req.sl.rq.u,
-                                                      s->txn.req.sl.rq.u_l);
+                               if (s->txn.meth == HTTP_METH_POST &&
+                                    memchr(s->txn.req.sol + s->txn.req.sl.rq.u, '&',
+                                           s->txn.req.sl.rq.u_l ) == NULL)
+                                       s->srv = get_server_ph_post(s);
+                               else
+                                       s->srv = get_server_ph(s->be,
+                                                              s->txn.req.sol + s->txn.req.sl.rq.u,
+                                                              s->txn.req.sl.rq.u_l);
+
                                if (!s->srv) {
                                        /* parameter not found, fall back to round robin on the map */
                                        s->srv = get_server_rr_with_conns(s->be, srvtoavoid);
@@ -1620,7 +1724,7 @@ int connect_server(struct session *s)
                        return SN_ERR_RESOURCE;
                }
        }
-       
+
        if ((connect(fd, (struct sockaddr *)&s->srv_addr, sizeof(s->srv_addr)) == -1) &&
            (errno != EINPROGRESS) && (errno != EALREADY) && (errno != EISCONN)) {
 
@@ -1879,6 +1983,21 @@ int backend_parse_balance(const char **args, char *err, int errlen, struct proxy
                        free(curproxy->url_param_name);
                curproxy->url_param_name = strdup(args[1]);
                curproxy->url_param_len = strlen(args[1]);
+               if ( *args[2] ) {
+                       if (strcmp(args[2], "check_post")) {
+                               snprintf(err, errlen, "'balance url_param' only accepts check_post modifier.");
+                               return -1;
+                       }
+                       if (*args[3]) {
+                               /* TODO: maybe issue a warning if there is no value, no digits or too long */
+                               curproxy->url_param_post_limit = str2ui(args[3]);
+                       }
+                       /* if no limit, or faul value in args[3], then default to a moderate wordlen */
+                       if (!curproxy->url_param_post_limit)
+                               curproxy->url_param_post_limit = 48;
+                       else if ( curproxy->url_param_post_limit < 3 )
+                               curproxy->url_param_post_limit = 3; /* minimum example: S=3 or \r\nS=6& */
+               }
        }
        else {
                snprintf(err, errlen, "'balance' only supports 'roundrobin', 'leastconn', 'source', 'uri' and 'url_param' options.");
index bff5cd9ee9f91868000c413e565e5c8ac01e9d27..410c3f0e3d57da8b95cc1f1020c5734e34e27daa 100644 (file)
@@ -232,7 +232,8 @@ int event_accept(int fd) {
 
                if (p->mode == PR_MODE_HTTP) {
                        txn->status = -1;
-
+                       txn->req.hdr_content_len = 0LL;
+                       txn->rsp.hdr_content_len = 0LL;
                        txn->req.msg_state = HTTP_MSG_RQBEFORE; /* at the very beginning of the request */
                        txn->rsp.msg_state = HTTP_MSG_RPBEFORE; /* at the very beginning of the response */
                        txn->req.sol = txn->req.eol = NULL;
index 0166bd6aef4f92616b5ee19e2ab2fdfd4594170b..54cd1389a985c54666b60de1e8298aa004524013 100644 (file)
@@ -102,8 +102,8 @@ REGPRM2 static void _do_poll(struct poller *p, struct timeval *exp)
 #define FDSETS_ARE_INT_ALIGNED
 #ifdef FDSETS_ARE_INT_ALIGNED
 
-#define WE_REALLY_NOW_THAT_FDSETS_ARE_INTS
-#ifdef WE_REALLY_NOW_THAT_FDSETS_ARE_INTS
+#define WE_REALLY_KNOW_THAT_FDSETS_ARE_INTS
+#ifdef WE_REALLY_KNOW_THAT_FDSETS_ARE_INTS
                                sr = (rn >> count) & 1;
                                sw = (wn >> count) & 1;
 #else
index 3f8e0ac88da59cef6b7e218c5dbf454d424ee400..2c07030639a7e81b5e997512fd56cdaab685c3a1 100644 (file)
@@ -287,7 +287,7 @@ const struct http_method_desc http_methods[26][3] = {
 };
 
 /* It is about twice as fast on recent architectures to lookup a byte in a
- * table than two perform a boolean AND or OR between two tests. Refer to
+ * table than to perform a boolean AND or OR between two tests. Refer to
  * RFC2616 for those chars.
  */
 
@@ -2065,6 +2065,83 @@ int process_cli(struct session *t)
                                goto return_bad_req;
                        t->flags |= SN_CONN_CLOSED;
                }
+               /* Before we switch to data, was assignment set in manage_client_side_cookie?
+                * If not assigned, perhaps we are balancing on url_param, but this is a
+                * POST; and the parameters are in the body, maybe scan there to find our server.
+                * (unless headers overflowed the buffer?)
+                 */
+               if (!(t->flags & (SN_ASSIGNED|SN_DIRECT)) &&
+                    t->txn.meth == HTTP_METH_POST && t->be->url_param_name != NULL &&
+                    t->be->url_param_post_limit != 0 && req->total < BUFSIZE &&
+                    memchr(msg->sol + msg->sl.rq.u, '?', msg->sl.rq.u_l) == NULL) {
+                       /* are there enough bytes here? total == l || r || rlim ?
+                        * len is unsigned, but eoh is int,
+                        * how many bytes of body have we received?
+                        * eoh is the first empty line of the header
+                        */
+                        /* already established CRLF or LF at eoh, move to start of message, find message length in buffer */
+                       unsigned long len = req->total - (msg->sol[msg->eoh] == '\r' ? msg->eoh + 2 : msg->eoh + 1);
+
+                       /* If we have HTTP/1.1 and Expect: 100-continue, then abort.
+                        * We can't assume responsibility for the server's decision,
+                        * on this URI and header set. See rfc2616: 14.20, 8.2.3,
+                        * We also can't change our mind later, about which server to choose, so round robin.
+                        */
+                       if ((likely(msg->sl.rq.v_l == 8) && req->data[msg->som + msg->sl.rq.v + 7] == '1')) {
+                               struct hdr_ctx ctx;
+                               ctx.idx = 0;
+                               /* Expect is allowed in 1.1, look for it */
+                               http_find_header2("Expect", 6, msg->sol, &txn->hdr_idx, &ctx);
+                               if (ctx.idx != 0  &&
+                                    unlikely(ctx.vlen == 12 && strncasecmp(ctx.line+ctx.val,"100-continue",12)==0))
+                                       /* We can't reliablly stall and wait for data, because of
+                                        * .NET clients that don't conform to rfc2616; so, no need for
+                                        * the next block to check length expectations.
+                                         * We could send 100 status back to the client, but then we need to
+                                         * re-write headers, and send the message. And this isn't the right
+                                         * place for that action.
+                                         * TODO: support Expect elsewhere and delete this block.
+                                        */
+                                       goto end_check_maybe_wait_for_body;
+                       }
+                       if ( likely(len > t->be->url_param_post_limit) ) {
+                               /* nothing to do, we got enough */
+                       } else {
+                               /* limit implies we are supposed to need this many bytes
+                                * to find the parameter. Let's see how many bytes we can wait for.
+                                */
+                               long long hint = len;
+                               struct hdr_ctx ctx;
+                               ctx.idx = 0;
+                               http_find_header2("Transfer-Encoding", 17, msg->sol, &txn->hdr_idx, &ctx);
+                               if (unlikely(ctx.idx && strncasecmp(ctx.line+ctx.val,"chunked",7)==0)) {
+                                       t->srv_state = SV_STANALYZE;
+                               } else {
+                                       ctx.idx = 0;
+                                       http_find_header2("Content-Length", 14, msg->sol, &txn->hdr_idx, &ctx);
+                                       /* now if we have a length, we'll take the hint */
+                                       if ( ctx.idx ) {
+                                               /* We have Content-Length */
+                                               if ( strl2llrc(ctx.line+ctx.val,ctx.vlen, &hint) )
+                                                       hint = 0;         /* parse failure, untrusted client */
+                                               else {
+                                                       if ( hint > 0 )
+                                                               msg->hdr_content_len = hint;
+                                                       else
+                                                               hint = 0; /* bad client, sent negative length */
+                                               }
+                                       }
+                                       /* but limited to what we care about, maybe we don't expect any entity data (hint == 0) */
+                                       if ( t->be->url_param_post_limit < hint )
+                                               hint = t->be->url_param_post_limit;
+                                       /* now do we really need to buffer more data? */
+                                       if ( len < hint )
+                                               t->srv_state = SV_STANALYZE;
+                                       /* else... There are no body bytes to wait for */
+                               }
+                       }
+               }
+        end_check_maybe_wait_for_body:
 
                /*************************************************************
                 * OK, that's finished for the headers. We have done what we *
@@ -2436,7 +2513,12 @@ int process_srv(struct session *t)
        //EV_FD_ISSET(t->srv_fd, DIR_RD), EV_FD_ISSET(t->srv_fd, DIR_WR)
        //);
        if (s == SV_STIDLE) {
-               if (c == CL_STHEADERS)
+               /* NOTE: The client processor may switch to SV_STANALYZE, which switches back SV_STIDLE.
+                *       This is logcially after CL_STHEADERS completed, CL_STDATA has started, but
+                *       we need to defer server selection until more data arrives, if possible.
+                 *       This is rare, and only if balancing on parameter hash with values in the entity of a POST
+                 */
+               if (c == CL_STHEADERS )
                        return 0;       /* stay in idle, waiting for data to reach the client side */
                else if (c == CL_STCLOSE || c == CL_STSHUTW ||
                         (c == CL_STSHUTR &&
@@ -3531,6 +3613,60 @@ int process_srv(struct session *t)
                }
                return 0;
        }
+       else if (s == SV_STANALYZE){
+               /* this server state is set by the client to study the body for server assignment */
+
+               /* Have we been through this long enough to timeout? */
+               if (!tv_isle(&req->rex, &now)) {
+                       /* balance url_param check_post should have been the only to get into this.
+                        * just wait for data, check to compare how much
+                        */
+                       struct http_msg * msg = &t->txn.req;
+                        unsigned long body = msg->sol[msg->eoh] == '\r' ? msg->eoh + 2 :msg->eoh + 1;
+                        unsigned long len  = req->total - body;
+                       long long limit = t->be->url_param_post_limit;
+                       struct hdr_ctx ctx;
+                       ctx.idx = 0;
+                       /* now if we have a length, we'll take the hint */
+                       http_find_header2("Transfer-Encoding", 17, msg->sol, &txn->hdr_idx, &ctx);
+                       if ( ctx.idx && strncasecmp(ctx.line+ctx.val,"chunked",ctx.vlen)==0) {
+                               unsigned int chunk = 0;
+                               while ( body < req->total && !HTTP_IS_CRLF(msg->sol[body])) {
+                                       char c = msg->sol[body];
+                                       if (ishex(c)) {
+                                               unsigned int hex = toupper(c) - '0';
+                                               if ( hex > 9 )
+                                                       hex -= 'A' - '9' - 1;
+                                               chunk = (chunk << 4) | hex;
+                                       }
+                                       else break;
+                                       body++;
+                                       len--;
+                               }
+                               if ( body == req->total )
+                                       return 0; /* end of buffer? data missing! */
+
+                               if ( memcmp(msg->sol+body, "\r\n", 2) != 0 )
+                                       return 0; /* chunked encoding len ends with CRLF, and we don't have it yet */
+
+                               /* if we support more then one chunk here, we have to do it again when assigning server
+                                  1. how much entity data do we have? new var
+                                  2. should save entity_start, entity_cursor, elen & rlen in req; so we don't repeat scanning here
+                                  3. test if elen > limit, or set new limit to elen if 0 (end of entity found)
+                               */
+
+                               if ( chunk < limit )
+                                       limit = chunk;                  /* only reading one chunk */
+                       } else {
+                               if ( msg->hdr_content_len < limit )
+                                       limit = msg->hdr_content_len;
+                       }
+                       if ( len < limit )
+                               return 0;
+               }
+               t->srv_state=SV_STIDLE;
+               return 1;
+       }
        else { /* SV_STCLOSE : nothing to do */
                if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) {
                        int len;
@@ -3549,7 +3685,7 @@ int process_srv(struct session *t)
  * called with s->cli_state == CL_STSHUTR. Right now, only statistics can be
  * produced. It stops by itself by unsetting the SN_SELF_GEN flag from the
  * session, which it uses to keep on being called when there is free space in
- * the buffer, of simply by letting an empty buffer upon return. It returns 1
+ * the buffer, or simply by letting an empty buffer upon return. It returns 1
  * if it changes the session state from CL_STSHUTR, otherwise 0.
  */
 int produce_content(struct session *s)
@@ -3640,7 +3776,7 @@ int apply_filter_to_req_headers(struct session *t, struct buffer *req, struct hd
                                /* Swithing Proxy */
                                t->be = (struct proxy *) exp->replace;
 
-                               /* right now, the backend switch is not too much complicated
+                               /* right now, the backend switch is not overly complicated
                                 * because we have associated req_cap and rsp_cap to the
                                 * frontend, and the beconn will be updated later.
                                 */