MAJOR: mux-h1/proto_htx: Handle keep-alive connections in the mux

Now, the connection mode is detected in the mux and not in HTX analyzers
anymore. Keep-alive connections are now managed by the mux. A new stream is
created for each transaction. This removes the most important part of the
synchronization between channels and the HTTP transaction cleanup. These changes
only affect the HTX part (proto_htx.c). Legacy HTTP analyzers remain untouched
for now.

On the client-side, the mux is responsible to create new streams when a new
request starts. It is also responsible to parse and update the "Connection:"
header of the response. On the server-side, the mux is responsible to parse and
update the "Connection:" header of the request. Muxes on each side are
independent. For now, there is no connection pool on the server-side, so it
always close the server connection.
diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h
index ff3b59e..6c84f35 100644
--- a/include/proto/proto_http.h
+++ b/include/proto/proto_http.h
@@ -52,7 +52,7 @@
 void http_txn_reset_req(struct http_txn *txn);
 void http_txn_reset_res(struct http_txn *txn);
 
-/* Export HTX analyzers */
+/* Export HTX analyzers and helpers */
 int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit);
 int htx_process_req_common(struct stream *s, struct channel *req, int an_bit, struct proxy *px);
 int htx_process_request(struct stream *s, struct channel *req, int an_bit);
@@ -63,6 +63,8 @@
 int htx_process_res_common(struct stream *s, struct channel *rep, int an_bit, struct proxy *px);
 int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit);
 int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit);
+void htx_adjust_conn_mode(struct stream *s, struct http_txn *txn, struct http_msg *msg);
+int htx_apply_redirect_rule(struct redirect_rule *rule, struct stream *s, struct http_txn *txn);
 
 void debug_hdr(const char *dir, struct stream *s, const char *start, const char *end);
 int apply_filter_to_req_headers(struct stream *s, struct channel *req, struct hdr_exp *exp);
diff --git a/src/mux_h1.c b/src/mux_h1.c
index 9501c5f..e2d22a4 100644
--- a/src/mux_h1.c
+++ b/src/mux_h1.c
@@ -12,6 +12,9 @@
 #include <common/cfgparse.h>
 #include <common/config.h>
 
+#include <types/proxy.h>
+#include <types/session.h>
+
 #include <proto/connection.h>
 #include <proto/h1.h>
 #include <proto/log.h>
@@ -42,13 +45,21 @@
 #define H1C_F_CS_SHUTW_NOW   0x00002000 /* connection must be shut down for writes ASAP */
 #define H1C_F_CS_SHUTW       0x00004000 /* connection is already shut down */
 
+#define H1C_F_WAIT_NEXT_REQ  0x00010000 /*  waiting for the next request to start, use keep-alive timeout */
 
 /*
  * H1 Stream flags (32 bits)
  */
 #define H1S_F_NONE           0x00000000
 #define H1S_F_ERROR          0x00000001 /* An error occurred on the H1 stream */
-#define H1S_F_MSG_XFERED     0x00000002 /* current message was transferred to the data layer */
+#define H1S_F_REQ_ERROR      0x00000002 /* An error occurred during the request parsing/xfer */
+#define H1S_F_RES_ERROR      0x00000004 /* An error occurred during the response parsing/xfer */
+#define H1S_F_MSG_XFERED     0x00000008 /* current message was transferred to the data layer */
+#define H1S_F_WANT_KAL       0x00000010
+#define H1S_F_WANT_TUN       0x00000020
+#define H1S_F_WANT_CLO       0x00000040
+#define H1S_F_WANT_MSK       0x00000070
+#define H1S_F_NOT_FIRST      0x00000080 /* The H1 stream is not the first one */
 
 
 /* H1 connection descriptor */
@@ -209,7 +220,7 @@
 /*****************************************************************/
 /* functions below are dedicated to the mux setup and management */
 /*****************************************************************/
-static struct h1s *h1s_create(struct h1c *h1c)
+static struct h1s *h1s_create(struct h1c *h1c, struct conn_stream *cs)
 {
 	struct h1s *h1s;
 
@@ -236,31 +247,73 @@
 	if (!conn_is_back(h1c->conn)) {
 		if (h1c->px->options2 & PR_O2_REQBUG_OK)
 			h1s->req.err_pos = -1;
+
+		if (h1c->flags & H1C_F_WAIT_NEXT_REQ)
+			h1s->flags |= H1S_F_NOT_FIRST;
+		h1c->flags &= ~H1C_F_WAIT_NEXT_REQ;
+		h1c->http_exp = tick_add_ifset(now_ms, h1c->px->timeout.httpreq);
 	}
 	else {
 		if (h1c->px->options2 & PR_O2_RSPBUG_OK)
 			h1s->res.err_pos = -1;
 	}
+
+	/* If a conn_stream already exists, attach it to this H1S */
+	if (cs) {
+		cs->ctx = h1s;
+		h1s->cs = cs;
+	}
   end:
 	return h1s;
 }
 
 static void h1s_destroy(struct h1s *h1s)
 {
-	struct h1c *h1c = h1s->h1c;
+	if (h1s) {
+		struct h1c *h1c = h1s->h1c;
 
-	h1c->h1s = NULL;
-	h1c->flags &= ~H1C_F_RX_FULL;
+		h1c->h1s = NULL;
+		h1c->flags &= ~(H1C_F_RX_FULL|H1C_F_RX_ALLOC);
 
-	if (h1s->recv_wait != NULL)
-		h1s->recv_wait->wait_reason &= ~SUB_CAN_RECV;
-	if (h1s->send_wait != NULL)
-		h1s->send_wait->wait_reason &= ~SUB_CAN_SEND;
+		if (h1s->recv_wait != NULL)
+			h1s->recv_wait->wait_reason &= ~SUB_CAN_RECV;
+		if (h1s->send_wait != NULL)
+			h1s->send_wait->wait_reason &= ~SUB_CAN_SEND;
+
+		if (!conn_is_back(h1c->conn)) {
+			h1c->flags |= H1C_F_WAIT_NEXT_REQ;
+			h1c->http_exp = tick_add_ifset(now_ms, h1c->px->timeout.httpka);
+		}
 
-	h1_release_buf(h1c, &h1s->rxbuf);
-	pool_free(pool_head_h1s, h1s);
+		h1_release_buf(h1c, &h1s->rxbuf);
+		cs_free(h1s->cs);
+		pool_free(pool_head_h1s, h1s);
+	}
 }
 
+static struct conn_stream *h1s_new_cs(struct h1s *h1s)
+{
+	struct conn_stream *cs;
+
+	cs = cs_new(h1s->h1c->conn);
+	if (!cs)
+		goto err;
+	h1s->cs = cs;
+	cs->ctx = h1s;
+
+	if (h1s->flags & H1S_F_NOT_FIRST)
+		cs->flags |= CS_FL_NOT_FIRST;
+
+	if (stream_create_from_cs(cs) < 0)
+		goto err;
+	return cs;
+
+  err:
+	cs_free(cs);
+	h1s->cs = NULL;
+	return NULL;
+}
+
 /*
  * Initialize the mux once it's attached. It is expected that conn->mux_ctx
  * points to the existing conn_stream (for outgoing connections) or NULL (for
@@ -268,7 +321,6 @@
  */
 static int h1_init(struct connection *conn, struct proxy *proxy)
 {
-	struct conn_stream *cs = conn->mux_ctx;
 	struct h1c *h1c;
 	struct task *t = NULL;
 
@@ -302,21 +354,9 @@
 	h1c->wait_event.task->context = h1c;
 	h1c->wait_event.wait_reason   = 0;
 
-
-	/* For backend mux connection, the CS already exists. In such case,
-	 * create h1s and attached the cs to it.
-	 */
-	if (cs) {
-		struct h1s *h1s = cs->ctx;
-
-		if (!h1s) {
-			h1s = h1s_create(h1c);
-			if (!h1s)
-				goto fail;
-			cs->ctx = h1s;
-			h1s->cs = cs;
-		}
-	}
+	/* Always Create a new H1S */
+	if (!h1s_create(h1c, conn->mux_ctx))
+		goto fail;
 
 	conn->mux_ctx = h1c;
 	task_wakeup(t, TASK_WOKEN_INIT);
@@ -367,9 +407,7 @@
 		if (h1c->wait_event.task)
 			tasklet_free(h1c->wait_event.task);
 
-		if (h1c->h1s)
-			h1s_destroy(h1c->h1s);
-
+		h1s_destroy(h1c->h1s);
 		if (h1c->wait_event.wait_reason != 0)
 			conn->xprt->unsubscribe(conn, h1c->wait_event.wait_reason,
 			    &h1c->wait_event);
@@ -404,14 +442,242 @@
 	b_putblk(dst, b_head(err), b_data(err));
 }
 
