From 9c33612f53486105421eda8b2a50e09683b2f0ef Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Wed, 13 Feb 2008 00:45:24 +0100 Subject: [PATCH] [MEDIUM] completely implement the server redirection method Now when a server has "redir " on its config line, any HEAD or GET request addressing it will lead to a 302 with Location set to "" immediately followed by the relative URI of the incoming request. This makes it very easy to send redirect to browsers to check remote static servers, as well as to provide redirection for remote sites when the local one is down. --- include/types/session.h | 2 +- src/backend.c | 14 +++++ src/proto_http.c | 134 ++++++++++++++++++++++++++++++---------- 3 files changed, 116 insertions(+), 34 deletions(-) diff --git a/include/types/session.h b/include/types/session.h index 46569a5719..9437198d95 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 5ea6590d89..20e3a040da 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 16b2f075d3..429f571422 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 '/' ! */