MAJOR: stats: move the HTTP stats handling to its applet

There is a big trouble with the way POST is handled for the admin
stats page. The POST parameters are extracted from some http-request
rules, and if not round they return zero hoping for being called again
when more data passes. This results in the HTTP analyser being called
several times and all the rules prior to the stats being executed
multiple times as well. That includes rewrite rules.

So instead of doing this, we now move all the processing of the stats
into the stats applet.

That way we just set the stats applet in the HTTP analyser when a stats
request is detected, and the applet takes the time it needs to read the
arguments and respond. We could even imagine improving the applet to
support requests larger than a single buffer.

The code was almost only moved and minimally changed. Several new HTTP
states were added to the stats applet to emit headers, redirects and
to read POST. It was necessary to do this because the headers sent
depend on the parsing of the POST request. In the end it's beneficial
because we removed two stream_int_retnclose() calls.
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index 653067b..e488a5e 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -57,7 +57,10 @@
 /* HTTP stats : applet.st0 */
 enum {
 	STAT_HTTP_DONE = 0,  /* finished */
+	STAT_HTTP_HEAD,      /* send headers before dump */
 	STAT_HTTP_DUMP,      /* dumping stats */
+	STAT_HTTP_POST,      /* waiting post data */
+	STAT_HTTP_LAST,      /* sending last chunk of response */
 };
 
 /* HTML form to limit output scope */
diff --git a/src/dumpstats.c b/src/dumpstats.c
index ffaebfd..2332379 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -3477,6 +3477,282 @@
 	}
 }
 