+/* Remove all "Connection:" headers from the buffer <buf>, using the array of
+ * parsed headers <hdrs>. It returns the number of bytes removed. This should
+ * happen just after the headers parsing, so the buffer should not wrap. At the
+ * ends, all entries of <hdrs> reamin valid.
+ */
+static int h1_remove_conn_hdrs(struct h1m *h1m, struct http_hdr *hdrs, struct buffer *buf)
+{
+	int src, dst, delta;
+
+	delta = 0;
+	for (src = 0, dst = 0; hdrs[src].n.len; src++) {
+
+		if (hdrs[src].n.ptr >= buf->area && hdrs[src].n.ptr < buf->area + buf->size)
+			hdrs[src].n.ptr += delta;
+		hdrs[src].v.ptr += delta;
+
+		if (!isteqi(hdrs[src].n, ist("Connection"))) {
+			if (src != dst)
+				hdrs[dst] = hdrs[src];
+			dst++;
+			continue;
+		}
+		delta += b_rep_blk(buf, hdrs[src].n.ptr, hdrs[src+1].n.ptr+delta, NULL, 0);
+	}
+
+	/* Don't forget to copy EOH */
+	hdrs[src].n.ptr += delta;
+	hdrs[dst] = hdrs[src];
+
+	h1m->flags &= ~(H1_MF_CONN_KAL|H1_MF_CONN_CLO);
+	return delta;
+}
+
+/* Add a "Connection:" header into the buffer <buf>. If <type> is 0, the header
+ * is set to "keep-alive", otherwise it is set to "close", It returns the number
+ * of bytes added. This should happen just after the headers parsing, so the
+ * buffer should not wrap. At the ends, all entries of <hdrs> reamin valid.
+ */
+static int h1_add_conn_hdrs(struct h1m *h1m, struct http_hdr *hdrs, struct buffer *buf,
+			    int type)
+{
+	const char *conn_hdr;
+	size_t nlen, vlen;
+	int i, delta;
+
+	if (type == 0) { /* keep-alive */
+		conn_hdr = "Connection: keep-alive\r\n";
+		nlen = 10; vlen = 10;
+	}
+	else { /* close */
+		conn_hdr = "Connection: close\r\n";
+		nlen = 10; vlen = 5;
+	}
+
+	/* Find EOH*/
+	for (i = 0; hdrs[i].n.len; i++);
+
+	/* Insert the "Connection: " header */
+	delta = b_rep_blk(buf, hdrs[i].n.ptr, hdrs[i].n.ptr, conn_hdr, nlen+vlen+4);
+
+	/* Update the header list */
+	http_set_hdr(&hdrs[i], ist2(hdrs[i].n.ptr, nlen), ist2(hdrs[i].n.ptr+nlen+2, vlen));
+	http_set_hdr(&hdrs[i+1], ist2(hdrs[i].n.ptr+delta, 0), ist(""));
+
+	return delta;
+}
+
+/* Deduce the connection mode of the client connection, depending on the
+ * configuration and the H1 message flags. This function is called twice, the
+ * first time when the request is parsed and the second time when the response
+ * is parsed.
+ */
+static void h1_set_cli_conn_mode(struct h1s *h1s, struct h1m *h1m)
+{
+	struct proxy *fe = h1s->h1c->px;
+	int flag = H1S_F_WANT_KAL; /* For client connection: server-close == keepalive */
+
+	/* Tunnel mode can only by set on the frontend */
+	if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_TUN)
+		flag = H1S_F_WANT_TUN;
+	else if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_CLO)
+		flag = H1S_F_WANT_CLO;
+
+	/* flags order: CLO > SCL > TUN > KAL */
+	if ((h1s->flags & H1S_F_WANT_MSK) < flag)
+		h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | flag;
+
+	if (h1m->flags & H1_MF_RESP) {
+		/* Either we've established an explicit tunnel, or we're
+		 * switching the protocol. In both cases, we're very unlikely to
+		 * understand the next protocols. We have to switch to tunnel
+		 * mode, so that we transfer the request and responses then let
+		 * this protocol pass unmodified. When we later implement
+		 * specific parsers for such protocols, we'll want to check the
+		 * Upgrade header which contains information about that protocol
+		 * for responses with status 101 (eg: see RFC2817 about TLS).
+		 */
+		if ((h1s->meth == HTTP_METH_CONNECT && h1s->status == 200) ||
+		    h1s->status == 101)
+			h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_TUN;
+		else if (!(h1m->flags & H1_MF_XFER_LEN)) /* no length known => close */
+			h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO;
+	}
+	else {
+		if (h1s->flags & H1S_F_WANT_KAL &&
+		    (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL)) || /* no KA in HTTP/1.0 */
+		     h1m->flags & H1_MF_CONN_CLO))                    /* explicit close */
+			h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO;
+	}
+
+	/* If KAL, check if the frontend is stopping. If yes, switch in CLO mode */
+	if (h1s->flags & H1S_F_WANT_KAL && fe->state == PR_STSTOPPED)
+		h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO;
+}
+
+/* Deduce the connection mode of the client connection, depending on the
+ * configuration and the H1 message flags. This function is called twice, the
+ * first time when the request is parsed and the second time when the response
+ * is parsed.
+ */
+static void h1_set_srv_conn_mode(struct h1s *h1s, struct h1m *h1m)
+{
+	struct proxy *be = h1s->h1c->px;
+	struct proxy *fe = strm_fe(si_strm(h1s->cs->data));
+	int flag =  H1S_F_WANT_KAL;
+
+	/* Tunnel mode can only by set on the frontend */
+	if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_TUN)
+		flag = H1S_F_WANT_TUN;
+
+	/* For the server connection: server-close == httpclose */
+	if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_SCL ||
+	    (be->options & PR_O_HTTP_MODE) == PR_O_HTTP_SCL ||
+	    (fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_CLO ||
+	    (be->options & PR_O_HTTP_MODE) == PR_O_HTTP_CLO)
+		flag = H1S_F_WANT_CLO;
+
+	/* flags order: CLO > SCL > TUN > KAL */
+	if ((h1s->flags & H1S_F_WANT_MSK) < flag)
+		h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | flag;
+
+	if (h1m->flags & H1_MF_RESP) {
+		/* Either we've established an explicit tunnel, or we're
+		 * switching the protocol. In both cases, we're very unlikely to
+		 * understand the next protocols. We have to switch to tunnel
+		 * mode, so that we transfer the request and responses then let
+		 * this protocol pass unmodified. When we later implement
+		 * specific parsers for such protocols, we'll want to check the
+		 * Upgrade header which contains information about that protocol
+		 * for responses with status 101 (eg: see RFC2817 about TLS).
+		 */
+		if ((h1s->meth == HTTP_METH_CONNECT && h1s->status == 200) ||
+		    h1s->status == 101)
+			h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_TUN;
+		else if (!(h1m->flags & H1_MF_XFER_LEN)) /* no length known => close */
+			h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO;
+		else if (h1s->flags & H1S_F_WANT_KAL &&
+			 (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL)) || /* no KA in HTTP/1.0 */
+			  h1m->flags & H1_MF_CONN_CLO))                    /* explicit close */
+			h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO;
+	}
+
+	/* If KAL, check if the backend is stopping. If yes, switch in CLO mode */
+	if (h1s->flags & H1S_F_WANT_KAL && be->state == PR_STSTOPPED)
+		h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO;
+
+	/* TODO: For now on the server-side, we disable keep-alive */
+	if (h1s->flags & H1S_F_WANT_KAL)
+		h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO;
+}
+
+static int h1_update_req_conn_hdr(struct h1s *h1s, struct h1m *h1m,
+				   struct http_hdr *hdrs, struct buffer *buf)
+{
+	struct proxy *px = h1s->h1c->px;
+	int ret = 0;
+
+	/* Don't update "Connection:" header in TUNNEL mode or if "Upgrage"
+	 * token is found
+	 */
+	if (h1s->flags & H1S_F_WANT_TUN || h1m->flags & H1_MF_CONN_UPG)
+		goto end;
+
+	if (h1s->flags & H1S_F_WANT_KAL || px->options2 & PR_O2_FAKE_KA) {
+		if (h1m->flags & H1_MF_CONN_CLO)
+			ret += h1_remove_conn_hdrs(h1m, hdrs, buf);
+		if (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL)))
+			ret += h1_add_conn_hdrs(h1m, hdrs, buf, 0);
+	}
+	else { /* H1S_F_WANT_CLO && !PR_O2_FAKE_KA */
+		if (h1m->flags & H1_MF_CONN_KAL)
+			ret += h1_remove_conn_hdrs(h1m, hdrs, buf);
+		if ((h1m->flags & (H1_MF_VER_11|H1_MF_CONN_CLO)) == H1_MF_VER_11)
+			ret += h1_add_conn_hdrs(h1m, hdrs, buf, 1);
+	}
+
+  end:
+	return ret;
+}
+
+static int h1_update_res_conn_hdr(struct h1s *h1s, struct h1m *h1m,
+				   struct http_hdr *hdrs, struct buffer *buf)
+{
+	int ret = 0;
+
+	/* Don't update "Connection:" header in TUNNEL mode or if "Upgrage"
+	 * token is found
+	 */
+	if (h1s->flags & H1S_F_WANT_TUN || h1m->flags & H1_MF_CONN_UPG)
+		goto end;
+
+	if (h1s->flags & H1S_F_WANT_KAL) {
+		if (h1m->flags & H1_MF_CONN_CLO)
+			ret += h1_remove_conn_hdrs(h1m, hdrs, buf);
+		if (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL)))
+			ret += h1_add_conn_hdrs(h1m, hdrs, buf, 0);
+	}
+	else { /* H1S_F_WANT_CLO */
+		if (h1m->flags & H1_MF_CONN_KAL)
+			ret += h1_remove_conn_hdrs(h1m, hdrs, buf);
+		if ((h1m->flags & (H1_MF_VER_11|H1_MF_CONN_CLO)) == H1_MF_VER_11)
+			ret += h1_add_conn_hdrs(h1m, hdrs, buf, 1);
+	}
+
+  end:
+	return ret;
+}
+
 /*
  * Parse HTTP/1 headers. It returns the number of bytes parsed if > 0, or 0 if
- * it couldn't proceed. Parsing errors are reported by setting H1S_F_ERROR flag
- * and filling h1s->err_pos and h1s->err_state fields. This functions is
+ * it couldn't proceed. Parsing errors are reported by setting H1S_F_*_ERROR
+ * flag and filling h1s->err_pos and h1s->err_state fields. This functions is
  * responsibile to update the parser state <h1m>.
  */
 static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m,
