]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MAJOR] http: add support for option http-server-close
authorWilly Tarreau <w@1wt.eu>
Sat, 2 Jan 2010 21:47:18 +0000 (22:47 +0100)
committerWilly Tarreau <w@1wt.eu>
Sat, 2 Jan 2010 21:47:18 +0000 (22:47 +0100)
This option enables HTTP keep-alive on the client side and close mode
on the server side. This offers the best latency on the slow client
side, and still saves as many resources as possible on the server side
by actively closing connections. Pipelining is supported on both requests
and responses, though there is currently no reason to get pipelined
responses.

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

index 5a31e855ee374b31e87791cb5018e98c44dd43eb..7e7cc5dcc256e1e0b917f559e5c61895b800b5fe 100644 (file)
@@ -758,6 +758,8 @@ monitor-uri                 X          X         X         -
 [no] option forceclose      X          X         X         X
 option forwardfor           X          X         X         X
 option httpchk              X          -         X         X
+[no] option http-server-
+            close           X          X         X         X
 [no] option httpclose       X          X         X         X
 option httplog              X          X         X         X
 [no] option http_proxy      X          X         X         X
@@ -2510,6 +2512,38 @@ option httpchk <method> <uri> <version>
              "check", "port" and "interval" server options.
 
 
+option http-server-close
+no option http-server-close
+  Enable or disable HTTP connection closing on the server side
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    yes   |   yes  |   yes
+  Arguments : none
+
+  This mode enables HTTP connection-close mode on the server side while keeping
+  the ability to support HTTP keep-alive and pipelining on the client side.
+  This provides the lowest latency on the client side (slow network) and the
+  fastest session reuse on the server side to save server resources, similarly
+  to "option forceclose". It also permits non-keepalive capable servers to be
+  served in keep-alive mode to the clients if they conform to the requirements
+  of RFC2616.
+
+  At the moment, logs will not indicate whether requests came from the same
+  session or not. The accept date reported in the logs corresponds to the end
+  of the previous request, and the request time corresponds to the time spent
+  waiting for a new request. The keep-alive request time is still bound to the
+  timeout defined by "timeout http-request".
+
+  This option may be set both in a frontend and in a backend. It is enabled if
+  at least one of the frontend or backend holding a connection has it enabled.
+  It is worth noting that "option forceclose" has precedence over "httpclose",
+  which itself has precedence over "option http-server-close".
+
+  If this option has been enabled in a "defaults" section, it can be disabled
+  in a specific instance by prepending the "no" keyword before it.
+
+  See also : "option forceclose" and "option httpclose"
+
+
 option httpclose
 no option httpclose
   Enable or disable passive HTTP connection closing
@@ -2540,7 +2574,7 @@ no option httpclose
   If this option has been enabled in a "defaults" section, it can be disabled
   in a specific instance by prepending the "no" keyword before it.
 
-  See also : "option forceclose"
+  See also : "option forceclose" and "option http-server-close"
 
 
 option httplog [ clf ]
index 8a1293988b1d925488685e84c46d3714d539660d..c9ec7e3492b00e317a7f56f3ddf4b7145ea50f08 100644 (file)
@@ -56,7 +56,9 @@
 #define TX_SVDENY      0x00000004      /* a server header matches a deny regex */
 #define TX_SVALLOW     0x00000008      /* a server header matches an allow regex */
 #define TX_CLTARPIT    0x00000010      /* the session is tarpitted (anti-dos) */
-/* unused:              0x00000020 */
+
+/* used only for keep-alive purposes, to indicate we're on a second transaction */
+#define TX_NOT_FIRST   0x00000020      /* the transaction is not the first one */
 
 /* transaction flags dedicated to cookies : bits values 0x40, 0x80 (0-3 shift 6) */
 #define TX_CK_NONE     0x00000000      /* this session had no cookie */