+/* We reached the stats page through a POST request.
+ * Parse the posted data and enable/disable servers if necessary.
+ * Returns 1 if request was parsed or zero if it needs more data.
+ */
+static int stats_process_http_post(struct stream_interface *si)
+{
+	struct session *s = session_from_task(si->owner);
+
+	struct proxy *px = NULL;
+	struct server *sv = NULL;
+
+	char key[LINESIZE];
+	int action = ST_ADM_ACTION_NONE;
+	int reprocess = 0;
+
+	int total_servers = 0;
+	int altered_servers = 0;
+
+	char *first_param, *cur_param, *next_param, *end_params;
+	char *st_cur_param = NULL;
+	char *st_next_param = NULL;
+
+	struct chunk *temp;
+	int reql;
+
+	temp = get_trash_chunk();
+	if (temp->size < s->txn.req.body_len) {
+		/* too large request */
+		si->applet.ctx.stats.st_code = STAT_STATUS_EXCD;
+		goto out;
+	}
+
+	reql = bo_getblk(si->ob, temp->str, s->txn.req.body_len, s->txn.req.eoh + 2);
+	if (reql <= 0) {
+		/* we need more data */
+		si->applet.ctx.stats.st_code = STAT_STATUS_NONE;
+		return 0;
+	}
+
+	first_param = temp->str;
+	end_params  = temp->str + reql;
+	cur_param = next_param = end_params;
+	*end_params = '\0';
+
+	si->applet.ctx.stats.st_code = STAT_STATUS_NONE;
+
+	/*
+	 * Parse the parameters in reverse order to only store the last value.
+	 * From the html form, the backend and the action are at the end.
+	 */
+	while (cur_param > first_param) {
+		char *value;
+		int poffset, plen;
+
+		cur_param--;
+
+		if ((*cur_param == '&') || (cur_param == first_param)) {
+		reprocess_servers:
+			/* Parse the key */
+			poffset = (cur_param != first_param ? 1 : 0);
+			plen = next_param - cur_param + (cur_param == first_param ? 1 : 0);
+			if ((plen > 0) && (plen <= sizeof(key))) {
+				strncpy(key, cur_param + poffset, plen);
+				key[plen - 1] = '\0';
+			} else {
+				si->applet.ctx.stats.st_code = STAT_STATUS_EXCD;
+				goto out;
+			}
+
+			/* Parse the value */
+			value = key;
+			while (*value != '\0' && *value != '=') {
+				value++;
+			}
+			if (*value == '=') {
+				/* Ok, a value is found, we can mark the end of the key */
+				*value++ = '\0';
+			}
+			if (url_decode(key) < 0 || url_decode(value) < 0)
+				break;
+
+			/* Now we can check the key to see what to do */
+			if (!px && (strcmp(key, "b") == 0)) {
+				if ((px = findproxy(value, PR_CAP_BE)) == NULL) {
+					/* the backend name is unknown or ambiguous (duplicate names) */
+					si->applet.ctx.stats.st_code = STAT_STATUS_ERRP;
+					goto out;
+				}
+			}
+			else if (!action && (strcmp(key, "action") == 0)) {
+				if (strcmp(value, "disable") == 0) {
+					action = ST_ADM_ACTION_DISABLE;
+				}
+				else if (strcmp(value, "enable") == 0) {
+					action = ST_ADM_ACTION_ENABLE;
+				}
+				else if (strcmp(value, "stop") == 0) {
+					action = ST_ADM_ACTION_STOP;
+				}
+				else if (strcmp(value, "start") == 0) {
+					action = ST_ADM_ACTION_START;
+				}
+				else if (strcmp(value, "shutdown") == 0) {
+					action = ST_ADM_ACTION_SHUTDOWN;
+				}
+				else {
+					si->applet.ctx.stats.st_code = STAT_STATUS_ERRP;
+					goto out;
+				}
+			}
+			else if (strcmp(key, "s") == 0) {
+				if (!(px && action)) {
+					/*
+					 * Indicates that we'll need to reprocess the parameters
+					 * as soon as backend and action are known
+					 */
+					if (!reprocess) {
+						st_cur_param  = cur_param;
+						st_next_param = next_param;
+					}
+					reprocess = 1;
+				}
+				else if ((sv = findserver(px, value)) != NULL) {
+					switch (action) {
+					case ST_ADM_ACTION_DISABLE:
+						if ((px->state != PR_STSTOPPED) && !(sv->state & SRV_MAINTAIN)) {
+							/* Not already in maintenance, we can change the server state */
+							sv->state |= SRV_MAINTAIN;
+							set_server_down(&sv->check);
+							altered_servers++;
+							total_servers++;
+						}
+						break;
+					case ST_ADM_ACTION_ENABLE:
+						if ((px->state != PR_STSTOPPED) && (sv->state & SRV_MAINTAIN)) {
+							/* Already in maintenance, we can change the server state */
+							set_server_up(&sv->check);
+							sv->check.health = sv->check.rise;	/* up, but will fall down at first failure */
+							altered_servers++;
+							total_servers++;
+						}
+						break;
+					case ST_ADM_ACTION_STOP:
+					case ST_ADM_ACTION_START:
+						if (action == ST_ADM_ACTION_START)
+							sv->uweight = sv->iweight;
+						else
+							sv->uweight = 0;
+
+						server_recalc_eweight(sv);
+						set_server_drain_state(sv);
+
+						altered_servers++;
+						total_servers++;
+						break;
+					case ST_ADM_ACTION_SHUTDOWN:
+						if (px->state != PR_STSTOPPED) {
+							struct session *sess, *sess_bck;
+
+							list_for_each_entry_safe(sess, sess_bck, &sv->actconns, by_srv)
+								if (sess->srv_conn == sv)
+									session_shutdown(sess, SN_ERR_KILLED);
+
+							altered_servers++;
+							total_servers++;
+						}
+						break;
+					}
+				} else {
+					/* the server name is unknown or ambiguous (duplicate names) */
+					total_servers++;
+				}
+			}
+			if (reprocess && px && action) {
+				/* Now, we know the backend and the action chosen by the user.
+				 * We can safely restart from the first server parameter
+				 * to reprocess them
+				 */
+				cur_param  = st_cur_param;
+				next_param = st_next_param;
+				reprocess = 0;
+				goto reprocess_servers;
+			}
+
+			next_param = cur_param;
+		}
+	}
+
+	if (total_servers == 0) {
+		si->applet.ctx.stats.st_code = STAT_STATUS_NONE;
+	}
+	else if (altered_servers == 0) {
+		si->applet.ctx.stats.st_code = STAT_STATUS_ERRP;
+	}
+	else if (altered_servers == total_servers) {
+		si->applet.ctx.stats.st_code = STAT_STATUS_DONE;
+	}
+	else {
+		si->applet.ctx.stats.st_code = STAT_STATUS_PART;
+	}
+ out:
+	return 1;
+}
+
+
+static int stats_send_http_headers(struct stream_interface *si)
+{
+	struct session *s = session_from_task(si->owner);
+	struct uri_auth *uri = s->be->uri_auth;
+
+	chunk_printf(&trash,
+		     "HTTP/1.0 200 OK\r\n"
+		     "Cache-Control: no-cache\r\n"
+		     "Connection: close\r\n"
+		     "Content-Type: %s\r\n",
+		     (si->applet.ctx.stats.flags & STAT_FMT_HTML) ? "text/html" : "text/plain");
+
+	if (uri->refresh > 0 && !(si->applet.ctx.stats.flags & STAT_NO_REFRESH))
+		chunk_appendf(&trash, "Refresh: %d\r\n",
+			      uri->refresh);
+
+	chunk_appendf(&trash, "\r\n");
+
+	s->txn.status = 200;
+	s->logs.tv_request = now;
+
+	if (bi_putchk(si->ib, &trash) == -1)
+		return 0;
+
+	return 1;
+}
+
+static int stats_send_http_redirect(struct stream_interface *si)
+{
+	char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN];
+	struct session *s = session_from_task(si->owner);
+	struct uri_auth *uri = s->be->uri_auth;
+
+	/* scope_txt = search pattern + search query, si->applet.ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */
+	scope_txt[0] = 0;
+	if (si->applet.ctx.stats.scope_len) {
+		strcpy(scope_txt, STAT_SCOPE_PATTERN);
+		memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), bo_ptr(si->ob->buf) + si->applet.ctx.stats.scope_str, si->applet.ctx.stats.scope_len);
+		scope_txt[strlen(STAT_SCOPE_PATTERN) + si->applet.ctx.stats.scope_len] = 0;
+	}
+
+	/* We don't want to land on the posted stats page because a refresh will
+	 * repost the data. We don't want this to happen on accident so we redirect
+	 * the browse to the stats page with a GET.
+	 */
+	chunk_printf(&trash,
+		     "HTTP/1.1 303 See Other\r\n"
+		     "Cache-Control: no-cache\r\n"
+		     "Content-Type: text/plain\r\n"
+		     "Connection: close\r\n"
+		     "Location: %s;st=%s%s%s%s\r\n"
+		     "\r\n",
+		     uri->uri_prefix,
+		     ((si->applet.ctx.stats.st_code > STAT_STATUS_INIT) &&
+		      (si->applet.ctx.stats.st_code < STAT_STATUS_SIZE) &&
+		      stat_status_codes[si->applet.ctx.stats.st_code]) ?
+		     stat_status_codes[si->applet.ctx.stats.st_code] :
+		     stat_status_codes[STAT_STATUS_UNKN],
+		     (si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
+		     (si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
+		     scope_txt);
+
+	s->txn.status = 303;
+	s->logs.tv_request = now;
+
+	if (bi_putchk(si->ib, &trash) == -1)
+		return 0;
+
+	return 1;
+}
+
 /* This I/O handler runs as an applet embedded in a stream interface. It is
  * used to send HTTP stats over a TCP socket. The mechanism is very simple.
  * si->applet.st0 contains the operation in progress (dump, done). The handler
@@ -3495,15 +3771,36 @@
 	if (res->flags & (CF_SHUTW|CF_SHUTW_NOW))
 		si->applet.st0 = STAT_HTTP_DONE;
 
-	switch (si->applet.st0) {
-	case STAT_HTTP_DUMP:
-		if (stats_dump_stat_to_buffer(si, s->be->uri_auth)) {
-			si->applet.st0 = STAT_HTTP_DONE;
-			si_shutw(si);
+	/* all states are processed in sequence */
+	if (si->applet.st0 == STAT_HTTP_HEAD) {
+		if (stats_send_http_headers(si)) {
+			if (s->txn.meth == HTTP_METH_HEAD)
+				si->applet.st0 = STAT_HTTP_DONE;
+			else
+				si->applet.st0 = STAT_HTTP_DUMP;
 		}
-		break;
+	}
+
+	if (si->applet.st0 == STAT_HTTP_DUMP) {
+		if (stats_dump_stat_to_buffer(si, s->be->uri_auth))
+			si->applet.st0 = STAT_HTTP_DONE;
 	}
 
+	if (si->applet.st0 == STAT_HTTP_POST) {
+		if (stats_process_http_post(si))
+			si->applet.st0 = STAT_HTTP_LAST;
+		else if (si->ob->flags & CF_SHUTR)
+			si->applet.st0 = STAT_HTTP_DONE;
+	}
+
+	if (si->applet.st0 == STAT_HTTP_LAST) {
+		if (stats_send_http_redirect(si))
+			si->applet.st0 = STAT_HTTP_DONE;
+	}
+
+	if (si->applet.st0 == STAT_HTTP_DONE)
+		si_shutw(si);
+
 	if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST))
 		si_shutw(si);
 
