MEDIUM: stats: add support for HTTP keep-alive on the stats page

In theory the principle is simple as we just need to send HTTP chunks
if the client is 1.1 compatible. In practice it's harder because we
have to append a CR LF after each block of data and we're never sure
to have the room for this. In order not to have to deal with this, we
instead send the CR LF prior to each chunk size. The only issue is for
the first chunk and for this reason we avoid to send the empty header
line when using chunked encoding.
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index 0da3091..3afff94 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -31,6 +31,7 @@
 #define STAT_HIDE_DOWN  0x00000008	/* hide 'down' servers in the stats page */
 #define STAT_NO_REFRESH 0x00000010	/* do not automatically refresh the stats page */
 #define STAT_ADMIN      0x00000020	/* indicate a stats admin level */
+#define STAT_CHUNKED    0x00000040      /* use chunked encoding (HTTP/1.1) */
 #define STAT_BOUND      0x00800000	/* bound statistics to selected proxies/types/services */
 
 #define STATS_TYPE_FE  0
diff --git a/src/dumpstats.c b/src/dumpstats.c
index eeb662b..9068634 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -4136,17 +4136,23 @@
 	struct appctx *appctx = objt_appctx(si->end);
 
 	chunk_printf(&trash,
-		     "HTTP/1.0 200 OK\r\n"
+		     "HTTP/1.%c 200 OK\r\n"
 		     "Cache-Control: no-cache\r\n"
 		     "Connection: close\r\n"
 		     "Content-Type: %s\r\n",
+		     (appctx->ctx.stats.flags & STAT_CHUNKED) ? '1' : '0',
 		     (appctx->ctx.stats.flags & STAT_FMT_HTML) ? "text/html" : "text/plain");
 
 	if (uri->refresh > 0 && !(appctx->ctx.stats.flags & STAT_NO_REFRESH))
 		chunk_appendf(&trash, "Refresh: %d\r\n",
 			      uri->refresh);
 
-	chunk_appendf(&trash, "\r\n");
+	/* we don't send the CRLF in chunked mode, it will be sent with the first chunk's size */
+
+	if (appctx->ctx.stats.flags & STAT_CHUNKED)
+		chunk_appendf(&trash, "Transfer-Encoding: chunked\r\n");
+	else if (appctx->ctx.stats.flags & STAT_CHUNKED)
+		chunk_appendf(&trash, "\r\n");
 
 	s->txn.status = 200;
 	s->logs.tv_request = now;
@@ -4232,8 +4238,53 @@
 	}
 
 	if (appctx->st0 == STAT_HTTP_DUMP) {
+		unsigned int prev_len = si->ib->buf->i;
+		unsigned int data_len;
+		unsigned int last_len;
+		unsigned int last_fwd;
+
+		if (appctx->ctx.stats.flags & STAT_CHUNKED) {
+			/* One difficulty we're facing is that we must prevent
+			 * the input data from being automatically forwarded to
+			 * the output area. For this, we temporarily disable
+			 * forwarding on the channel.
+			 */
+			last_fwd = si->ib->to_forward;
+			si->ib->to_forward = 0;
+			chunk_printf(&trash, "\r\n000000\r\n");
+			if (bi_putchk(si->ib, &trash) == -1) {
+				si->ib->to_forward = last_fwd;
+				goto fail;
+			}
+		}
+
+		data_len = si->ib->buf->i;
 		if (stats_dump_stat_to_buffer(si, s->be->uri_auth))
 			appctx->st0 = STAT_HTTP_DONE;
+
+		last_len = si->ib->buf->i;
+
+		/* Now we must either adjust or remove the chunk size. This is
+		 * not easy because the chunk size might wrap at the end of the
+		 * buffer, so we pretend we have nothing in the buffer, we write
+		 * the size, then restore the buffer's contents. Note that we can
+		 * only do that because no forwarding is scheduled on the stats
+		 * applet.
+		 */
+		if (appctx->ctx.stats.flags & STAT_CHUNKED) {
+			si->ib->total  -= (last_len - prev_len);
+			si->ib->buf->i -= (last_len - prev_len);
+
+			if (last_len != data_len) {
+				chunk_printf(&trash, "\r\n%06x\r\n", (last_len - data_len));
+				bi_putchk(si->ib, &trash);
+
+				si->ib->total  += (last_len - data_len);
+				si->ib->buf->i += (last_len - data_len);
+			}
+			/* now re-enable forwarding */
+			channel_forward(si->ib, last_fwd);
+		}
 	}
 
 	if (appctx->st0 == STAT_HTTP_POST) {
@@ -4248,8 +4299,17 @@
 			appctx->st0 = STAT_HTTP_DONE;
 	}
 
-	if (appctx->st0 == STAT_HTTP_DONE)
-		si_shutw(si);
+	if (appctx->st0 == STAT_HTTP_DONE) {
+		if (appctx->ctx.stats.flags & STAT_CHUNKED) {
+			chunk_printf(&trash, "\r\n0\r\n\r\n");
+			if (bi_putchk(si->ib, &trash) == -1)
+				goto fail;
+		}
+		/* eat the whole request */
+		bo_skip(si->ob, si->ob->buf->o);
+		res->flags |= CF_READ_NULL;
+		si_shutr(si);
+	}
 
 	if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST))
 		si_shutw(si);
@@ -4261,6 +4321,7 @@
 		}
 	}
 
+ fail:
 	/* update all other flags and resync with the other side */
 	si_update(si);
 
diff --git a/src/proto_http.c b/src/proto_http.c
index 18da38f..259cc58 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -2825,6 +2825,8 @@
 	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))
+		appctx->ctx.stats.flags |= STAT_CHUNKED;
 
 	uri = msg->chn->buf->p + msg->sl.rq.u;
 	lookup = uri + uri_auth->uri_len;
@@ -3641,7 +3643,7 @@
 			s->flags |= SN_FINST_R;
 
 		req->analyse_exp = TICK_ETERNITY;
-		req->analysers = 0;
+		req->analysers = AN_REQ_HTTP_XFER_BODY;
 		return 1;
 	}