diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h index 56d8abbe7..7038f469e 100644 --- a/include/proto/dumpstats.h +++ b/include/proto/dumpstats.h @@ -53,6 +53,12 @@ #define STAT_CLI_O_ERR 7 /* dump errors */ #define STAT_CLI_O_TAB 8 /* dump tables */ +/* status codes (strictly 4 chars) used in the URL to display a message */ +#define STAT_STATUS_UNKN "UNKN" /* an unknown error occured, shouldn't happen */ +#define STAT_STATUS_DONE "DONE" /* the action is successful */ +#define STAT_STATUS_NONE "NONE" /* nothing happened (no action chosen or servers state didn't change) */ +#define STAT_STATUS_EXCD "EXCD" /* an error occured becayse the buffer couldn't store all data */ + int stats_accept(struct session *s); int stats_sock_parse_request(struct stream_interface *si, char *line); diff --git a/include/types/session.h b/include/types/session.h index d1e54acff..0bbb9bf2f 100644 --- a/include/types/session.h +++ b/include/types/session.h @@ -217,6 +217,7 @@ struct session { short px_st, sv_st; /* DATA_ST_INIT or DATA_ST_DATA */ unsigned int flags; /* STAT_* */ int iid, type, sid; /* proxy id, type and service id if bounding of stats is enabled */ + const char *st_code; /* pointer to the status code returned by an action */ } stats; struct { struct bref bref; /* back-reference from the session being dumped */ diff --git a/src/dumpstats.c b/src/dumpstats.c index 59607f95b..5195d8c1e 100644 --- a/src/dumpstats.c +++ b/src/dumpstats.c @@ -1135,6 +1135,44 @@ int stats_dump_raw_to_buffer(struct session *s, struct buffer *rep) } +/* 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. + */ +int stats_http_redir(struct session *s, struct buffer *rep, struct uri_auth *uri) +{ + struct chunk msg; + + chunk_init(&msg, trash, sizeof(trash)); + + switch (s->data_state) { + case DATA_ST_INIT: + chunk_printf(&msg, + "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, s->data_ctx.stats.st_code); + chunk_printf(&msg, "\r\n\r\n"); + + if (buffer_feed_chunk(rep, &msg) >= 0) + 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; + + s->data_state = DATA_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->st0 becomes non-zero once the transfer is finished. The handler @@ -1154,9 +1192,16 @@ void http_stats_io_handler(struct stream_interface *si) si->st0 = 1; if (!si->st0) { - if (stats_dump_http(s, res, s->be->uri_auth)) { - si->st0 = 1; - si->shutw(si); + if (s->txn.meth == HTTP_METH_POST) { + if (stats_http_redir(s, res, s->be->uri_auth)) { + si->st0 = 1; + si->shutw(si); + } + } else { + if (stats_dump_http(s, res, s->be->uri_auth)) { + si->st0 = 1; + si->shutw(si); + } } } @@ -1446,6 +1491,39 @@ int stats_dump_http(struct session *s, struct buffer *rep, struct uri_auth *uri) "" ); + if (s->data_ctx.stats.st_code) { + if (strcmp(s->data_ctx.stats.st_code, STAT_STATUS_DONE) == 0) { + chunk_printf(&msg, + "
\n"); + } + if (buffer_feed_chunk(rep, &msg) >= 0) return 0; } @@ -1546,6 +1624,13 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri) case DATA_ST_PX_TH: if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) { + if (px->cap & PR_CAP_BE && px->srv) { + /* A form to enable/disable this proxy servers */ + chunk_printf(&msg, + "
", + px->id); + } + + chunk_printf(&msg, "\n"); if (buffer_feed_chunk(rep, &msg) >= 0) return 0; diff --git a/src/proto_http.c b/src/proto_http.c index 859b7fcbb..3d408e13b 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -2839,6 +2839,110 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit) return 0; } +/* We reached the stats page through a POST request. + * Parse the posted data and enable/disable servers if necessary. + * Returns 0 if request was parsed. + * Returns 1 if there was a problem parsing the posted data. + */ +int http_process_req_stat_post(struct session *s, struct buffer *req) +{ + struct http_txn *txn = &s->txn; + struct proxy *px; + struct server *sv; + + char *backend = NULL; + int action = 0; + + char *first_param, *cur_param, *next_param, *end_params; + + first_param = req->data + txn->req.eoh + 2; + end_params = first_param + txn->req.hdr_content_len; + + cur_param = next_param = end_params; + + if (end_params >= req->data + req->size) { + /* Prevent buffer overflow */ + s->data_ctx.stats.st_code = STAT_STATUS_EXCD; + return 1; + } + else if (end_params > req->data + req->l) { + /* This condition also rejects a request with Expect: 100-continue */ + s->data_ctx.stats.st_code = STAT_STATUS_EXCD; + return 1; + } + + *end_params = '\0'; + + s->data_ctx.stats.st_code = STAT_STATUS_NONE; + + /* + * Parse the parameters in reverse order to only store the last value. + * From the html form, the backend and the action are at the end. + */ + while (cur_param > first_param) { + char *key, *value; + + cur_param--; + if ((*cur_param == '&') || (cur_param == first_param)) { + /* Parse the key */ + key = cur_param; + if (cur_param != first_param) { + /* delimit the string for the next loop */ + *key++ = '\0'; + } + + /* Parse the value */ + value = key; + while (*value != '\0' && *value != '=') { + value++; + } + if (*value == '=') { + /* Ok, a value is found, we can mark the end of the key */ + *value++ = '\0'; + } + + /* Now we can check the key to see what to do */ + if (!backend && strcmp(key, "b") == 0) { + backend = value; + } + else if (!action && strcmp(key, "action") == 0) { + if (strcmp(value, "disable") == 0) { + action = 1; + } + else if (strcmp(value, "enable") == 0) { + action = 2; + } else { + /* unknown action, no need to continue */ + break; + } + } + else if (strcmp(key, "s") == 0) { + if (backend && action && get_backend_server(backend, value, &px, &sv)) { + switch (action) { + case 1: + if (! (sv->state & SRV_MAINTAIN)) { + /* Not already in maintenance, we can change the server state */ + sv->state |= SRV_MAINTAIN; + set_server_down(sv); + s->data_ctx.stats.st_code = STAT_STATUS_DONE; + } + break; + case 2: + if ((sv->state & SRV_MAINTAIN)) { + /* Already in maintenance, we can change the server state */ + set_server_up(sv); + s->data_ctx.stats.st_code = STAT_STATUS_DONE; + } + break; + } + } + } + next_param = cur_param; + } + } + return 0; +} + /* This stream analyser runs all HTTP request processing which is common to * frontends and backends, which means blocking ACLs, filters, connection-close, * reqadd, stats and redirects. This is performed for the designated proxy. @@ -3053,6 +3157,11 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s * make it follow standard rules (eg: clear req->analysers). */ + /* Was the status page requested with a POST ? */ + if (txn->meth == HTTP_METH_POST) { + http_process_req_stat_post(s, req); + } + s->logs.tv_request = now; s->data_source = DATA_SRC_STATS; s->data_state = DATA_ST_INIT; @@ -6943,10 +7052,10 @@ void get_srv_from_appsession(struct session *t, const char *begin, int len) } /* - * In a GET or HEAD request, check if the requested URI matches the stats uri + * In a GET, HEAD or POST request, check if the requested URI matches the stats uri * for the current backend. * - * It is assumed that the request is either a HEAD or GET and that the + * It is assumed that the request is either a HEAD, GET, or POST and that the * t->be->uri_auth field is valid. * * Returns 1 if stats should be provided, otherwise 0. @@ -6960,7 +7069,7 @@ int stats_check_uri(struct session *t, struct proxy *backend) if (!uri_auth) return 0; - if (txn->meth != HTTP_METH_GET && txn->meth != HTTP_METH_HEAD) + if (txn->meth != HTTP_METH_GET && txn->meth != HTTP_METH_HEAD && txn->meth != HTTP_METH_POST) return 0; memset(&t->data_ctx.stats, 0, sizeof(t->data_ctx.stats)); @@ -7004,6 +7113,24 @@ int stats_check_uri(struct session *t, struct proxy *backend) h++; } + h = txn->req.sol + txn->req.sl.rq.u + uri_auth->uri_len; + while (h <= txn->req.sol + txn->req.sl.rq.u + txn->req.sl.rq.u_l - 8) { + if (memcmp(h, ";st=", 4) == 0) { + h += 4; + + if (memcmp(h, STAT_STATUS_DONE, 4) == 0) + t->data_ctx.stats.st_code = STAT_STATUS_DONE; + else if (memcmp(h, STAT_STATUS_NONE, 4) == 0) + t->data_ctx.stats.st_code = STAT_STATUS_NONE; + else if (memcmp(h, STAT_STATUS_EXCD, 4) == 0) + t->data_ctx.stats.st_code = STAT_STATUS_EXCD; + else + t->data_ctx.stats.st_code = STAT_STATUS_UNKN; + break; + } + h++; + } + t->data_ctx.stats.flags |= STAT_SHOW_STAT | STAT_SHOW_INFO; return 1;