diff --git a/src/proto_http.c b/src/proto_http.c
index faa060d..dd8d123 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -2789,208 +2789,12 @@
 	return 0;
 }
 
-/* We reached the stats page through a POST request.
- * Parse the posted data and enable/disable servers if necessary.
- * Returns 1 if request was parsed or zero if it needs more data.
- */
-int http_process_req_stat_post(struct stream_interface *si, struct http_txn *txn, struct channel *req)
-{
-	struct proxy *px = NULL;
-	struct server *sv = NULL;
-
-	char key[LINESIZE];
-	int action = ST_ADM_ACTION_NONE;
-	int reprocess = 0;
-
-	int total_servers = 0;
-	int altered_servers = 0;
-
-	char *first_param, *cur_param, *next_param, *end_params;
-	char *st_cur_param = NULL;
-	char *st_next_param = NULL;
-
-	first_param = req->buf->p + txn->req.eoh + 2;
-	end_params  = first_param + txn->req.body_len;
-
-	cur_param = next_param = end_params;
-
-	if (end_params >= req->buf->data + req->buf->size - global.tune.maxrewrite) {
-		/* Prevent buffer overflow */
-		si->applet.ctx.stats.st_code = STAT_STATUS_EXCD;
-		return 1;
-	}
-	else if (end_params > req->buf->p + req->buf->i) {
-		/* we need more data */
-		si->applet.ctx.stats.st_code = STAT_STATUS_NONE;
-		return 0;
-	}
-
-	*end_params = '\0';
-
-	si->applet.ctx.stats.st_code = STAT_STATUS_NONE;
-
-	/*
-	 * Parse the parameters in reverse order to only store the last value.
-	 * From the html form, the backend and the action are at the end.
-	 */
-	while (cur_param > first_param) {
-		char *value;
-		int poffset, plen;
-
-		cur_param--;
-		if ((*cur_param == '&') || (cur_param == first_param)) {
- reprocess_servers:
-			/* Parse the key */
-			poffset = (cur_param != first_param ? 1 : 0);
-			plen = next_param - cur_param + (cur_param == first_param ? 1 : 0);
-			if ((plen > 0) && (plen <= sizeof(key))) {
-				strncpy(key, cur_param + poffset, plen);
-				key[plen - 1] = '\0';
-			} else {
-				si->applet.ctx.stats.st_code = STAT_STATUS_EXCD;
-				goto out;
-			}
-
-			/* Parse the value */
-			value = key;
-			while (*value != '\0' && *value != '=') {
-				value++;
-			}
-			if (*value == '=') {
-				/* Ok, a value is found, we can mark the end of the key */
-				*value++ = '\0';
-			}
-
-			if (url_decode(key) < 0 || url_decode(value) < 0)
-				break;
-
-			/* Now we can check the key to see what to do */
-			if (!px && (strcmp(key, "b") == 0)) {
-				if ((px = findproxy(value, PR_CAP_BE)) == NULL) {
-					/* the backend name is unknown or ambiguous (duplicate names) */
-					si->applet.ctx.stats.st_code = STAT_STATUS_ERRP;
-					goto out;
-				}
-			}
-			else if (!action && (strcmp(key, "action") == 0)) {
-				if (strcmp(value, "disable") == 0) {
-					action = ST_ADM_ACTION_DISABLE;
-				}
-				else if (strcmp(value, "enable") == 0) {
-					action = ST_ADM_ACTION_ENABLE;
-				}
-				else if (strcmp(value, "stop") == 0) {
-					action = ST_ADM_ACTION_STOP;
-				}
-				else if (strcmp(value, "start") == 0) {
-					action = ST_ADM_ACTION_START;
-				}
-				else if (strcmp(value, "shutdown") == 0) {
-					action = ST_ADM_ACTION_SHUTDOWN;
-				}
-				else {
-					si->applet.ctx.stats.st_code = STAT_STATUS_ERRP;
-					goto out;
-				}
-			}
-			else if (strcmp(key, "s") == 0) {
-				if (!(px && action)) {
-					/*
-					 * Indicates that we'll need to reprocess the parameters
-					 * as soon as backend and action are known
-					 */
-					if (!reprocess) {
-						st_cur_param  = cur_param;
-						st_next_param = next_param;
-					}
-					reprocess = 1;
-				}
-				else if ((sv = findserver(px, value)) != NULL) {
-					switch (action) {
-					case ST_ADM_ACTION_DISABLE:
-						if ((px->state != PR_STSTOPPED) && !(sv->state & SRV_MAINTAIN)) {
-							/* Not already in maintenance, we can change the server state */
-							sv->state |= SRV_MAINTAIN;
-							set_server_down(&sv->check);
-							altered_servers++;
-							total_servers++;
-						}
-						break;
-					case ST_ADM_ACTION_ENABLE:
-						if ((px->state != PR_STSTOPPED) && (sv->state & SRV_MAINTAIN)) {
-							/* Already in maintenance, we can change the server state */
-							set_server_up(&sv->check);
-							sv->check.health = sv->check.rise;	/* up, but will fall down at first failure */
-							altered_servers++;
-							total_servers++;
-						}
-						break;
-					case ST_ADM_ACTION_STOP:
-					case ST_ADM_ACTION_START:
-						if (action == ST_ADM_ACTION_START)
-							sv->uweight = sv->iweight;
-						else
-							sv->uweight = 0;
-
-						server_recalc_eweight(sv);
-						set_server_drain_state(sv);
-
-						altered_servers++;
-						total_servers++;
-						break;
-					case ST_ADM_ACTION_SHUTDOWN:
-						if (px->state != PR_STSTOPPED) {
-							struct session *sess, *sess_bck;
 
-							list_for_each_entry_safe(sess, sess_bck, &sv->actconns, by_srv)
-								if (sess->srv_conn == sv)
-									session_shutdown(sess, SN_ERR_KILLED);
-
-							altered_servers++;
-							total_servers++;
-						}
-						break;
-					}
-				} else {
-					/* the server name is unknown or ambiguous (duplicate names) */
-					total_servers++;
-				}
-			}
-			if (reprocess && px && action) {
-				/* Now, we know the backend and the action chosen by the user.
-				 * We can safely restart from the first server parameter
-				 * to reprocess them
-				 */
-				cur_param  = st_cur_param;
-				next_param = st_next_param;
-				reprocess = 0;
-				goto reprocess_servers;
-			}
-
-			next_param = cur_param;
-		}
-	}
-
-	if (total_servers == 0) {
-		si->applet.ctx.stats.st_code = STAT_STATUS_NONE;
-	}
-	else if (altered_servers == 0) {
-		si->applet.ctx.stats.st_code = STAT_STATUS_ERRP;
-	}
-	else if (altered_servers == total_servers) {
-		si->applet.ctx.stats.st_code = STAT_STATUS_DONE;
-	}
-	else {
-		si->applet.ctx.stats.st_code = STAT_STATUS_PART;
-	}
- out:
-	return 1;
-}
-
-/* This function checks whether we need to enable a POST analyser to parse a
- * stats request, and also registers the stats I/O handler. It returns zero
- * if it needs to come back again, otherwise non-zero if it finishes. In the
- * latter case, it also clears the request analysers.
+/* This function prepares an applet to handle the stats. It can deal with the
+ * "100-continue" expectation, check that admin rules are met for POST requests,
+ * and program a response message if something was unexpected. It cannot fail
+ * and always relies on the stats applet to complete the job. It does not touch
+ * analysers nor counters.
  */
 int http_handle_stats(struct session *s, struct channel *req)
 {
@@ -2998,7 +2802,6 @@
 	struct stream_interface *si = s->rep->prod;
 	struct http_txn *txn = &s->txn;
 	struct http_msg *msg = &txn->req;
-	struct uri_auth *uri = s->be->uri_auth;
 
 	/* now check whether we have some admin rules for this request */
 	list_for_each_entry(stats_admin_rule, &s->be->uri_auth->admin_rules, list) {
@@ -3019,9 +2822,7 @@
 	}
 
 	/* Was the status page requested with a POST ? */
-	if (unlikely(txn->meth == HTTP_METH_POST)) {
-		char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN];
-
+	if (unlikely(txn->meth == HTTP_METH_POST && txn->req.body_len > 0)) {
 		if (si->applet.ctx.stats.flags & STAT_ADMIN) {
 			if (msg->msg_state < HTTP_MSG_100_SENT) {
 				/* If we have HTTP/1.1 and Expect: 100-continue, then we must
@@ -3039,100 +2840,22 @@
 				msg->msg_state = HTTP_MSG_100_SENT;
 				s->logs.tv_request = now;  /* update the request timer to reflect full request */
 			}
-			if (!http_process_req_stat_post(si, txn, req))
-				return 0;   /* we need more data */
+			s->rep->prod->applet.st0 = STAT_HTTP_POST;
 		}
-		else
+		else {
 			si->applet.ctx.stats.st_code = STAT_STATUS_DENY;
-		/* scope_txt = search pattern + search query, si->applet.ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */
-		scope_txt[0] = 0;
-		if (si->applet.ctx.stats.scope_len) {
-			strcpy(scope_txt, STAT_SCOPE_PATTERN);
-			memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), bo_ptr(req->buf) + si->applet.ctx.stats.scope_str, si->applet.ctx.stats.scope_len);
-			scope_txt[strlen(STAT_SCOPE_PATTERN) + si->applet.ctx.stats.scope_len] = 0;
+			s->rep->prod->applet.st0 = STAT_HTTP_LAST;
 		}
-
-
-		/* We don't want to land on the posted stats page because a refresh will
-		 * repost the data. We don't want this to happen on accident so we redirect
-		 * the browse to the stats page with a GET.
-		 */
-		chunk_printf(&trash,
-		             "HTTP/1.1 303 See Other\r\n"
-		             "Cache-Control: no-cache\r\n"
-		             "Content-Type: text/plain\r\n"
-		             "Connection: close\r\n"
-		             "Location: %s;st=%s%s%s%s\r\n"
-		             "\r\n",
-		             uri->uri_prefix,
-		             ((si->applet.ctx.stats.st_code > STAT_STATUS_INIT) &&
-		              (si->applet.ctx.stats.st_code < STAT_STATUS_SIZE) &&
-		              stat_status_codes[si->applet.ctx.stats.st_code]) ?
-		             stat_status_codes[si->applet.ctx.stats.st_code] :
-		             stat_status_codes[STAT_STATUS_UNKN],
-			     (si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
-			     (si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
-			     scope_txt);
-
-		s->txn.status = 303;
-		s->logs.tv_request = now;
-		stream_int_retnclose(req->prod, &trash);
-		s->target = &http_stats_applet.obj_type; /* just for logging the applet name */
-
-		if (s->fe == s->be) /* report it if the request was intercepted by the frontend */
-			s->fe->fe_counters.intercepted_req++;
-
-		if (!(s->flags & SN_ERR_MASK))      // this is not really an error but it is
-			s->flags |= SN_ERR_LOCAL;   // to mark that it comes from the proxy
-		if (!(s->flags & SN_FINST_MASK))
-			s->flags |= SN_FINST_R;
-		req->analysers = 0;
-		return 1;
 	}
-
-	/* OK, let's go on now */
-
-	chunk_printf(&trash,
-	             "HTTP/1.0 200 OK\r\n"
-	             "Cache-Control: no-cache\r\n"
-	             "Connection: close\r\n"
-	             "Content-Type: %s\r\n",
-	             (si->applet.ctx.stats.flags & STAT_FMT_HTML) ? "text/html" : "text/plain");
-
-	if (uri->refresh > 0 && !(si->applet.ctx.stats.flags & STAT_NO_REFRESH))
-		chunk_appendf(&trash, "Refresh: %d\r\n",
-		              uri->refresh);
-
-	chunk_appendf(&trash, "\r\n");
-
-	s->txn.status = 200;
-	s->logs.tv_request = now;
-
-	if (s->fe == s->be) /* report it if the request was intercepted by the frontend */
-		s->fe->fe_counters.intercepted_req++;
-
-	if (!(s->flags & SN_ERR_MASK))      // this is not really an error but it is
-		s->flags |= SN_ERR_LOCAL;   // to mark that it comes from the proxy
-	if (!(s->flags & SN_FINST_MASK))
-		s->flags |= SN_FINST_R;
-
-	if (s->txn.meth == HTTP_METH_HEAD) {
-		/* that's all we return in case of HEAD request, so let's immediately close. */
-		stream_int_retnclose(req->prod, &trash);
-		s->target = &http_stats_applet.obj_type; /* just for logging the applet name */
-		req->analysers = 0;
-		return 1;
+	else {
+		/* So it was another method (GET/HEAD) */
+		s->rep->prod->applet.st0 = STAT_HTTP_HEAD;
 	}
 
-	/* OK, push the response and hand over to the stats I/O handler */
-	bi_putchk(s->rep, &trash);
-
 	s->task->nice = -32; /* small boost for HTTP statistics */
 	stream_int_register_handler(s->rep->prod, &http_stats_applet);
 	s->target = s->rep->prod->conn->target; // for logging only
-	s->rep->prod->applet.st0 = STAT_HTTP_DUMP;
 	s->rep->prod->applet.st1 = s->rep->prod->applet.st2 = 0;
-	req->analysers = 0;
 	return 1;
 }
 
@@ -3799,12 +3522,18 @@
 
 	if (unlikely(do_stats)) {
 		/* process the stats request now */
-		if (!http_handle_stats(s, req)) {
-			/* we need more data, let's come back here later */
-			req->analysers |= an_bit;
-			channel_dont_connect(req);
-			return 0;
-		}
+		http_handle_stats(s, req);
+
+		if (s->fe == s->be) /* report it if the request was intercepted by the frontend */
+			s->fe->fe_counters.intercepted_req++;
+
+		if (!(s->flags & SN_ERR_MASK))      // this is not really an error but it is
+			s->flags |= SN_ERR_LOCAL;   // to mark that it comes from the proxy
+		if (!(s->flags & SN_FINST_MASK))
+			s->flags |= SN_FINST_R;
+
+		req->analyse_exp = TICK_ETERNITY;
+		req->analysers = 0;
 		return 1;
 	}