From a1940c2fffe989631d36c4fd2debd695a3c9d025 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Thu, 20 Nov 2014 17:34:53 +0100 Subject: MAJOR: httpterm: include a copy-paste of the stats applet This one is always enabled on any proxy if the stats are not used. Currently it's a copy-paste of the stats applet which only returns headers. --- src/proto_http.c | 310 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) diff --git a/src/proto_http.c b/src/proto_http.c index f5c7c4c..13a9019 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -3159,6 +3159,285 @@ int http_handle_stats(struct session *s, struct channel *req) return 1; } +/************************* THE CODE BELOW IS FOR HTTPTERM ******************/ + +static int httpterm_send_http_headers(struct stream_interface *si) +{ + struct session *s = session_from_task(si->owner); + struct appctx *appctx = objt_appctx(si->end); + + chunk_printf(&trash, + "HTTP/1.1 200 OK\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "Content-Type: %s\r\n", + (appctx->ctx.stats.flags & STAT_FMT_HTML) ? "text/html" : "text/plain"); + + /* 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 + 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; +} + +/* 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. + * appctx->st0 contains the operation in progress (dump, done). The handler + * automatically unregisters itself once transfer is complete. + */ +static void httpterm_io_handler(struct stream_interface *si) +{ + struct appctx *appctx = __objt_appctx(si->end); + struct session *s = session_from_task(si->owner); + struct channel *req = si->ob; + struct channel *res = si->ib; + + if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO)) + goto out; + + /* check that the output is not closed */ + if (res->flags & (CF_SHUTW|CF_SHUTW_NOW)) + appctx->st0 = STAT_HTTP_DONE; + + /* all states are processed in sequence */ + if (appctx->st0 == STAT_HTTP_HEAD) { + if (httpterm_send_http_headers(si)) { + if (s->txn.meth == HTTP_METH_HEAD) + appctx->st0 = STAT_HTTP_DONE; + else + appctx->st0 = STAT_HTTP_DUMP; + } + } + + 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 = 0; + + 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) { +// if (stats_process_http_post(si)) +// appctx->st0 = STAT_HTTP_LAST; +// else if (si->ob->flags & CF_SHUTR) +// appctx->st0 = STAT_HTTP_DONE; +// } +// +// if (appctx->st0 == STAT_HTTP_LAST) { +// if (stats_send_http_redirect(si)) +// appctx->st0 = STAT_HTTP_DONE; +// } + + 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); + + if (appctx->st0 == STAT_HTTP_DONE) { + if ((req->flags & CF_SHUTW) && (si->state == SI_ST_EST)) { + si_shutr(si); + res->flags |= CF_READ_NULL; + } + } + + fail: + /* update all other flags and resync with the other side */ + si_update(si); + + /* we don't want to expire timeouts while we're processing requests */ + si->ib->rex = TICK_ETERNITY; + si->ob->wex = TICK_ETERNITY; + + out: + if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO)) { + /* check that we have released everything then unregister */ + stream_int_unregister_handler(si); + } +} + +struct si_applet httpterm_applet = { + .obj_type = OBJ_TYPE_APPLET, + .name = "", /* used for logging */ + .fct = httpterm_io_handler, + .release = NULL, +}; + +/* This function prepares an applet to handle the httpterm request. */ +int http_handle_httpterm(struct session *s, struct channel *req) +{ + struct stream_interface *si = s->rep->prod; + 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; + struct appctx *appctx; + + appctx = si_appctx(si); + memset(&appctx->ctx.stats, 0, sizeof(appctx->ctx.stats)); + 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 + 1; // "/" // + uri_auth->uri_len; + + for (h = lookup; h <= uri + msg->sl.rq.u_l - 3; h++) { + if (memcmp(h, ";up", 3) == 0) { + appctx->ctx.stats.flags |= STAT_HIDE_DOWN; + break; + } + } + + //if (uri_auth->refresh) { + // for (h = lookup; h <= uri + msg->sl.rq.u_l - 10; h++) { + // if (memcmp(h, ";norefresh", 10) == 0) { + // appctx->ctx.stats.flags |= STAT_NO_REFRESH; + // break; + // } + // } + //} + + for (h = lookup; h <= uri + msg->sl.rq.u_l - 4; h++) { + if (memcmp(h, ";csv", 4) == 0) { + appctx->ctx.stats.flags &= ~STAT_FMT_HTML; + break; + } + } + + for (h = lookup; h <= uri + msg->sl.rq.u_l - 8; h++) { + if (memcmp(h, ";st=", 4) == 0) { + int i; + h += 4; + appctx->ctx.stats.st_code = STAT_STATUS_UNKN; + for (i = STAT_STATUS_INIT + 1; i < STAT_STATUS_SIZE; i++) { + if (strncmp(stat_status_codes[i], h, 4) == 0) { + appctx->ctx.stats.st_code = i; + break; + } + } + break; + } + } + + appctx->ctx.stats.scope_str = 0; + appctx->ctx.stats.scope_len = 0; + for (h = lookup; h <= uri + msg->sl.rq.u_l - 8; h++) { + if (memcmp(h, STAT_SCOPE_INPUT_NAME "=", strlen(STAT_SCOPE_INPUT_NAME) + 1) == 0) { + int itx = 0; + const char *h2; + char scope_txt[STAT_SCOPE_TXT_MAXLEN + 1]; + const char *err; + + h += strlen(STAT_SCOPE_INPUT_NAME) + 1; + h2 = h; + appctx->ctx.stats.scope_str = h2 - msg->chn->buf->p; + while (*h != ';' && *h != '\0' && *h != '&' && *h != ' ' && *h != '\n') { + itx++; + h++; + } + + if (itx > STAT_SCOPE_TXT_MAXLEN) + itx = STAT_SCOPE_TXT_MAXLEN; + appctx->ctx.stats.scope_len = itx; + + /* scope_txt = search query, appctx->ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */ + memcpy(scope_txt, h2, itx); + scope_txt[itx] = '\0'; + err = invalid_char(scope_txt); + if (err) { + /* bad char in search text => clear scope */ + appctx->ctx.stats.scope_str = 0; + appctx->ctx.stats.scope_len = 0; + } + break; + } + } + + /* Was the status page requested with a POST ? */ + if (unlikely(txn->meth == HTTP_METH_POST && txn->req.body_len > 0)) { + if (appctx->ctx.stats.flags & STAT_ADMIN) { + /* we'll need the request body, possibly after sending 100-continue */ + req->analysers |= AN_REQ_HTTP_BODY; + appctx->st0 = STAT_HTTP_POST; + } + else { + appctx->ctx.stats.st_code = STAT_STATUS_DENY; + appctx->st0 = STAT_HTTP_LAST; + } + } + else { + /* So it was another method (GET/HEAD) */ + appctx->st0 = STAT_HTTP_HEAD; + } + + s->task->nice = -32; /* small boost for HTTP statistics */ + return 1; +} + /* Sets the TOS header in IPv4 and the traffic class header in IPv6 packets * (as per RFC3260 #4 and BCP37 #4.2 and #5.2). */ @@ -4140,6 +4419,37 @@ int http_process_req_common(struct session *s, struct channel *req, int an_bit, goto done; } + /********* OK if we drop here, we want to run the HTTP term applet **********/ + s->target = &httpterm_applet.obj_type; + if (unlikely(!stream_int_register_handler(s->rep->prod, objt_applet(s->target)))) { + txn->status = 500; + s->logs.tv_request = now; + stream_int_retnclose(req->prod, http_error_message(s, HTTP_ERR_500)); + + if (!(s->flags & SN_ERR_MASK)) + s->flags |= SN_ERR_RESOURCE; + goto return_prx_cond; + } + + /* parse the whole stats request and extract the relevant information */ + http_handle_httpterm(s, req); + + /* process the stats 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; + + /* we may want to compress the stats page */ + if (s->fe->comp || s->be->comp) + select_compression_request_header(s, req->buf); + + /* enable the minimally required analyzers to handle keep-alive and compression on the HTTP response */ + req->analysers = (req->analysers & AN_REQ_HTTP_BODY) | AN_REQ_HTTP_XFER_BODY; + /* POST requests may be accompanied with an "Expect: 100-Continue" header. * If this happens, then the data will not come immediately, so we must * send all what we have without waiting. Note that due to the small gain -- 1.7.12.1