[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 option tcplog X X X X
[no] option tcpsplice X X X X [no] option tcpsplice X X X X
[no] option transparent X X X - [no] option transparent X X X -
redirect - X X X
redisp X - X X (deprecated) redisp X - X X (deprecated)
redispatch X - X X (deprecated) redispatch X - X X (deprecated)
reqadd - X X X reqadd - X X X
@ -2312,6 +2313,32 @@ no option transparent
"transparent" option of the "bind" keyword. "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) redisp (deprecated)
redispatch (deprecated) redispatch (deprecated)
Enable or disable session redistribution in case of connection failure Enable or disable session redistribution in case of connection failure

View File

@ -162,6 +162,15 @@ enum {
DATA_ST_PX_FIN, 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 */ /* Known HTTP methods */
typedef enum { typedef enum {
HTTP_METH_NONE = 0, HTTP_METH_NONE = 0,

View File

@ -129,6 +129,7 @@ struct proxy {
} defbe; } defbe;
struct list acl; /* ACL declared on this proxy */ struct list acl; /* ACL declared on this proxy */
struct list block_cond; /* early blocking conditions (chained) */ 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 list switching_rules; /* content switching rules (chained) */
struct server *srv; /* known servers */ struct server *srv; /* known servers */
int srv_act, srv_bck; /* # of servers eligible for LB (UP|!checked) AND (enabled+weight!=0) */ int srv_act, srv_bck; /* # of servers eligible for LB (UP|!checked) AND (enabled+weight!=0) */
@ -252,6 +253,15 @@ struct switching_rule {
} be; } 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 struct proxy *proxy;
extern int next_pxid; 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->pendconns);
LIST_INIT(&curproxy->acl); LIST_INIT(&curproxy->acl);
LIST_INIT(&curproxy->block_cond); LIST_INIT(&curproxy->block_cond);
LIST_INIT(&curproxy->redirect_rules);
LIST_INIT(&curproxy->mon_fail_cond); LIST_INIT(&curproxy->mon_fail_cond);
LIST_INIT(&curproxy->switching_rules); 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); 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 */ else if (!strcmp(args[0], "use_backend")) { /* early blocking based on ACLs */
int pol = ACL_COND_NONE; int pol = ACL_COND_NONE;
struct acl_cond *cond; struct acl_cond *cond;

View File

@ -648,6 +648,7 @@ void deinit(void)
struct hdr_exp *exp, *expb; struct hdr_exp *exp, *expb;
struct acl *acl, *aclb; struct acl *acl, *aclb;
struct switching_rule *rule, *ruleb; struct switching_rule *rule, *ruleb;
struct redirect_rule *rdr, *rdrb;
struct uri_auth *uap, *ua = NULL; struct uri_auth *uap, *ua = NULL;
struct user_auth *user; struct user_auth *user;
int i; int i;
@ -758,6 +759,14 @@ void deinit(void)
free(rule); 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) if (p->appsession_name)
free(p->appsession_name); free(p->appsession_name);

View File

@ -88,6 +88,12 @@ const struct chunk http_200_chunk = {
.len = sizeof(HTTP_200)-1 .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 = const char *HTTP_302 =
"HTTP/1.0 302 Found\r\n" "HTTP/1.0 302 Found\r\n"
"Cache-Control: no-cache\r\n" "Cache-Control: no-cache\r\n"
@ -1800,9 +1806,90 @@ int process_cli(struct session *t)
do { do {
struct acl_cond *cond; struct acl_cond *cond;
struct redirect_rule *rule;
struct proxy *rule_set = t->be; struct proxy *rule_set = t->be;
cur_proxy = 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 */ /* first check whether we have some ACLs set to block this request */
list_for_each_entry(cond, &cur_proxy->block_cond, list) { list_for_each_entry(cond, &cur_proxy->block_cond, list) {
int ret = acl_exec_cond(cond, cur_proxy, t, txn, ACL_DIR_REQ); 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