]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: http: add a new option http-buffer-request
authorWilly Tarreau <w@1wt.eu>
Fri, 1 May 2015 20:42:08 +0000 (22:42 +0200)
committerWilly Tarreau <w@1wt.eu>
Fri, 1 May 2015 22:10:44 +0000 (00:10 +0200)
It is sometimes desirable to wait for the body of an HTTP request before
taking a decision. This is what is being done by "balance url_param" for
example. The first use case is to buffer requests from slow clients before
connecting to the server. Another use case consists in taking the routing
decision based on the request body's contents. This option placed in a
frontend or backend forces the HTTP processing to wait until either the whole
body is received, or the request buffer is full, or the first chunk is
complete in case of chunked encoding. It can have undesired side effects with
some applications abusing HTTP by expecting unbufferred transmissions between
the frontend and the backend, so this should definitely not be used by
default.

Note that it would not work for the response because we don't reset the
message state before starting to forward. For the response we need to
1) reset the message state to MSG_100_SENT or BODY , and 2) to reset
body_len in case of chunked encoding to avoid counting it twice.

doc/configuration.txt
include/types/proxy.h
src/cfgparse.c
src/proto_http.c
src/proxy.c

index 2dab50aac9c891b016510bc411b0e5a1bbc7150c..69dfbe7bbca19ddcb315751a2f1790582d872cb7 100644 (file)
@@ -1482,6 +1482,7 @@ option dontlognull                   (*)  X          X         X         -
 option forceclose                    (*)  X          X         X         X
 -- keyword -------------------------- defaults - frontend - listen -- backend -
 option forwardfor                         X          X         X         X
+option http-buffer-request           (*)  X          X         X         X
 option http-ignore-probes            (*)  X          X         X         -
 option http-keep-alive               (*)  X          X         X         X
 option http-no-delay                 (*)  X          X         X         X
@@ -4453,6 +4454,28 @@ option forwardfor [ except <network> ] [ header <name> ] [ if-none ]
              "option forceclose", "option http-keep-alive"
 
 
+option http-buffer-request
+no option http-buffer-request
+  Enable or disable waiting for whole HTTP request body before proceeding
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    yes   |   yes  |   yes
+  Arguments : none
+
+  It is sometimes desirable to wait for the body of an HTTP request before
+  taking a decision. This is what is being done by "balance url_param" for
+  example. The first use case is to buffer requests from slow clients before
+  connecting to the server. Another use case consists in taking the routing
+  decision based on the request body's contents. This option placed in a
+  frontend or backend forces the HTTP processing to wait until either the whole
+  body is received, or the request buffer is full, or the first chunk is
+  complete in case of chunked encoding. It can have undesired side effects with
+  some applications abusing HTTP by expecting unbufferred transmissions between
+  the frontend and the backend, so this should definitely not be used by
+  default.
+
+  See also : "option http-no-delay"
+
+
 option http-ignore-probes
 no option http-ignore-probes
   Enable or disable logging of null connections and request timeouts
@@ -4578,6 +4601,8 @@ no option http-no-delay
   usage and CPU usage, which may significantly lower performance in high
   latency environments.
 
+  See also : "option http-buffer-request"
+
 
 option http-pretend-keepalive
 no option http-pretend-keepalive