index 92517ca2ec21fe7e05e43e61f38c4857d7df90b5..54190ee8e532bc8d7a6de41a95523743071cd1bc 100644 (file)
 #define PR_O_TPXY_CLI  0x06000000      /* bind to the client's IP+port when connect()ing */
 #define PR_O_TPXY_MASK 0x06000000      /* bind to a non-local address when connect()ing */
 
-/* unused : tcpsplice   0x08000000 */
+#define PR_O_SERVER_CLO 0x08000000     /* option http-server-close */
 #define PR_O_CONTSTATS 0x10000000      /* continous counters */
 #define PR_O_HTTP_PROXY 0x20000000     /* Enable session to use HTTP proxy operations */
 #define PR_O_DISABLE404 0x40000000      /* Disable a server on a 404 response to a health-check */
 #define PR_O_ORGTO      0x80000000      /* insert x-original-to with destination address */
-/* unused: 0x80000000 - now used by PR_O_ORGTO */
 
 /* bits for proxy->options2 */
 #define PR_O2_SPLIC_REQ        0x00000001      /* transfer requests using linux kernel's splice() */
index 7b344c81089cef93faa5dddec349055cd9ea177d..fdaa6062b94980f793a15d2712a8de6bb9893dba 100644 (file)
@@ -115,6 +115,7 @@ static const struct cfg_opt cfg_opts[] =
        { "http_proxy",   PR_O_HTTP_PROXY, PR_CAP_FE | PR_CAP_BE, 0 },
        { "httpclose",    PR_O_HTTP_CLOSE, PR_CAP_FE | PR_CAP_BE, 0 },
        { "keepalive",    PR_O_KEEPALIVE,  PR_CAP_NONE, 0 },
+       { "http-server-close", PR_O_SERVER_CLO,  PR_CAP_FE | PR_CAP_BE, 0 },
        { "logasap",      PR_O_LOGASAP,    PR_CAP_FE, 0 },
        { "nolinger",     PR_O_TCP_NOLING, PR_CAP_FE | PR_CAP_BE, 0 },
        { "persist",      PR_O_PERSIST,    PR_CAP_BE, 0 },