-				 struct buffer *buf, size_t ofs, size_t max)
+				 struct buffer *buf, size_t *ofs, size_t max)
 {
 	struct http_hdr hdrs[MAX_HTTP_HDR];
 	union h1_sl sl;
@@ -421,18 +687,14 @@
 	if (b_head(buf) + b_data(buf) > b_wrap(buf))
 		b_slow_realign(buf, trash.area, 0);
 
-	ret = h1_headers_to_hdr_list(b_peek(buf, ofs), b_peek(buf, ofs) + max,
+	ret = h1_headers_to_hdr_list(b_peek(buf, *ofs), b_peek(buf, *ofs) + max,
 				     hdrs, sizeof(hdrs)/sizeof(hdrs[0]), h1m, &sl);
 	if (ret <= 0) {
 		/* Incomplete or invalid message. If the buffer is full, it's an
 		 * error because headers are too large to be handled by the
 		 * parser. */
-		if (ret < 0 || (!ret && b_full(buf))) {
-			h1s->flags |= H1S_F_ERROR;
-			h1m->err_state = h1m->state;
-			h1m->err_pos = h1m->next;
-			ret = 0;
-		}
+		if (ret < 0 || (!ret && b_full(buf)))
+			goto error;
 		goto end;
 	}
 
@@ -441,13 +703,8 @@
 	 */
 
 	/* Be sure to keep some space to do headers rewritting */
-	if (ret > (b_size(buf) - global.tune.maxrewrite)) {
-		h1s->flags    |= H1S_F_ERROR;
-		h1m->err_state = h1m->state;
-		h1m->err_pos   = h1m->next;
-		ret = 0;
-		goto end;
-	}
+	if (ret > (b_size(buf) - global.tune.maxrewrite))
+		goto error;
 
 	/* Save the request's method or the response's status and check if the
 	 * body length is known */
@@ -479,18 +736,36 @@
 			h1m->state = H1_MSG_TUNNEL;
 	}
 
+	*ofs += ret;
+	if (!conn_is_back(h1s->h1c->conn)) {
+		h1_set_cli_conn_mode(h1s, h1m);
+		if (h1m->flags & H1_MF_RESP)
+			*ofs += h1_update_res_conn_hdr(h1s, h1m, hdrs, buf);
+	}
+	else {
+		h1_set_srv_conn_mode(h1s, h1m);
+		if (!(h1m->flags & H1_MF_RESP))
+			*ofs += h1_update_req_conn_hdr(h1s, h1m, hdrs, buf);
+	}
   end:
 	return ret;
+
+  error:
+	h1s->flags |= (!(h1m->flags & H1_MF_RESP) ? H1S_F_REQ_ERROR : H1S_F_RES_ERROR);
+	h1m->err_state = h1m->state;
+	h1m->err_pos = h1m->next;
+	ret = 0;
+	goto end;
 }
 
 /*
- * Parse HTTP/1 body. It returns the number of bytes parsed if > 0, or 0 if
- * it couldn't proceed. Parsing errors are reported by setting H1S_F_ERROR flag
+ * Parse HTTP/1 body. It returns the number of bytes parsed if > 0, or 0 if it
+ * couldn't proceed. Parsing errors are reported by setting H1S_F_*_ERROR flag
  * and filling h1s->err_pos and h1s->err_state fields. This functions is
  * responsibile to update the parser state <h1m>.
  */
 static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m,
-			      struct buffer *buf, size_t ofs, size_t max)
+			      struct buffer *buf, size_t *ofs, size_t max)
 {
 	size_t total = 0;
 	int ret = 0;
@@ -502,6 +777,7 @@
 			if ((uint64_t)ret > h1m->curr_len)
 				ret = h1m->curr_len;
 			h1m->curr_len -= ret;
+			*ofs += ret;
 			total += ret;
 			if (!h1m->curr_len)
 				h1m->state = H1_MSG_DONE;
@@ -510,11 +786,11 @@
 		  new_chunk:
 			/* te:chunked : parse chunks */
 			if (h1m->state == H1_MSG_CHUNK_CRLF) {
-				ret = h1_skip_chunk_crlf(buf, ofs, ofs + max);
+				ret = h1_skip_chunk_crlf(buf, *ofs, *ofs + max);
 				if (ret <= 0)
 					goto end;
 				max -= ret;
-				ofs += ret;
+				*ofs += ret;
 				total += ret;
 				h1m->state = H1_MSG_CHUNK_SIZE;
 			}
@@ -522,13 +798,13 @@
 			if (h1m->state == H1_MSG_CHUNK_SIZE) {
 				unsigned int chksz;
 
-				ret = h1_parse_chunk_size(buf, ofs, ofs + max, &chksz);
+				ret = h1_parse_chunk_size(buf, *ofs, *ofs + max, &chksz);
 				if (ret <= 0)
 					goto end;
 				h1m->curr_len  = chksz;
 				h1m->body_len += chksz;
 				max -= ret;
-				ofs += ret;
+				*ofs += ret;
 				total += ret;
 				h1m->state = (!chksz ? H1_MSG_TRAILERS : H1_MSG_DATA);
 			}
@@ -541,7 +817,7 @@
 					ret = h1m->curr_len;
 				h1m->curr_len -= ret;
 				max -= ret;
-				ofs += ret;
+				*ofs += ret;
 				total += ret;
 				if (h1m->curr_len)
 					goto end;
@@ -550,11 +826,11 @@
 			}
 
 			if (h1m->state == H1_MSG_TRAILERS) {
-				ret = h1_measure_trailers(buf, ofs, ofs + max);
+				ret = h1_measure_trailers(buf, *ofs, *ofs + max);
 				if (ret <= 0)
 					goto end;
 				max -= ret;
-				ofs += ret;
+				*ofs += ret;
 				total += ret;
 				h1m->state = H1_MSG_DONE;
 			}
@@ -568,14 +844,15 @@
 	}
 	else {
 		/* no content length, read till SHUTW */
+		*ofs += max;
 		total = max;
 	}
 
   end:
 	if (ret < 0) {
-		h1s->flags |= H1S_F_ERROR;
+		h1s->flags |= (!(h1m->flags & H1_MF_RESP) ? H1S_F_REQ_ERROR : H1S_F_RES_ERROR);
 		h1m->err_state = h1m->state;
-		h1m->err_pos = ofs + max + ret;
+		h1m->err_pos = *ofs + max + ret;
 		return 0;
 	}
 
