]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: stats: add support for HTTP keep-alive on the stats page
authorWilly Tarreau <w@1wt.eu>
Sat, 28 Dec 2013 20:11:35 +0000 (21:11 +0100)
committerWilly Tarreau <w@1wt.eu>
Sat, 28 Dec 2013 20:40:16 +0000 (21:40 +0100)
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.

include/proto/dumpstats.h
src/dumpstats.c
src/proto_http.c

index 0da3091c5e89f436879e1f45a848e7fd11a145da..3afff941a92c19caaa4adc8ae800a758860b3c16 100644 (file)
@@ -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
index eeb662b0b9b05226cb14eb2e24a60683a2452c39..90686342db74a21d3b8a66a79fe4976bc0bedc1b 100644 (file)
@@ -4136,17 +4136,23 @@ static int stats_send_http_headers(struct stream_interface *si)
        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 @@ static void http_stats_io_handler(struct stream_interface *si)
        }
 
        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 @@ static void http_stats_io_handler(struct stream_interface *si)
                        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 @@ static void http_stats_io_handler(struct stream_interface *si)
                }
        }
 
+ fail:
        /* update all other flags and resync with the other side */
        si_update(si);
 
index 18da38f7c57600732f5a9eccedcade3fd0152e50..259cc58d1d0591b668154705c7e12fd600d62296 100644 (file)
@@ -2825,6 +2825,8 @@ int http_handle_stats(struct session *s, struct channel *req)
        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 @@ int http_process_req_common(struct session *s, struct channel *req, int an_bit,
                        s->flags |= SN_FINST_R;
 
                req->analyse_exp = TICK_ETERNITY;
-               req->analysers = 0;
+               req->analysers = AN_REQ_HTTP_XFER_BODY;
                return 1;
        }