[MEDIUM] add support for conditional HTTP redirection

A new "redirect" keyword adds the ability to send an HTTP 301/302/303
redirection to either an absolute location or to a prefix followed by
the original URI. The redirection is conditionned by ACL rules, so it
becomes very easy to move parts of a site to another site using this.

This work was almost entirely done at Exceliance by Emeric Brun.

A test-case has been added in the tests/ directory.
This commit is contained in:
Willy Tarreau 2008-06-07 23:08:56 +02:00
parent 8001d6162e
commit b463dfb2de
7 changed files with 276 additions and 0 deletions

View File

@ -568,6 +568,7 @@ option tcpka X X X X
option tcplog X X X X
[no] option tcpsplice X X X X
[no] option transparent X X X -
redirect - X X X
redisp X - X X (deprecated)
redispatch X - X X (deprecated)
reqadd - X X X
@ -2312,6 +2313,32 @@ no option transparent
"transparent" option of the "bind" keyword.
redirect {location | prefix} <to> [code <code>] {if | unless} <condition>
Return an HTTP redirection if/unless a condition is matched
May be used in sections : defaults | frontend | listen | backend
no | yes | yes | yes
If/unless the condition is matched, the HTTP request will lead to a redirect
response. There are currently two types of redirections : "location" and
"prefix". With "location", the exact value in <to> is placed into the HTTP
"Location" header. With "prefix", the "Location" header is built from the
concatenation of <to> and the URI. It is particularly suited for global site
redirections.
The code is optional. It indicates in <code> which type of HTTP redirection
is desired. Only codes 301, 302 and 303 are supported. 302 is used if no code
is specified.
Example: move the login URL only to HTTPS.
acl clear dst_port 80
acl secure dst_port 8080
acl login_page url_beg /login
redirect prefix https://mysite.com if login_page !secure
redirect location http://mysite.com/ if !login_page secure
See section 2.3 about ACL usage.
redisp (deprecated)
redispatch (deprecated)
Enable or disable session redistribution in case of connection failure

View File

@ -162,6 +162,15 @@ enum {
DATA_ST_PX_FIN,
};
/* Redirect types (location, prefix, extended ) */
enum {
REDIRECT_TYPE_NONE = 0, /* no redirection */
REDIRECT_TYPE_LOCATION, /* location redirect */
REDIRECT_TYPE_PREFIX, /* prefix redirect */
};
/* Known HTTP methods */
typedef enum {
HTTP_METH_NONE = 0,

View File

@ -129,6 +129,7 @@ struct proxy {
} defbe;
struct list acl; /* ACL declared on this proxy */
struct list block_cond; /* early blocking conditions (chained) */
struct list redirect_rules; /* content redirecting rules (chained) */
struct list switching_rules; /* content switching rules (chained) */
struct server *srv; /* known servers */
int srv_act, srv_bck; /* # of servers eligible for LB (UP|!checked) AND (enabled+weight!=0) */
@ -252,6 +253,15 @@ struct switching_rule {
} be;
};
struct redirect_rule {
struct list list; /* list linked to from the proxy */
struct acl_cond *cond; /* acl condition to meet */
int type;
int rdr_len;
char *rdr_str;
int code;
};
extern struct proxy *proxy;
extern int next_pxid;

View File