@@ -590,27 +867,27 @@
  */
 static void h1_sync_messages(struct h1c *h1c)
 {
-	if (!h1c->h1s)
+	struct h1s *h1s = h1c->h1s;
+
+	if (!h1s)
 		return;
 
-	if (h1c->h1s->res.state >= H1_MSG_DONE &&
-	    (h1c->h1s->status < 200 && (h1c->h1s->status == 100 || h1c->h1s->status >= 102)) &&
-	    ((conn_is_back(h1c->conn) && !b_data(&h1c->obuf)) || !b_data(&h1c->h1s->rxbuf))) {
+	if (h1s->res.state == H1_MSG_DONE &&
+	    (h1s->status < 200 && (h1s->status == 100 || h1s->status >= 102)) &&
+	    ((conn_is_back(h1c->conn) && !b_data(&h1c->obuf)) || !b_data(&h1s->rxbuf))) {
 		/* For 100-Continue response or any other informational 1xx
 		 * response which is non-final, don't reset the request, the
 		 * transaction is not finished. We take care the response was
 		 * transferred before.
 		 */
-		h1m_init_res(&h1c->h1s->res);
+		h1m_init_res(&h1s->res);
 	}
-	else if (!b_data(&h1c->h1s->rxbuf) && !b_data(&h1c->obuf) &&
-		 h1c->h1s->req.state >= H1_MSG_DONE && h1c->h1s->res.state >= H1_MSG_DONE) {
-		h1m_init_req(&h1c->h1s->req);
-		h1m_init_res(&h1c->h1s->res);
-
-		// TODO: For now, the Keep-alive timeout is handled by the stream.
-		//if (h1c->task && !conn_is_back(h1c->conn))
-		//	h1c->http_exp = tick_add_ifset(now_ms, h1c->px->timeout.httpka);
+	else if (!b_data(&h1s->rxbuf) && !b_data(&h1c->obuf) &&
+		 h1s->req.state == H1_MSG_DONE && h1s->res.state == H1_MSG_DONE) {
+		if (h1s->flags & H1S_F_WANT_TUN) {
+			h1s->req.state = H1_MSG_TUNNEL;
+			h1s->res.state = H1_MSG_TUNNEL;
+		}
 	}
 }
 
@@ -621,20 +898,27 @@
  */
 static size_t h1_process_input(struct h1c *h1c, struct buffer *buf, size_t count)
 {
-	struct h1s *h1s = h1c->h1s;
-	struct conn_stream *cs = NULL;
+	struct h1s *h1s = NULL;
 	struct h1m *h1m;
 	size_t total = 0;
 	size_t ret = 0;
+	size_t max;
+	int errflag;
 
 	if (h1c->flags & H1C_F_CS_ERROR)
 		goto end;
 
-	if (!h1s) {
-		h1s = h1s_create(h1c);
-		if (h1s == NULL)
-			goto err;
-	}
+	/* Create a new H1S without CS if not already done */
+	if (!h1c->h1s && !h1s_create(h1c, NULL))
+		goto err;
+	h1s = h1c->h1s;
+
+#if 0
+	// FIXME: Use a proxy option to enable early creation of the CS
+	/* Create the CS if not already attached to the H1S */
+	if (!h1s->cs && !h1s_new_cs(h1s))
+		goto err;
+#endif
 
 	if (!h1_get_buf(h1c, &h1s->rxbuf)) {
 		h1c->flags |= H1C_F_RX_ALLOC;
@@ -643,32 +927,28 @@
 
 	if (count > b_room(&h1s->rxbuf))
 		count = b_room(&h1s->rxbuf);
+	max = count;
 
-	h1m   = (!conn_is_back(h1c->conn) ? &h1s->req : &h1s->res);
-	while (h1m->state < H1_MSG_DONE && count) {
+	if (!conn_is_back(h1c->conn)) {
+		h1m = &h1s->req;
+		errflag = H1S_F_REQ_ERROR;
+	}
+	else {
+		h1m = &h1s->res;
+		errflag = H1S_F_RES_ERROR;
+	}
+	while (!(h1s->flags & errflag) && max) {
 		if (h1m->state <= H1_MSG_LAST_LF) {
-			if (h1m->state == H1_MSG_RQBEFORE) {
-				if (h1c->task && !conn_is_back(h1c->conn))
-					if (!h1s->cs)
-						h1c->http_exp = tick_add_ifset(now_ms, h1c->px->timeout.httpreq);
-			}
-			ret = h1_process_headers(h1s, h1m, buf, total, count);
+			ret = h1_process_headers(h1s, h1m, buf, &total, max);
 			if (!ret)
 				break;
 
-			/* Create the CS if not already attached to the H1S */
-			if (!h1s->cs) {
-				cs = cs_new(h1c->conn);
-				if (!cs)
-					goto err;
-				h1s->cs = cs;
-				cs->ctx = h1s;
-				if (stream_create_from_cs(cs) < 0)
-					goto err;
-			}
+			/* Reset request timeout */
+			h1s->h1c->http_exp = TICK_ETERNITY;
 
-			if (h1c->task && !conn_is_back(h1c->conn))
-				h1c->http_exp = TICK_ETERNITY;
+			/* Create the CS if not already attached to the H1S */
+			if (!h1s->cs && !h1s_new_cs(h1s))
+				goto err;
 		}
 		else if (h1m->state <= H1_MSG_TRAILERS) {
 			/* Do not parse the body if the header part is not yet
@@ -676,23 +956,26 @@
 			 */
 			if (!(h1s->flags & H1S_F_MSG_XFERED))
 				break;
-			ret = h1_process_data(h1s, h1m, buf, total, count);
+			ret = h1_process_data(h1s, h1m, buf, &total, max);
 			if (!ret)
 				break;
 		}
+		else if (h1m->state == H1_MSG_DONE)
+			break;
+		else if (h1m->state == H1_MSG_TUNNEL) {
+			total += max;
+			max = 0;
+			break;
+		}
 		else {
-			h1s->flags |= H1S_F_ERROR;
+			h1s->flags |= errflag;
 			break;
 		}
 
-		total += ret;
-		count -= ret;
-
-		if ((h1s->flags & H1S_F_ERROR))
-			break;
+		max -= ret;
 	}
 
-	if (h1s->flags & H1S_F_ERROR) {
+	if (h1s->flags & errflag) {
 		/* For now, if an error occurred during the message parsing when
 		 * a stream is already attached to the mux, we transfer
 		 * everything to let the stream handle the error itself. We
@@ -707,25 +990,23 @@
 			h1_cpy_error_message(h1c, &h1c->obuf, 400);
 			goto err;
 		}
-		total += count;
+		total += max;
+		max = 0;
 	}
 
-	ret = b_xfer(&h1s->rxbuf, buf, total);
+	b_xfer(&h1s->rxbuf, buf, total);
 
 	if (b_data(&h1s->rxbuf)) {
 		h1s->cs->flags |= CS_FL_RCV_MORE;
 		if (b_full(&h1s->rxbuf))
 			h1c->flags |= H1C_F_RX_FULL;
 	}
-
+	ret = count - max;
   end:
 	return ret;
 
   err:
-	if (cs)
-		cs_free(cs);
-	if (h1s)
-		h1s_destroy(h1s);
+	h1s_destroy(h1s);
 	h1c->flags |= H1C_F_CS_ERROR;
 	sess_log(h1c->conn->owner);
 	ret = 0;
@@ -741,8 +1022,10 @@
 {
 	struct h1s *h1s = h1c->h1s;
 	struct h1m *h1m;
+	size_t max;
 	size_t total = 0;
 	size_t ret = 0;
+	int errflag;
 
 	if (!h1_get_buf(h1c, &h1c->obuf)) {
 		h1c->flags |= H1C_F_OUT_ALLOC;
@@ -751,42 +1034,54 @@
 	if (count > b_room(&h1c->obuf))
 		count = b_room(&h1c->obuf);
 
-	h1m  = (!conn_is_back(h1c->conn) ? &h1s->res : &h1s->req);
-	while (h1m->state < H1_MSG_DONE && count) {
+	max = count;
+	if (!conn_is_back(h1c->conn)) {
+		h1m = &h1s->res;
+		errflag = H1S_F_RES_ERROR;
+	}
+	else {
+		h1m = &h1s->req;
+		errflag = H1S_F_REQ_ERROR;
+	}
+	while (!(h1s->flags & errflag) && max) {
 		if (h1m->state <= H1_MSG_LAST_LF) {
-			ret = h1_process_headers(h1s, h1m, buf, total, count);
+			ret = h1_process_headers(h1s, h1m, buf, &total, max);
 			if (!ret) {
 				/* incomplete or invalid response, this is abnormal coming from
 				 * haproxy and may only result in a bad errorfile or bad Lua code
 				 * so that won't be fixed, raise an error now.
 				 */
-				h1s->flags |= H1S_F_ERROR;
+				h1s->flags |= errflag;
 				break;
 			}
 		}
 		else if (h1m->state <= H1_MSG_TRAILERS) {
-			ret = h1_process_data(h1s, h1m, buf, total, count);
+			ret = h1_process_data(h1s, h1m, buf, &total, max);
 			if (!ret)
 				break;
 		}
+		else if (h1m->state == H1_MSG_DONE)
+			break;
+		else if (h1m->state == H1_MSG_TUNNEL) {
+			total += max;
+			max = 0;
+			break;
+		}
 		else {
-			h1s->flags |= H1S_F_ERROR;
+			h1s->flags |= errflag;
 			break;
 		}
 
-		total += ret;
-		count -= ret;
-
-		if ((h1s->flags & H1S_F_ERROR))
-			break;
+		max -= ret;
 	}
 
 	// TODO: Handle H1S errors
-	ret = b_xfer(&h1c->obuf, buf, total);
+	b_xfer(&h1c->obuf, buf, total);
 
 	if (b_full(&h1c->obuf))
 		h1c->flags |= H1C_F_OUT_FULL;
-  end:
+	ret = count - max;
+ end:
 	return ret;
 }
 
@@ -893,7 +1188,6 @@
 	if (ret > 0) {
 		h1c->flags &= ~H1C_F_OUT_FULL;
 		b_del(&h1c->obuf, ret);
-		h1_sync_messages(h1c);
 		sent = 1;
 	}
 
@@ -901,6 +1195,7 @@
 	/* We're done, no more to send */
 	if (!b_data(&h1c->obuf)) {
 		h1_release_buf(h1c, &h1c->obuf);
+		h1_sync_messages(h1c);
 		if (h1c->flags & H1C_F_CS_SHUTW_NOW)
 			h1_shutw_conn(conn);
 	}
@@ -976,13 +1271,20 @@
 		}
 	}
 
-	if (h1c->task && !conn_is_back(conn)) {
-		if (!h1c->h1s || !h1c->h1s->cs)
-			h1c->idle_exp = tick_add_ifset(now_ms, h1c->px->timeout.client);
-		else
-			h1c->idle_exp =  TICK_ETERNITY;
-		h1c->task->expire = tick_first(h1c->http_exp, h1c->idle_exp);
+	/* If there is a stream attached to the mux, let it
+	 * handle the timeout.
+	 */
+	if (h1c->h1s && h1c->h1s->cs)
+		h1c->idle_exp = TICK_ETERNITY;
+	else {
+		int tout = (!conn_is_back(conn)
+			    ? h1c->px->timeout.client
+			    : h1c->px->timeout.server);
+		h1c->idle_exp = tick_add_ifset(now_ms, tout);
 	}
+	h1c->task->expire = tick_first(h1c->http_exp, h1c->idle_exp);
+	if (tick_isset(h1c->task->expire))
+		task_queue(h1c->task);
 	return 0;
 }
 
@@ -1021,27 +1323,44 @@
 		goto end;
 
 	if (!expired) {
-		/* For now, do not handle timeout for server-side mux */
-		if (!conn_is_back(h1c->conn))
-			t->expire = tick_first(t->expire, tick_first(h1c->idle_exp, h1c->http_exp));
+		t->expire = tick_first(t->expire, tick_first(h1c->idle_exp, h1c->http_exp));
 		return t;
 	}
 
-	if (!(h1c->px->options & PR_O_IGNORE_PRB) && h1_get_buf(h1c, &h1c->obuf)) {
-		// TODO: do not send error if ka timeout
-		h1_cpy_error_message(h1c, &h1c->obuf, 408);
-		h1c->flags   |= H1C_F_CS_ERROR;
-		h1c->idle_exp = TICK_ETERNITY;
-		h1c->http_exp = TICK_ETERNITY;
-		t->expire     = TICK_ETERNITY;
+	h1c->flags   |= H1C_F_CS_ERROR;
+	h1c->idle_exp = TICK_ETERNITY;
+	h1c->http_exp = TICK_ETERNITY;
+	t->expire     = TICK_ETERNITY;
+
+	/* Don't try send error message on the server-side */
+	if (conn_is_back(h1c->conn))
+		goto release;
+
+	/* Don't send error message if no input data is pending _AND_ if null
+	 * requests is ignored or it's not the first request.
+	 */
+	if (!b_data(&h1c->ibuf) && (h1c->px->options & PR_O_IGNORE_PRB ||
+				    h1c->flags & H1C_F_WAIT_NEXT_REQ))
+		goto release;
+
+	/* Try to allocate output buffer to store the error message. If
+	 * allocation fails, just go away.
+	 */
+	if (!h1_get_buf(h1c, &h1c->obuf))
+		goto release;
+
+	h1_cpy_error_message(h1c, &h1c->obuf, 408);
+	tasklet_wakeup(h1c->wait_event.task);
+	sess_log(h1c->conn->owner);
+	return t;
+
+  release:
+	if (h1c->h1s) {
 		tasklet_wakeup(h1c->wait_event.task);
-		sess_log(h1c->conn->owner);
 		return t;
 	}
-
 	h1c->task = NULL;
-	if (!h1c->h1s || !h1c->h1s->cs)
-		h1_release(h1c->conn);
+	h1_release(h1c->conn);
   end:
 	task_delete(t);
 	task_free(t);
@@ -1068,7 +1387,7 @@
 	if (!cs)
 		goto end;
 
-	h1s = h1s_create(h1c);
+	h1s = h1s_create(h1c, cs);
 	if (h1s == NULL)
 		goto end;
 
@@ -1133,6 +1452,9 @@
 	if (!h1s)
 		return;
 
+	if ((h1s->flags & H1S_F_WANT_KAL) && !(cs->flags & (CS_FL_REOS|CS_FL_EOS)))
+		return;
+
 	/* NOTE: Be sure to handle abort (cf. h2_shutr) */
 	if (cs->flags & CS_FL_SHR)
 		return;
@@ -1153,6 +1475,11 @@
 		return;
 	h1c = h1s->h1c;
 
+	if ((h1s->flags & H1S_F_WANT_KAL) &&
+	    !(cs->flags & (CS_FL_REOS|CS_FL_EOS)) &&
+	    h1s->req.state == H1_MSG_DONE && h1s->res.state == H1_MSG_DONE)
+		return;
+
 	h1c->flags |= H1C_F_CS_SHUTW_NOW;
 	if ((cs->flags & CS_FL_SHW) || b_data(&h1c->obuf))
 		return;
diff --git a/src/proto_http.c b/src/proto_http.c
index face0bf..bd4221a 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -790,6 +790,9 @@
 	struct proxy *fe = strm_fe(s);
 	int tmp = TX_CON_WANT_KAL;
 
+	if (IS_HTX_STRM(s))
+		return htx_adjust_conn_mode(s, txn, msg);
+
 	if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_TUN ||
 	    (s->be->options & PR_O_HTTP_MODE) == PR_O_HTTP_TUN)
 		tmp = TX_CON_WANT_TUN;
@@ -2563,6 +2566,9 @@
 	struct buffer *chunk;
 	int ret = 0;
 
+	if (IS_HTX_STRM(s))
+		return htx_apply_redirect_rule(rule, s, txn);
+
 	chunk = alloc_trash_chunk();
 	if (!chunk)
 		goto leave;
@@ -7657,8 +7663,11 @@
 {
 	struct http_txn *txn = s->txn;
 	struct proxy *fe = strm_fe(s);
+	struct conn_stream *cs = objt_cs(s->si[0].end);
 
-	txn->flags = 0;
+	txn->flags = ((cs && cs->flags & CS_FL_NOT_FIRST)
+		      ? (TX_NOT_FIRST|TX_WAIT_NEXT_RQ)
+		      : 0);
 	txn->status = -1;
 
 	txn->cookie_first_date = 0;
diff --git a/src/proto_htx.c b/src/proto_htx.c
index dbe0890..4eb22bc 100644
--- a/src/proto_htx.c
+++ b/src/proto_htx.c
@@ -30,6 +30,10 @@
 #include <proto/stream_interface.h>
 #include <proto/stats.h>
 
+
+static void htx_end_request(struct stream *s);
+static void htx_end_response(struct stream *s);
+
 /* This stream analyser waits for a complete HTTP request. It returns 1 if the
  * processing can continue on next analysers, or zero if it either needs more
  * data or wants to immediately abort the request (eg: timeout, error, ...). It
@@ -132,7 +136,6 @@
 		}
 	}
 
-
 	/*
 	 * Now we quickly check if we have found a full valid request.
 	 * If not so, we check the FD and buffer states before leaving.
@@ -148,7 +151,6 @@
 	 * a timeout or connection reset is not counted as an error. However
 	 * a bad request is.
 	 */
-
 	if (unlikely(msg->msg_state < HTTP_MSG_BODY)) {
 		/*
 		 * First, let's catch bad requests.
@@ -462,9 +464,6 @@
 	      (ci_head(req)[msg->sl.rq.v + 7] >= '1'))))
 		msg->flags |= HTTP_MSGF_VER_11;
 
-	/* "connection" has not been parsed yet */
-	txn->flags &= ~(TX_HDR_CONN_PRS | TX_HDR_CONN_CLO | TX_HDR_CONN_KAL | TX_HDR_CONN_UPG);
-
 	/* if the frontend has "option http-use-proxy-header", we'll check if
 	 * we have what looks like a proxied connection instead of a connection,
 	 * and in this case set the TX_USE_PX_CONN flag to use Proxy-connection.
@@ -612,9 +611,8 @@
 	 * one is non-null, or one of them is non-null and we are there for the first
 	 * time.
 	 */
-	if (!(txn->flags & TX_HDR_CONN_PRS) ||
-	    ((sess->fe->options & PR_O_HTTP_MODE) != (s->be->options & PR_O_HTTP_MODE)))
-		http_adjust_conn_mode(s, txn, msg);
+	if ((sess->fe->options & PR_O_HTTP_MODE) != (s->be->options & PR_O_HTTP_MODE))
+		htx_adjust_conn_mode(s, txn, msg);
 
 	/* we may have to wait for the request's body */
 	if ((s->be->options & PR_O_WREQ_BODY) &&
@@ -846,7 +844,7 @@
 			if (!ret)
 				continue;
 		}
-		if (!http_apply_redirect_rule(rule, s, txn))
+		if (!htx_apply_redirect_rule(rule, s, txn))
 			goto return_bad_req;
 		goto done;
 	}
@@ -1203,31 +1201,8 @@
 					goto return_bad_req;
 			}
 		}
