[MAJOR] http: add support for option http-server-close

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.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 5a31e85..7e7cc5d 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -758,6 +758,8 @@
 [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 @@
              "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 @@
   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 ]
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index 8a12939..c9ec7e3 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -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 */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 92517ca..54190ee 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -100,12 +100,11 @@
 #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() */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 7b344c8..fdaa606 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -115,6 +115,7 @@
 	{ "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 },
diff --git a/src/proto_http.c b/src/proto_http.c
index 5eec4b6..fd26673 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -2176,6 +2176,9 @@
 
 		/* 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 @@
 
 		/* 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 @@
 
 		/* 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 @@
 
 		/* 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 @@
 	 */
 
 	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 @@
 			 * 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 @@
 		}
 		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 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 @@
 		 * 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 @@
 		 */
 		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 @@
 	 * 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 @@
 
 		/* 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 @@
 				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 @@
 				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 @@
 	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 @@
 				/* 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)