mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-07 15:47:01 +02:00
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.
This commit is contained in:
parent
d9bdcd5139
commit
1facd6d67e
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
208
src/proto_http.c
208
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 */
|
||||
|
Loading…
Reference in New Issue
Block a user