-	}
-
-	/* 11: add "Connection: close" or "Connection: keep-alive" if needed and not yet set.
-	 * If an "Upgrade" token is found, the header is left untouched in order not to have
-	 * to deal with some servers bugs : some of them fail an Upgrade if anything but
-	 * "Upgrade" is present in the Connection header.
-	 */
-	if (!(txn->flags & TX_HDR_CONN_UPG) && (txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) {
-		unsigned int want_flags = 0;
-
-		if (msg->flags & HTTP_MSGF_VER_11) {
-			if ((txn->flags & TX_CON_WANT_MSK) >= TX_CON_WANT_SCL &&
-			    !((sess->fe->options2|s->be->options2) & PR_O2_FAKE_KA))
-				want_flags |= TX_CON_CLO_SET;
-		} else {
-			if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL ||
-			    ((sess->fe->options2|s->be->options2) & PR_O2_FAKE_KA))
-				want_flags |= TX_CON_KAL_SET;
-		}
-
-		if (want_flags != (txn->flags & (TX_CON_CLO_SET|TX_CON_KAL_SET)))
-			http_change_connection_header(txn, msg, want_flags);
 	}
 
-
 	/* If we have no server assigned yet and we're balancing on url_param
 	 * with a POST request, we may be interested in checking the body for
 	 * that parameter. This will be done in another analyser.
@@ -1527,7 +1502,8 @@
 		 */
 		msg->err_state = msg->msg_state;
 		msg->msg_state = HTTP_MSG_ERROR;
-		http_resync_states(s);
+		htx_end_request(s);
+		htx_end_response(s);
 		return 1;
 	}
 
@@ -1586,8 +1562,9 @@
 	if ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN)
 		channel_dont_close(req);
 