@ -587,6 +587,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
LIST_INIT(&curproxy->pendconns);
LIST_INIT(&curproxy->acl);
LIST_INIT(&curproxy->block_cond);
LIST_INIT(&curproxy->redirect_rules);
LIST_INIT(&curproxy->mon_fail_cond);
LIST_INIT(&curproxy->switching_rules);
@ -1119,6 +1120,98 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
}
LIST_ADDQ(&curproxy->block_cond, &cond->list);
}
else if (!strcmp(args[0], "redirect")) {
int pol = ACL_COND_NONE;
struct acl_cond *cond;
struct redirect_rule *rule;
int cur_arg;
int type = REDIRECT_TYPE_NONE;
int code = 302;
char *destination = NULL;
cur_arg = 1;
while (*(args[cur_arg])) {
if (!strcmp(args[cur_arg], "location")) {
if (!*args[cur_arg + 1]) {
Alert("parsing [%s:%d] : '%s': missing argument for '%s'.\n",
file, linenum, args[0], args[cur_arg]);
return -1;
}
type = REDIRECT_TYPE_LOCATION;
cur_arg++;
destination = args[cur_arg];
}
else if (!strcmp(args[cur_arg], "prefix")) {
if (!*args[cur_arg + 1]) {
Alert("parsing [%s:%d] : '%s': missing argument for '%s'.\n",
file, linenum, args[0], args[cur_arg]);
return -1;
}
type = REDIRECT_TYPE_PREFIX;
cur_arg++;
destination = args[cur_arg];
}
else if (!strcmp(args[cur_arg],"code")) {
if (!*args[cur_arg + 1]) {
Alert("parsing [%s:%d] : '%s': missing HTTP code.\n",
file, linenum, args[0]);
return -1;
}
cur_arg++;
code = atol(args[cur_arg]);
if (code < 301 || code > 303) {
Alert("parsing [%s:%d] : '%s': unsupported HTTP code '%d'.\n",
file, linenum, args[0], code);
return -1;
}
}
else if (!strcmp(args[cur_arg], "if")) {
pol = ACL_COND_IF;
cur_arg++;
break;
}
else if (!strcmp(args[cur_arg], "unless")) {
pol = ACL_COND_UNLESS;
cur_arg++;
break;
}
else {
Alert("parsing [%s:%d] : '%s' expects 'code', 'prefix' or 'location' (was '%s').\n",
file, linenum, args[0], args[cur_arg]);
return -1;
}
cur_arg++;
}
if (type == REDIRECT_TYPE_NONE) {
Alert("parsing [%s:%d] : '%s' expects a redirection type ('prefix' or 'location').\n",
file, linenum, args[0]);
return -1;
}
if (pol == ACL_COND_NONE) {
Alert("parsing [%s:%d] : '%s' requires either 'if' or 'unless' followed by a condition.\n",
file, linenum, args[0]);
return -1;
}
if ((cond = parse_acl_cond((const char **)args + cur_arg, &curproxy->acl, pol)) == NULL) {
Alert("parsing [%s:%d] : '%s': error detected while parsing condition.\n",
file, linenum, args[0]);
return -1;
}
rule = (struct redirect_rule *)calloc(1, sizeof(*rule));
rule->cond = cond;
rule->rdr_str = strdup(destination);
rule->rdr_len = strlen(destination);
rule->type = type;
rule->code = code;
LIST_INIT(&rule->list);
LIST_ADDQ(&curproxy->redirect_rules, &rule->list);
}
else if (!strcmp(args[0], "use_backend")) { /* early blocking based on ACLs */
int pol = ACL_COND_NONE;
struct acl_cond *cond;

View File

@ -648,6 +648,7 @@ void deinit(void)
struct hdr_exp *exp, *expb;
struct acl *acl, *aclb;
struct switching_rule *rule, *ruleb;
struct redirect_rule *rdr, *rdrb;
struct uri_auth *uap, *ua = NULL;
struct user_auth *user;
int i;
@ -758,6 +759,14 @@ void deinit(void)
free(rule);
}
list_for_each_entry_safe(rdr, rdrb, &p->redirect_rules, list) {
LIST_DEL(&rdr->list);
prune_acl_cond(rdr->cond);
free(rdr->cond);
free(rdr->rdr_str);
free(rdr);
}
if (p->appsession_name)
free(p->appsession_name);

View File

@ -88,6 +88,12 @@ const struct chunk http_200_chunk = {
.len = sizeof(HTTP_200)-1
};
const char *HTTP_301 =
"HTTP/1.0 301 Moved Permantenly\r\n"
"Cache-Control: no-cache\r\n"
"Connection: close\r\n"
"Location: "; /* not terminated since it will be concatenated with the URL */
const char *HTTP_302 =
"HTTP/1.0 302 Found\r\n"
"Cache-Control: no-cache\r\n"
@ -1800,9 +1806,90 @@ int process_cli(struct session *t)
do {
struct acl_cond *cond;
struct redirect_rule *rule;
struct proxy *rule_set = t->be;
cur_proxy = t->be;
/* 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, t, txn, ACL_DIR_REQ);
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;
} else {
path = "/";
pathlen = 1;
}
if (rdr.len + rule->rdr_len + pathlen > sizeof(trash) - 4)
goto return_bad_req;
/* add prefix */
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 */
memcpy(rdr.str + rdr.len, rule->rdr_str, rule->rdr_len);
rdr.len += rule->rdr_len;
break;
}
/* 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 */
t->logs.t_request = tv_ms_elapsed(&t->logs.tv_accept, &now);
client_retnclose(t, &rdr);
goto return_prx_cond;
}
}
/* 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, t, txn, ACL_DIR_REQ);

41
tests/test-redirect.cfg Normal file
View File

@ -0,0 +1,41 @@
# This is a test configuration.
# It is used to check the redirect keyword.
global
maxconn 400
stats timeout 3s
listen sample1
mode http
retries 1
option redispatch
timeout client 1m
timeout connect 5s
timeout server 1m
maxconn 400
bind :8000
acl url_test1 url_reg test1
acl url_test2 url_reg test2
redirect location /abs/test code 301 if url_test1
redirect prefix /pfx/test code 302 if url_test2
redirect prefix /pfx/test code 303 if url_test2
### unconditional redirection
#redirect location https://example.com/ if TRUE
### parser must detect invalid syntaxes below
#redirect
#redirect blah
#redirect location
#redirect location /abs/test
#redirect location /abs/test code
#redirect location /abs/test code 300
#redirect location /abs/test code 301
#redirect location /abs/test code 304
balance roundrobin
server act1 127.0.0.1:80 weight 10
option httpclose
stats uri /stats
stats refresh 5000ms