mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-10 09:07:02 +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()
|
-> stats_dump_px_end()
|
||||||
|
|
||||||
http_stats_io_handler()
|
http_stats_io_handler()
|
||||||
-> stats_http_redir()
|
-> stats_dump_http()
|
||||||
-> stats_dump_http() // also emits the HTTP headers
|
|
||||||
-> stats_dump_html_head() // emits the HTML headers
|
-> stats_dump_html_head() // emits the HTML headers
|
||||||
-> stats_dump_csv_header() // emits the CSV headers (same as above)
|
-> stats_dump_csv_header() // emits the CSV headers (same as above)
|
||||||
-> stats_dump_http_info() // note: ignores non-HTML output
|
-> 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)
|
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 channel *rep = si->ib;
|
||||||
struct proxy *px;
|
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) {
|
switch (si->conn->xprt_st) {
|
||||||
case STAT_ST_INIT:
|
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 */
|
si->conn->xprt_st = STAT_ST_HEAD; /* let's start producing data */
|
||||||
/* fall through */
|
/* 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
|
/* 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.
|
* 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
|
* 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;
|
si->applet.st0 = 1;
|
||||||
|
|
||||||
if (!si->applet.st0) {
|
if (!si->applet.st0) {
|
||||||
if (s->txn.meth == HTTP_METH_POST) {
|
if (stats_dump_http(si, s->be->uri_auth)) {
|
||||||
if (stats_http_redir(si, s->be->uri_auth)) {
|
si->applet.st0 = 1;
|
||||||
si->applet.st0 = 1;
|
si_shutw(si);
|
||||||
si_shutw(si);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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;
|
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),
|
/* returns a pointer to the first rule which forbids access (deny or http_auth),
|
||||||
* or NULL if everything's OK.
|
* 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;
|
goto return_bad_req;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (do_stats) {
|
if (unlikely(do_stats)) {
|
||||||
struct stats_admin_rule *stats_admin_rule;
|
/* process the stats request now */
|
||||||
|
if (!http_handle_stats(s, req)) {
|
||||||
/* We need to provide stats for this request.
|
/* we need more data, let's come back here later */
|
||||||
* FIXME!!! that one is rather dangerous, we want to
|
req->analysers |= an_bit;
|
||||||
* make it follow standard rules (eg: clear req->analysers).
|
channel_dont_connect(req);
|
||||||
*/
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return 1;
|
||||||
/* 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;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* check whether we have some ACLs set to redirect this request */
|
/* check whether we have some ACLs set to redirect this request */
|
||||||
|
Loading…
Reference in New Issue
Block a user