-	http_resync_states(s);
+	htx_end_request(s);
 	if (!(req->analysers & an_bit)) {
+		htx_end_response(s);
 		if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) {
 			if (req->flags & CF_SHUTW) {
 				/* request errors are most likely due to the
@@ -2054,9 +2031,6 @@
 	     ((ci_head(rep)[5] == '1') && (ci_head(rep)[7] >= '1'))))
 		msg->flags |= HTTP_MSGF_VER_11;
 
-	/* "connection" has not been parsed yet */
-	txn->flags &= ~(TX_HDR_CONN_PRS|TX_HDR_CONN_CLO|TX_HDR_CONN_KAL|TX_HDR_CONN_UPG|TX_CON_CLO_SET|TX_CON_KAL_SET);
-
 	/* transfer length unknown*/
 	msg->flags &= ~HTTP_MSGF_XFER_LEN;
 
@@ -2226,7 +2200,7 @@
 	    (txn->status >= 100 && txn->status < 200) ||
 	    txn->status == 204 || txn->status == 304) {
 		msg->flags |= HTTP_MSGF_XFER_LEN;
-		goto skip_content_length;
+		goto end;
 	}
 
 	use_close_only = 0;
@@ -2275,56 +2249,6 @@
 		msg->body_len = msg->chunk_len = cl;
 	}
 
- skip_content_length:
-	/* Now we have to check if we need to modify the Connection header.
-	 * This is more difficult on the response than it is on the request,
-	 * because we can have two different HTTP versions and we don't know
-	 * how the client will interprete a response. For instance, let's say
-	 * that the client sends a keep-alive request in HTTP/1.0 and gets an
-	 * HTTP/1.1 response without any header. Maybe it will bound itself to
-	 * HTTP/1.0 because it only knows about it, and will consider the lack
-	 * of header as a close, or maybe it knows HTTP/1.1 and can consider
-	 * the lack of header as a keep-alive. Thus we will use two flags
-	 * indicating how a request MAY be understood by the client. In case
-	 * of multiple possibilities, we'll fix the header to be explicit. If
-	 * ambiguous cases such as both close and keepalive are seen, then we
-	 * will fall back to explicit close. Note that we won't take risks with
-	 * HTTP/1.0 clients which may not necessarily understand keep-alive.
-	 * See doc/internals/connection-header.txt for the complete matrix.
-	 */
-	if ((txn->status >= 200) && !(txn->flags & TX_HDR_CONN_PRS) &&
-	    (txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) {
-		int to_del = 0;
-
-		/* on unknown transfer length, we must close */
-		if (!(msg->flags & HTTP_MSGF_XFER_LEN))
-			txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_CLO;
-
-		/* now adjust header transformations depending on current state */
-		if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_CLO) {
-			to_del |= 2; /* remove "keep-alive" on any response */
-			if (!(msg->flags & HTTP_MSGF_VER_11))
-				to_del |= 1; /* remove "close" for HTTP/1.0 responses */
-		}
-		else { /* SCL / KAL */
-			to_del |= 1; /* remove "close" on any response */
-			if (txn->req.flags & msg->flags & HTTP_MSGF_VER_11)
-				to_del |= 2; /* remove "keep-alive" on pure 1.1 responses */
-		}
-
-		/* Parse and remove some headers from the connection header */
-		http_parse_connection_header(txn, msg, to_del);
-
-		/* Some keep-alive responses are converted to Server-close if
-		 * the server wants to close.
-		 */
-		if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL) {
-			if ((txn->flags & TX_HDR_CONN_CLO) ||
-			    (!(txn->flags & TX_HDR_CONN_KAL) && !(msg->flags & HTTP_MSGF_VER_11)))
-				txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_SCL;
-		}
-	}
-
  end:
 	/* we want to have the response time before we start processing it */
 	s->logs.t_data = tv_ms_elapsed(&s->logs.tv_accept, &now);
@@ -2384,7 +2308,7 @@
 	if (unlikely(objt_applet(s->target) == &http_stats_applet)) {
 		rep->analysers &= ~an_bit;
 		rep->analyse_exp = TICK_ETERNITY;
-		goto skip_filters;
+		goto end;
 	}
 
 	/*
@@ -2502,7 +2426,7 @@
 
 	/* OK that's all we can do for 1xx responses */
 	if (unlikely(txn->status < 200 && txn->status != 101))
-		goto skip_header_mangling;
+		goto end;
 
 	/*
 	 * Now check for a server cookie.
@@ -2626,40 +2550,7 @@
 		goto return_srv_prx_502;
 	}
 
- skip_filters:
-	/*
-	 * Adjust "Connection: close" or "Connection: keep-alive" if needed.
-	 * If an "Upgrade" token is found, the header is left untouched in order
-	 * not to have to deal with some client bugs : some of them fail an upgrade
-	 * if anything but "Upgrade" is present in the Connection header. We don't
-	 * want to touch any 101 response either since it's switching to another
-	 * protocol.
-	 */
-	if ((txn->status != 101) && !(txn->flags & TX_HDR_CONN_UPG) &&
-	    (txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) {
-		unsigned int want_flags = 0;
-
-		if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL ||
-		    (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL) {
-			/* we want a keep-alive response here. Keep-alive header
-			 * required if either side is not 1.1.
-			 */
-			if (!(txn->req.flags & msg->flags & HTTP_MSGF_VER_11))
-				want_flags |= TX_CON_KAL_SET;
-		}
-		else { /* CLO */
-			/* we want a close response here. Close header required if
-			 * the server is 1.1, regardless of the client.
-			 */
-			if (msg->flags & HTTP_MSGF_VER_11)
-				want_flags |= TX_CON_CLO_SET;
-		}
-
-		if (want_flags != (txn->flags & (TX_CON_CLO_SET|TX_CON_KAL_SET)))
-			http_change_connection_header(txn, msg, want_flags);
-	}
-
- skip_header_mangling:
+ end:
 	/* Always enter in the body analyzer */
 	rep->analysers &= ~AN_RES_FLT_XFER_DATA;
 	rep->analysers |= AN_RES_HTTP_XFER_BODY;
@@ -2726,14 +2617,14 @@
 		return 0;
 
 	if ((res->flags & (CF_READ_ERROR|CF_READ_TIMEOUT|CF_WRITE_ERROR|CF_WRITE_TIMEOUT)) ||
-	    ((res->flags & CF_SHUTW) && (res->to_forward || co_data(res))) ||
-	     !s->req.analysers) {
+	    ((res->flags & CF_SHUTW) && (res->to_forward || co_data(res)))) {
 		/* Output closed while we were sending data. We must abort and
 		 * wake the other side up.
 		 */
 		msg->err_state = msg->msg_state;
 		msg->msg_state = HTTP_MSG_ERROR;
-		http_resync_states(s);
+		htx_end_response(s);
+		htx_end_request(s);
 		return 1;
 	}
 
@@ -2763,13 +2654,9 @@
 	}
 
 	/* other states, DONE...TUNNEL */