index 5eec4b6c2d32ddf51d0741ff457f92b0f5af409f..fd266730f2f4d275acf8b1ffba8ab5ebe6be3278 100644 (file)
@@ -2176,6 +2176,9 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
 
                /* 2: have we encountered a read error ? */
                else if (req->flags & BF_READ_ERROR) {
+                       if (txn->flags & TX_NOT_FIRST)
+                               goto failed_keep_alive;
+
                        /* we cannot return any message on error */
                        if (msg->err_pos >= 0)
                                http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
@@ -2195,6 +2198,9 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
 
                /* 3: has the read timeout expired ? */
                else if (req->flags & BF_READ_TIMEOUT || tick_is_expired(req->analyse_exp, now_ms)) {
+                       if (txn->flags & TX_NOT_FIRST)
+                               goto failed_keep_alive;
+
                        /* read timeout : give up with an error message. */
                        if (msg->err_pos >= 0)
                                http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
@@ -2216,6 +2222,9 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
 
                /* 4: have we encountered a close ? */
                else if (req->flags & BF_SHUTR) {
+                       if (txn->flags & TX_NOT_FIRST)
+                               goto failed_keep_alive;
+
                        if (msg->err_pos >= 0)
                                http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
                        txn->status = 400;
@@ -2243,6 +2252,19 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
 
                /* we're not ready yet */
                return 0;
+
+       failed_keep_alive:
+               /* Here we process low-level errors for keep-alive requests. In
+                * short, if the request is not the first one and it experiences
+                * a timeout, read error or shutdown, we just silently close so
+                * that the client can try again.
+                */
+               txn->status = 0;
+               msg->msg_state = HTTP_MSG_RQBEFORE;
+               req->analysers = 0;
+               s->logs.logwait = 0;
+               stream_int_cond_close(req->prod, NULL);
+               return 0;
        }
 
        /* OK now we have a complete HTTP request with indexed headers. Let's
@@ -2562,11 +2584,12 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
         */
 
        if ((txn->meth != HTTP_METH_CONNECT) &&
-           ((s->fe->options|s->be->options) & (PR_O_KEEPALIVE|PR_O_HTTP_CLOSE|PR_O_FORCE_CLO))) {
+           ((s->fe->options|s->be->options) & (PR_O_KEEPALIVE|PR_O_SERVER_CLO|PR_O_HTTP_CLOSE|PR_O_FORCE_CLO))) {
                int tmp = TX_CON_WANT_TUN;
                if ((s->fe->options|s->be->options) & PR_O_KEEPALIVE)
                        tmp = TX_CON_WANT_KAL;
-               /* FIXME: for now, we don't support server-close mode */
+               if ((s->fe->options|s->be->options) & PR_O_SERVER_CLO)
+                       tmp = TX_CON_WANT_SCL;
                if ((s->fe->options|s->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO))
                        tmp = TX_CON_WANT_CLO;
 
@@ -3340,7 +3363,12 @@ int http_request_forward_body(struct session *s, struct buffer *req, int an_bit)
                         * to reset the transaction here.
                         */
 
-                       if ((s->fe->options | s->be->options) & PR_O_FORCE_CLO) {
+                       if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL) {
+                               /* initiate a connection close to the server */
+                               req->cons->flags |= SI_FL_NOLINGER;
+                               buffer_shutw_now(req);
+                       }
+                       else if ((s->fe->options | s->be->options) & PR_O_FORCE_CLO) {
                                /* Option forceclose is set, let's enforce it now
                                 * that the transfer is complete. We can safely speed
                                 * up the close because we know the server has received
@@ -3372,6 +3400,111 @@ int http_request_forward_body(struct session *s, struct buffer *req, int an_bit)
                }
                else if (msg->msg_state == HTTP_MSG_CLOSED) {
                        req->flags &= ~BF_DONT_READ;
+
+                       if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL) {
+
+                               /* FIXME : this part is 1) awful, 2) tricky, 3) duplicated
+                                * ... but it works.
+                                * We need a better way to force a connection close without
+                                * any risk of propagation to the other side. We need a more
+                                * portable way of releasing a backend's and a server's
+                                * connections. We need a safer way to reinitialize buffer
+                                * flags. We also need a more accurate method for computing
+                                * per-request data.
+                                */
+                               s->req->cons->flags |= SI_FL_NOLINGER;
+                               s->req->cons->shutr(s->req->cons);
+                               s->req->cons->shutw(s->req->cons);
+
+                               if (s->flags & SN_BE_ASSIGNED)
+                                       s->be->beconn--;
+
+                               s->logs.t_close = tv_ms_elapsed(&s->logs.tv_accept, &now);
+                               session_process_counters(s);
+
+                               if (s->txn.status) {
+                                       int n;
+
+                                       n = s->txn.status / 100;
+                                       if (n < 1 || n > 5)
+                                               n = 0;
+
+                                       if (s->fe->mode == PR_MODE_HTTP)
+                                               s->fe->counters.p.http.rsp[n]++;
+
+                                       if ((s->flags & SN_BE_ASSIGNED) && (s->fe != s->be) &&
+                                           (s->be->mode == PR_MODE_HTTP))
+                                               s->be->counters.p.http.rsp[n]++;
+                               }
+
+                               /* don't count other requests' data */
+                               s->logs.bytes_in  -= s->req->l - s->req->send_max;
+                               s->logs.bytes_out -= s->rep->l - s->rep->send_max;
+
+                               /* let's do a final log if we need it */
+                               if (s->logs.logwait &&
+                                   !(s->flags & SN_MONITOR) &&
+                                   (!(s->fe->options & PR_O_NULLNOLOG) || s->req->total)) {
+                                       s->do_log(s);
+                               }
+
+                               s->logs.accept_date = date; /* user-visible date for logging */
+                               s->logs.tv_accept = now;  /* corrected date for internal use */
+                               tv_zero(&s->logs.tv_request);
+                               s->logs.t_queue = -1;
+                               s->logs.t_connect = -1;
+                               s->logs.t_data = -1;
+                               s->logs.t_close = 0;
+                               s->logs.prx_queue_size = 0;  /* we get the number of pending conns before us */
+                               s->logs.srv_queue_size = 0; /* we will get this number soon */
+
+                               s->logs.bytes_in = s->req->total = s->req->l - s->req->send_max;
+                               s->logs.bytes_out = s->rep->total = s->rep->l - s->rep->send_max;
+
+                               if (s->pend_pos)
+                                       pendconn_free(s->pend_pos);
+
+                               if (s->srv) {
+                                       if (s->flags & SN_CURR_SESS) {
+                                               s->flags &= ~SN_CURR_SESS;
+                                               s->srv->cur_sess--;
+                                       }
+                                       if (may_dequeue_tasks(s->srv, s->be))
+                                               process_srv_queue(s->srv);
+                               }
+
+                               if (unlikely(s->srv_conn))
+                                       sess_change_server(s, NULL);
+                               s->srv = NULL;
+
+                               s->req->cons->state     = s->req->cons->prev_state = SI_ST_INI;
+                               s->req->cons->fd        = -1; /* just to help with debugging */
+                               s->req->cons->err_type  = SI_ET_NONE;
+                               s->req->cons->err_loc   = NULL;
+                               s->req->cons->exp       = TICK_ETERNITY;
+                               s->req->cons->flags     = SI_FL_NONE;
+                               s->req->flags &= ~(BF_SHUTW|BF_SHUTW_NOW|BF_AUTO_CONNECT|BF_WRITE_ERROR|BF_STREAMER|BF_STREAMER_FAST|BF_AUTO_CLOSE);
+                               s->rep->flags &= ~(BF_SHUTR|BF_SHUTR_NOW|BF_READ_ATTACHED|BF_READ_ERROR|BF_READ_NOEXP|BF_STREAMER|BF_STREAMER_FAST|BF_AUTO_CLOSE|BF_WRITE_PARTIAL);
+                               s->flags &= ~(SN_DIRECT|SN_ASSIGNED|SN_ADDR_SET|SN_BE_ASSIGNED);
+                               s->flags &= ~(SN_CURR_SESS|SN_REDIRECTABLE);
+                               s->txn.meth = 0;
+                               http_reset_txn(s);
+                               txn->flags |= TX_NOT_FIRST;
+                               if (s->be->options2 & PR_O2_INDEPSTR)
+                                       s->req->cons->flags |= SI_FL_INDEP_STR;
+
+                               /* make ->lr point to the first non-forwarded byte */
+                               s->req->lr = s->req->w + s->req->send_max;
+                               if (s->req->lr >= s->req->data + s->req->size)
+                                       s->req->lr -= s->req->size;
+                               s->rep->lr = s->rep->w + s->rep->send_max;
+                               if (s->rep->lr >= s->rep->data + s->rep->size)
+                                       s->rep->lr -= s->req->size;
+
+                               s->req->analysers |= s->fe->fe_req_ana;
+                               s->rep->analysers = 0;
+                       }
+
                        /* FIXME: we're still forced to do that here */
                        s->rep->flags &= ~BF_DONT_READ;
                        break;
@@ -3828,6 +3961,7 @@ int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, s
        int cur_idx;
        int conn_ka = 0, conn_cl = 0;
        int must_close = 0;
+       int must_del_close = 0, must_keep = 0;
 
        DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n",
                now_ms, __FUNCTION__,
@@ -3903,7 +4037,9 @@ int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, s
                 * handled. We also explicitly state that we will close in
                 * case of an ambiguous response having no content-length.
                 */
-               if (may_close || !(txn->flags & TX_RES_XFER_LEN))
+               if ((may_close &&
+                    (may_keep || ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_SCL))) ||
+                   !(txn->flags & TX_RES_XFER_LEN))
                        txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_CLO;
 
                /* Now we must adjust the response header :
@@ -3915,6 +4051,12 @@ int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, s
                 */
                if (may_keep && (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_CLO)
                        must_close = 1;
+               else if (((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL) &&
+                        may_close && (txn->flags & TX_RES_XFER_LEN)) {
+                       must_del_close = 1;
+                       if (!(txn->flags & TX_REQ_VER_11))
+                               must_keep = 1;
+               }
 
                txn->flags |= TX_CON_HDR_PARS;
        }
@@ -3923,7 +4065,7 @@ int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, s
         * returns a connection status that is not compatible with
         * the client's or with the config.
         */
-       if ((txn->status >= 200) && must_close && (conn_cl|conn_ka)) {
+       if ((txn->status >= 200) && (must_del_close|must_close) && (conn_cl|conn_ka)) {
                char *cur_ptr, *cur_end, *cur_next;
                int cur_idx, old_idx, delta, val;
                int must_delete;
@@ -3931,6 +4073,10 @@ int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, s
 
                /* we just have to remove the headers if both sides are 1.0 */
                must_delete = !(txn->flags & TX_REQ_VER_11) && !(txn->flags & TX_RES_VER_11);
+
+               /* same if we want to re-enable keep-alive on 1.1 */
+               must_delete |= must_del_close;
+
                cur_next = rep->data + txn->rsp.som + hdr_idx_first_pos(&txn->hdr_idx);
 
                for (old_idx = 0; (cur_idx = txn->hdr_idx.v[old_idx].next); old_idx = cur_idx) {
@@ -3959,6 +4105,7 @@ int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, s
                                txn->hdr_idx.used--;
                                cur_hdr->len = 0;
                                must_close = 0;
+                               must_del_close = 0;
                        } else {
                                if (cur_end - cur_ptr - val != 5 ||
                                    strncasecmp(cur_ptr + val, "close", 5) != 0) {
@@ -4147,6 +4294,12 @@ int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, s
                                goto return_bad_resp;
                        must_close = 0;
                }
+               else if (must_keep && !(txn->flags & TX_REQ_VER_11)) {
+                       if (unlikely(http_header_add_tail2(rep, &txn->rsp, &txn->hdr_idx,
+                                                          "Connection: keep-alive", 22) < 0))
+                               goto return_bad_resp;
+                       must_keep = 0;
+               }
 
                if (txn->flags & TX_RES_XFER_LEN)
                        rep->analysers |= AN_RES_HTTP_XFER_BODY;
@@ -4197,6 +4350,12 @@ int http_response_forward_body(struct session *s, struct buffer *res, int an_bit
        if (unlikely(msg->msg_state < HTTP_MSG_BODY))
                return 0;
 
+       /* note: in server-close mode, we don't want to automatically close the
+        * output when the input is closed.
+        */
+       if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL)
+               buffer_dont_close(res);
+
        if (msg->msg_state < HTTP_MSG_CHUNK_SIZE) {
                /* we have msg->col and msg->sov which both point to the first
                 * byte of message body. msg->som still points to the beginning
@@ -4302,6 +4461,11 @@ int http_response_forward_body(struct session *s, struct buffer *res, int an_bit
                                /* option forceclose is set, let's enforce it now that the transfer is complete. */
                                buffer_abort(res);
                        }
+                       else if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL) {
+                               /* server close is handled entirely on the req analyser */
+                               s->req->cons->flags |= SI_FL_NOLINGER;
+                               buffer_shutw_now(s->req);
+                       }
 
                        if (res->flags & (BF_SHUTW|BF_SHUTW_NOW)) {
                                if (res->flags & BF_OUT_EMPTY)