diff --git a/include/types/session.h b/include/types/session.h index 46569a571..9437198d9 100644 --- a/include/types/session.h +++ b/include/types/session.h @@ -50,7 +50,7 @@ #define SN_FRT_ADDR_SET 0x00000080 /* set if the frontend address has been filled */ #define SN_REDISP 0x00000100 /* set if this session was redispatched from one server to another */ #define SN_CONN_TAR 0x00000200 /* set if this session is turning around before reconnecting */ -/* unused: 0x00000400 */ +#define SN_REDIRECTABLE 0x00000400 /* set if this session is redirectable (GET or HEAD) */ /* unused: 0x00000800 */ /* session termination conditions, bits values 0x1000 to 0x7000 (0-7 shift 12) */ diff --git a/src/backend.c b/src/backend.c index 5ea6590d8..20e3a040d 100644 --- a/src/backend.c +++ b/src/backend.c @@ -1059,6 +1059,13 @@ int assign_server_and_queue(struct session *s) return SRV_STATUS_INTERNAL; if (s->flags & SN_ASSIGNED) { + if ((s->flags & SN_REDIRECTABLE) && s->srv && s->srv->rdr_len) { + /* server scheduled for redirection, and already assigned. We + * don't want to go further nor check the queue. + */ + return SRV_STATUS_OK; + } + if (s->srv && s->srv->maxqueue > 0 && s->srv->nbpend >= s->srv->maxqueue) { s->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET); s->srv = NULL; @@ -1084,6 +1091,13 @@ int assign_server_and_queue(struct session *s) err = assign_server(s); switch (err) { case SRV_STATUS_OK: + if ((s->flags & SN_REDIRECTABLE) && s->srv && s->srv->rdr_len) { + /* server supporting redirection and it is possible. + * Let's report that and ignore maxconn ! + */ + return SRV_STATUS_OK; + } + /* in balance mode, we might have servers with connection limits */ if (s->srv && s->srv->maxconn && s->srv->cur_sess >= srv_dynamic_maxconn(s->srv)) { diff --git a/src/proto_http.c b/src/proto_http.c index 16b2f075d..429f57142 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -592,6 +592,54 @@ static http_meth_t find_http_meth(const char *str, const int len) } +/* Parse the URI from the given transaction (which is assumed to be in request + * phase) and look for the "/" beginning the PATH. If not found, return NULL. + * It is returned otherwise. + */ +static char * +http_get_path(struct http_txn *txn) +{ + char *ptr, *end; + + ptr = txn->req.sol + txn->req.sl.rq.u; + end = ptr + txn->req.sl.rq.u_l; + + if (ptr >= end) + return NULL; + + /* RFC2616, par. 5.1.2 : + * Request-URI = "*" | absuri | abspath | authority + */ + + if (*ptr == '*') + return NULL; + + if (isalpha((unsigned char)*ptr)) { + /* this is a scheme as described by RFC3986, par. 3.1 */ + ptr++; + while (ptr < end && + (isalnum((unsigned char)*ptr) || *ptr == '+' || *ptr == '-' || *ptr == '.')) + ptr++; + /* skip '://' */ + if (ptr == end || *ptr++ != ':') + return NULL; + if (ptr == end || *ptr++ != '/') + return NULL; + if (ptr == end || *ptr++ != '/') + return NULL; + } + /* skip [user[:passwd]@]host[:[port]] */ + + while (ptr < end && *ptr != '/') + ptr++; + + if (ptr == end) + return NULL; + + /* OK, we got the '/' ! */ + return ptr; +} + /* Processes the client and server 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 @@ -2451,9 +2499,59 @@ int process_srv(struct session *t) do { /* first, get a connection */ + if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD) + t->flags |= SN_REDIRECTABLE; + if (srv_redispatch_connect(t)) return t->srv_state != SV_STIDLE; + if ((t->flags & SN_REDIRECTABLE) && t->srv && t->srv->rdr_len) { + /* Server supporting redirection and it is possible. + * Invalid requests are reported as such. It concerns all + * the largest ones. + */ + struct chunk rdr; + char *path; + int len; + + /* 1: create the response header */ + rdr.len = strlen(HTTP_302); + rdr.str = trash; + memcpy(rdr.str, HTTP_302, rdr.len); + + /* 2: add the server's prefix */ + if (rdr.len + t->srv->rdr_len > sizeof(trash)) + goto cancel_redir; + + memcpy(rdr.str + rdr.len, t->srv->rdr_pfx, t->srv->rdr_len); + rdr.len += t->srv->rdr_len; + + /* 3: add the request URI */ + path = http_get_path(txn); + if (!path) + goto cancel_redir; + len = txn->req.sl.rq.u_l + (txn->req.sol+txn->req.sl.rq.u) - path; + if (rdr.len + len > sizeof(trash) - 4) /* 4 for CRLF-CRLF */ + goto cancel_redir; + + memcpy(rdr.str + rdr.len, path, len); + rdr.len += len; + memcpy(rdr.str + rdr.len, "\r\n\r\n", 4); + rdr.len += 4; + + srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C, 302, &rdr); + /* FIXME: we should increase a counter of redirects per server and per backend. */ + if (t->srv) + t->srv->cum_sess++; + return 1; + cancel_redir: + txn->status = 400; + t->fe->failed_req++; + srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C, + 400, error_message(t, HTTP_ERR_400)); + return 1; + } + /* try to (re-)connect to the server, and fail if we expire the * number of retries. */ @@ -5226,39 +5324,9 @@ acl_fetch_path(struct proxy *px, struct session *l4, void *l7, int dir, /* ensure the indexes are not affected */ return 0; - ptr = txn->req.sol + txn->req.sl.rq.u; - end = ptr + txn->req.sl.rq.u_l; - - if (ptr >= end) - return 0; - - /* RFC2616, par. 5.1.2 : - * Request-URI = "*" | absuri | abspath | authority - */ - - if (*ptr == '*') - return 0; - - if (isalpha((unsigned char)*ptr)) { - /* this is a scheme as described by RFC3986, par. 3.1 */ - ptr++; - while (ptr < end && - (isalnum((unsigned char)*ptr) || *ptr == '+' || *ptr == '-' || *ptr == '.')) - ptr++; - /* skip '://' */ - if (ptr == end || *ptr++ != ':') - return 0; - if (ptr == end || *ptr++ != '/') - return 0; - if (ptr == end || *ptr++ != '/') - return 0; - } - /* skip [user[:passwd]@]host[:[port]] */ - - while (ptr < end && *ptr != '/') - ptr++; - - if (ptr == end) + end = txn->req.sol + txn->req.sl.rq.u + txn->req.sl.rq.u_l; + ptr = http_get_path(txn); + if (!ptr) return 0; /* OK, we got the '/' ! */