[MAJOR] http: complete splitting of the remaining stages

The HTTP processing has been splitted into 7 steps, one of which
is not anymore HTTP-specific (content-switching). That way, it
becomes possible to use "use_backend" rules in TCP mode. A new
"use_server" directive should follow soon.
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 3852c45..5afb11e 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -3775,9 +3775,11 @@
 			listener->accept = event_accept;
 			listener->private = curproxy;
 			listener->handler = process_session;
+			/* both TCP and HTTP must check switching rules */
+			listener->analysers |= AN_REQ_SWITCHING_RULES;
 
 			if (curproxy->mode == PR_MODE_HTTP)
-				listener->analysers |= AN_REQ_WAIT_HTTP | AN_REQ_HTTP_HDR;
+				listener->analysers |= AN_REQ_WAIT_HTTP | AN_REQ_HTTP_PROCESS_FE | AN_REQ_HTTP_INNER | AN_REQ_HTTP_PROCESS_BE;
 
 			/* smart accept mode is automatic in HTTP mode */
 			if ((curproxy->options2 & PR_O2_SMARTACC) ||
@@ -3789,6 +3791,7 @@
 			    !LIST_ISEMPTY(&curproxy->tcp_req.inspect_rules))
 				listener->analysers |= AN_REQ_INSPECT;
 
+			/* We want the use_backend and default_backend rules to apply */
 			listener = listener->next;
 		}
 
diff --git a/src/client.c b/src/client.c
index 3e2099e..abf4d35 100644
--- a/src/client.c
+++ b/src/client.c
@@ -165,18 +165,12 @@
 
 		s->task = t;
 		s->listener = l;
-		s->be = s->fe = p;
 
-		/* in HTTP mode, content switching requires that the backend
-		 * first points to the same proxy as the frontend. However, in
-		 * TCP mode there will be no header processing so any default
-		 * backend must be assigned if set.
+		/* Note: initially, the session's backend points to the frontend.
+		 * This changes later when switching rules are executed or
+		 * when the default backend is assigned.
 		 */
-		if (p->mode == PR_MODE_TCP) {
-			if (p->defbe.be)
-				s->be = p->defbe.be;
-			s->flags |= SN_BE_ASSIGNED;
-		}
+		s->be = s->fe = p;
 
 		s->ana_state = 0;  /* analysers may change it but must reset it upon exit */
 		s->req = s->rep = NULL; /* will be allocated later */
@@ -459,12 +453,6 @@
 		if (p->feconn > p->feconn_max)
 			p->feconn_max = p->feconn;
 
-		if (s->flags & SN_BE_ASSIGNED) {
-			proxy_inc_be_ctr(s->be);
-			s->be->beconn++;
-			if (s->be->beconn > s->be->beconn_max)
-				s->be->beconn_max = s->be->beconn;
-		}
 		actconn++;
 		totalconn++;
 
diff --git a/src/proto_http.c b/src/proto_http.c
index 33e9466..64e93d3 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -354,10 +354,6 @@
 };
 
 
-#ifdef DEBUG_FULL
-static char *cli_stnames[4] = { "DAT", "SHR", "SHW", "CLS" };
-#endif
-
 /*
  * Adds a header and its CRLF at the tail of buffer <b>, just before the last
  * CRLF. Text length is measured first, so it cannot be NULL.
@@ -1809,17 +1805,20 @@
 	return 0;
 }
 
-/* This function performs all the processing enabled for the current request.
+/* This stream analyser runs all HTTP request processing which is common to
+ * frontends and backends, which means blocking ACLs, filters, connection-close,
+ * reqadd, stats and redirects. This is performed for the designated proxy.
  * It returns 1 if the processing can continue on next analysers, or zero if it
- * needs more data, encounters an error, or wants to immediately abort the
- * request. It relies on buffers flags, and updates s->req->analysers.
+ * either needs more data or wants to immediately abort the request (eg: deny,
+ * error, ...).
  */
