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.
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 661aa3f..e9e7d14 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -87,8 +87,7 @@
-> 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(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 @@
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 */
@@ -3410,48 +3380,6 @@
si->conn->xprt_st = STAT_ST_FIN;
return -1;
}
-}
-
-/* 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
@@ -3473,16 +3401,9 @@
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 f6535f2..f664b8e 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -2927,6 +2927,140 @@
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 @@
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 */