index 34501f249279ad879e5ea09252b71c228258a19e..826a0f2b60247991bde1dcd956cc537376057331 100644 (file)
@@ -83,7 +83,8 @@ enum pr_mode {
 #define PR_O_FWDFOR     0x00000100      /* conditionally insert x-forwarded-for with client address */
 #define PR_O_IGNORE_PRB 0x00000200      /* ignore empty requests (aborts and timeouts) */
 #define PR_O_NULLNOLOG  0x00000400      /* a connect without request will not be logged */
-/* unused: 0x0800, 0x1000 */
+#define PR_O_WREQ_BODY  0x00000800      /* always wait for the HTTP request body */
+/* unused: 0x1000 */
 #define PR_O_FF_ALWAYS  0x00002000      /* always set x-forwarded-for */
 #define PR_O_PERSIST    0x00004000      /* server persistence stays effective even when server is down */
 #define PR_O_LOGASAP    0x00008000      /* log as soon as possible, without waiting for the stream to complete */
index e8004c6282b8e8e82befb91e9e4efc72357da4c5..8578bc51777c450b1dbe320509b57bd2948197bb 100644 (file)
@@ -154,6 +154,7 @@ static const struct cfg_opt cfg_opts[] =
        { "contstats",    PR_O_CONTSTATS,  PR_CAP_FE, 0, 0 },
        { "dontlognull",  PR_O_NULLNOLOG,  PR_CAP_FE, 0, 0 },
        { "http_proxy",   PR_O_HTTP_PROXY, PR_CAP_FE | PR_CAP_BE, 0, PR_MODE_HTTP },
+       { "http-buffer-request", PR_O_WREQ_BODY,  PR_CAP_FE | PR_CAP_BE, 0, PR_MODE_HTTP },
        { "http-ignore-probes", PR_O_IGNORE_PRB, PR_CAP_FE, 0, PR_MODE_HTTP },
        { "prefer-last-server", PR_O_PREF_LAST,  PR_CAP_BE, 0, PR_MODE_HTTP },
        { "logasap",      PR_O_LOGASAP,    PR_CAP_FE, 0, 0 },
index dc599815af10cc20342290debe936edf51bf77d4..6bd89bc22c17e0f1819efb446e729d6d8dd2815a 100644 (file)
@@ -3133,6 +3133,11 @@ int http_wait_for_request(struct stream *s, struct channel *req, int an_bit)
            ((sess->fe->options & PR_O_HTTP_MODE) != (s->be->options & PR_O_HTTP_MODE)))
                http_adjust_conn_mode(s, txn, msg);
 
+       /* we may have to wait for the request's body */
+       if ((s->be->options & PR_O_WREQ_BODY) &&
+           (msg->body_len || (msg->flags & HTTP_MSGF_TE_CHNK)))
+               req->analysers |= AN_REQ_HTTP_BODY;
+
        /* end of job, return OK */
        req->analysers &= ~an_bit;
        req->analyse_exp = TICK_ETERNITY;
@@ -3288,7 +3293,7 @@ int http_handle_stats(struct stream *s, struct channel *req)
        }
 
        /* Was the status page requested with a POST ? */
-       if (unlikely(txn->meth == HTTP_METH_POST && txn->req.body_len > 0)) {
+       if (unlikely(txn->meth == HTTP_METH_POST && txn->req.body_len > 0 && msg->msg_state < HTTP_MSG_BODY)) {
                if (appctx->ctx.stats.flags & STAT_ADMIN) {
                        /* we'll need the request body, possibly after sending 100-continue */
                        req->analysers |= AN_REQ_HTTP_BODY;
index 12e8aa333768000e3abee6c00814f82451bc1dc4..ed3b58262d850622d3f6fba9b6235a2b217b2d31 100644 (file)
@@ -940,6 +940,13 @@ int stream_set_backend(struct stream *s, struct proxy *be)
        if (be->options2 & PR_O2_INDEPSTR)
                s->si[1].flags |= SI_FL_INDEP_STR;
 
+       /* We want to enable the backend-specific analysers except those which
+        * were already run as part of the frontend/listener. Note that it would
+        * be more reliable to store the list of analysers that have been run,
+        * but what we do here is OK for now.
+        */
+       s->req.analysers |= be->be_req_ana & ~strm_li(s)->analysers;
+
        /* If the target backend requires HTTP processing, we have to allocate
         * the HTTP transaction and hdr_idx if we did not have one.
         */
@@ -971,6 +978,11 @@ int stream_set_backend(struct stream *s, struct proxy *be)
                if (be->mode == PR_MODE_HTTP &&
                    (be->lbprm.algo & (BE_LB_KIND | BE_LB_PARM)) == (BE_LB_KIND_HI | BE_LB_HASH_PRM))
                        s->txn->req.flags |= HTTP_MSGF_WAIT_CONN;
+
+               /* we may request to parse a request body */
+               if ((be->options & PR_O_WREQ_BODY) &&
+                   (s->txn->req.body_len || (s->txn->req.flags & HTTP_MSGF_TE_CHNK)))
+                       s->req.analysers |= AN_REQ_HTTP_BODY;
        }
 
        s->flags |= SF_BE_ASSIGNED;
@@ -979,13 +991,6 @@ int stream_set_backend(struct stream *s, struct proxy *be)
                s->res.flags |= CF_NEVER_WAIT;
        }
 
-       /* We want to enable the backend-specific analysers except those which
-        * were already run as part of the frontend/listener. Note that it would
-        * be more reliable to store the list of analysers that have been run,
-        * but what we do here is OK for now.
-        */
-       s->req.analysers |= be->be_req_ana & ~strm_li(s)->analysers;
-
        return 1;
 }