-int http_process_request(struct session *s, struct buffer *req, int an_bit)
+int http_process_req_common(struct session *s, struct buffer *req, int an_bit, struct proxy *px)
 {
-	int cur_idx;
 	struct http_txn *txn = &s->txn;
 	struct http_msg *msg = &txn->req;
-	struct proxy *cur_proxy;
+	struct acl_cond *cond;
+	struct redirect_rule *rule;
+	int cur_idx;
 
 	req->analysers &= ~an_bit;
 	req->analyse_exp = TICK_ETERNITY;
@@ -1833,70 +1832,27 @@
 		req->l,
 		req->analysers);
 
-	/*
-	 * 6: we will have to evaluate the filters.
-	 * As opposed to version 1.2, now they will be evaluated in the
-	 * filters order and not in the header order. This means that
-	 * each filter has to be validated among all headers.
-	 *
-	 * We can now check whether we want to switch to another
-	 * backend, in which case we will re-check the backend's
-	 * filters and various options. In order to support 3-level
-	 * switching, here's how we should proceed :
-	 *
-	 *  a) run be.
-	 *     if (switch) then switch ->be to the new backend.
-	 *  b) run be if (be != fe).
-	 *     There cannot be any switch from there, so ->be cannot be
-	 *     changed anymore.
-	 *
-	 * => filters always apply to ->be, then ->be may change.
-	 *
-	 * The response path will be able to apply either ->be, or
-	 * ->be then ->fe filters in order to match the reverse of
-	 * the forward sequence.
-	 */
+	/* first check whether we have some ACLs set to block this request */
+	list_for_each_entry(cond, &px->block_cond, list) {
+		int ret = acl_exec_cond(cond, px, s, txn, ACL_DIR_REQ);
 
-	do {
-		struct acl_cond *cond;
-		struct redirect_rule *rule;
-		struct proxy *rule_set = s->be;
-		cur_proxy = s->be;
-
-		/* first check whether we have some ACLs set to block this request */
-		list_for_each_entry(cond, &cur_proxy->block_cond, list) {
-			int ret = acl_exec_cond(cond, cur_proxy, s, txn, ACL_DIR_REQ);
+		ret = acl_pass(ret);
+		if (cond->pol == ACL_COND_UNLESS)
+			ret = !ret;
 
-			ret = acl_pass(ret);
-			if (cond->pol == ACL_COND_UNLESS)
-				ret = !ret;
-
-			if (ret) {
-				txn->status = 403;
-				/* let's log the request time */
-				s->logs.tv_request = now;
-				stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403));
-				goto return_prx_cond;
-			}
-		}
-
-		/* try headers filters */
-		if (rule_set->req_exp != NULL) {
-			if (apply_filters_to_request(s, req, rule_set->req_exp) < 0)
-				goto return_bad_req;
+		if (ret) {
+			txn->status = 403;
+			/* let's log the request time */
+			s->logs.tv_request = now;
+			stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403));
+			goto return_prx_cond;
 		}
+	}
 
-		if (!(s->flags & SN_BE_ASSIGNED) && (s->be != cur_proxy)) {
-			/* to ensure correct connection accounting on
-			 * the backend, we count the connection for the
-			 * one managing the queue.
-			 */
-			s->be->beconn++;
-			if (s->be->beconn > s->be->beconn_max)
-				s->be->beconn_max = s->be->beconn;
-			proxy_inc_be_ctr(s->be);
-			s->flags |= SN_BE_ASSIGNED;
-		}
+	/* try headers filters */
+	if (px->req_exp != NULL) {
+		if (apply_filters_to_request(s, req, px->req_exp) < 0)
+			goto return_bad_req;
 
 		/* has the request been denied ? */
 		if (txn->flags & TX_CLDENY) {
@@ -1907,248 +1863,234 @@
 			stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403));
 			goto return_prx_cond;
 		}
