]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] add support for conditional HTTP redirection
authorWilly Tarreau <w@1wt.eu>
Sat, 7 Jun 2008 21:08:56 +0000 (23:08 +0200)
committerWilly Tarreau <w@1wt.eu>
Sat, 7 Jun 2008 21:08:56 +0000 (23:08 +0200)
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.

doc/configuration.txt
include/types/proto_http.h
include/types/proxy.h
src/cfgparse.c
src/haproxy.c
src/proto_http.c
tests/test-redirect.cfg [new file with mode: 0644]

index 220990fee773bfed0da47fbd14ecf7d32ed8b3a4..65404f5649951b0cb4f3d952cfb51f631a8c8a54 100644 (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
index 885fbc63411a368c5b5a676f93e9c08420874fcf..5b0914f0407467d81ff71e71f9fdd58bc9435698 100644 (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,
index 9129861c36fbfad0d79b714d29bf51a1db7a0138..10a69b5f2eb60f6338343acc92ea1a680a747d79 100644 (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;
 
index e4308bf6dd26f255ea45499fb4687c94e6679c94..8804a67f23a969f6fabca5f7e03ba6ba9af9dc22 100644 (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;
index d52524f6bc466faf30f37c2c0456624320f96102..a4e02e0457f4dddeb53be78a1d335e8da9f7c882 100644 (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);
 
index 066db30dcacecdfb27150fa6c563040e66c75654..6b7ced2613f903185d92e77a09fefbeb2fa88d38 100644 (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);
diff --git a/tests/test-redirect.cfg b/tests/test-redirect.cfg
new file mode 100644 (file)
index 0000000..efafa4f
--- /dev/null
@@ -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