MAJOR: http: move http_txn out of struct stream

Now this one is dynamically allocated. It means that 280 bytes of memory
are saved per TCP stream, but more importantly that it will become
possible to remove the l7 pointer from fetches and converters since
it will be deduced from the stream and will support being null.

A lot of care was taken because it's easy to forget a test somewhere,
and the previous code used to always trust s->txn for being valid, but
all places seem to have been visited.

All HTTP fetch functions check the txn first so we shouldn't have any
issue there even when called from TCP. When branching from a TCP frontend
to an HTTP backend, the txn is properly allocated at the same time as the
hdr_idx.
diff --git a/src/backend.c b/src/backend.c
index 2c2a61e..a8cf644 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -300,7 +300,7 @@
 struct server *get_server_ph_post(struct stream *s)
 {
 	unsigned int hash = 0;
-	struct http_txn *txn  = &s->txn;
+	struct http_txn *txn  = s->txn;
 	struct channel  *req  = &s->req;
 	struct http_msg *msg  = &txn->req;
 	struct proxy    *px   = s->be;
@@ -378,7 +378,7 @@
 struct server *get_server_hh(struct stream *s)
 {
 	unsigned int hash = 0;
-	struct http_txn *txn  = &s->txn;
+	struct http_txn *txn  = s->txn;
 	struct proxy    *px   = s->be;
 	unsigned int     plen = px->hh_len;
 	unsigned long    len;
@@ -553,7 +553,7 @@
 	if (conn &&
 	    (conn->flags & CO_FL_CONNECTED) &&
 	    objt_server(conn->target) && __objt_server(conn->target)->proxy == s->be &&
-	    ((s->txn.flags & TX_PREFER_LAST) ||
+	    ((s->txn && s->txn->flags & TX_PREFER_LAST) ||
 	     ((s->be->options & PR_O_PREF_LAST) &&
 	      (!s->be->max_ka_queue ||
 	       server_has_room(__objt_server(conn->target)) ||
@@ -627,29 +627,29 @@
 
 			case BE_LB_HASH_URI:
 				/* URI hashing */
-				if (s->txn.req.msg_state < HTTP_MSG_BODY)
+				if (!s->txn || s->txn->req.msg_state < HTTP_MSG_BODY)
 					break;
 				srv = get_server_uh(s->be,
-						    b_ptr(s->req.buf, -http_uri_rewind(&s->txn.req)),
-						    s->txn.req.sl.rq.u_l);
+						    b_ptr(s->req.buf, -http_uri_rewind(&s->txn->req)),
+						    s->txn->req.sl.rq.u_l);
 				break;
 
 			case BE_LB_HASH_PRM:
 				/* URL Parameter hashing */
-				if (s->txn.req.msg_state < HTTP_MSG_BODY)
+				if (!s->txn || s->txn->req.msg_state < HTTP_MSG_BODY)
 					break;
 
 				srv = get_server_ph(s->be,
-						    b_ptr(s->req.buf, -http_uri_rewind(&s->txn.req)),
-						    s->txn.req.sl.rq.u_l);
+						    b_ptr(s->req.buf, -http_uri_rewind(&s->txn->req)),
+						    s->txn->req.sl.rq.u_l);
 
-				if (!srv && s->txn.meth == HTTP_METH_POST)
+				if (!srv && s->txn->meth == HTTP_METH_POST)
 					srv = get_server_ph_post(s);
 				break;
 
 			case BE_LB_HASH_HDR:
 				/* Header Parameter hashing */
-				if (s->txn.req.msg_state < HTTP_MSG_BODY)
+				if (!s->txn || s->txn->req.msg_state < HTTP_MSG_BODY)
 					break;
 				srv = get_server_hh(s);
 				break;
@@ -861,9 +861,9 @@
 			 */
 
 			if (prev_srv != objt_server(s->target)) {
-				if ((s->txn.flags & TX_CK_MASK) == TX_CK_VALID) {
-					s->txn.flags &= ~TX_CK_MASK;
-					s->txn.flags |= TX_CK_DOWN;
+				if (s->txn && (s->txn->flags & TX_CK_MASK) == TX_CK_VALID) {
+					s->txn->flags &= ~TX_CK_MASK;
+					s->txn->flags |= TX_CK_DOWN;
 				}
 				s->flags |= SF_REDISP;
 				prev_srv->counters.redispatches++;
@@ -973,7 +973,7 @@
 			memset(&srv_conn->addr.from, 0, sizeof(srv_conn->addr.from));
 		break;
 	case CO_SRC_TPROXY_DYN:
-		if (src->bind_hdr_occ) {
+		if (src->bind_hdr_occ && s->txn) {
 			char *vptr;
 			int vlen;
 			int rewind;
@@ -983,9 +983,9 @@
 			((struct sockaddr_in *)&srv_conn->addr.from)->sin_port = 0;
 			((struct sockaddr_in *)&srv_conn->addr.from)->sin_addr.s_addr = 0;
 
-			b_rew(s->req.buf, rewind = http_hdr_rewind(&s->txn.req));
-			if (http_get_hdr(&s->txn.req, src->bind_hdr_name, src->bind_hdr_len,
-					 &s->txn.hdr_idx, src->bind_hdr_occ, NULL, &vptr, &vlen)) {
+			b_rew(s->req.buf, rewind = http_hdr_rewind(&s->txn->req));
+			if (http_get_hdr(&s->txn->req, src->bind_hdr_name, src->bind_hdr_len,
+					 &s->txn->hdr_idx, src->bind_hdr_occ, NULL, &vptr, &vlen)) {
 				((struct sockaddr_in *)&srv_conn->addr.from)->sin_addr.s_addr =
 					htonl(inetaddr_host_lim(vptr, vptr + vlen));
 			}
diff --git a/src/compression.c b/src/compression.c
index 0ae634f..50d41c1 100644
--- a/src/compression.c
+++ b/src/compression.c
@@ -184,7 +184,7 @@
  */
 int http_compression_buffer_add_data(struct stream *s, struct buffer *in, struct buffer *out)
 {
-	struct http_msg *msg = &s->txn.rsp;
+	struct http_msg *msg = &s->txn->rsp;
 	int consumed_data = 0;
 	int data_process_len;
 	int block1, block2;
@@ -234,7 +234,7 @@
 {
 	int to_forward;
 	int left;
-	struct http_msg *msg = &s->txn.rsp;
+	struct http_msg *msg = &s->txn->rsp;
 	struct buffer *ib = *in, *ob = *out;
 	char *tail;
 
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 2aed81a..c24f815 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -4422,13 +4422,13 @@
 	int reql;
 
 	temp = get_trash_chunk();
-	if (temp->size < s->txn.req.body_len) {
+	if (temp->size < s->txn->req.body_len) {
 		/* too large request */
 		appctx->ctx.stats.st_code = STAT_STATUS_EXCD;
 		goto out;
 	}
 
-	reql = bo_getblk(si_oc(si), temp->str, s->txn.req.body_len, s->txn.req.eoh + 2);
+	reql = bo_getblk(si_oc(si), temp->str, s->txn->req.body_len, s->txn->req.eoh + 2);
 	if (reql <= 0) {
 		/* we need more data */
 		appctx->ctx.stats.st_code = STAT_STATUS_NONE;
@@ -4742,7 +4742,7 @@
 	else
 		chunk_appendf(&trash, "\r\n");
 
-	s->txn.status = 200;
+	s->txn->status = 200;
 	s->logs.tv_request = now;
 
 	if (bi_putchk(si_ic(si), &trash) == -1) {
@@ -4789,7 +4789,7 @@
 		     (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
 		     scope_txt);
 
-	s->txn.status = 303;
+	s->txn->status = 303;
 	s->logs.tv_request = now;
 
 	if (bi_putchk(si_ic(si), &trash) == -1) {
@@ -4822,7 +4822,7 @@
 	/* all states are processed in sequence */
 	if (appctx->st0 == STAT_HTTP_HEAD) {
 		if (stats_send_http_headers(si)) {
-			if (s->txn.meth == HTTP_METH_HEAD)
+			if (s->txn->meth == HTTP_METH_HEAD)
 				appctx->st0 = STAT_HTTP_DONE;
 			else
 				appctx->st0 = STAT_HTTP_DUMP;
@@ -5134,10 +5134,11 @@
 			     " age=%s)\n",
 			     human_time(now.tv_sec - sess->logs.accept_date.tv_sec, 1));
 
-		chunk_appendf(&trash,
+		if (sess->txn)
+			chunk_appendf(&trash,
 			     "  txn=%p flags=0x%x meth=%d status=%d req.st=%s rsp.st=%s waiting=%d\n",
-			     &sess->txn, sess->txn.flags, sess->txn.meth, sess->txn.status,
-			      http_msg_state_str(sess->txn.req.msg_state), http_msg_state_str(sess->txn.rsp.msg_state), !LIST_ISEMPTY(&sess->buffer_wait));
+			      sess->txn, sess->txn->flags, sess->txn->meth, sess->txn->status,
+			      http_msg_state_str(sess->txn->req.msg_state), http_msg_state_str(sess->txn->rsp.msg_state), !LIST_ISEMPTY(&sess->buffer_wait));
 
 		chunk_appendf(&trash,
 			     "  si[0]=%p (state=%s flags=0x%02x endp0=%s:%p exp=%s, et=0x%03x)\n",
@@ -5247,7 +5248,7 @@
 			     sess->req.buf,
 			     sess->req.buf->data, sess->req.buf->o,
 			     (int)(sess->req.buf->p - sess->req.buf->data),
-			     sess->txn.req.next, sess->req.buf->i,
+			     sess->txn ? sess->txn->req.next : 0, sess->req.buf->i,
 			     sess->req.buf->size);
 
 		chunk_appendf(&trash,
@@ -5276,7 +5277,7 @@
 			     sess->res.buf,
 			     sess->res.buf->data, sess->res.buf->o,
 			     (int)(sess->res.buf->p - sess->res.buf->data),
-			     sess->txn.rsp.next, sess->res.buf->i,
+			     sess->txn ? sess->txn->rsp.next : 0, sess->res.buf->i,
 			     sess->res.buf->size);
 
 		if (bi_putchk(si_ic(si), &trash) == -1) {
diff --git a/src/frontend.c b/src/frontend.c
index 3a71bc4..3a28d6f 100644
--- a/src/frontend.c
+++ b/src/frontend.c
@@ -127,9 +127,7 @@
 		 * that we may make use of them. This of course includes
 		 * (mode == PR_MODE_HTTP).
 		 */
-		s->txn.hdr_idx.size = global.tune.max_http_hdr;
-
-		if (unlikely((s->txn.hdr_idx.v = pool_alloc2(pool2_hdr_idx)) == NULL))
+		if (unlikely(!http_alloc_txn(s)))
 			goto out_free_rspcap; /* no memory */
 
 		/* and now initialize the HTTP transaction state */
diff --git a/src/hlua.c b/src/hlua.c
index e54c029..43d9c1b 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -2169,13 +2169,7 @@
 	socket->s->res_cap = NULL;
 
 	/* XXX: See later. */
-	socket->s->txn.sessid = NULL;
-	socket->s->txn.srv_cookie = NULL;
-	socket->s->txn.cli_cookie = NULL;
-	socket->s->txn.uri = NULL;
-	socket->s->txn.hdr_idx.v = NULL;
-	socket->s->txn.hdr_idx.size = 0;
-	socket->s->txn.hdr_idx.used = 0;
+	socket->s->txn = NULL;
 
 	/* Configure "left" stream interface as applet. This "si" produce
 	 * and use the data received from the server. The applet is initialized
@@ -2971,17 +2965,20 @@
 	/* Create the table. */
 	lua_newtable(L);
 
+	if (!htxn->s->txn)
+		return 1;
+
 	/* Build array of headers. */
 	old_idx = 0;
-	cur_next = msg->chn->buf->p + hdr_idx_first_pos(&htxn->s->txn.hdr_idx);
+	cur_next = msg->chn->buf->p + hdr_idx_first_pos(&htxn->s->txn->hdr_idx);
 
 	while (1) {
-		cur_idx = htxn->s->txn.hdr_idx.v[old_idx].next;
+		cur_idx = htxn->s->txn->hdr_idx.v[old_idx].next;
 		if (!cur_idx)
 			break;
 		old_idx = cur_idx;
 
-		cur_hdr  = &htxn->s->txn.hdr_idx.v[cur_idx];
+		cur_hdr  = &htxn->s->txn->hdr_idx.v[cur_idx];
 		cur_ptr  = cur_next;
 		cur_next = cur_ptr + cur_hdr->len + cur_hdr->cr + 1;
 
@@ -3021,7 +3018,7 @@
 	MAY_LJMP(check_args(L, 1, "req_get_headers"));
 	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 
-	return hlua_http_get_headers(L, htxn, &htxn->s->txn.req);
+	return hlua_http_get_headers(L, htxn, &htxn->s->txn->req);
 }
 
 __LJMP static int hlua_http_res_get_headers(lua_State *L)
@@ -3031,7 +3028,7 @@
 	MAY_LJMP(check_args(L, 1, "res_get_headers"));
 	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 
-	return hlua_http_get_headers(L, htxn, &htxn->s->txn.rsp);
+	return hlua_http_get_headers(L, htxn, &htxn->s->txn->rsp);
 }
 
 /* This function replace full header, or just a value in
@@ -3062,7 +3059,7 @@
 	MAY_LJMP(check_args(L, 4, "req_rep_hdr"));
 	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 
-	return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn.req, HTTP_REQ_ACT_REPLACE_HDR));
+	return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn->req, HTTP_REQ_ACT_REPLACE_HDR));
 }
 
 __LJMP static int hlua_http_res_rep_hdr(lua_State *L)
@@ -3072,7 +3069,7 @@
 	MAY_LJMP(check_args(L, 4, "res_rep_hdr"));
 	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 
-	return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn.rsp, HTTP_RES_ACT_REPLACE_HDR));
+	return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn->rsp, HTTP_RES_ACT_REPLACE_HDR));
 }
 
 __LJMP static int hlua_http_req_rep_val(lua_State *L)
@@ -3082,7 +3079,7 @@
 	MAY_LJMP(check_args(L, 4, "req_rep_hdr"));
 	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 
-	return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn.req, HTTP_REQ_ACT_REPLACE_VAL));
+	return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn->req, HTTP_REQ_ACT_REPLACE_VAL));
 }
 
 __LJMP static int hlua_http_res_rep_val(lua_State *L)
@@ -3092,7 +3089,7 @@
 	MAY_LJMP(check_args(L, 4, "res_rep_val"));
 	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 
-	return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn.rsp, HTTP_RES_ACT_REPLACE_VAL));
+	return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn->rsp, HTTP_RES_ACT_REPLACE_VAL));
 }
 
 /* This function deletes all the occurences of an header.
@@ -3103,7 +3100,7 @@
 	size_t len;
 	const char *name = MAY_LJMP(luaL_checklstring(L, 2, &len));
 	struct hdr_ctx ctx;
-	struct http_txn *txn = &htxn->s->txn;
+	struct http_txn *txn = htxn->s->txn;
 
 	ctx.idx = 0;
 	while (http_find_header2(name, len, msg->chn->buf->p, &txn->hdr_idx, &ctx))
@@ -3118,7 +3115,7 @@
 	MAY_LJMP(check_args(L, 2, "req_del_hdr"));
 	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 
-	return hlua_http_del_hdr(L, htxn, &htxn->s->txn.req);
+	return hlua_http_del_hdr(L, htxn, &htxn->s->txn->req);
 }
 
 __LJMP static int hlua_http_res_del_hdr(lua_State *L)
@@ -3128,7 +3125,7 @@
 	MAY_LJMP(check_args(L, 2, "req_del_hdr"));
 	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 
-	return hlua_http_del_hdr(L, htxn, &htxn->s->txn.rsp);
+	return hlua_http_del_hdr(L, htxn, &htxn->s->txn->rsp);
 }
 
 /* This function adds an header. It is a wrapper used by
@@ -3157,7 +3154,7 @@
 	p++;
 	memcpy(p, value, value_len);
 
-	lua_pushboolean(L, http_header_add_tail2(msg, &htxn->s->txn.hdr_idx,
+	lua_pushboolean(L, http_header_add_tail2(msg, &htxn->s->txn->hdr_idx,
 	                                         trash.str, trash.len) != 0);
 
 	return 0;
@@ -3170,7 +3167,7 @@
 	MAY_LJMP(check_args(L, 3, "req_add_hdr"));
 	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 
-	return hlua_http_add_hdr(L, htxn, &htxn->s->txn.req);
+	return hlua_http_add_hdr(L, htxn, &htxn->s->txn->req);
 }
 
 __LJMP static int hlua_http_res_add_hdr(lua_State *L)
@@ -3180,7 +3177,7 @@
 	MAY_LJMP(check_args(L, 3, "res_add_hdr"));
 	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 
-	return hlua_http_add_hdr(L, htxn, &htxn->s->txn.rsp);
+	return hlua_http_add_hdr(L, htxn, &htxn->s->txn->rsp);
 }
 
 static int hlua_http_req_set_hdr(lua_State *L)
@@ -3190,8 +3187,8 @@
 	MAY_LJMP(check_args(L, 3, "req_set_hdr"));
 	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 
-	hlua_http_del_hdr(L, htxn, &htxn->s->txn.req);
-	return hlua_http_add_hdr(L, htxn, &htxn->s->txn.req);
+	hlua_http_del_hdr(L, htxn, &htxn->s->txn->req);
+	return hlua_http_add_hdr(L, htxn, &htxn->s->txn->req);
 }
 
 static int hlua_http_res_set_hdr(lua_State *L)
@@ -3201,8 +3198,8 @@
 	MAY_LJMP(check_args(L, 3, "res_set_hdr"));
 	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 
-	hlua_http_del_hdr(L, htxn, &htxn->s->txn.rsp);
-	return hlua_http_add_hdr(L, htxn, &htxn->s->txn.rsp);
+	hlua_http_del_hdr(L, htxn, &htxn->s->txn->rsp);
+	return hlua_http_add_hdr(L, htxn, &htxn->s->txn->rsp);
 }
 
 /* This function set the method. */
@@ -3211,7 +3208,7 @@
 	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 	size_t name_len;
 	const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
-	struct http_txn *txn = &htxn->s->txn;
+	struct http_txn *txn = htxn->s->txn;
 
 	lua_pushboolean(L, http_replace_req_line(0, name, name_len, htxn->p, htxn->s, txn) != -1);
 	return 1;
@@ -3223,7 +3220,7 @@
 	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 	size_t name_len;
 	const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
-	struct http_txn *txn = &htxn->s->txn;
+	struct http_txn *txn = htxn->s->txn;
 
 	lua_pushboolean(L, http_replace_req_line(1, name, name_len, htxn->p, htxn->s, txn) != -1);
 	return 1;
@@ -3235,7 +3232,7 @@
 	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 	size_t name_len;
 	const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
-	struct http_txn *txn = &htxn->s->txn;
+	struct http_txn *txn = htxn->s->txn;
 
 	/* Check length. */
 	if (name_len > trash.size - 1) {
@@ -3259,7 +3256,7 @@
 	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
 	size_t name_len;
 	const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
-	struct http_txn *txn = &htxn->s->txn;
+	struct http_txn *txn = htxn->s->txn;
 
 	lua_pushboolean(L, http_replace_req_line(3, name, name_len, htxn->p, htxn->s, txn) != -1);
 	return 1;
diff --git a/src/log.c b/src/log.c
index 949dc29..97a2080 100644
--- a/src/log.c
+++ b/src/log.c
@@ -922,7 +922,7 @@
 	struct session *sess = strm_sess(s);
 	struct proxy *fe = sess->fe;
 	struct proxy *be = s->be;
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	char *uri;
 	struct tm tm;
 	int t_request;
@@ -1615,7 +1615,7 @@
               ((s->flags & SF_ERR_MASK) > SF_ERR_LOCAL) ||
 	      (((s->flags & SF_ERR_MASK) == SF_ERR_NONE) &&
 	       (s->si[1].conn_retries != s->be->conn_retries)) ||
-	      ((sess->fe->mode == PR_MODE_HTTP) && s->txn.status >= 500);
+	      ((sess->fe->mode == PR_MODE_HTTP) && s->txn && s->txn->status >= 500);
 
 	if (!err && (sess->fe->options2 & PR_O2_NOLOGNORM))
 		return;
diff --git a/src/peers.c b/src/peers.c
index 08ccfa9..b5d1d8e 100644
--- a/src/peers.c
+++ b/src/peers.c
@@ -1113,7 +1113,6 @@
 	struct proxy *p = (struct proxy *)l->frontend; /* attached frontend */
 	struct appctx *appctx;
 	struct stream *s;
-	struct http_txn *txn;
 	struct task *t;
 	struct connection *conn;
 
@@ -1218,17 +1217,7 @@
 	s->uniq_id = 0;
 	s->unique_id = NULL;
 
-	txn = &s->txn;
-	/* Those variables will be checked and freed if non-NULL in
-	 * stream.c:stream_free(). It is important that they are
-	 * properly initialized.
-	 */
-	txn->sessid = NULL;
-	txn->srv_cookie = NULL;
-	txn->cli_cookie = NULL;
-	txn->uri = NULL;
-	txn->hdr_idx.v = NULL;
-	txn->hdr_idx.size = txn->hdr_idx.used = 0;
+	s->txn = NULL;
 
 	channel_init(&s->req);
 	s->req.flags |= CF_READ_ATTACHED; /* the producer is already connected */
diff --git a/src/proto_http.c b/src/proto_http.c
index 8bd6b5e..9b68270 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -834,7 +834,7 @@
 	channel_auto_close(si_ic(si));
 	channel_auto_read(si_ic(si));
 	if (status > 0 && msg) {
-		s->txn.status = status;
+		s->txn->status = status;
 		bo_inject(si_ic(si), msg->str, msg->len);
 	}
 	if (!(s->flags & SF_ERR_MASK))
@@ -1001,7 +1001,7 @@
 	/* 3: add the request URI. Since it was already forwarded, we need
 	 * to temporarily rewind the buffer.
 	 */
-	txn = &s->txn;
+	txn = s->txn;
 	b_rew(s->req.buf, rewind = http_hdr_rewind(&txn->req));
 
 	path = http_get_path(txn);
@@ -1060,7 +1060,7 @@
 				  503, http_error_message(s, HTTP_ERR_503));
 	else if (err_type & SI_ET_CONN_ABRT)
 		http_server_error(s, si, SF_ERR_CLICL, SF_FINST_C,
-				  503, (s->txn.flags & TX_NOT_FIRST) ? NULL :
+				  503, (s->txn->flags & TX_NOT_FIRST) ? NULL :
 				  http_error_message(s, HTTP_ERR_503));
 	else if (err_type & SI_ET_QUEUE_TO)
 		http_server_error(s, si, SF_ERR_SRVTO, SF_FINST_Q,
@@ -1070,7 +1070,7 @@
 				  503, http_error_message(s, HTTP_ERR_503));
 	else if (err_type & SI_ET_CONN_TO)
 		http_server_error(s, si, SF_ERR_SRVTO, SF_FINST_C,
-				  503, (s->txn.flags & TX_NOT_FIRST) ? NULL :
+				  503, (s->txn->flags & TX_NOT_FIRST) ? NULL :
 				  http_error_message(s, HTTP_ERR_503));
 	else if (err_type & SI_ET_CONN_ERR)
 		http_server_error(s, si, SF_ERR_SRVCL, SF_FINST_C,
@@ -1078,7 +1078,7 @@
 				  http_error_message(s, HTTP_ERR_503));
 	else if (err_type & SI_ET_CONN_RES)
 		http_server_error(s, si, SF_ERR_RESOURCE, SF_FINST_C,
-				  503, (s->txn.flags & TX_NOT_FIRST) ? NULL :
+				  503, (s->txn->flags & TX_NOT_FIRST) ? NULL :
 				  http_error_message(s, HTTP_ERR_503));
 	else /* SI_ET_CONN_OTHER and others */
 		http_server_error(s, si, SF_ERR_INTERNAL, SF_FINST_C,
@@ -1437,7 +1437,7 @@
 get_http_auth(struct stream *s)
 {
 
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct chunk auth_method;
 	struct hdr_ctx ctx;
 	char *h, *p;
@@ -2240,7 +2240,7 @@
  */
 int select_compression_request_header(struct stream *s, struct buffer *req)
 {
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct http_msg *msg = &txn->req;
 	struct hdr_ctx ctx;
 	struct comp_algo *comp_algo = NULL;
@@ -2351,7 +2351,7 @@
  */
 int select_compression_response_header(struct stream *s, struct buffer *res)
 {
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct http_msg *msg = &txn->rsp;
 	struct hdr_ctx ctx;
 	struct comp_type *comp_type;
@@ -2550,7 +2550,7 @@
 	int cur_idx;
 	int use_close_only;
 	struct session *sess = s->sess;
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct http_msg *msg = &txn->req;
 	struct hdr_ctx ctx;
 
@@ -3103,7 +3103,7 @@
 {
 	struct stats_admin_rule *stats_admin_rule;
 	struct stream_interface *si = &s->si[1];
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct http_msg *msg = &txn->req;
 	struct uri_auth *uri_auth = s->be->uri_auth;
 	const char *uri, *h, *lookup;
@@ -3114,7 +3114,7 @@
 	appctx->st1 = appctx->st2 = 0;
 	appctx->ctx.stats.st_code = STAT_STATUS_INIT;
 	appctx->ctx.stats.flags |= STAT_FMT_HTML; /* assume HTML mode by default */
-	if ((msg->flags & HTTP_MSGF_VER_11) && (s->txn.meth != HTTP_METH_HEAD))
+	if ((msg->flags & HTTP_MSGF_VER_11) && (s->txn->meth != HTTP_METH_HEAD))
 		appctx->ctx.stats.flags |= STAT_CHUNKED;
 
 	uri = msg->chn->buf->p + msg->sl.rq.u;
@@ -3197,7 +3197,7 @@
 		int ret = 1;
 
 		if (stats_admin_rule->cond) {
-			ret = acl_exec_cond(stats_admin_rule->cond, s->be, s, &s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
+			ret = acl_exec_cond(stats_admin_rule->cond, s->be, s, s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
 			ret = acl_pass(ret);
 			if (stats_admin_rule->cond->pol == ACL_COND_UNLESS)
 				ret = !ret;
@@ -3258,7 +3258,7 @@
 {
 	struct hdr_ctx ctx;
 	char *buf = msg->chn->buf->p;
-	struct hdr_idx *idx = &s->txn.hdr_idx;
+	struct hdr_idx *idx = &s->txn->hdr_idx;
 	int (*http_find_hdr_func)(const char *name, int len, char *sol,
 	                          struct hdr_idx *idx, struct hdr_ctx *ctx);
 	struct chunk *output = get_trash_chunk();
@@ -3565,7 +3565,7 @@
 				void *ptr;
 
 				t = rule->act_prm.trk_ctr.table.t;
-				key = stktable_fetch_key(t, s->be, s, &s->txn, SMP_OPT_DIR_REQ | SMP_OPT_FINAL, rule->act_prm.trk_ctr.expr, NULL);
+				key = stktable_fetch_key(t, s->be, s, s->txn, SMP_OPT_DIR_REQ | SMP_OPT_FINAL, rule->act_prm.trk_ctr.expr, NULL);
 
 				if (key && (ts = stktable_get_entry(t, key))) {
 					stream_track_stkctr(&s->stkctr[http_req_trk_idx(rule->action)], t, ts);
@@ -4073,7 +4073,7 @@
 int http_process_req_common(struct stream *s, struct channel *req, int an_bit, struct proxy *px)
 {
 	struct session *sess = s->sess;
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct http_msg *msg = &txn->req;
 	struct redirect_rule *rule;
 	struct cond_wordlist *wl;
@@ -4314,7 +4314,7 @@
 int http_process_request(struct stream *s, struct channel *req, int an_bit)
 {
 	struct session *sess = s->sess;
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct http_msg *msg = &txn->req;
 	struct connection *cli_conn = objt_conn(s->si[1].end);
 
@@ -4592,7 +4592,7 @@
 	 * that parameter. This will be done in another analyser.
 	 */
 	if (!(s->flags & (SF_ASSIGNED|SF_DIRECT)) &&
-	    s->txn.meth == HTTP_METH_POST && s->be->url_param_name != NULL &&
+	    s->txn->meth == HTTP_METH_POST && s->be->url_param_name != NULL &&
 	    (msg->flags & (HTTP_MSGF_CNT_LEN|HTTP_MSGF_TE_CHNK))) {
 		channel_dont_connect(req);
 		req->analysers |= AN_REQ_HTTP_BODY;
@@ -4661,7 +4661,7 @@
  */
 int http_process_tarpit(struct stream *s, struct channel *req, int an_bit)
 {
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 
 	/* This connection is being tarpitted. The CLIENT side has
 	 * already set the connect expiration date to the right
@@ -4707,8 +4707,8 @@
 int http_wait_for_request_body(struct stream *s, struct channel *req, int an_bit)
 {
 	struct session *sess = s->sess;
-	struct http_txn *txn = &s->txn;
-	struct http_msg *msg = &s->txn.req;
+	struct http_txn *txn = s->txn;
+	struct http_msg *msg = &s->txn->req;
 
 	/* We have to parse the HTTP request body to find any required data.
 	 * "balance url_param check_post" should have been the only way to get
@@ -4903,7 +4903,7 @@
  */
 void http_end_txn_clean_session(struct stream *s)
 {
-	int prev_status = s->txn.status;
+	int prev_status = s->txn->status;
 	struct proxy *fe = strm_sess(s)->fe;
 
 	/* FIXME: We need a more portable way of releasing a backend's and a
@@ -4915,7 +4915,7 @@
 	/* unless we're doing keep-alive, we want to quickly close the connection
 	 * to the server.
 	 */
-	if (((s->txn.flags & TX_CON_WANT_MSK) != TX_CON_WANT_KAL) ||
+	if (((s->txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_KAL) ||
 	    !si_conn_ready(&s->si[1])) {
 		s->si[1].flags |= SI_FL_NOLINGER | SI_FL_NOHALF;
 		si_shutr(&s->si[1]);
@@ -4931,10 +4931,10 @@
 	s->logs.t_close = tv_ms_elapsed(&s->logs.tv_accept, &now);
 	stream_process_counters(s);
 
-	if (s->txn.status) {
+	if (s->txn->status) {
 		int n;
 
-		n = s->txn.status / 100;
+		n = s->txn->status / 100;
 		if (n < 1 || n > 5)
 			n = 0;
 
@@ -4997,7 +4997,7 @@
 	/* only release our endpoint if we don't intend to reuse the
 	 * connection.
 	 */
-	if (((s->txn.flags & TX_CON_WANT_MSK) != TX_CON_WANT_KAL) ||
+	if (((s->txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_KAL) ||
 	    !si_conn_ready(&s->si[1])) {
 		si_release_endpoint(&s->si[1]);
 	}
@@ -5013,9 +5013,9 @@
 	s->flags &= ~(SF_CURR_SESS|SF_REDIRECTABLE|SF_SRV_REUSED);
 	s->flags &= ~(SF_ERR_MASK|SF_FINST_MASK|SF_REDISP);
 
-	s->txn.meth = 0;
+	s->txn->meth = 0;
 	http_reset_txn(s);
-	s->txn.flags |= TX_NOT_FIRST | TX_WAIT_NEXT_RQ;
+	s->txn->flags |= TX_NOT_FIRST | TX_WAIT_NEXT_RQ;
 
 	if (prev_status == 401 || prev_status == 407) {
 		/* In HTTP keep-alive mode, if we receive a 401, we still have
@@ -5025,7 +5025,7 @@
 		 * an opportunity for sending the challenge to the proper place,
 		 * it's better to do it (at least it helps with debugging).
 		 */
-		s->txn.flags |= TX_PREFER_LAST;
+		s->txn->flags |= TX_PREFER_LAST;
 	}
 
 	if (fe->options2 & PR_O2_INDEPSTR)
@@ -5074,7 +5074,7 @@
 int http_sync_req_state(struct stream *s)
 {
 	struct channel *chn = &s->req;
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	unsigned int old_flags = chn->flags;
 	unsigned int old_state = txn->req.msg_state;
 
@@ -5212,7 +5212,7 @@
 int http_sync_res_state(struct stream *s)
 {
 	struct channel *chn = &s->res;
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	unsigned int old_flags = chn->flags;
 	unsigned int old_state = txn->rsp.msg_state;
 
@@ -5340,7 +5340,7 @@
  */
 int http_resync_states(struct stream *s)
 {
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	int old_req_state = txn->req.msg_state;
 	int old_res_state = txn->rsp.msg_state;
 
@@ -5417,8 +5417,8 @@
 int http_request_forward_body(struct stream *s, struct channel *req, int an_bit)
 {
 	struct session *sess = s->sess;
-	struct http_txn *txn = &s->txn;
-	struct http_msg *msg = &s->txn.req;
+	struct http_txn *txn = s->txn;
+	struct http_msg *msg = &s->txn->req;
 
 	if (unlikely(msg->msg_state < HTTP_MSG_BODY))
 		return 0;
@@ -5589,7 +5589,7 @@
 				channel_auto_read(req);
 				channel_auto_close(req);
 			}
-			else if (s->txn.meth == HTTP_METH_POST) {
+			else if (s->txn->meth == HTTP_METH_POST) {
 				/* POST requests may require to read extra CRLF
 				 * sent by broken browsers and which could cause
 				 * an RST to be sent upon close on some systems
@@ -5722,7 +5722,7 @@
 int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit)
 {
 	struct session *sess = s->sess;
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct http_msg *msg = &txn->rsp;
 	struct hdr_ctx ctx;
 	int use_close_only;
@@ -6290,7 +6290,7 @@
 int http_process_res_common(struct stream *s, struct channel *rep, int an_bit, struct proxy *px)
 {
 	struct session *sess = s->sess;
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct http_msg *msg = &txn->rsp;
 	struct proxy *cur_proxy;
 	struct cond_wordlist *wl;
@@ -6630,8 +6630,8 @@
 int http_response_forward_body(struct stream *s, struct channel *res, int an_bit)
 {
 	struct session *sess = s->sess;
-	struct http_txn *txn = &s->txn;
-	struct http_msg *msg = &s->txn.rsp;
+	struct http_txn *txn = s->txn;
+	struct http_msg *msg = &s->txn->rsp;
 	static struct buffer *tmpbuf = &buf_empty;
 	int compressing = 0;
 	int ret;
@@ -6947,7 +6947,7 @@
 {
 	char *cur_ptr, *cur_end, *cur_next;
 	int cur_idx, old_idx, last_hdr;
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct hdr_idx_elem *cur_hdr;
 	int delta;
 
@@ -7059,7 +7059,7 @@
 {
 	char *cur_ptr, *cur_end;
 	int done;
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	int delta;
 
 	if (unlikely(txn->flags & (TX_CLDENY | TX_CLTARPIT)))
@@ -7151,7 +7151,7 @@
  */
 int apply_filters_to_request(struct stream *s, struct channel *req, struct proxy *px)
 {
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct hdr_exp *exp;
 
 	for (exp = px->req_exp; exp; exp = exp->next) {
@@ -7207,7 +7207,7 @@
  * If the server is found, it's assigned to the stream.
  */
 void manage_client_side_appsession(struct stream *s, const char *buf, int len) {
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	appsess *asession = NULL;
 	char *sessid_temp = NULL;
 
@@ -7359,7 +7359,7 @@
  */
 void manage_client_side_cookies(struct stream *s, struct channel *req)
 {
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct session *sess = s->sess;
 	int preserve_hdr;
 	int cur_idx, old_idx;
@@ -7811,7 +7811,7 @@
 {
 	char *cur_ptr, *cur_end, *cur_next;
 	int cur_idx, old_idx, last_hdr;
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct hdr_idx_elem *cur_hdr;
 	int delta;
 
@@ -7902,7 +7902,7 @@
 {
 	char *cur_ptr, *cur_end;
 	int done;
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	int delta;
 
 
@@ -7975,7 +7975,7 @@
  */
 int apply_filters_to_response(struct stream *s, struct channel *rtr, struct proxy *px)
 {
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct hdr_exp *exp;
 
 	for (exp = px->rsp_exp; exp; exp = exp->next) {
@@ -8033,7 +8033,7 @@
  */
 void manage_server_side_cookies(struct stream *s, struct channel *res)
 {
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct session *sess = s->sess;
 	struct server *srv;
 	int is_cookie2;
@@ -8397,7 +8397,7 @@
  */
 void check_response_for_cacheability(struct stream *s, struct channel *rtr)
 {
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	char *p1, *p2;
 
 	char *cur_ptr, *cur_end, *cur_next;
@@ -8493,7 +8493,7 @@
 	int mode = s->be->options2 & PR_O2_AS_M_ANY;
 
 	if (s->be->appsession_name == NULL ||
-	    (s->txn.meth != HTTP_METH_GET && s->txn.meth != HTTP_METH_POST && s->txn.meth != HTTP_METH_HEAD)) {
+	    (s->txn->meth != HTTP_METH_GET && s->txn->meth != HTTP_METH_POST && s->txn->meth != HTTP_METH_HEAD)) {
 		return;
 	}
 
@@ -8625,7 +8625,7 @@
 	es->ev_id = error_snapshot_id++;
 	es->b_flags = chn->flags;
 	es->s_flags = s->flags;
-	es->t_flags = s->txn.flags;
+	es->t_flags = s->txn->flags;
 	es->m_flags = msg->flags;
 	es->b_out = chn->buf->o;
 	es->b_wrap = chn->buf->data + chn->buf->size - chn->buf->p;
@@ -8789,6 +8789,34 @@
 	shut_your_big_mouth_gcc(write(1, trash.str, trash.len));
 }
 
+
+/* Allocate a new HTTP transaction for stream <s> unless there is one already.
+ * The hdr_idx is allocated as well. In case of allocation failure, everything
+ * allocated is freed and NULL is returned. Otherwise the new transaction is
+ * assigned to the stream and returned.
+ */
+struct http_txn *http_alloc_txn(struct stream *s)
+{
+	struct http_txn *txn = s->txn;
+
+	if (txn)
+		return txn;
+
+	txn = pool_alloc2(pool2_http_txn);
+	if (!txn)
+		return txn;
+
+	txn->hdr_idx.size = global.tune.max_http_hdr;
+	txn->hdr_idx.v    = pool_alloc2(pool2_hdr_idx);
+	if (!txn->hdr_idx.v) {
+		pool_free2(pool2_http_txn, txn);
+		return NULL;
+	}
+
+	s->txn = txn;
+	return txn;
+}
+
 /*
  * Initialize a new HTTP transaction for stream <s>. It is assumed that all
  * the required fields are properly allocated and that we only need to (re)init
@@ -8796,7 +8824,7 @@
  */
 void http_init_txn(struct stream *s)
 {
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct proxy *fe = strm_sess(s)->fe;
 
 	txn->flags = 0;
@@ -8805,6 +8833,11 @@
 	txn->cookie_first_date = 0;
 	txn->cookie_last_date = 0;
 
+	txn->sessid = NULL;
+	txn->srv_cookie = NULL;
+	txn->cli_cookie = NULL;
+	txn->uri = NULL;
+
 	txn->req.flags = 0;
 	txn->req.sol = txn->req.eol = txn->req.eoh = 0; /* relative to the buffer */
 	txn->req.next = 0;
@@ -8833,7 +8866,7 @@
 /* to be used at the end of a transaction */
 void http_end_txn(struct stream *s)
 {
-	struct http_txn *txn = &s->txn;
+	struct http_txn *txn = s->txn;
 	struct proxy *fe = strm_sess(s)->fe;
 
 	/* release any possible compression context */
@@ -9876,8 +9909,10 @@
 	struct http_txn *txn = l7;
 	struct http_msg *msg = &txn->req;
 
-	/* Note: hdr_idx.v cannot be NULL in this ACL because the ACL is tagged
-	 * as a layer7 ACL, which involves automatic allocation of hdr_idx.
+	/* Note: this function may only be used from places where
+	 * http_init_txn() has already been done, and implies that <s>,
+	 * <txn>, and <hdr_idx.v> are properly set. An extra check protects
+	 * against an eventual mistake in the fetch capability matrix.
 	 */
 
 	if (unlikely(!s || !txn))
@@ -10668,7 +10703,7 @@
                          const struct arg *args, struct sample *smp, const char *kw, void *private)
 {
 	smp->type = SMP_T_BOOL;
-	smp->data.uint = !(s->txn.flags & TX_NOT_FIRST);
+	smp->data.uint = !(s->txn->flags & TX_NOT_FIRST);
 	return 1;
 }
 
@@ -10687,7 +10722,7 @@
 		return 0;
 
 	smp->type = SMP_T_BOOL;
-	smp->data.uint = check_user(args->data.usr, l4->txn.auth.user, l4->txn.auth.pass);
+	smp->data.uint = check_user(args->data.usr, l4->txn->auth.user, l4->txn->auth.pass);
 	return 1;
 }
 
@@ -10709,7 +10744,7 @@
 	 * report that it unconditionally does not match. Otherwise we return
 	 * a string containing the username.
 	 */
-	if (!check_user(args->data.usr, l4->txn.auth.user, l4->txn.auth.pass))
+	if (!check_user(args->data.usr, l4->txn->auth.user, l4->txn->auth.pass))
 		return 0;
 
 	/* pat_match_auth() will need the user list */
@@ -10717,8 +10752,8 @@
 
 	smp->type = SMP_T_STR;
 	smp->flags = SMP_F_CONST;
-	smp->data.str.str = l4->txn.auth.user;
-	smp->data.str.len = strlen(l4->txn.auth.user);
+	smp->data.str.str = l4->txn->auth.user;
+	smp->data.str.len = strlen(l4->txn->auth.user);
 
 	return 1;
 }
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index e77edc4..af61c54 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -1134,7 +1134,7 @@
 		enum acl_test_res ret = ACL_TEST_PASS;
 
 		if (rule->cond) {
-			ret = acl_exec_cond(rule->cond, s->be, s, &s->txn, SMP_OPT_DIR_REQ | partial);
+			ret = acl_exec_cond(rule->cond, s->be, s, s->txn, SMP_OPT_DIR_REQ | partial);
 			if (ret == ACL_TEST_MISS)
 				goto missing_data;
 
@@ -1175,7 +1175,7 @@
 					continue;
 
 				t = rule->act_prm.trk_ctr.table.t;
-				key = stktable_fetch_key(t, s->be, s, &s->txn, SMP_OPT_DIR_REQ | partial, rule->act_prm.trk_ctr.expr, &smp);
+				key = stktable_fetch_key(t, s->be, s, s->txn, SMP_OPT_DIR_REQ | partial, rule->act_prm.trk_ctr.expr, &smp);
 
 				if ((smp.flags & SMP_F_MAY_CHANGE) && !(partial & SMP_OPT_FINAL))
 					goto missing_data; /* key might appear later */
@@ -1193,7 +1193,7 @@
 				char **cap = s->req_cap;
 				int len;
 
-				key = sample_fetch_string(s->be, s, &s->txn, SMP_OPT_DIR_REQ | partial, rule->act_prm.cap.expr);
+				key = sample_fetch_string(s->be, s, s->txn, SMP_OPT_DIR_REQ | partial, rule->act_prm.cap.expr);
 				if (!key)
 					continue;
 
@@ -1292,7 +1292,7 @@
 		enum acl_test_res ret = ACL_TEST_PASS;
 
 		if (rule->cond) {
-			ret = acl_exec_cond(rule->cond, s->be, s, &s->txn, SMP_OPT_DIR_RES | partial);
+			ret = acl_exec_cond(rule->cond, s->be, s, s->txn, SMP_OPT_DIR_RES | partial);
 			if (ret == ACL_TEST_MISS) {
 				/* just set the analyser timeout once at the beginning of the response */
 				if (!tick_isset(rep->analyse_exp) && s->be->tcp_rep.inspect_delay)
@@ -1407,7 +1407,7 @@
 					continue;
 
 				t = rule->act_prm.trk_ctr.table.t;
-				key = stktable_fetch_key(t, s->be, s, &s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->act_prm.trk_ctr.expr, NULL);
+				key = stktable_fetch_key(t, s->be, s, s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->act_prm.trk_ctr.expr, NULL);
 
 				if (key && (ts = stktable_get_entry(t, key)))
 					stream_track_stkctr(&s->stkctr[tcp_trk_idx(rule->action)], t, ts);
diff --git a/src/proxy.c b/src/proxy.c
index 9b23db0..f55aaa4 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -940,39 +940,40 @@
 	if (be->options2 & PR_O2_INDEPSTR)
 		s->si[1].flags |= SI_FL_INDEP_STR;
 
-	if (be->options2 & PR_O2_RSPBUG_OK)
-		s->txn.rsp.err_pos = -1; /* let buggy responses pass */
-	s->flags |= SF_BE_ASSIGNED;
-
 	/* If the target backend requires HTTP processing, we have to allocate
-	 * a struct hdr_idx for it if we did not have one.
+	 * the HTTP transaction and hdr_idx if we did not have one.
 	 */
-	if (unlikely(!s->txn.hdr_idx.v && be->http_needed)) {
-		s->txn.hdr_idx.size = global.tune.max_http_hdr;
-		if ((s->txn.hdr_idx.v = pool_alloc2(pool2_hdr_idx)) == NULL)
+	if (unlikely(!s->txn && be->http_needed)) {
+		if (unlikely(!http_alloc_txn(s)))
 			return 0; /* not enough memory */
 
 		/* and now initialize the HTTP transaction state */
 		http_init_txn(s);
 	}
 
-	/* If we chain to an HTTP backend running a different HTTP mode, we
-	 * have to re-adjust the desired keep-alive/close mode to accommodate
-	 * both the frontend's and the backend's modes.
-	 */
-	if (strm_sess(s)->fe->mode == PR_MODE_HTTP && be->mode == PR_MODE_HTTP &&
-	    ((strm_sess(s)->fe->options & PR_O_HTTP_MODE) != (be->options & PR_O_HTTP_MODE)))
-		http_adjust_conn_mode(s, &s->txn, &s->txn.req);
+	if (s->txn) {
+		if (be->options2 & PR_O2_RSPBUG_OK)
+			s->txn->rsp.err_pos = -1; /* let buggy responses pass */
 
-	/* If an LB algorithm needs to access some pre-parsed body contents,
-	 * we must not start to forward anything until the connection is
-	 * confirmed otherwise we'll lose the pointer to these data and
-	 * prevent the hash from being doable again after a redispatch.
-	 */
-	if (be->mode == PR_MODE_HTTP &&
-	    (be->lbprm.algo & (BE_LB_KIND | BE_LB_PARM)) == (BE_LB_KIND_HI | BE_LB_HASH_PRM))
-		s->txn.req.flags |= HTTP_MSGF_WAIT_CONN;
+		/* If we chain to an HTTP backend running a different HTTP mode, we
+		 * have to re-adjust the desired keep-alive/close mode to accommodate
+		 * both the frontend's and the backend's modes.
+		 */
+		if (strm_sess(s)->fe->mode == PR_MODE_HTTP && be->mode == PR_MODE_HTTP &&
+		    ((strm_sess(s)->fe->options & PR_O_HTTP_MODE) != (be->options & PR_O_HTTP_MODE)))
+			http_adjust_conn_mode(s, s->txn, &s->txn->req);
 
+		/* If an LB algorithm needs to access some pre-parsed body contents,
+		 * we must not start to forward anything until the connection is
+		 * confirmed otherwise we'll lose the pointer to these data and
+		 * prevent the hash from being doable again after a redispatch.
+		 */
+		if (be->mode == PR_MODE_HTTP &&
+		    (be->lbprm.algo & (BE_LB_KIND | BE_LB_PARM)) == (BE_LB_KIND_HI | BE_LB_HASH_PRM))
+			s->txn->req.flags |= HTTP_MSGF_WAIT_CONN;
+	}
+
+	s->flags |= SF_BE_ASSIGNED;
 	if (be->options2 & PR_O2_NODELAY) {
 		s->req.flags |= CF_NEVER_WAIT;
 		s->res.flags |= CF_NEVER_WAIT;
diff --git a/src/stream.c b/src/stream.c
index cd0f8bd..f12b2c3 100644
--- a/src/stream.c
+++ b/src/stream.c
@@ -425,7 +425,6 @@
 	struct session *sess = s->sess;
 	struct listener *l = sess->listener;
 	struct proxy *p = sess->fe;
-	struct http_txn *txn;
 	struct task *t = s->task;
 	struct connection *conn = __objt_conn(s->target);
 	int ret;
@@ -531,23 +530,7 @@
 	s->res.wex = TICK_ETERNITY;
 	s->res.analyse_exp = TICK_ETERNITY;
 
-	txn = &s->txn;
-	/* Those variables will be checked and freed if non-NULL in
-	 * stream.c:stream_free(). It is important that they are
-	 * properly initialized.
-	 */
-	txn->sessid = NULL;
-	txn->srv_cookie = NULL;
-	txn->cli_cookie = NULL;
-	txn->uri = NULL;
-	txn->hdr_idx.v = NULL;
-	txn->hdr_idx.size = txn->hdr_idx.used = 0;
-	txn->flags = 0;
-	txn->req.flags = 0;
-	txn->rsp.flags = 0;
-	/* the HTTP messages need to know what buffer they're associated with */
-	txn->req.chn = &s->req;
-	txn->rsp.chn = &s->res;
+	s->txn = NULL;
 
 	HLUA_INIT(&s->hlua);
 
@@ -591,7 +574,6 @@
  */
 static void stream_free(struct stream *s)
 {
-	struct http_txn *txn = &s->txn;
 	struct session *sess = strm_sess(s);
 	struct proxy *fe = sess->fe;
 	struct bref *bref, *back;
@@ -636,7 +618,8 @@
 		stream_offer_buffers();
 
 	hlua_ctx_destroy(&s->hlua);
-	http_end_txn(s);
+	if (s->txn)
+		http_end_txn(s);
 
 	/* ensure the client-side transport layer is destroyed */
 	if (cli_conn)
@@ -649,7 +632,12 @@
 		s->store[i].ts = NULL;
 	}
 
+	if (s->txn) {
+		pool_free2(pool2_hdr_idx, s->txn->hdr_idx.v);
+		pool_free2(pool2_http_txn, s->txn);
+		s->txn = NULL;
+	}
+
-	pool_free2(pool2_hdr_idx, txn->hdr_idx.v);
 	if (fe) {
 		pool_free2(fe->rsp_cap_pool, s->res_cap);
 		pool_free2(fe->req_cap_pool, s->req_cap);
@@ -1080,7 +1068,6 @@
 		}
 	}
 	else {
-		s->txn.rsp.msg_state = HTTP_MSG_RPBEFORE;
 		rep->flags |= CF_READ_DONTWAIT; /* a single read is enough to get response headers */
 	}
 
@@ -1404,7 +1391,7 @@
 			int ret = 1;
 
 			if (rule->cond) {
-				ret = acl_exec_cond(rule->cond, fe, s, &s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
+				ret = acl_exec_cond(rule->cond, fe, s, s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
 				ret = acl_pass(ret);
 				if (rule->cond->pol == ACL_COND_UNLESS)
 					ret = !ret;
@@ -1457,7 +1444,7 @@
 		int ret = 1;
 
 		if (prst_rule->cond) {
-	                ret = acl_exec_cond(prst_rule->cond, s->be, s, &s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
+	                ret = acl_exec_cond(prst_rule->cond, s->be, s, s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
 			ret = acl_pass(ret);
 			if (prst_rule->cond->pol == ACL_COND_UNLESS)
 				ret = !ret;
@@ -1486,7 +1473,8 @@
 	if (!(s->flags & SF_FINST_MASK))
 		s->flags |= SF_FINST_R;
 
-	s->txn.status = 500;
+	if (s->txn)
+		s->txn->status = 500;
 	s->req.analysers = 0;
 	s->req.analyse_exp = TICK_ETERNITY;
 	return 0;
@@ -1514,7 +1502,7 @@
 		list_for_each_entry(rule, &px->server_rules, list) {
 			int ret;
 
-			ret = acl_exec_cond(rule->cond, s->be, s, &s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
+			ret = acl_exec_cond(rule->cond, s->be, s, s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
 			ret = acl_pass(ret);
 			if (rule->cond->pol == ACL_COND_UNLESS)
 				ret = !ret;
@@ -1579,7 +1567,7 @@
 			continue;
 
 		if (rule->cond) {
-	                ret = acl_exec_cond(rule->cond, px, s, &s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
+	                ret = acl_exec_cond(rule->cond, px, s, s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
 			ret = acl_pass(ret);
 			if (rule->cond->pol == ACL_COND_UNLESS)
 				ret = !ret;
@@ -1588,7 +1576,7 @@
 		if (ret) {
 			struct stktable_key *key;
 
-			key = stktable_fetch_key(rule->table.t, px, s, &s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->expr, NULL);
+			key = stktable_fetch_key(rule->table.t, px, s, s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->expr, NULL);
 			if (!key)
 				continue;
 
@@ -1682,7 +1670,7 @@
 			continue;
 
 		if (rule->cond) {
-	                ret = acl_exec_cond(rule->cond, px, s, &s->txn, SMP_OPT_DIR_RES|SMP_OPT_FINAL);
+	                ret = acl_exec_cond(rule->cond, px, s, s->txn, SMP_OPT_DIR_RES|SMP_OPT_FINAL);
 	                ret = acl_pass(ret);
 			if (rule->cond->pol == ACL_COND_UNLESS)
 				ret = !ret;
@@ -1691,7 +1679,7 @@
 		if (ret) {
 			struct stktable_key *key;
 
-			key = stktable_fetch_key(rule->table.t, px, s, &s->txn, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->expr, NULL);
+			key = stktable_fetch_key(rule->table.t, px, s, s->txn, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->expr, NULL);
 			if (!key)
 				continue;
 
@@ -1780,7 +1768,8 @@
 	//        si_f->state, si_b->state, si_b->err_type, req->flags, res->flags);
 
 	/* this data may be no longer valid, clear it */
-	memset(&s->txn.auth, 0, sizeof(s->txn.auth));
+	if (s->txn)
+		memset(&s->txn->auth, 0, sizeof(s->txn->auth));
 
 	/* This flag must explicitly be set every time */
 	req->flags &= ~(CF_READ_NOEXP|CF_WAKE_WRITE);
@@ -2445,7 +2434,7 @@
 			    (s->be->server_id_hdr_name != NULL) &&
 			    (s->be->mode == PR_MODE_HTTP) &&
 			    objt_server(s->target)) {
-				http_send_name_header(&s->txn, s->be, objt_server(s->target)->id);
+				http_send_name_header(s->txn, s->be, objt_server(s->target)->id);
 			}
 
 			srv = objt_server(s->target);
@@ -2722,10 +2711,10 @@
 	s->logs.t_close = tv_ms_elapsed(&s->logs.tv_accept, &now);
 	stream_process_counters(s);
 
-	if (s->txn.status) {
+	if (s->txn && s->txn->status) {
 		int n;
 
-		n = s->txn.status / 100;
+		n = s->txn->status / 100;
 		if (n < 1 || n > 5)
 			n = 0;