[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.
This commit is contained in:
Willy Tarreau 2009-07-07 15:10:31 +02:00
parent 3a816293e9
commit 1d0dfb155d
9 changed files with 356 additions and 345 deletions

View File

@ -1440,11 +1440,6 @@ default_backend <backend>
used when no rule has matched. It generally is the dynamic backend which used when no rule has matched. It generally is the dynamic backend which
will catch all undetermined requests. 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 : Example :
use_backend dynamic if url_dyn use_backend dynamic if url_dyn
@ -4254,7 +4249,7 @@ transparent (deprecated)
use_backend <backend> if <condition> use_backend <backend> if <condition>
use_backend <backend> unless <condition> use_backend <backend> unless <condition>
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 May be used in sections : defaults | frontend | listen | backend
no | yes | yes | no no | yes | yes | no
Arguments : Arguments :
@ -4265,7 +4260,10 @@ use_backend <backend> unless <condition>
When doing content-switching, connections arrive on a frontend and are then When doing content-switching, connections arrive on a frontend and are then
dispatched to various backends depending on a number of conditions. The dispatched to various backends depending on a number of conditions. The
relation between the conditions and the backends is described with 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 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 evaluated in their declaration order, and the first one which matches will
@ -4278,7 +4276,7 @@ use_backend <backend> unless <condition>
used (in case of a "listen" section) or, in case of a frontend, no server is 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. 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 5. Server options

View File

@ -62,6 +62,7 @@ int process_cli(struct session *t);
int process_srv_data(struct session *t); int process_srv_data(struct session *t);
int process_srv_conn(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_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_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_tarpit(struct session *s, struct buffer *req, int an_bit);
int http_process_request_body(struct session *s, struct buffer *req, int an_bit); int http_process_request_body(struct session *s, struct buffer *req, int an_bit);

View File

@ -35,6 +35,7 @@ void pause_proxy(struct proxy *p);
void stop_proxy(struct proxy *p); void stop_proxy(struct proxy *p);
void pause_proxies(void); void pause_proxies(void);
void listen_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_cap_str(int cap);
const char *proxy_mode_str(int mode); const char *proxy_mode_str(int mode);

View File

@ -106,12 +106,16 @@
* afterwards. * afterwards.
*/ */
#define AN_REQ_INSPECT 0x00000001 /* inspect request contents */ #define AN_REQ_INSPECT 0x00000001 /* inspect request contents */
#define AN_REQ_HTTP_HDR 0x00000002 /* inspect HTTP request headers */ #define AN_REQ_WAIT_HTTP 0x00000002 /* wait for an HTTP request */
#define AN_REQ_HTTP_BODY 0x00000004 /* inspect HTTP request body */ #define AN_REQ_HTTP_PROCESS_FE 0x00000004 /* process the frontend's HTTP part */
#define AN_REQ_HTTP_TARPIT 0x00000008 /* wait for end of HTTP tarpit */ #define AN_REQ_SWITCHING_RULES 0x00000008 /* apply the switching rules */
#define AN_RTR_HTTP_HDR 0x00000010 /* inspect HTTP response headers */ #define AN_REQ_HTTP_PROCESS_BE 0x00000010 /* process the backend's HTTP part */
#define AN_REQ_UNIX_STATS 0x00000020 /* process unix stats socket request */ #define AN_REQ_HTTP_INNER 0x00000020 /* inner processing of HTTP request */
#define AN_REQ_WAIT_HTTP 0x00000040 /* wait for an 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 */ /* describes a chunk of string */
struct chunk { struct chunk {

View File

@ -3775,9 +3775,11 @@ int check_config_validity()
listener->accept = event_accept; listener->accept = event_accept;
listener->private = curproxy; listener->private = curproxy;
listener->handler = process_session; listener->handler = process_session;
/* both TCP and HTTP must check switching rules */
listener->analysers |= AN_REQ_SWITCHING_RULES;
if (curproxy->mode == PR_MODE_HTTP) 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 */ /* smart accept mode is automatic in HTTP mode */
if ((curproxy->options2 & PR_O2_SMARTACC) || if ((curproxy->options2 & PR_O2_SMARTACC) ||
@ -3789,6 +3791,7 @@ int check_config_validity()
!LIST_ISEMPTY(&curproxy->tcp_req.inspect_rules)) !LIST_ISEMPTY(&curproxy->tcp_req.inspect_rules))
listener->analysers |= AN_REQ_INSPECT; listener->analysers |= AN_REQ_INSPECT;
/* We want the use_backend and default_backend rules to apply */
listener = listener->next; listener = listener->next;
} }

View File

@ -165,18 +165,12 @@ int event_accept(int fd) {
s->task = t; s->task = t;
s->listener = l; s->listener = l;
s->be = s->fe = p;
/* in HTTP mode, content switching requires that the backend /* Note: initially, the session's backend points to the frontend.
* first points to the same proxy as the frontend. However, in * This changes later when switching rules are executed or
* TCP mode there will be no header processing so any default * when the default backend is assigned.
* backend must be assigned if set.
*/ */
if (p->mode == PR_MODE_TCP) { s->be = s->fe = p;
if (p->defbe.be)
s->be = p->defbe.be;
s->flags |= SN_BE_ASSIGNED;
}
s->ana_state = 0; /* analysers may change it but must reset it upon exit */ s->ana_state = 0; /* analysers may change it but must reset it upon exit */
s->req = s->rep = NULL; /* will be allocated later */ s->req = s->rep = NULL; /* will be allocated later */
@ -459,12 +453,6 @@ int event_accept(int fd) {
if (p->feconn > p->feconn_max) if (p->feconn > p->feconn_max)
p->feconn_max = p->feconn; 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++; actconn++;
totalconn++; totalconn++;

View File

@ -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 <b>, just before the last * Adds a header and its CRLF at the tail of buffer <b>, just before the last
* CRLF. Text length is measured first, so it cannot be NULL. * 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; 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 * 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 * either needs more data or wants to immediately abort the request (eg: deny,
* request. It relies on buffers flags, and updates s->req->analysers. * 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_txn *txn = &s->txn;
struct http_msg *msg = &txn->req; 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->analysers &= ~an_bit;
req->analyse_exp = TICK_ETERNITY; 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->l,
req->analysers); req->analysers);
/* /* first check whether we have some ACLs set to block this request */
* 6: we will have to evaluate the filters. list_for_each_entry(cond, &px->block_cond, list) {
* As opposed to version 1.2, now they will be evaluated in the int ret = acl_exec_cond(cond, px, s, txn, ACL_DIR_REQ);
* 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.
*/
do { ret = acl_pass(ret);
struct acl_cond *cond; if (cond->pol == ACL_COND_UNLESS)
struct redirect_rule *rule; ret = !ret;
struct proxy *rule_set = s->be;
cur_proxy = s->be;
/* first check whether we have some ACLs set to block this request */ if (ret) {
list_for_each_entry(cond, &cur_proxy->block_cond, list) { txn->status = 403;
int ret = acl_exec_cond(cond, cur_proxy, s, txn, ACL_DIR_REQ); /* let's log the request time */
s->logs.tv_request = now;
ret = acl_pass(ret); stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403));
if (cond->pol == ACL_COND_UNLESS) goto return_prx_cond;
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;
}
} }
}
/* try headers filters */ /* try headers filters */
if (rule_set->req_exp != NULL) { if (px->req_exp != NULL) {
if (apply_filters_to_request(s, req, rule_set->req_exp) < 0) if (apply_filters_to_request(s, req, px->req_exp) < 0)
goto return_bad_req; 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;
}
/* has the request been denied ? */ /* has the request been denied ? */
if (txn->flags & TX_CLDENY) { 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)); stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403));
goto return_prx_cond; goto return_prx_cond;
} }
}
/* We might have to check for "Connection:" */ /* We might have to check for "Connection:" */
if (((s->fe->options | s->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO)) && if (((s->fe->options | s->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO)) &&
!(s->flags & SN_CONN_CLOSED)) { !(s->flags & SN_CONN_CLOSED)) {
char *cur_ptr, *cur_end, *cur_next; char *cur_ptr, *cur_end, *cur_next;
int cur_idx, old_idx, delta, val; int old_idx, delta, val;
struct hdr_idx_elem *cur_hdr; struct hdr_idx_elem *cur_hdr;
cur_next = req->data + txn->req.som + hdr_idx_first_pos(&txn->hdr_idx); cur_next = req->data + txn->req.som + hdr_idx_first_pos(&txn->hdr_idx);
old_idx = 0; old_idx = 0;
while ((cur_idx = txn->hdr_idx.v[old_idx].next)) { while ((cur_idx = txn->hdr_idx.v[old_idx].next)) {
cur_hdr = &txn->hdr_idx.v[cur_idx]; cur_hdr = &txn->hdr_idx.v[cur_idx];
cur_ptr = cur_next; cur_ptr = cur_next;
cur_end = cur_ptr + cur_hdr->len; cur_end = cur_ptr + cur_hdr->len;
cur_next = cur_end + cur_hdr->cr + 1; cur_next = cur_end + cur_hdr->cr + 1;
val = http_header_match2(cur_ptr, cur_end, "Connection", 10); val = http_header_match2(cur_ptr, cur_end, "Connection", 10);
if (val) { if (val) {
/* 3 possibilities : /* 3 possibilities :
* - we have already set Connection: close, * - we have already set Connection: close,
* so we remove this line. * so we remove this line.
* - we have not yet set Connection: close, * - we have not yet set Connection: close,
* but this line indicates close. We leave * but this line indicates close. We leave
* it untouched and set the flag. * it untouched and set the flag.
* - we have not yet set Connection: close, * - we have not yet set Connection: close,
* and this line indicates non-close. We * and this line indicates non-close. We
* replace it. * replace it.
*/ */
if (s->flags & SN_CONN_CLOSED) { if (s->flags & SN_CONN_CLOSED) {
delta = buffer_replace2(req, cur_ptr, cur_next, NULL, 0); delta = buffer_replace2(req, cur_ptr, cur_next, NULL, 0);
txn->req.eoh += delta; 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; cur_next += delta;
txn->hdr_idx.v[old_idx].next = cur_hdr->next; cur_hdr->len += delta;
txn->hdr_idx.used--; txn->req.eoh += delta;
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;
} }
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++) { /* add request headers from the rule sets in the same order */
if (unlikely(http_header_add_tail(req, for (cur_idx = 0; cur_idx < px->nb_reqadd; cur_idx++) {
&txn->req, if (unlikely(http_header_add_tail(req,
&txn->hdr_idx, &txn->req,
rule_set->req_add[cur_idx])) < 0) &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; goto return_bad_req;
} memcpy(rdr.str, msg_fmt, rdr.len);
/* check if stats URI was requested, and if an auth is needed */ switch(rule->type) {
if (rule_set->uri_auth != NULL && case REDIRECT_TYPE_PREFIX: {
(txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)) { const char *path;
/* we have to check the URI and auth for this request. int pathlen;
* 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;
}
}
/* first check whether we have some ACLs set to redirect this request */ path = http_get_path(txn);
list_for_each_entry(rule, &cur_proxy->redirect_rules, list) { /* build message using path */
int ret = acl_exec_cond(rule->cond, cur_proxy, s, txn, ACL_DIR_REQ); if (path) {
pathlen = txn->req.sl.rq.u_l + (txn->req.sol+txn->req.sl.rq.u) - path;
ret = acl_pass(ret); if (rule->flags & REDIRECT_FLAG_DROP_QS) {
if (rule->cond->pol == ACL_COND_UNLESS) int qs = 0;
ret = !ret; while (qs < pathlen) {
if (path[qs] == '?') {
if (ret) { pathlen = qs;
struct chunk rdr = { trash, 0 }; break;
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++;
} }
qs++;
} }
} else {
path = "/";
pathlen = 1;
} }
} else {
if (rdr.len + rule->rdr_len + pathlen > sizeof(trash) - 4) path = "/";
goto return_bad_req; pathlen = 1;
/* 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;
} }
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); memcpy(rdr.str + rdr.len, rule->rdr_str, rule->rdr_len);
rdr.len += rule->rdr_len; rdr.len += rule->rdr_len;
break;
} }
if (rule->cookie_len) { /* add path */
memcpy(rdr.str + rdr.len, "\r\nSet-Cookie: ", 14); memcpy(rdr.str + rdr.len, path, pathlen);
rdr.len += 14; rdr.len += pathlen;
memcpy(rdr.str + rdr.len, rule->cookie_str, rule->cookie_len); break;
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;
} }
} 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 */ /* add location */
if (!(s->flags & SN_BE_ASSIGNED)) { memcpy(rdr.str + rdr.len, rule->rdr_str, rule->rdr_len);
struct switching_rule *rule; rdr.len += rule->rdr_len;
break;
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;
}
} }
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 * Right now, we know that we have processed the entire headers
* and that unwanted requests have been filtered out. We can do * 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)); stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_400));
s->fe->failed_req++; s->fe->failed_req++;
return_prx_cond:
if (!(s->flags & SN_ERR_MASK)) if (!(s->flags & SN_ERR_MASK))
s->flags |= SN_ERR_PRXCOND; s->flags |= SN_ERR_PRXCOND;
if (!(s->flags & SN_FINST_MASK)) 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; break;
/* Swithing Proxy */ /* Swithing Proxy */
t->be = (struct proxy *) exp->replace; session_set_backend(t, (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 */
last_hdr = 1; last_hdr = 1;
break; break;
@ -3265,18 +3195,7 @@ int apply_filter_to_req_line(struct session *t, struct buffer *req, struct hdr_e
break; break;
/* Swithing Proxy */ /* Swithing Proxy */
t->be = (struct proxy *) exp->replace; session_set_backend(t, (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 */
done = 1; done = 1;
break; break;

View File

@ -631,6 +631,30 @@ void listen_proxies(void)
} }
} }
/* Set current session's backend to <be>. 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 = {{ },{ static struct cfg_kw_list cfg_kws = {{ },{
{ CFG_LISTEN, "timeout", proxy_parse_timeout }, { CFG_LISTEN, "timeout", proxy_parse_timeout },
{ CFG_LISTEN, "clitimeout", proxy_parse_timeout }, { CFG_LISTEN, "clitimeout", proxy_parse_timeout },

View File

@ -19,6 +19,7 @@
#include <types/capture.h> #include <types/capture.h>
#include <types/global.h> #include <types/global.h>
#include <proto/acl.h>
#include <proto/backend.h> #include <proto/backend.h>
#include <proto/buffers.h> #include <proto/buffers.h>
#include <proto/hdr_idx.h> #include <proto/hdr_idx.h>
@ -27,6 +28,7 @@
#include <proto/pipe.h> #include <proto/pipe.h>
#include <proto/proto_http.h> #include <proto/proto_http.h>
#include <proto/proto_tcp.h> #include <proto/proto_tcp.h>
#include <proto/proxy.h>
#include <proto/queue.h> #include <proto/queue.h>
#include <proto/server.h> #include <proto/server.h>
#include <proto/stream_interface.h> #include <proto/stream_interface.h>
@ -552,6 +554,59 @@ static void sess_prepare_conn_req(struct session *s, struct stream_interface *si
si->state = SI_ST_ASS; 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, /* 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 * then puts it back to the wait queue in a clean state, or cleans up its
* resources if it must be deleted. Returns in <next> the date the task wants * resources if it must be deleted. Returns in <next> the date the task wants
@ -736,9 +791,27 @@ resync_stream_interface:
break; break;
} }
if (s->req->analysers & AN_REQ_HTTP_HDR) { if (s->req->analysers & AN_REQ_HTTP_PROCESS_FE) {
last_ana |= AN_REQ_HTTP_HDR; last_ana |= AN_REQ_HTTP_PROCESS_FE;
if (!http_process_request(s, s->req, AN_REQ_HTTP_HDR)) 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; break;
} }