mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-03-06 03:18:43 +00:00
[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:
parent
8001d6162e
commit
b463dfb2de
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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
41
tests/test-redirect.cfg
Normal 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
|
Loading…
Reference in New Issue
Block a user