+	}
 
-		/* We might have to check for "Connection:" */
-		if (((s->fe->options | s->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO)) &&
-		    !(s->flags & SN_CONN_CLOSED)) {
-			char *cur_ptr, *cur_end, *cur_next;
-			int cur_idx, old_idx, delta, val;
-			struct hdr_idx_elem *cur_hdr;
+	/* We might have to check for "Connection:" */
+	if (((s->fe->options | s->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO)) &&
+	    !(s->flags & SN_CONN_CLOSED)) {
+		char *cur_ptr, *cur_end, *cur_next;
+		int old_idx, delta, val;
+		struct hdr_idx_elem *cur_hdr;
 
-			cur_next = req->data + txn->req.som + hdr_idx_first_pos(&txn->hdr_idx);
-			old_idx = 0;
+		cur_next = req->data + txn->req.som + hdr_idx_first_pos(&txn->hdr_idx);
+		old_idx = 0;
 
-			while ((cur_idx = txn->hdr_idx.v[old_idx].next)) {
-				cur_hdr  = &txn->hdr_idx.v[cur_idx];
-				cur_ptr  = cur_next;
-				cur_end  = cur_ptr + cur_hdr->len;
-				cur_next = cur_end + cur_hdr->cr + 1;
+		while ((cur_idx = txn->hdr_idx.v[old_idx].next)) {
+			cur_hdr  = &txn->hdr_idx.v[cur_idx];
+			cur_ptr  = cur_next;
+			cur_end  = cur_ptr + cur_hdr->len;
+			cur_next = cur_end + cur_hdr->cr + 1;
 
-				val = http_header_match2(cur_ptr, cur_end, "Connection", 10);
-				if (val) {
-					/* 3 possibilities :
-					 * - we have already set Connection: close,
-					 *   so we remove this line.
-					 * - we have not yet set Connection: close,
-					 *   but this line indicates close. We leave
-					 *   it untouched and set the flag.
-					 * - we have not yet set Connection: close,
-					 *   and this line indicates non-close. We
-					 *   replace it.
-					 */
-					if (s->flags & SN_CONN_CLOSED) {
-						delta = buffer_replace2(req, cur_ptr, cur_next, NULL, 0);
-						txn->req.eoh += delta;
+			val = http_header_match2(cur_ptr, cur_end, "Connection", 10);
+			if (val) {
+				/* 3 possibilities :
+				 * - we have already set Connection: close,
+				 *   so we remove this line.
+				 * - we have not yet set Connection: close,
+				 *   but this line indicates close. We leave
+				 *   it untouched and set the flag.
+				 * - we have not yet set Connection: close,
+				 *   and this line indicates non-close. We
+				 *   replace it.
+				 */
+				if (s->flags & SN_CONN_CLOSED) {
+					delta = buffer_replace2(req, cur_ptr, cur_next, NULL, 0);
+					txn->req.eoh += delta;
+					cur_next += delta;
+					txn->hdr_idx.v[old_idx].next = cur_hdr->next;
+					txn->hdr_idx.used--;
+					cur_hdr->len = 0;
+				} else {
+					if (strncasecmp(cur_ptr + val, "close", 5) != 0) {
+						delta = buffer_replace2(req, cur_ptr + val, cur_end,
+									"close", 5);
 						cur_next += delta;
-						txn->hdr_idx.v[old_idx].next = cur_hdr->next;
-						txn->hdr_idx.used--;
-						cur_hdr->len = 0;
-					} else {
-						if (strncasecmp(cur_ptr + val, "close", 5) != 0) {
-							delta = buffer_replace2(req, cur_ptr + val, cur_end,
-										"close", 5);
-							cur_next += delta;
-							cur_hdr->len += delta;
-							txn->req.eoh += delta;
-						}
-						s->flags |= SN_CONN_CLOSED;
+						cur_hdr->len += delta;
+						txn->req.eoh += delta;
 					}
+					s->flags |= SN_CONN_CLOSED;
 				}
-				old_idx = cur_idx;
 			}
-		}
-		/* add request headers from the rule sets in the same order */
-		for (cur_idx = 0; cur_idx < rule_set->nb_reqadd; cur_idx++) {
-			if (unlikely(http_header_add_tail(req,
-							  &txn->req,
-							  &txn->hdr_idx,
-							  rule_set->req_add[cur_idx])) < 0)
-				goto return_bad_req;
+			old_idx = cur_idx;
 		}
+	}
+	/* add request headers from the rule sets in the same order */
+	for (cur_idx = 0; cur_idx < px->nb_reqadd; cur_idx++) {
+		if (unlikely(http_header_add_tail(req,
+						  &txn->req,
+						  &txn->hdr_idx,
+						  px->req_add[cur_idx])) < 0)
+			goto return_bad_req;
+	}
 
-		/* check if stats URI was requested, and if an auth is needed */
-		if (rule_set->uri_auth != NULL &&
-		    (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)) {
-			/* we have to check the URI and auth for this request.
-			 * FIXME!!! that one is rather dangerous, we want to
-			 * make it follow standard rules (eg: clear req->analysers).
-			 */
-			if (stats_check_uri_auth(s, rule_set)) {
-				req->analysers = 0;
-				return 0;
-			}
+	/* check if stats URI was requested, and if an auth is needed */
+	if (px->uri_auth != NULL &&
+	    (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)) {
+		/* we have to check the URI and auth for this request.
+		 * FIXME!!! that one is rather dangerous, we want to
+		 * make it follow standard rules (eg: clear req->analysers).
+		 */
+		if (stats_check_uri_auth(s, px)) {
+			req->analysers = 0;
+			return 0;
 		}
+	}
 
-		/* first check whether we have some ACLs set to redirect this request */
-		list_for_each_entry(rule, &cur_proxy->redirect_rules, list) {
-			int ret = acl_exec_cond(rule->cond, cur_proxy, s, txn, ACL_DIR_REQ);
+	/* check whether we have some ACLs set to redirect this request */
+	list_for_each_entry(rule, &px->redirect_rules, list) {
+		int ret = acl_exec_cond(rule->cond, px, s, txn, ACL_DIR_REQ);
 
-			ret = acl_pass(ret);
-			if (rule->cond->pol == ACL_COND_UNLESS)
-				ret = !ret;
+		ret = acl_pass(ret);
+		if (rule->cond->pol == ACL_COND_UNLESS)
+			ret = !ret;
 
-			if (ret) {
-				struct chunk rdr = { trash, 0 };
-				const char *msg_fmt;
+		if (ret) {
+			struct chunk rdr = { trash, 0 };
+			const char *msg_fmt;
 
-				/* build redirect message */
-				switch(rule->code) {
-				case 303:
-					rdr.len = strlen(HTTP_303);
-					msg_fmt = HTTP_303;
-					break;
-				case 301:
-					rdr.len = strlen(HTTP_301);
-					msg_fmt = HTTP_301;
-					break;
-				case 302:
-				default:
-					rdr.len = strlen(HTTP_302);
-					msg_fmt = HTTP_302;
-					break;
-				}
+			/* build redirect message */
+			switch(rule->code) {
+			case 303:
+				rdr.len = strlen(HTTP_303);
+				msg_fmt = HTTP_303;
+				break;
+			case 301:
+				rdr.len = strlen(HTTP_301);
+				msg_fmt = HTTP_301;
+				break;
+			case 302:
+			default:
+				rdr.len = strlen(HTTP_302);
+				msg_fmt = HTTP_302;
+				break;
+			}
 
-				if (unlikely(rdr.len > sizeof(trash)))
-					goto return_bad_req;
-				memcpy(rdr.str, msg_fmt, rdr.len);
+			if (unlikely(rdr.len > sizeof(trash)))
+				goto return_bad_req;
+			memcpy(rdr.str, msg_fmt, rdr.len);
 
-				switch(rule->type) {
-				case REDIRECT_TYPE_PREFIX: {
-					const char *path;
-					int pathlen;
+			switch(rule->type) {
+			case REDIRECT_TYPE_PREFIX: {
+				const char *path;
+				int pathlen;
 
-					path = http_get_path(txn);
-					/* build message using path */
-					if (path) {
-						pathlen = txn->req.sl.rq.u_l + (txn->req.sol+txn->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++;
+				path = http_get_path(txn);
+				/* build message using path */
+				if (path) {
+					pathlen = txn->req.sl.rq.u_l + (txn->req.sol+txn->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 (rdr.len + rule->rdr_len + pathlen > sizeof(trash) - 4)
-						goto return_bad_req;
-
-					/* 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(rdr.str + rdr.len, rule->rdr_str, rule->rdr_len);
-						rdr.len += rule->rdr_len;
-					}
-
-					/* add path */
-					memcpy(rdr.str + rdr.len, path, pathlen);
-					rdr.len += pathlen;
-					break;
+				} else {
+					path = "/";
+					pathlen = 1;
 				}
-				case REDIRECT_TYPE_LOCATION:
-				default:
-					if (rdr.len + rule->rdr_len > sizeof(trash) - 4)
-						goto return_bad_req;
 
-					/* add location */
+				if (rdr.len + rule->rdr_len + pathlen > sizeof(trash) - 4)
+					goto return_bad_req;
+
+				/* 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(rdr.str + rdr.len, rule->rdr_str, rule->rdr_len);
 					rdr.len += rule->rdr_len;
-					break;
 				}
 
-				if (rule->cookie_len) {
-					memcpy(rdr.str + rdr.len, "\r\nSet-Cookie: ", 14);
-					rdr.len += 14;
-					memcpy(rdr.str + rdr.len, rule->cookie_str, rule->cookie_len);
-					rdr.len += rule->cookie_len;
-					memcpy(rdr.str + rdr.len, "\r\n", 2);
-					rdr.len += 2;
-				}
+				/* add path */
+				memcpy(rdr.str + rdr.len, path, pathlen);
+				rdr.len += pathlen;
+				break;
+			}
+			case REDIRECT_TYPE_LOCATION:
+			default:
+				if (rdr.len + rule->rdr_len > sizeof(trash) - 4)
+					goto return_bad_req;
 
-				/* add end of headers */
-				memcpy(rdr.str + rdr.len, "\r\n\r\n", 4);
-				rdr.len += 4;
+				/* add location */
+				memcpy(rdr.str + rdr.len, rule->rdr_str, rule->rdr_len);
+				rdr.len += rule->rdr_len;
+				break;
+			}
 
-				txn->status = rule->code;
-				/* let's log the request time */
-				s->logs.tv_request = now;
-				stream_int_retnclose(req->prod, &rdr);
-				goto return_prx_cond;
+			if (rule->cookie_len) {
+				memcpy(rdr.str + rdr.len, "\r\nSet-Cookie: ", 14);
+				rdr.len += 14;
+				memcpy(rdr.str + rdr.len, rule->cookie_str, rule->cookie_len);
+				rdr.len += rule->cookie_len;
+				memcpy(rdr.str + rdr.len, "\r\n", 2);
+				rdr.len += 2;
 			}
-		}
 
-		/* now check whether we have some switching rules for this request */
-		if (!(s->flags & SN_BE_ASSIGNED)) {
-			struct switching_rule *rule;
+			/* add end of headers */
+			memcpy(rdr.str + rdr.len, "\r\n\r\n", 4);
+			rdr.len += 4;
 
-			list_for_each_entry(rule, &cur_proxy->switching_rules, list) {
-				int ret;
+			txn->status = rule->code;
+			/* let's log the request time */
+			s->logs.tv_request = now;
+			stream_int_retnclose(req->prod, &rdr);
+			goto return_prx_cond;
+		}
+	}
 
-				ret = acl_exec_cond(rule->cond, cur_proxy, s, txn, ACL_DIR_REQ);
+	/* that's OK for us now, let's move on to next analysers */
+	return 1;
 
-				ret = acl_pass(ret);
-				if (rule->cond->pol == ACL_COND_UNLESS)
-					ret = !ret;
+ return_bad_req:
+	/* We centralize bad requests processing here */
+	if (unlikely(msg->msg_state == HTTP_MSG_ERROR) || msg->err_pos >= 0) {
+		/* we detected a parsing error. We want to archive this request
+		 * in the dedicated proxy area for later troubleshooting.
+		 */
+		http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
+	}
 
-				if (ret) {
-					s->be = rule->be.backend;
-					s->be->beconn++;
-					if (s->be->beconn > s->be->beconn_max)
-						s->be->beconn_max = s->be->beconn;
-					proxy_inc_be_ctr(s->be);
+	txn->req.msg_state = HTTP_MSG_ERROR;
+	txn->status = 400;
+	stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_400));
+	s->fe->failed_req++;
 
-					/* assign new parameters to the session from the new backend */
-					s->rep->rto = s->req->wto = s->be->timeout.server;
-					s->req->cto = s->be->timeout.connect;
-					s->conn_retries = s->be->conn_retries;
-					if (s->be->options2 & PR_O2_RSPBUG_OK)
-						s->txn.rsp.err_pos = -1; /* let buggy responses pass */
-					s->flags |= SN_BE_ASSIGNED;
-					break;
-				}
-			}
-		}
+ return_prx_cond:
+	if (!(s->flags & SN_ERR_MASK))
+		s->flags |= SN_ERR_PRXCOND;
+	if (!(s->flags & SN_FINST_MASK))
+		s->flags |= SN_FINST_R;
 
-		if (!(s->flags & SN_BE_ASSIGNED) && cur_proxy->defbe.be) {
-			/* No backend was set, but there was a default
-			 * backend set in the frontend, so we use it and
-			 * loop again.
-			 */
-			s->be = cur_proxy->defbe.be;
-			s->be->beconn++;
-			if (s->be->beconn > s->be->beconn_max)
-				s->be->beconn_max = s->be->beconn;
-			proxy_inc_be_ctr(s->be);
+	req->analysers = 0;
+	req->analyse_exp = TICK_ETERNITY;
+	return 0;
+}
 
-			/* assign new parameters to the session from the new backend */
-			s->rep->rto = s->req->wto = s->be->timeout.server;
-			s->req->cto = s->be->timeout.connect;
-			s->conn_retries = s->be->conn_retries;
-			if (s->be->options2 & PR_O2_RSPBUG_OK)
-				s->txn.rsp.err_pos = -1; /* let buggy responses pass */
-			s->flags |= SN_BE_ASSIGNED;
-		}
-	} while (s->be != cur_proxy);  /* we loop only if s->be has changed */
+/* This function performs all the processing enabled for the current request.
+ * It returns 1 if the processing can continue on next analysers, or zero if it
+ * needs more data, encounters an error, or wants to immediately abort the
+ * request. It relies on buffers flags, and updates s->req->analysers.
+ */
+int http_process_request(struct session *s, struct buffer *req, int an_bit)
+{
+	struct http_txn *txn = &s->txn;
+	struct http_msg *msg = &txn->req;
 
-	if (!(s->flags & SN_BE_ASSIGNED)) {
-		/* To ensure correct connection accounting on
-		 * the backend, we count the connection for the
-		 * one managing the queue.
-		 */
-		s->be->beconn++;
-		if (s->be->beconn > s->be->beconn_max)
-			s->be->beconn_max = s->be->beconn;
-		proxy_inc_be_ctr(s->be);
-		s->flags |= SN_BE_ASSIGNED;
-	}
+	req->analysers &= ~an_bit;
+	req->analyse_exp = TICK_ETERNITY;
+
+	DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n",
+		now_ms, __FUNCTION__,
+		s,
+		req,
+		req->rex, req->wex,
+		req->flags,
+		req->l,
+		req->analysers);
 
 	/*
 	 * Right now, we know that we have processed the entire headers
@@ -2436,7 +2378,6 @@
 	stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_400));
 	s->fe->failed_req++;
 
- return_prx_cond:
 	if (!(s->flags & SN_ERR_MASK))
 		s->flags |= SN_ERR_PRXCOND;
 	if (!(s->flags & SN_FINST_MASK))
@@ -3142,18 +3083,7 @@
 					break;
 
 				/* Swithing Proxy */
-				t->be = (struct proxy *) exp->replace;
-
-				/* right now, the backend switch is not overly complicated
-				 * because we have associated req_cap and rsp_cap to the
-				 * frontend, and the beconn will be updated later.
-				 */
-
-				t->rep->rto = t->req->wto = t->be->timeout.server;
-				t->req->cto = t->be->timeout.connect;
-				t->conn_retries = t->be->conn_retries;
-				if (t->be->options2 & PR_O2_RSPBUG_OK)
-					t->txn.rsp.err_pos = -1; /* let buggy responses pass */
+				session_set_backend(t, (struct proxy *)exp->replace);
 				last_hdr = 1;
 				break;
 
@@ -3265,18 +3195,7 @@
 				break;
 
 			/* Swithing Proxy */
-			t->be = (struct proxy *) exp->replace;
-
-			/* right now, the backend switch is not too much complicated
-			 * because we have associated req_cap and rsp_cap to the
-			 * frontend, and the beconn will be updated later.
-			 */
-
-			t->rep->rto = t->req->wto = t->be->timeout.server;
-			t->req->cto = t->be->timeout.connect;
-			t->conn_retries = t->be->conn_retries;
-			if (t->be->options2 & PR_O2_RSPBUG_OK)
-				t->txn.rsp.err_pos = -1; /* let buggy responses pass */
+			session_set_backend(t, (struct proxy *)exp->replace);
 			done = 1;
 			break;
 
diff --git a/src/proxy.c b/src/proxy.c
index 69b070e..dcfb12d 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -631,6 +631,30 @@
 	}
 }
 
+/* Set current session's backend to <be>. Nothing is done if the
+ * session already had a backend assigned, which is indicated by
+ * s->flags & SN_BE_ASSIGNED.
+ * All flags, stats and counters which need be updated are updated.
+ */
+void session_set_backend(struct session *s, struct proxy *be)
+{
+	if (s->flags & SN_BE_ASSIGNED)
+		return;
+	s->be = be;
+	be->beconn++;
+	if (be->beconn > be->beconn_max)
+		be->beconn_max = be->beconn;
+	proxy_inc_be_ctr(be);
+
+	/* assign new parameters to the session from the new backend */
+	s->rep->rto = s->req->wto = be->timeout.server;
+	s->req->cto = be->timeout.connect;
+	s->conn_retries = be->conn_retries;
+	if (be->options2 & PR_O2_RSPBUG_OK)
+		s->txn.rsp.err_pos = -1; /* let buggy responses pass */
+	s->flags |= SN_BE_ASSIGNED;
+}
+
 static struct cfg_kw_list cfg_kws = {{ },{
 	{ CFG_LISTEN, "timeout", proxy_parse_timeout },
 	{ CFG_LISTEN, "clitimeout", proxy_parse_timeout },
diff --git a/src/session.c b/src/session.c
index 1d223d0..d4817ac 100644
--- a/src/session.c
+++ b/src/session.c
@@ -19,6 +19,7 @@
 #include <types/capture.h>
 #include <types/global.h>
 
+#include <proto/acl.h>
 #include <proto/backend.h>
 #include <proto/buffers.h>
 #include <proto/hdr_idx.h>
@@ -27,6 +28,7 @@
 #include <proto/pipe.h>
 #include <proto/proto_http.h>
 #include <proto/proto_tcp.h>
+#include <proto/proxy.h>
 #include <proto/queue.h>
 #include <proto/server.h>
 #include <proto/stream_interface.h>
@@ -552,6 +554,59 @@
 	si->state = SI_ST_ASS;
 }
 
+/* This stream analyser checks the switching rules and changes the backend
+ * if appropriate. The default_backend rule is also considered.
+ * 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.
+ */
+int process_switching_rules(struct session *s, struct buffer *req, int an_bit)
+{
+	req->analysers &= ~an_bit;
+	req->analyse_exp = TICK_ETERNITY;
+
+	DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n",
+		now_ms, __FUNCTION__,
+		s,
+		req,
+		req->rex, req->wex,
+		req->flags,
+		req->l,
+		req->analysers);
+
+	/* now check whether we have some switching rules for this request */
+	if (!(s->flags & SN_BE_ASSIGNED)) {
+		struct switching_rule *rule;
+
+		list_for_each_entry(rule, &s->fe->switching_rules, list) {
+			int ret;
+
+			ret = acl_exec_cond(rule->cond, s->fe, s, &s->txn, ACL_DIR_REQ);
+			ret = acl_pass(ret);
+			if (rule->cond->pol == ACL_COND_UNLESS)
+				ret = !ret;
+
+			if (ret) {
+				session_set_backend(s, rule->be.backend);
+				break;
+			}
+		}
+
+		/* To ensure correct connection accounting on the backend, we
+		 * have to assign one if it was not set (eg: a listen). This
+		 * measure also takes care of correctly setting the default
+		 * backend if any.
+		 */
+		if (!(s->flags & SN_BE_ASSIGNED))
+			session_set_backend(s, s->fe->defbe.be ? s->fe->defbe.be : s->be);
+	}
+
+	/* we don't want to run the HTTP filters again if the backend has not changed */
+	if (s->fe == s->be)
+		s->req->analysers &= ~AN_REQ_HTTP_PROCESS_BE;
+
+	return 1;
+}
+
 /* Processes the client, server, request and response jobs of a session task,
  * then puts it back to the wait queue in a clean state, or cleans up its
  * resources if it must be deleted. Returns in <next> the date the task wants
@@ -736,9 +791,27 @@
 						break;
 				}
 
+				if (s->req->analysers & AN_REQ_HTTP_PROCESS_FE) {
+					last_ana |= AN_REQ_HTTP_PROCESS_FE;
+					if (!http_process_req_common(s, s->req, AN_REQ_HTTP_PROCESS_FE, s->fe))
+						break;
+				}
+
+				if (s->req->analysers & AN_REQ_SWITCHING_RULES) {
+					last_ana |= AN_REQ_SWITCHING_RULES;
+					if (!process_switching_rules(s, s->req, AN_REQ_SWITCHING_RULES))
+						break;
+				}
+
+				if (s->req->analysers & AN_REQ_HTTP_PROCESS_BE) {
+					last_ana |= AN_REQ_HTTP_PROCESS_BE;
+					if (!http_process_req_common(s, s->req, AN_REQ_HTTP_PROCESS_BE, s->be))
+						break;
+				}
+
-				if (s->req->analysers & AN_REQ_HTTP_HDR) {
-					last_ana |= AN_REQ_HTTP_HDR;
-					if (!http_process_request(s, s->req, AN_REQ_HTTP_HDR))
+				if (s->req->analysers & AN_REQ_HTTP_INNER) {
+					last_ana |= AN_REQ_HTTP_INNER;
+					if (!http_process_request(s, s->req, AN_REQ_HTTP_INNER))
 						break;
 				}