-	/* for keep-alive we don't want to forward closes on DONE */
-	if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL ||
-	    (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL)
-		channel_dont_close(res);
-
-	http_resync_states(s);
+	htx_end_response(s);
 	if (!(res->analysers & an_bit)) {
+		htx_end_request(s);
 		if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) {
 			if (res->flags & CF_SHUTW) {
 				/* response errors are most likely due to the
@@ -2807,20 +2694,12 @@
 		}
 	}
 
-	/* we need to obey the req analyser, so if it leaves, we must too */
-	if (!s->req.analysers)
-		goto return_bad_res;
-
 	/* When TE: chunked is used, we need to get there again to parse
 	 * remaining chunks even if the server has closed, so we don't want to
-	 * set CF_DONTCLOSE. Similarly, if keep-alive is set on the client side
-	 * or if there are filters registered on the stream, we don't want to
-	 * forward a close
+	 * set CF_DONTCLOSE. Similarly, if there are filters registered on the
+	 * stream, we don't want to forward a close
 	 */
-	if ((msg->flags & HTTP_MSGF_TE_CHNK) ||
-	    HAS_DATA_FILTERS(s, res) ||
-	    (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL ||
-	    (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL)
+	if ((msg->flags & HTTP_MSGF_TE_CHNK) || HAS_DATA_FILTERS(s, res))
 		channel_dont_close(res);
 
 	/* We know that more data are expected, but we couldn't send more that
@@ -2878,6 +2757,534 @@
 	return 0;
 }
 
+void htx_adjust_conn_mode(struct stream *s, struct http_txn *txn, struct http_msg *msg)
+{
+	struct proxy *fe = strm_fe(s);
+	int tmp = TX_CON_WANT_CLO;
+
+	if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_TUN)
+		tmp = TX_CON_WANT_TUN;
+
+	if ((txn->flags & TX_CON_WANT_MSK) < tmp)
+		txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_CLO;
+}
+
+/* Perform an HTTP redirect based on the information in <rule>. The function
+ * returns non-zero on success, or zero in case of a, irrecoverable error such
+ * as too large a request to build a valid response.
+ */
+int htx_apply_redirect_rule(struct redirect_rule *rule, struct stream *s, struct http_txn *txn)
+{
+	struct http_msg *req = &txn->req;
+	struct http_msg *res = &txn->rsp;
+	const char *msg_fmt;
+	struct buffer *chunk;
+	int ret = 0;
+
+	chunk = alloc_trash_chunk();
+	if (!chunk)
+		goto leave;
+
+	/* build redirect message */
+	switch(rule->code) {
+	case 308:
+		msg_fmt = HTTP_308;
+		break;
+	case 307:
+		msg_fmt = HTTP_307;
+		break;
+	case 303:
+		msg_fmt = HTTP_303;
+		break;
+	case 301:
+		msg_fmt = HTTP_301;
+		break;
+	case 302:
+	default:
+		msg_fmt = HTTP_302;
+		break;
+	}
+
+	if (unlikely(!chunk_strcpy(chunk, msg_fmt)))
+		goto leave;
+
+	switch(rule->type) {
+	case REDIRECT_TYPE_SCHEME: {
+		const char *path;
+		const char *host;
+		struct hdr_ctx ctx;
+		int pathlen;
+		int hostlen;
+
+		host = "";
+		hostlen = 0;
+		ctx.idx = 0;
+		if (http_find_header2("Host", 4, ci_head(req->chn), &txn->hdr_idx, &ctx)) {
+			host = ctx.line + ctx.val;
+			hostlen = ctx.vlen;
+		}
+
+		path = http_txn_get_path(txn);
+		/* build message using path */
+		if (path) {
+			pathlen = req->sl.rq.u_l + (ci_head(req->chn) + req->sl.rq.u) - path;
+			if (rule->flags & REDIRECT_FLAG_DROP_QS) {
+				int qs = 0;
+				while (qs < pathlen) {
+					if (path[qs] == '?') {
+						pathlen = qs;
+						break;
+					}
+					qs++;
+				}
+			}
+		} else {
+			path = "/";
+			pathlen = 1;
+		}
+
+		if (rule->rdr_str) { /* this is an old "redirect" rule */
+			/* check if we can add scheme + "://" + host + path */
+			if (chunk->data + rule->rdr_len + 3 + hostlen + pathlen > chunk->size - 4)
+				goto leave;
+
+			/* add scheme */
+			memcpy(chunk->area + chunk->data, rule->rdr_str,
+			       rule->rdr_len);
+			chunk->data += rule->rdr_len;
+		}
+		else {
+			/* add scheme with executing log format */
+			chunk->data += build_logline(s,
+						    chunk->area + chunk->data,
+						    chunk->size - chunk->data,
+						    &rule->rdr_fmt);
+
+			/* check if we can add scheme + "://" + host + path */
+			if (chunk->data + 3 + hostlen + pathlen > chunk->size - 4)
+				goto leave;
+		}
+		/* add "://" */
+		memcpy(chunk->area + chunk->data, "://", 3);
+		chunk->data += 3;
+
+		/* add host */
+		memcpy(chunk->area + chunk->data, host, hostlen);
+		chunk->data += hostlen;
+
+		/* add path */
+		memcpy(chunk->area + chunk->data, path, pathlen);
+		chunk->data += pathlen;
+
+		/* append a slash at the end of the location if needed and missing */
+		if (chunk->data && chunk->area[chunk->data - 1] != '/' &&
+		    (rule->flags & REDIRECT_FLAG_APPEND_SLASH)) {
+			if (chunk->data > chunk->size - 5)
+				goto leave;
+			chunk->area[chunk->data] = '/';
+			chunk->data++;
+		}
+
+		break;
+	}
+	case REDIRECT_TYPE_PREFIX: {
+		const char *path;
+		int pathlen;
+
+		path = http_txn_get_path(txn);
+		/* build message using path */
+		if (path) {
+			pathlen = req->sl.rq.u_l + (ci_head(req->chn) + req->sl.rq.u) - path;
+			if (rule->flags & REDIRECT_FLAG_DROP_QS) {
+				int qs = 0;
+				while (qs < pathlen) {
+					if (path[qs] == '?') {
+						pathlen = qs;
+						break;
+					}
+					qs++;
+				}
+			}
+		} else {
+			path = "/";
+			pathlen = 1;
+		}
+
+		if (rule->rdr_str) { /* this is an old "redirect" rule */
+			if (chunk->data + rule->rdr_len + pathlen > chunk->size - 4)
+				goto leave;
+
+			/* add prefix. Note that if prefix == "/", we don't want to
+			 * add anything, otherwise it makes it hard for the user to
+			 * configure a self-redirection.
+			 */
+			if (rule->rdr_len != 1 || *rule->rdr_str != '/') {
+				memcpy(chunk->area + chunk->data,
+				       rule->rdr_str, rule->rdr_len);
+				chunk->data += rule->rdr_len;
+			}
+		}
+		else {
+			/* add prefix with executing log format */
+			chunk->data += build_logline(s,
+						    chunk->area + chunk->data,
+						    chunk->size - chunk->data,
+						    &rule->rdr_fmt);
+
+			/* Check length */
+			if (chunk->data + pathlen > chunk->size - 4)
+				goto leave;
+		}
+
+		/* add path */
+		memcpy(chunk->area + chunk->data, path, pathlen);
+		chunk->data += pathlen;
+
+		/* append a slash at the end of the location if needed and missing */
+		if (chunk->data && chunk->area[chunk->data - 1] != '/' &&
+		    (rule->flags & REDIRECT_FLAG_APPEND_SLASH)) {
+			if (chunk->data > chunk->size - 5)
+				goto leave;
+			chunk->area[chunk->data] = '/';
+			chunk->data++;
+		}
+
+		break;
+	}
+	case REDIRECT_TYPE_LOCATION:
+	default:
+		if (rule->rdr_str) { /* this is an old "redirect" rule */
+			if (chunk->data + rule->rdr_len > chunk->size - 4)
+				goto leave;
+
+			/* add location */
+			memcpy(chunk->area + chunk->data, rule->rdr_str,
+			       rule->rdr_len);
+			chunk->data += rule->rdr_len;
+		}
+		else {
+			/* add location with executing log format */
+			chunk->data += build_logline(s,
+						    chunk->area + chunk->data,
+						    chunk->size - chunk->data,
+						    &rule->rdr_fmt);
+
+			/* Check left length */
+			if (chunk->data > chunk->size - 4)
+				goto leave;
+		}
+		break;
+	}
+
+	if (rule->cookie_len) {
+		memcpy(chunk->area + chunk->data, "\r\nSet-Cookie: ", 14);
+		chunk->data += 14;
+		memcpy(chunk->area + chunk->data, rule->cookie_str,
+		       rule->cookie_len);
+		chunk->data += rule->cookie_len;
+	}
+
+	/* add end of headers and the keep-alive/close status. */
+	txn->status = rule->code;
+	/* let's log the request time */
+	s->logs.tv_request = now;
+
+	if (((!(req->flags & HTTP_MSGF_TE_CHNK) && !req->body_len) || (req->msg_state == HTTP_MSG_DONE))) {
+		/* keep-alive possible */
+		if (!(req->flags & HTTP_MSGF_VER_11)) {
+			if (unlikely(txn->flags & TX_USE_PX_CONN)) {
+				memcpy(chunk->area + chunk->data,
+				       "\r\nProxy-Connection: keep-alive", 30);
+				chunk->data += 30;
+			} else {
+				memcpy(chunk->area + chunk->data,
+				       "\r\nConnection: keep-alive", 24);
+				chunk->data += 24;
+			}
+		}
+		memcpy(chunk->area + chunk->data, "\r\n\r\n", 4);
+		chunk->data += 4;
+		FLT_STRM_CB(s, flt_http_reply(s, txn->status, chunk));
+		co_inject(res->chn, chunk->area, chunk->data);
+		/* "eat" the request */
+		b_del(&req->chn->buf, req->sov);
+		req->next -= req->sov;
+		req->sov = 0;
+		s->req.analysers = AN_REQ_HTTP_XFER_BODY | (s->req.analysers & AN_REQ_FLT_END);
+		s->res.analysers = AN_RES_HTTP_XFER_BODY | (s->res.analysers & AN_RES_FLT_END);
+		req->msg_state = HTTP_MSG_CLOSED;
+		res->msg_state = HTTP_MSG_DONE;
+		/* Trim any possible response */
+		b_set_data(&res->chn->buf, co_data(res->chn));
+		res->next = res->sov = 0;
+		/* let the server side turn to SI_ST_CLO */
+		channel_shutw_now(req->chn);
+	} else {
+		/* keep-alive not possible */
+		if (unlikely(txn->flags & TX_USE_PX_CONN)) {
+			memcpy(chunk->area + chunk->data,
+			       "\r\nProxy-Connection: close\r\n\r\n", 29);
+			chunk->data += 29;
+		} else {
+			memcpy(chunk->area + chunk->data,
+			       "\r\nConnection: close\r\n\r\n", 23);
+			chunk->data += 23;
+		}
+		http_reply_and_close(s, txn->status, chunk);
+		req->chn->analysers &= AN_REQ_FLT_END;
+	}
+
+	if (!(s->flags & SF_ERR_MASK))
+		s->flags |= SF_ERR_LOCAL;
+	if (!(s->flags & SF_FINST_MASK))
+		s->flags |= SF_FINST_R;
+
+	ret = 1;
+ leave:
+	free_trash_chunk(chunk);
+	return ret;
+}
+
+/* This function terminates the request because it was completly analyzed or
+ * because an error was triggered during the body forwarding.
+ */
+static void htx_end_request(struct stream *s)
+{
+	struct channel *chn = &s->req;
+	struct http_txn *txn = s->txn;
+
+	DPRINTF(stderr,"[%u] %s: stream=%p states=%s,%s req->analysers=0x%08x res->analysers=0x%08x\n",
+		now_ms, __FUNCTION__, s,
+		h1_msg_state_str(txn->req.msg_state), h1_msg_state_str(txn->rsp.msg_state),
+		s->req.analysers, s->res.analysers);
+
+	if (unlikely(txn->req.msg_state == HTTP_MSG_ERROR)) {
+		channel_abort(chn);
+		channel_truncate(chn);
+		goto end;
+	}
+
+	if (unlikely(txn->req.msg_state < HTTP_MSG_DONE))
+		return;
+
+	if (txn->req.msg_state == HTTP_MSG_DONE) {
+		if (txn->rsp.msg_state < HTTP_MSG_DONE) {
+			/* The server has not finished to respond, so we
+			 * don't want to move in order not to upset it.
+			 */
+			return;
+		}
+
+		/* No need to read anymore, the request was completely parsed.
+		 * We can shut the read side unless we want to abort_on_close,
+		 * or we have a POST request. The issue with POST requests is
+		 * that some browsers still send a CRLF after the request, and
+		 * this CRLF must be read so that it does not remain in the kernel
+		 * buffers, otherwise a close could cause an RST on some systems
+		 * (eg: Linux).
+		 */
+		if ((!(s->be->options & PR_O_ABRT_CLOSE) || (s->si[0].flags & SI_FL_CLEAN_ABRT)) &&
+		    txn->meth != HTTP_METH_POST)
+			channel_dont_read(chn);
+
+		/* if the server closes the connection, we want to immediately react
+		 * and close the socket to save packets and syscalls.
+		 */
+		s->si[1].flags |= SI_FL_NOHALF;
+
+		/* In any case we've finished parsing the request so we must
+		 * disable Nagle when sending data because 1) we're not going
+		 * to shut this side, and 2) the server is waiting for us to
+		 * send pending data.
+		 */
+		chn->flags |= CF_NEVER_WAIT;
+
+		/* When we get here, it means that both the request and the
+		 * response have finished receiving. Depending on the connection
+		 * mode, we'll have to wait for the last bytes to leave in either
+		 * direction, and sometimes for a close to be effective.
+		 */
+		if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_TUN) {
+			/* Tunnel mode will not have any analyser so it needs to
+			 * poll for reads.
+			 */
+			channel_auto_read(chn);
+			txn->req.msg_state = HTTP_MSG_TUNNEL;
+		}
+		else {
+			/* we're not expecting any new data to come for this
+			 * transaction, so we can close it.
+			 * However, there is an exception if the response length
+			 * is undefined. In this case, we need to wait the close
+			 * from the server. The response will be switched in
+			 * TUNNEL mode until the end.
+			 */
+			if (!(txn->rsp.flags & HTTP_MSGF_XFER_LEN) &&
+			    txn->rsp.msg_state != HTTP_MSG_CLOSED)
+				return;
+
+			if (!(chn->flags & (CF_SHUTW|CF_SHUTW_NOW))) {
+				channel_shutr_now(chn);
+				channel_shutw_now(chn);
+			}
+		}
+
+		goto check_channel_flags;
+	}
+
+	if (txn->req.msg_state == HTTP_MSG_CLOSING) {
+	  http_msg_closing:
+		/* nothing else to forward, just waiting for the output buffer
+		 * to be empty and for the shutw_now to take effect.
+		 */
+		if (channel_is_empty(chn)) {
+			txn->req.msg_state = HTTP_MSG_CLOSED;
+			goto http_msg_closed;
+		}
+		else if (chn->flags & CF_SHUTW) {
+			txn->req.err_state = txn->req.msg_state;
+			txn->req.msg_state = HTTP_MSG_ERROR;
+			goto end;
+		}
+		return;
+	}
+
+	if (txn->req.msg_state == HTTP_MSG_CLOSED) {
+	  http_msg_closed:
+
+		/* if we don't know whether the server will close, we need to hard close */
+		if (txn->rsp.flags & HTTP_MSGF_XFER_LEN)
+			s->si[1].flags |= SI_FL_NOLINGER;  /* we want to close ASAP */
+
+		/* see above in MSG_DONE why we only do this in these states */
+		if ((!(s->be->options & PR_O_ABRT_CLOSE) || (s->si[0].flags & SI_FL_CLEAN_ABRT)))
+			channel_dont_read(chn);
+		goto end;
+	}
+
+  check_channel_flags:
+	/* Here, we are in HTTP_MSG_DONE or HTTP_MSG_TUNNEL */
+	if (chn->flags & (CF_SHUTW|CF_SHUTW_NOW)) {
+		/* if we've just closed an output, let's switch */
+		txn->req.msg_state = HTTP_MSG_CLOSING;
+		goto http_msg_closing;
+	}
+
+  end:
+	chn->analysers &= AN_REQ_FLT_END;
+	if (txn->req.msg_state == HTTP_MSG_TUNNEL && HAS_REQ_DATA_FILTERS(s))
+			chn->analysers |= AN_REQ_FLT_XFER_DATA;
+	channel_auto_close(chn);
+	channel_auto_read(chn);
+}
+
+
+/* This function terminates the response because it was completly analyzed or
+ * because an error was triggered during the body forwarding.
+ */
+static void htx_end_response(struct stream *s)
+{
+	struct channel *chn = &s->res;
+	struct http_txn *txn = s->txn;
+
+	DPRINTF(stderr,"[%u] %s: stream=%p states=%s,%s req->analysers=0x%08x res->analysers=0x%08x\n",
+		now_ms, __FUNCTION__, s,
+		h1_msg_state_str(txn->req.msg_state), h1_msg_state_str(txn->rsp.msg_state),
+		s->req.analysers, s->res.analysers);
+
+	if (unlikely(txn->rsp.msg_state == HTTP_MSG_ERROR)) {
+		channel_abort(chn);
+		channel_truncate(chn);
+		goto end;
+	}
+
+	if (unlikely(txn->rsp.msg_state < HTTP_MSG_DONE))
+		return;
+
+	if (txn->rsp.msg_state == HTTP_MSG_DONE) {
+		/* In theory, we don't need to read anymore, but we must
+		 * still monitor the server connection for a possible close
+		 * while the request is being uploaded, so we don't disable
+		 * reading.
+		 */
+		/* channel_dont_read(chn); */
+
+		if (txn->req.msg_state < HTTP_MSG_DONE) {
+			/* The client seems to still be sending data, probably
+			 * because we got an error response during an upload.
+			 * We have the choice of either breaking the connection
+			 * or letting it pass through. Let's do the later.
+			 */
+			return;
+		}
+
+		/* When we get here, it means that both the request and the
+		 * response have finished receiving. Depending on the connection
+		 * mode, we'll have to wait for the last bytes to leave in either
+		 * direction, and sometimes for a close to be effective.
+		 */
+		if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_TUN) {
+			channel_auto_read(chn);
+			chn->flags |= CF_NEVER_WAIT;
+			txn->rsp.msg_state = HTTP_MSG_TUNNEL;
+		}
+		else {
+			/* we're not expecting any new data to come for this
+			 * transaction, so we can close it.
+			 */
+			if (!(chn->flags & (CF_SHUTW|CF_SHUTW_NOW))) {
+				channel_shutr_now(chn);
+				channel_shutw_now(chn);
+			}
+		}
+		goto check_channel_flags;
+	}
+
+	if (txn->rsp.msg_state == HTTP_MSG_CLOSING) {
+	  http_msg_closing:
+		/* nothing else to forward, just waiting for the output buffer
+		 * to be empty and for the shutw_now to take effect.
+		 */
+		if (channel_is_empty(chn)) {
+			txn->rsp.msg_state = HTTP_MSG_CLOSED;
+			goto http_msg_closed;
+		}
+		else if (chn->flags & CF_SHUTW) {
+			txn->rsp.err_state = txn->rsp.msg_state;
+			txn->rsp.msg_state = HTTP_MSG_ERROR;
+			HA_ATOMIC_ADD(&s->be->be_counters.cli_aborts, 1);
+			if (objt_server(s->target))
+				HA_ATOMIC_ADD(&objt_server(s->target)->counters.cli_aborts, 1);
+			goto end;
+		}
+		return;
+	}
+
+	if (txn->rsp.msg_state == HTTP_MSG_CLOSED) {
+	  http_msg_closed:
+		/* drop any pending data */
+		channel_truncate(chn);
+		channel_auto_close(chn);
+		channel_auto_read(chn);
+		goto end;
+	}
+
+  check_channel_flags:
+	/* Here, we are in HTTP_MSG_DONE or HTTP_MSG_TUNNEL */
+	if (chn->flags & (CF_SHUTW|CF_SHUTW_NOW)) {
+		/* if we've just closed an output, let's switch */
+		txn->rsp.msg_state = HTTP_MSG_CLOSING;
+		goto http_msg_closing;
+	}
+
+  end:
+	chn->analysers &= AN_RES_FLT_END;
+	if (txn->rsp.msg_state == HTTP_MSG_TUNNEL && HAS_RSP_DATA_FILTERS(s))
+		chn->analysers |= AN_RES_FLT_XFER_DATA;
+	channel_auto_close(chn);
+	channel_auto_read(chn);
+}
+
 __attribute__((constructor))
 static void __htx_protocol_init(void)
 {
diff --git a/src/stream.c b/src/stream.c
index ae8318d..515dc0d 100644
--- a/src/stream.c
+++ b/src/stream.c
@@ -666,8 +666,11 @@
 		return 1;
 	}
 
+	/* FIXME: Add CF_WROTE_DATA because data was already move in the mux in
+	 * h1. Without it, the SI remains in SI_ST_CON state.
+	 */
 	/* we need to wait a bit more if there was no activity either */
-	if (!(req->flags & CF_WRITE_ACTIVITY))
+	if (!(req->flags & (CF_WROTE_DATA|CF_WRITE_ACTIVITY)))
 		return 1;
 
 	/* OK, this means that a connection succeeded. The caller will be