From 1d0dfb155df56f256b615049a0d9333595a35501 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Tue, 7 Jul 2009 15:10:31 +0200 Subject: [PATCH] [MAJOR] http: complete splitting of the remaining stages The HTTP processing has been splitted into 7 steps, one of which is not anymore HTTP-specific (content-switching). That way, it becomes possible to use "use_backend" rules in TCP mode. A new "use_server" directive should follow soon. --- doc/configuration.txt | 14 +- include/proto/proto_http.h | 1 + include/proto/proxy.h | 1 + include/types/buffers.h | 16 +- src/cfgparse.c | 5 +- src/client.c | 20 +- src/proto_http.c | 541 ++++++++++++++++--------------------- src/proxy.c | 24 ++ src/session.c | 79 +++++- 9 files changed, 356 insertions(+), 345 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 89525e822..bcd7c27fa 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -1440,11 +1440,6 @@ default_backend used when no rule has matched. It generally is the dynamic backend which will catch all undetermined requests. - The "default_backend" keyword is also supported in TCP mode frontends to - facilitate the ordering of configurations in frontends and backends, - eventhough it does not make much more sense in case of TCP due to the fact - that use_backend currently does not work in TCP mode. - Example : use_backend dynamic if url_dyn @@ -4254,7 +4249,7 @@ transparent (deprecated) use_backend if use_backend unless - Switch to a specific backend if/unless a Layer 7 condition is matched. + Switch to a specific backend if/unless an ACL-based condition is matched. May be used in sections : defaults | frontend | listen | backend no | yes | yes | no Arguments : @@ -4265,7 +4260,10 @@ use_backend unless When doing content-switching, connections arrive on a frontend and are then dispatched to various backends depending on a number of conditions. The relation between the conditions and the backends is described with the - "use_backend" keyword. This is supported only in HTTP mode. + "use_backend" keyword. While it is normally used with HTTP processing, it can + also be used in pure TCP, either without content using stateless ACLs (eg: + source address validation) or combined with a "tcp-request" rule to wait for + some payload. There may be as many "use_backend" rules as desired. All of these rules are evaluated in their declaration order, and the first one which matches will @@ -4278,7 +4276,7 @@ use_backend unless used (in case of a "listen" section) or, in case of a frontend, no server is used and a 503 service unavailable response is returned. - See also: "default_backend" and section 7 about ACLs. + See also: "default_backend", "tcp-request", and section 7 about ACLs. 5. Server options diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index d70603c83..35a216c10 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -62,6 +62,7 @@ int process_cli(struct session *t); int process_srv_data(struct session *t); int process_srv_conn(struct session *t); int http_wait_for_request(struct session *s, struct buffer *req, int an_bit); +int http_process_req_common(struct session *s, struct buffer *req, int an_bit, struct proxy *px); int http_process_request(struct session *t, struct buffer *req, int an_bit); int http_process_tarpit(struct session *s, struct buffer *req, int an_bit); int http_process_request_body(struct session *s, struct buffer *req, int an_bit); diff --git a/include/proto/proxy.h b/include/proto/proxy.h index 8a2789df1..36360e5cf 100644 --- a/include/proto/proxy.h +++ b/include/proto/proxy.h @@ -35,6 +35,7 @@ void pause_proxy(struct proxy *p); void stop_proxy(struct proxy *p); void pause_proxies(void); void listen_proxies(void); +void session_set_backend(struct session *s, struct proxy *be); const char *proxy_cap_str(int cap); const char *proxy_mode_str(int mode); diff --git a/include/types/buffers.h b/include/types/buffers.h index a895477d1..234020f7e 100644 --- a/include/types/buffers.h +++ b/include/types/buffers.h @@ -106,12 +106,16 @@ * afterwards. */ #define AN_REQ_INSPECT 0x00000001 /* inspect request contents */ -#define AN_REQ_HTTP_HDR 0x00000002 /* inspect HTTP request headers */ -#define AN_REQ_HTTP_BODY 0x00000004 /* inspect HTTP request body */ -#define AN_REQ_HTTP_TARPIT 0x00000008 /* wait for end of HTTP tarpit */ -#define AN_RTR_HTTP_HDR 0x00000010 /* inspect HTTP response headers */ -#define AN_REQ_UNIX_STATS 0x00000020 /* process unix stats socket request */ -#define AN_REQ_WAIT_HTTP 0x00000040 /* wait for an HTTP request */ +#define AN_REQ_WAIT_HTTP 0x00000002 /* wait for an HTTP request */ +#define AN_REQ_HTTP_PROCESS_FE 0x00000004 /* process the frontend's HTTP part */ +#define AN_REQ_SWITCHING_RULES 0x00000008 /* apply the switching rules */ +#define AN_REQ_HTTP_PROCESS_BE 0x00000010 /* process the backend's HTTP part */ +#define AN_REQ_HTTP_INNER 0x00000020 /* inner processing of HTTP request */ +#define AN_REQ_HTTP_TARPIT 0x00000040 /* wait for end of HTTP tarpit */ +#define AN_REQ_HTTP_BODY 0x00000080 /* inspect HTTP request body */ +#define AN_REQ_UNIX_STATS 0x00000100 /* process unix stats socket request */ + +#define AN_RTR_HTTP_HDR 0x00000200 /* inspect HTTP response headers */ /* describes a chunk of string */ struct chunk { diff --git a/src/cfgparse.c b/src/cfgparse.c index 3852c459e..5afb11e7d 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -3775,9 +3775,11 @@ int check_config_validity() listener->accept = event_accept; listener->private = curproxy; listener->handler = process_session; + /* both TCP and HTTP must check switching rules */ + listener->analysers |= AN_REQ_SWITCHING_RULES; if (curproxy->mode == PR_MODE_HTTP) - listener->analysers |= AN_REQ_WAIT_HTTP | AN_REQ_HTTP_HDR; + listener->analysers |= AN_REQ_WAIT_HTTP | AN_REQ_HTTP_PROCESS_FE | AN_REQ_HTTP_INNER | AN_REQ_HTTP_PROCESS_BE; /* smart accept mode is automatic in HTTP mode */ if ((curproxy->options2 & PR_O2_SMARTACC) || @@ -3789,6 +3791,7 @@ int check_config_validity() !LIST_ISEMPTY(&curproxy->tcp_req.inspect_rules)) listener->analysers |= AN_REQ_INSPECT; + /* We want the use_backend and default_backend rules to apply */ listener = listener->next; } diff --git a/src/client.c b/src/client.c index 3e2099eed..abf4d351a 100644 --- a/src/client.c +++ b/src/client.c @@ -165,18 +165,12 @@ int event_accept(int fd) { s->task = t; s->listener = l; - s->be = s->fe = p; - /* in HTTP mode, content switching requires that the backend - * first points to the same proxy as the frontend. However, in - * TCP mode there will be no header processing so any default - * backend must be assigned if set. + /* Note: initially, the session's backend points to the frontend. + * This changes later when switching rules are executed or + * when the default backend is assigned. */ - if (p->mode == PR_MODE_TCP) { - if (p->defbe.be) - s->be = p->defbe.be; - s->flags |= SN_BE_ASSIGNED; - } + s->be = s->fe = p; s->ana_state = 0; /* analysers may change it but must reset it upon exit */ s->req = s->rep = NULL; /* will be allocated later */ @@ -459,12 +453,6 @@ int event_accept(int fd) { if (p->feconn > p->feconn_max) p->feconn_max = p->feconn; - if (s->flags & SN_BE_ASSIGNED) { - proxy_inc_be_ctr(s->be); - s->be->beconn++; - if (s->be->beconn > s->be->beconn_max) - s->be->beconn_max = s->be->beconn; - } actconn++; totalconn++; diff --git a/src/proto_http.c b/src/proto_http.c index 33e94664c..64e93d3ee 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -354,10 +354,6 @@ const char http_is_ver_token[256] = { }; -#ifdef DEBUG_FULL -static char *cli_stnames[4] = { "DAT", "SHR", "SHW", "CLS" }; -#endif - /* * Adds a header and its CRLF at the tail of buffer , just before the last * CRLF. Text length is measured first, so it cannot be NULL. @@ -1809,17 +1805,20 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit) return 0; } -/* This function performs all the processing enabled for the current request. +/* 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. * It returns 1 if the processing can continue on next analysers, or zero if it - * needs more data, encounters an error, or wants to immediately abort the - * request. It relies on buffers flags, and updates s->req->analysers. + * either needs more data or wants to immediately abort the request (eg: deny, + * error, ...). */ -int http_process_request(struct session *s, struct buffer *req, int an_bit) +int http_process_req_common(struct session *s, struct buffer *req, int an_bit, struct proxy *px) { - int cur_idx; struct http_txn *txn = &s->txn; struct http_msg *msg = &txn->req; - struct proxy *cur_proxy; + struct acl_cond *cond; + struct redirect_rule *rule; + int cur_idx; req->analysers &= ~an_bit; req->analyse_exp = TICK_ETERNITY; @@ -1833,70 +1832,27 @@ int http_process_request(struct session *s, struct buffer *req, int an_bit) req->l, req->analysers); - /* - * 6: we will have to evaluate the filters. - * As opposed to version 1.2, now they will be evaluated in the - * filters order and not in the header order. This means that - * each filter has to be validated among all headers. - * - * We can now check whether we want to switch to another - * backend, in which case we will re-check the backend's - * filters and various options. In order to support 3-level - * switching, here's how we should proceed : - * - * a) run be. - * if (switch) then switch ->be to the new backend. - * b) run be if (be != fe). - * There cannot be any switch from there, so ->be cannot be - * changed anymore. - * - * => filters always apply to ->be, then ->be may change. - * - * The response path will be able to apply either ->be, or - * ->be then ->fe filters in order to match the reverse of - * the forward sequence. - */ + /* first check whether we have some ACLs set to block this request */ + list_for_each_entry(cond, &px->block_cond, list) { + int ret = acl_exec_cond(cond, px, s, txn, ACL_DIR_REQ); - do { - struct acl_cond *cond; - struct redirect_rule *rule; - struct proxy *rule_set = s->be; - cur_proxy = s->be; + ret = acl_pass(ret); + if (cond->pol == ACL_COND_UNLESS) + ret = !ret; - /* first check whether we have some ACLs set to block this request */ - list_for_each_entry(cond, &cur_proxy->block_cond, list) { - int ret = acl_exec_cond(cond, cur_proxy, s, txn, ACL_DIR_REQ); - - ret = acl_pass(ret); - if (cond->pol == ACL_COND_UNLESS) - ret = !ret; - - if (ret) { - txn->status = 403; - /* let's log the request time */ - s->logs.tv_request = now; - stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403)); - goto return_prx_cond; - } + if (ret) { + txn->status = 403; + /* let's log the request time */ + s->logs.tv_request = now; + stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403)); + goto return_prx_cond; } + } - /* try headers filters */ - if (rule_set->req_exp != NULL) { - if (apply_filters_to_request(s, req, rule_set->req_exp) < 0) - goto return_bad_req; - } - - if (!(s->flags & SN_BE_ASSIGNED) && (s->be != cur_proxy)) { - /* to ensure correct connection accounting on - * the backend, we count the connection for the - * one managing the queue. - */ - s->be->beconn++; - if (s->be->beconn > s->be->beconn_max) - s->be->beconn_max = s->be->beconn; - proxy_inc_be_ctr(s->be); - s->flags |= SN_BE_ASSIGNED; - } + /* try headers filters */ + if (px->req_exp != NULL) { + if (apply_filters_to_request(s, req, px->req_exp) < 0) + goto return_bad_req; /* has the request been denied ? */ if (txn->flags & TX_CLDENY) { @@ -1907,249 +1863,235 @@ int http_process_request(struct session *s, struct buffer *req, int an_bit) stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403)); goto return_prx_cond; } + } - /* We might have to check for "Connection:" */ - if (((s->fe->options | s->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO)) && - !(s->flags & SN_CONN_CLOSED)) { - char *cur_ptr, *cur_end, *cur_next; - int cur_idx, old_idx, delta, val; - struct hdr_idx_elem *cur_hdr; + /* We might have to check for "Connection:" */ + if (((s->fe->options | s->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO)) && + !(s->flags & SN_CONN_CLOSED)) { + char *cur_ptr, *cur_end, *cur_next; + int old_idx, delta, val; + struct hdr_idx_elem *cur_hdr; - cur_next = req->data + txn->req.som + hdr_idx_first_pos(&txn->hdr_idx); - old_idx = 0; + cur_next = req->data + txn->req.som + hdr_idx_first_pos(&txn->hdr_idx); + old_idx = 0; - while ((cur_idx = txn->hdr_idx.v[old_idx].next)) { - cur_hdr = &txn->hdr_idx.v[cur_idx]; - cur_ptr = cur_next; - cur_end = cur_ptr + cur_hdr->len; - cur_next = cur_end + cur_hdr->cr + 1; + while ((cur_idx = txn->hdr_idx.v[old_idx].next)) { + cur_hdr = &txn->hdr_idx.v[cur_idx]; + cur_ptr = cur_next; + cur_end = cur_ptr + cur_hdr->len; + cur_next = cur_end + cur_hdr->cr + 1; - val = http_header_match2(cur_ptr, cur_end, "Connection", 10); - if (val) { - /* 3 possibilities : - * - we have already set Connection: close, - * so we remove this line. - * - we have not yet set Connection: close, - * but this line indicates close. We leave - * it untouched and set the flag. - * - we have not yet set Connection: close, - * and this line indicates non-close. We - * replace it. - */ - if (s->flags & SN_CONN_CLOSED) { - delta = buffer_replace2(req, cur_ptr, cur_next, NULL, 0); - txn->req.eoh += delta; + val = http_header_match2(cur_ptr, cur_end, "Connection", 10); + if (val) { + /* 3 possibilities : + * - we have already set Connection: close, + * so we remove this line. + * - we have not yet set Connection: close, + * but this line indicates close. We leave + * it untouched and set the flag. + * - we have not yet set Connection: close, + * and this line indicates non-close. We + * replace it. + */ + if (s->flags & SN_CONN_CLOSED) { + delta = buffer_replace2(req, cur_ptr, cur_next, NULL, 0); + txn->req.eoh += delta; + cur_next += delta; + txn->hdr_idx.v[old_idx].next = cur_hdr->next; + txn->hdr_idx.used--; + cur_hdr->len = 0; + } else { + if (strncasecmp(cur_ptr + val, "close", 5) != 0) { + delta = buffer_replace2(req, cur_ptr + val, cur_end, + "close", 5); cur_next += delta; - txn->hdr_idx.v[old_idx].next = cur_hdr->next; - txn->hdr_idx.used--; - cur_hdr->len = 0; - } else { - if (strncasecmp(cur_ptr + val, "close", 5) != 0) { - delta = buffer_replace2(req, cur_ptr + val, cur_end, - "close", 5); - cur_next += delta; - cur_hdr->len += delta; - txn->req.eoh += delta; - } - s->flags |= SN_CONN_CLOSED; + cur_hdr->len += delta; + txn->req.eoh += delta; } + s->flags |= SN_CONN_CLOSED; } - old_idx = cur_idx; } + old_idx = cur_idx; } - /* add request headers from the rule sets in the same order */ - for (cur_idx = 0; cur_idx < rule_set->nb_reqadd; cur_idx++) { - if (unlikely(http_header_add_tail(req, - &txn->req, - &txn->hdr_idx, - rule_set->req_add[cur_idx])) < 0) + } + /* add request headers from the rule sets in the same order */ + for (cur_idx = 0; cur_idx < px->nb_reqadd; cur_idx++) { + if (unlikely(http_header_add_tail(req, + &txn->req, + &txn->hdr_idx, + px->req_add[cur_idx])) < 0) + goto return_bad_req; + } + + /* check if stats URI was requested, and if an auth is needed */ + if (px->uri_auth != NULL && + (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)) { + /* we have to check the URI and auth for this request. + * FIXME!!! that one is rather dangerous, we want to + * make it follow standard rules (eg: clear req->analysers). + */ + if (stats_check_uri_auth(s, px)) { + req->analysers = 0; + return 0; + } + } + + /* check whether we have some ACLs set to redirect this request */ + list_for_each_entry(rule, &px->redirect_rules, list) { + int ret = acl_exec_cond(rule->cond, px, s, txn, ACL_DIR_REQ); + + ret = acl_pass(ret); + if (rule->cond->pol == ACL_COND_UNLESS) + ret = !ret; + + if (ret) { + struct chunk rdr = { trash, 0 }; + const char *msg_fmt; + + /* build redirect message */ + switch(rule->code) { + case 303: + rdr.len = strlen(HTTP_303); + msg_fmt = HTTP_303; + break; + case 301: + rdr.len = strlen(HTTP_301); + msg_fmt = HTTP_301; + break; + case 302: + default: + rdr.len = strlen(HTTP_302); + msg_fmt = HTTP_302; + break; + } + + if (unlikely(rdr.len > sizeof(trash))) goto return_bad_req; - } + memcpy(rdr.str, msg_fmt, rdr.len); - /* check if stats URI was requested, and if an auth is needed */ - if (rule_set->uri_auth != NULL && - (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)) { - /* we have to check the URI and auth for this request. - * FIXME!!! that one is rather dangerous, we want to - * make it follow standard rules (eg: clear req->analysers). - */ - if (stats_check_uri_auth(s, rule_set)) { - req->analysers = 0; - return 0; - } - } + switch(rule->type) { + case REDIRECT_TYPE_PREFIX: { + const char *path; + int pathlen; - /* first check whether we have some ACLs set to redirect this request */ - list_for_each_entry(rule, &cur_proxy->redirect_rules, list) { - int ret = acl_exec_cond(rule->cond, cur_proxy, s, txn, ACL_DIR_REQ); - - ret = acl_pass(ret); - if (rule->cond->pol == ACL_COND_UNLESS) - ret = !ret; - - if (ret) { - struct chunk rdr = { trash, 0 }; - const char *msg_fmt; - - /* build redirect message */ - switch(rule->code) { - case 303: - rdr.len = strlen(HTTP_303); - msg_fmt = HTTP_303; - break; - case 301: - rdr.len = strlen(HTTP_301); - msg_fmt = HTTP_301; - break; - case 302: - default: - rdr.len = strlen(HTTP_302); - msg_fmt = HTTP_302; - break; - } - - if (unlikely(rdr.len > sizeof(trash))) - goto return_bad_req; - memcpy(rdr.str, msg_fmt, rdr.len); - - switch(rule->type) { - case REDIRECT_TYPE_PREFIX: { - const char *path; - int pathlen; - - path = http_get_path(txn); - /* build message using path */ - if (path) { - pathlen = txn->req.sl.rq.u_l + (txn->req.sol+txn->req.sl.rq.u) - path; - if (rule->flags & REDIRECT_FLAG_DROP_QS) { - int qs = 0; - while (qs < pathlen) { - if (path[qs] == '?') { - pathlen = qs; - break; - } - qs++; + path = http_get_path(txn); + /* build message using path */ + if (path) { + pathlen = txn->req.sl.rq.u_l + (txn->req.sol+txn->req.sl.rq.u) - path; + if (rule->flags & REDIRECT_FLAG_DROP_QS) { + int qs = 0; + while (qs < pathlen) { + if (path[qs] == '?') { + pathlen = qs; + break; } + qs++; } - } else { - path = "/"; - pathlen = 1; } - - if (rdr.len + rule->rdr_len + pathlen > sizeof(trash) - 4) - goto return_bad_req; - - /* add prefix. Note that if prefix == "/", we don't want to - * add anything, otherwise it makes it hard for the user to - * configure a self-redirection. - */ - if (rule->rdr_len != 1 || *rule->rdr_str != '/') { - memcpy(rdr.str + rdr.len, rule->rdr_str, rule->rdr_len); - rdr.len += rule->rdr_len; - } - - /* add path */ - memcpy(rdr.str + rdr.len, path, pathlen); - rdr.len += pathlen; - break; + } else { + path = "/"; + pathlen = 1; } - case REDIRECT_TYPE_LOCATION: - default: - if (rdr.len + rule->rdr_len > sizeof(trash) - 4) - goto return_bad_req; - /* add location */ + if (rdr.len + rule->rdr_len + pathlen > sizeof(trash) - 4) + goto return_bad_req; + + /* add prefix. Note that if prefix == "/", we don't want to + * add anything, otherwise it makes it hard for the user to + * configure a self-redirection. + */ + if (rule->rdr_len != 1 || *rule->rdr_str != '/') { memcpy(rdr.str + rdr.len, rule->rdr_str, rule->rdr_len); rdr.len += rule->rdr_len; - break; } - if (rule->cookie_len) { - memcpy(rdr.str + rdr.len, "\r\nSet-Cookie: ", 14); - rdr.len += 14; - memcpy(rdr.str + rdr.len, rule->cookie_str, rule->cookie_len); - rdr.len += rule->cookie_len; - memcpy(rdr.str + rdr.len, "\r\n", 2); - rdr.len += 2; - } - - /* add end of headers */ - memcpy(rdr.str + rdr.len, "\r\n\r\n", 4); - rdr.len += 4; - - txn->status = rule->code; - /* let's log the request time */ - s->logs.tv_request = now; - stream_int_retnclose(req->prod, &rdr); - goto return_prx_cond; + /* add path */ + memcpy(rdr.str + rdr.len, path, pathlen); + rdr.len += pathlen; + break; } - } + case REDIRECT_TYPE_LOCATION: + default: + if (rdr.len + rule->rdr_len > sizeof(trash) - 4) + goto return_bad_req; - /* now check whether we have some switching rules for this request */ - if (!(s->flags & SN_BE_ASSIGNED)) { - struct switching_rule *rule; - - list_for_each_entry(rule, &cur_proxy->switching_rules, list) { - int ret; - - ret = acl_exec_cond(rule->cond, cur_proxy, s, txn, ACL_DIR_REQ); - - ret = acl_pass(ret); - if (rule->cond->pol == ACL_COND_UNLESS) - ret = !ret; - - if (ret) { - s->be = rule->be.backend; - s->be->beconn++; - if (s->be->beconn > s->be->beconn_max) - s->be->beconn_max = s->be->beconn; - proxy_inc_be_ctr(s->be); - - /* assign new parameters to the session from the new backend */ - s->rep->rto = s->req->wto = s->be->timeout.server; - s->req->cto = s->be->timeout.connect; - s->conn_retries = s->be->conn_retries; - if (s->be->options2 & PR_O2_RSPBUG_OK) - s->txn.rsp.err_pos = -1; /* let buggy responses pass */ - s->flags |= SN_BE_ASSIGNED; - break; - } + /* add location */ + memcpy(rdr.str + rdr.len, rule->rdr_str, rule->rdr_len); + rdr.len += rule->rdr_len; + break; } + + if (rule->cookie_len) { + memcpy(rdr.str + rdr.len, "\r\nSet-Cookie: ", 14); + rdr.len += 14; + memcpy(rdr.str + rdr.len, rule->cookie_str, rule->cookie_len); + rdr.len += rule->cookie_len; + memcpy(rdr.str + rdr.len, "\r\n", 2); + rdr.len += 2; + } + + /* add end of headers */ + memcpy(rdr.str + rdr.len, "\r\n\r\n", 4); + rdr.len += 4; + + txn->status = rule->code; + /* let's log the request time */ + s->logs.tv_request = now; + stream_int_retnclose(req->prod, &rdr); + goto return_prx_cond; } - - if (!(s->flags & SN_BE_ASSIGNED) && cur_proxy->defbe.be) { - /* No backend was set, but there was a default - * backend set in the frontend, so we use it and - * loop again. - */ - s->be = cur_proxy->defbe.be; - s->be->beconn++; - if (s->be->beconn > s->be->beconn_max) - s->be->beconn_max = s->be->beconn; - proxy_inc_be_ctr(s->be); - - /* assign new parameters to the session from the new backend */ - s->rep->rto = s->req->wto = s->be->timeout.server; - s->req->cto = s->be->timeout.connect; - s->conn_retries = s->be->conn_retries; - if (s->be->options2 & PR_O2_RSPBUG_OK) - s->txn.rsp.err_pos = -1; /* let buggy responses pass */ - s->flags |= SN_BE_ASSIGNED; - } - } while (s->be != cur_proxy); /* we loop only if s->be has changed */ - - if (!(s->flags & SN_BE_ASSIGNED)) { - /* To ensure correct connection accounting on - * the backend, we count the connection for the - * one managing the queue. - */ - s->be->beconn++; - if (s->be->beconn > s->be->beconn_max) - s->be->beconn_max = s->be->beconn; - proxy_inc_be_ctr(s->be); - s->flags |= SN_BE_ASSIGNED; } + /* that's OK for us now, let's move on to next analysers */ + return 1; + + return_bad_req: + /* We centralize bad requests processing here */ + if (unlikely(msg->msg_state == HTTP_MSG_ERROR) || msg->err_pos >= 0) { + /* we detected a parsing error. We want to archive this request + * in the dedicated proxy area for later troubleshooting. + */ + http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe); + } + + txn->req.msg_state = HTTP_MSG_ERROR; + txn->status = 400; + stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_400)); + s->fe->failed_req++; + + return_prx_cond: + if (!(s->flags & SN_ERR_MASK)) + s->flags |= SN_ERR_PRXCOND; + if (!(s->flags & SN_FINST_MASK)) + s->flags |= SN_FINST_R; + + req->analysers = 0; + req->analyse_exp = TICK_ETERNITY; + return 0; +} + +/* This function performs all the processing enabled for the current request. + * It returns 1 if the processing can continue on next analysers, or zero if it + * needs more data, encounters an error, or wants to immediately abort the + * request. It relies on buffers flags, and updates s->req->analysers. + */ +int http_process_request(struct session *s, struct buffer *req, int an_bit) +{ + struct http_txn *txn = &s->txn; + struct http_msg *msg = &txn->req; + + req->analysers &= ~an_bit; + req->analyse_exp = TICK_ETERNITY; + + DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n", + now_ms, __FUNCTION__, + s, + req, + req->rex, req->wex, + req->flags, + req->l, + req->analysers); + /* * Right now, we know that we have processed the entire headers * and that unwanted requests have been filtered out. We can do @@ -2436,7 +2378,6 @@ int http_process_request(struct session *s, struct buffer *req, int an_bit) stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_400)); s->fe->failed_req++; - return_prx_cond: if (!(s->flags & SN_ERR_MASK)) s->flags |= SN_ERR_PRXCOND; if (!(s->flags & SN_FINST_MASK)) @@ -3142,18 +3083,7 @@ int apply_filter_to_req_headers(struct session *t, struct buffer *req, struct hd break; /* Swithing Proxy */ - t->be = (struct proxy *) exp->replace; - - /* right now, the backend switch is not overly complicated - * because we have associated req_cap and rsp_cap to the - * frontend, and the beconn will be updated later. - */ - - t->rep->rto = t->req->wto = t->be->timeout.server; - t->req->cto = t->be->timeout.connect; - t->conn_retries = t->be->conn_retries; - if (t->be->options2 & PR_O2_RSPBUG_OK) - t->txn.rsp.err_pos = -1; /* let buggy responses pass */ + session_set_backend(t, (struct proxy *)exp->replace); last_hdr = 1; break; @@ -3265,18 +3195,7 @@ int apply_filter_to_req_line(struct session *t, struct buffer *req, struct hdr_e break; /* Swithing Proxy */ - t->be = (struct proxy *) exp->replace; - - /* right now, the backend switch is not too much complicated - * because we have associated req_cap and rsp_cap to the - * frontend, and the beconn will be updated later. - */ - - t->rep->rto = t->req->wto = t->be->timeout.server; - t->req->cto = t->be->timeout.connect; - t->conn_retries = t->be->conn_retries; - if (t->be->options2 & PR_O2_RSPBUG_OK) - t->txn.rsp.err_pos = -1; /* let buggy responses pass */ + session_set_backend(t, (struct proxy *)exp->replace); done = 1; break; diff --git a/src/proxy.c b/src/proxy.c index 69b070e2b..dcfb12d87 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -631,6 +631,30 @@ void listen_proxies(void) } } +/* Set current session's backend to . Nothing is done if the + * session already had a backend assigned, which is indicated by + * s->flags & SN_BE_ASSIGNED. + * All flags, stats and counters which need be updated are updated. + */ +void session_set_backend(struct session *s, struct proxy *be) +{ + if (s->flags & SN_BE_ASSIGNED) + return; + s->be = be; + be->beconn++; + if (be->beconn > be->beconn_max) + be->beconn_max = be->beconn; + proxy_inc_be_ctr(be); + + /* assign new parameters to the session from the new backend */ + s->rep->rto = s->req->wto = be->timeout.server; + s->req->cto = be->timeout.connect; + s->conn_retries = be->conn_retries; + if (be->options2 & PR_O2_RSPBUG_OK) + s->txn.rsp.err_pos = -1; /* let buggy responses pass */ + s->flags |= SN_BE_ASSIGNED; +} + static struct cfg_kw_list cfg_kws = {{ },{ { CFG_LISTEN, "timeout", proxy_parse_timeout }, { CFG_LISTEN, "clitimeout", proxy_parse_timeout }, diff --git a/src/session.c b/src/session.c index 1d223d080..d4817ac2c 100644 --- a/src/session.c +++ b/src/session.c @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -552,6 +554,59 @@ static void sess_prepare_conn_req(struct session *s, struct stream_interface *si si->state = SI_ST_ASS; } +/* This stream analyser checks the switching rules and changes the backend + * if appropriate. The default_backend rule is also considered. + * It returns 1 if the processing can continue on next analysers, or zero if it + * either needs more data or wants to immediately abort the request. + */ +int process_switching_rules(struct session *s, struct buffer *req, int an_bit) +{ + req->analysers &= ~an_bit; + req->analyse_exp = TICK_ETERNITY; + + DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n", + now_ms, __FUNCTION__, + s, + req, + req->rex, req->wex, + req->flags, + req->l, + req->analysers); + + /* now check whether we have some switching rules for this request */ + if (!(s->flags & SN_BE_ASSIGNED)) { + struct switching_rule *rule; + + list_for_each_entry(rule, &s->fe->switching_rules, list) { + int ret; + + ret = acl_exec_cond(rule->cond, s->fe, s, &s->txn, ACL_DIR_REQ); + ret = acl_pass(ret); + if (rule->cond->pol == ACL_COND_UNLESS) + ret = !ret; + + if (ret) { + session_set_backend(s, rule->be.backend); + break; + } + } + + /* To ensure correct connection accounting on the backend, we + * have to assign one if it was not set (eg: a listen). This + * measure also takes care of correctly setting the default + * backend if any. + */ + if (!(s->flags & SN_BE_ASSIGNED)) + session_set_backend(s, s->fe->defbe.be ? s->fe->defbe.be : s->be); + } + + /* we don't want to run the HTTP filters again if the backend has not changed */ + if (s->fe == s->be) + s->req->analysers &= ~AN_REQ_HTTP_PROCESS_BE; + + return 1; +} + /* Processes the client, server, request and response jobs of a session task, * then puts it back to the wait queue in a clean state, or cleans up its * resources if it must be deleted. Returns in the date the task wants @@ -736,9 +791,27 @@ resync_stream_interface: break; } - if (s->req->analysers & AN_REQ_HTTP_HDR) { - last_ana |= AN_REQ_HTTP_HDR; - if (!http_process_request(s, s->req, AN_REQ_HTTP_HDR)) + if (s->req->analysers & AN_REQ_HTTP_PROCESS_FE) { + last_ana |= AN_REQ_HTTP_PROCESS_FE; + if (!http_process_req_common(s, s->req, AN_REQ_HTTP_PROCESS_FE, s->fe)) + break; + } + + if (s->req->analysers & AN_REQ_SWITCHING_RULES) { + last_ana |= AN_REQ_SWITCHING_RULES; + if (!process_switching_rules(s, s->req, AN_REQ_SWITCHING_RULES)) + break; + } + + if (s->req->analysers & AN_REQ_HTTP_PROCESS_BE) { + last_ana |= AN_REQ_HTTP_PROCESS_BE; + if (!http_process_req_common(s, s->req, AN_REQ_HTTP_PROCESS_BE, s->be)) + break; + } + + if (s->req->analysers & AN_REQ_HTTP_INNER) { + last_ana |= AN_REQ_HTTP_INNER; + if (!http_process_request(s, s->req, AN_REQ_HTTP_INNER)) break; }