From 1facd6d67e78d0628be7b7a7444afbf2b36aa5fd Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Sat, 22 Dec 2012 22:03:39 +0100 Subject: [PATCH] REORG: stats: move the HTTP header injection to proto_http The HTTP header injection that are performed in dumpstats when responding or when redirecting a POST request have nothing to do in dumpstats. They do not use any state from the stats, and are 100% HTTP. Let's make the headers there in the HTTP core, and have dumpstats only produce stats. --- src/dumpstats.c | 87 +------------------- src/proto_http.c | 208 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 145 insertions(+), 150 deletions(-) diff --git a/src/dumpstats.c b/src/dumpstats.c index 661aa3f539..e9e7d14b26 100644 --- a/src/dumpstats.c +++ b/src/dumpstats.c @@ -87,8 +87,7 @@ static int stats_dump_http(struct stream_interface *si, struct uri_auth *uri); -> stats_dump_px_end() http_stats_io_handler() - -> stats_http_redir() - -> stats_dump_http() // also emits the HTTP headers + -> stats_dump_http() -> stats_dump_html_head() // emits the HTML headers -> stats_dump_csv_header() // emits the CSV headers (same as above) -> stats_dump_http_info() // note: ignores non-HTML output @@ -3311,7 +3310,6 @@ static int stats_dump_http_end(struct stream_interface *si, struct proxy *px, st */ static int stats_dump_http(struct stream_interface *si, struct uri_auth *uri) { - struct session *s = si->conn->xprt_ctx; struct channel *rep = si->ib; struct proxy *px; @@ -3319,34 +3317,6 @@ static int stats_dump_http(struct stream_interface *si, struct uri_auth *uri) switch (si->conn->xprt_st) { case STAT_ST_INIT: - chunk_appendf(&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_CSV) ? "text/plain" : "text/html"); - - 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; - if (bi_putchk(rep, &trash) == -1) - return 0; - - if (!(s->flags & SN_ERR_MASK)) // this is not really an error but it is - s->flags |= SN_ERR_PRXCOND; // 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 */ - si->conn->xprt_st = STAT_ST_FIN; - return 1; - } - si->conn->xprt_st = STAT_ST_HEAD; /* let's start producing data */ /* fall through */ @@ -3412,48 +3382,6 @@ static int stats_dump_http(struct stream_interface *si, struct uri_auth *uri) } } -/* 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. - */ -static int stats_http_redir(struct stream_interface *si, struct uri_auth *uri) -{ - struct session *s = si->conn->xprt_ctx; - - chunk_reset(&trash); - - switch (si->conn->xprt_st) { - case STAT_ST_INIT: - chunk_appendf(&trash, - "HTTP/1.0 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", - 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]); - chunk_appendf(&trash, "\r\n\r\n"); - - if (bi_putchk(si->ib, &trash) == -1) - return 0; - - s->txn.status = 303; - - if (!(s->flags & SN_ERR_MASK)) // this is not really an error but it is - s->flags |= SN_ERR_PRXCOND; // to mark that it comes from the proxy - if (!(s->flags & SN_FINST_MASK)) - s->flags |= SN_FINST_R; - - si->conn->xprt_st = STAT_ST_FIN; - return 1; - } - 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 becomes non-zero once the transfer is finished. The handler @@ -3473,16 +3401,9 @@ static void http_stats_io_handler(struct stream_interface *si) si->applet.st0 = 1; if (!si->applet.st0) { - if (s->txn.meth == HTTP_METH_POST) { - if (stats_http_redir(si, s->be->uri_auth)) { - si->applet.st0 = 1; - si_shutw(si); - } - } else { - if (stats_dump_http(si, s->be->uri_auth)) { - si->applet.st0 = 1; - si_shutw(si); - } + if (stats_dump_http(si, s->be->uri_auth)) { + si->applet.st0 = 1; + si_shutw(si); } } diff --git a/src/proto_http.c b/src/proto_http.c index f6535f24d4..f664b8ee38 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -2927,6 +2927,140 @@ int http_process_req_stat_post(struct stream_interface *si, struct http_txn *txn 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. + */ +int http_handle_stats(struct session *s, struct channel *req) +{ + struct stats_admin_rule *stats_admin_rule; + 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) { + int ret = 1; + + if (stats_admin_rule->cond) { + ret = acl_exec_cond(stats_admin_rule->cond, s->be, s, &s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL); + ret = acl_pass(ret); + if (stats_admin_rule->cond->pol == ACL_COND_UNLESS) + ret = !ret; + } + + if (ret) { + /* no rule, or the rule matches */ + s->rep->prod->applet.ctx.stats.flags |= STAT_ADMIN; + break; + } + } + + /* Was the status page requested with a POST ? */ + if (unlikely(txn->meth == HTTP_METH_POST)) { + 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 + * send an HTTP/1.1 100 Continue intermediate response. + */ + if (msg->flags & HTTP_MSGF_VER_11) { + struct hdr_ctx ctx; + ctx.idx = 0; + /* Expect is allowed in 1.1, look for it */ + if (http_find_header2("Expect", 6, req->buf->p, &txn->hdr_idx, &ctx) && + unlikely(ctx.vlen == 12 && strncasecmp(ctx.line+ctx.val, "100-continue", 12) == 0)) { + bo_inject(s->rep, http_100_chunk.str, http_100_chunk.len); + } + } + 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 */ + } + else + si->applet.ctx.stats.st_code = STAT_STATUS_DENY; + + /* 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.0 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\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]); + + 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_PRXCOND; // to mark that it comes from the proxy + if (!(s->flags & SN_FINST_MASK)) + s->flags |= SN_FINST_R; + 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_CSV) ? "text/plain" : "text/html"); + + 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_PRXCOND; // 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 */ + return 1; + } + + /* 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->conn->xprt_ctx = s; + s->rep->prod->applet.st0 = s->rep->prod->applet.st1 = 0; + req->analysers = 0; + + return 1; +} + /* returns a pointer to the first rule which forbids access (deny or http_auth), * or NULL if everything's OK. */ @@ -3165,74 +3299,14 @@ int http_process_req_common(struct session *s, struct channel *req, int an_bit, goto return_bad_req; } - if (do_stats) { - struct stats_admin_rule *stats_admin_rule; - - /* We need to provide stats for this request. - * FIXME!!! that one is rather dangerous, we want to - * make it follow standard rules (eg: clear req->analysers). - */ - - /* 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) { - int ret = 1; - - if (stats_admin_rule->cond) { - ret = acl_exec_cond(stats_admin_rule->cond, s->be, s, &s->txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL); - ret = acl_pass(ret); - if (stats_admin_rule->cond->pol == ACL_COND_UNLESS) - ret = !ret; - } - - if (ret) { - /* no rule, or the rule matches */ - s->rep->prod->applet.ctx.stats.flags |= STAT_ADMIN; - break; - } - } - - /* Was the status page requested with a POST ? */ - if (txn->meth == HTTP_METH_POST) { - if (s->rep->prod->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 - * send an HTTP/1.1 100 Continue intermediate response. - */ - if (msg->flags & HTTP_MSGF_VER_11) { - struct hdr_ctx ctx; - ctx.idx = 0; - /* Expect is allowed in 1.1, look for it */ - if (http_find_header2("Expect", 6, req->buf->p, &txn->hdr_idx, &ctx) && - unlikely(ctx.vlen == 12 && strncasecmp(ctx.line+ctx.val, "100-continue", 12) == 0)) { - bo_inject(s->rep, http_100_chunk.str, http_100_chunk.len); - } - } - 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(s->rep->prod, txn, req)) { - /* we need more data */ - req->analysers |= an_bit; - channel_dont_connect(req); - return 0; - } - } else { - s->rep->prod->applet.ctx.stats.st_code = STAT_STATUS_DENY; - } + 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); } - - s->logs.tv_request = now; - 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->conn->xprt_ctx = s; - s->rep->prod->applet.st0 = s->rep->prod->applet.st1 = 0; - req->analysers = 0; - if (s->fe == s->be) /* report it if the request was intercepted by the frontend */ - s->fe->fe_counters.intercepted_req++; - - return 0; - + return 1; } /* check whether we have some ACLs set to redirect this request */ -- 2.39.5