]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] add support for "balance hdr(name)"
authorBenoit <maverick@maverick.eu.org>
Wed, 25 Mar 2009 12:02:10 +0000 (13:02 +0100)
committerWilly Tarreau <w@1wt.eu>
Sun, 10 May 2009 13:50:15 +0000 (15:50 +0200)
There is a patch made by me that allow for balancing on any http header
field.

[WT:
  made minor changes:
  - turned 'balance header name' into 'balance hdr(name)' to match more
    closely the ACL syntax for easier future convergence
  - renamed the proxy structure fields header_* => hh_*
  - made it possible to use the domain name reduction to any header, not
    only "host" since it makes sense to do it with other ones.
  Otherwise patch looks good.
/WT]

doc/configuration.txt
include/types/backend.h
include/types/proxy.h
src/backend.c
src/cfgparse.c

index 3545b29583b89237afcfc26d8d444787f5cb6f02..454bbd395f3ada67637237304d67f61d9c08a874 100644 (file)
@@ -937,6 +937,17 @@ balance url_param <param> [check_post [<max_wait>]]
                   backend. This algorithm is static, which means that changing a
                   server's weight on the fly will have no effect.
 
+      hdr(name)   The HTTP header <name> will be looked up in each HTTP request.
+                  Just as with the equivalent ACL 'hdr()' function, the header
+                  name in parenthesis is not case sensitive. If the header is
+                  absent or if it does not contain any value, the round-robin
+                  algorithm is applied instead.
+
+                  An optionnal 'use_domain_only' parameter is available, for
+                  reducing the hash algorithm to the main domain part with some
+                  specific headers such as 'Host'. For instance, in the Host
+                  value "haproxy.1wt.eu", only "1wt" will be considered.
+
     <arguments> is an optional list of arguments which may be needed by some
                 algorithms. Right now, only "url_param" and "uri" support an
                 optional argument.
@@ -952,6 +963,9 @@ balance url_param <param> [check_post [<max_wait>]]
         balance roundrobin
         balance url_param userid
         balance url_param session_id check_post 64
+        balance hdr(User-Agent)
+        balance hdr(host)
+        balance hdr(Host) use_domain_only
 
   Note: the following caveats and limitations on using the "check_post"
   extension with "url_param" must be considered :
index 6d576b8dbfac080e8c6466d486ccb98586cc2fc0..9f15c08d75c357daf3d429364c502b48dd8de731 100644 (file)
@@ -42,7 +42,8 @@
 #define BE_LB_ALGO_SH  (BE_LB_PROP_L4  | 0x02) /* balance on source IP hash */
 #define BE_LB_ALGO_UH  (BE_LB_PROP_L7  | 0x03) /* balance on URI hash */
 #define BE_LB_ALGO_PH  (BE_LB_PROP_L7  | 0x04) /* balance on URL parameter hash */
-#define BE_LB_ALGO_LC  (BE_LB_PROP_DYN | 0x05) /* fast weighted round-robin mode (dynamic) */
+#define BE_LB_ALGO_LC  (BE_LB_PROP_DYN | 0x05) /* fast weighted leastconn mode (dynamic) */
+#define BE_LB_ALGO_HH  (BE_LB_PROP_L7  | 0x06) /* balance on Http Header value */
 
 /* various constants */
 
index 48d0a82cc4253290dfd0b93c88dd81dd3f8afcda..3cb60c0f41263eee2c903047199fd556b46a344c 100644 (file)
@@ -199,6 +199,9 @@ struct proxy {
        unsigned url_param_post_limit;          /* if checking POST body for URI parameter, max body to wait for */
        int  uri_len_limit;                     /* character limit for uri balancing algorithm */
        int  uri_dirs_depth1;                   /* directories+1 (slashes) limit for uri balancing algorithm */
+       char *hh_name;                          /* name of the header parameter used for hashing */
+       int  hh_len;                            /* strlen(hh_name), computed only once */
+       int  hh_match_domain;                   /* toggle use of special match function */
        char *appsession_name;                  /* name of the cookie to look for */
        int  appsession_name_len;               /* strlen(appsession_name), computed only once */
        int  appsession_len;                    /* length of the appsession cookie value to be used */
index 6ca50595738f4b7bf588517df16a8ab5f28a59c3..321c8a85d3223fc5a968427dd9327072577c7957 100644 (file)
@@ -1278,6 +1278,80 @@ struct server *get_server_ph_post(struct session *s)
 }
 
 
+/*
+ * This function tries to find a running server for the proxy <px> following
+ * the Header parameter hash method. It looks for a specific parameter in the
+ * URL and hashes it to compute the server ID. This is useful to optimize
+ * performance by avoiding bounces between servers in contexts where sessions
+ * are shared but cookies are not usable. If the parameter is not found, NULL
+ * is returned. If any server is found, it will be returned. If no valid server
+ * is found, NULL is returned.
+ */
+struct server *get_server_hh(struct session *s)
+{
+       unsigned long    hash = 0;
+       struct http_txn *txn  = &s->txn;
+       struct http_msg *msg  = &txn->req;
+       struct proxy    *px   = s->be;
+       unsigned int     plen = px->hh_len;
+       unsigned long    len;
+       struct hdr_ctx   ctx;
+       const char      *p;
+
+       /* tot_weight appears to mean srv_count */
+       if (px->lbprm.tot_weight == 0)
+               return NULL;
+
+       if (px->lbprm.map.state & PR_MAP_RECALC)
+               recalc_server_map(px);
+
+       ctx.idx = 0;
+
+       /* if the message is chunked, we skip the chunk size, but use the value as len */
+       http_find_header2(px->hh_name, plen, msg->sol, &txn->hdr_idx, &ctx);
+
+       /* if the header is not found or empty, let's fallback to round robin */
+       if (!ctx.idx || !ctx.vlen)
+               return NULL;
+
+       /* Found a the hh_name in the headers.
+        * we will compute the hash based on this value ctx.val.
+        */
+       len = ctx.vlen;
+       p = (char *)ctx.line + ctx.val;
+       if (!px->hh_match_domain) {
+               while (len) {
+                       hash = *p + (hash << 6) + (hash << 16) - hash;
+                       len--;
+                       p++;
+               }
+       } else {
+               int dohash = 0;
+               p += len - 1;
+               /* special computation, use only main domain name, not tld/host
+                * going back from the end of string, start hashing at first
+                * dot stop at next.
+                * This is designed to work with the 'Host' header, and requires
+                * a special option to activate this.
+                */
+               while (len) {
+                       if (*p == '.') {
+                               if (!dohash)
+                                       dohash = 1;
+                               else
+                                       break;
+                       } else {
+                               if (dohash)
+                                       hash = *p + (hash << 6) + (hash << 16) - hash;
+                       }
+                       len--;
+                       p--;
+               }
+       }
+       return px->lbprm.map.srv[hash % px->lbprm.tot_weight];
+}
+
 /*
  * This function applies the load-balancing algorithm to the session, as
  * defined by the backend it is assigned to. The session is then marked as
@@ -1387,6 +1461,19 @@ int assign_server(struct session *s)
                                                       s->txn.req.sol + s->txn.req.sl.rq.u,
                                                       s->txn.req.sl.rq.u_l);
 
+                       if (!s->srv) {
+                               /* parameter not found, fall back to round robin on the map */
+                               s->srv = get_server_rr_with_conns(s->be, s->prev_srv);
+                               if (!s->srv) {
+                                       err = SRV_STATUS_FULL;
+                                       goto out;
+                               }
+                       }
+                       break;
+               case BE_LB_ALGO_HH:
+                       /* Header Parameter hashing */
+                       s->srv = get_server_hh(s);
+
                        if (!s->srv) {
                                /* parameter not found, fall back to round robin on the map */
                                s->srv = get_server_rr_with_conns(s->be, s->prev_srv);
@@ -2023,8 +2110,36 @@ int backend_parse_balance(const char **args, char *err, int errlen, struct proxy
                                curproxy->url_param_post_limit = 3; /* minimum example: S=3 or \r\nS=6& */
                }
        }
+       else if (!strncmp(args[0], "hdr(", 4)) {
+               const char *beg, *end;
+
+               beg = args[0] + 4;
+               end = strchr(beg, ')');
+
+               if (!end || end == beg) {
+                       snprintf(err, errlen, "'balance hdr(name)' requires an http header field name.");
+                       return -1;
+               }
+
+               curproxy->lbprm.algo &= ~BE_LB_ALGO;
+               curproxy->lbprm.algo |= BE_LB_ALGO_HH;
+
+               free(curproxy->hh_name);
+               curproxy->hh_len  = end - beg;
+               curproxy->hh_name = my_strndup(beg, end - beg);
+               curproxy->hh_match_domain = 0;
+
+               if (*args[1]) {
+                       if (strcmp(args[1], "use_domain_only")) {
+                               snprintf(err, errlen, "'balance hdr(name)' only accepts 'use_domain_only' modifier.");
+                               return -1;
+                       }
+                       curproxy->hh_match_domain = 1;
+               }
+
+       }
        else {
-               snprintf(err, errlen, "'balance' only supports 'roundrobin', 'leastconn', 'source', 'uri' and 'url_param' options.");
+               snprintf(err, errlen, "'balance' only supports 'roundrobin', 'leastconn', 'source', 'uri', 'url_param' and 'hdr(name)' options.");
                return -1;
        }
        return 0;
index 6a4744e531f2189cd58324fcf81e3ac3ba3326d5..3a71df2506db5d41fe660b30c07a32d6ceb06046 100644 (file)
@@ -803,6 +803,11 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
                                curproxy->url_param_name = strdup(defproxy.url_param_name);
                        curproxy->url_param_len = defproxy.url_param_len;
 
+                       if (defproxy.hh_name)
+                               curproxy->hh_name = strdup(defproxy.hh_name);
+                       curproxy->hh_len  = defproxy.hh_len;
+                       curproxy->hh_match_domain  = defproxy.hh_match_domain;
+
                        if (defproxy.iface_name)
                                curproxy->iface_name = strdup(defproxy.iface_name);
                        curproxy->iface_len  = defproxy.iface_len;
@@ -859,6 +864,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
                free(defproxy.check_req);
                free(defproxy.cookie_name);
                free(defproxy.url_param_name);
+               free(defproxy.hh_name);
                free(defproxy.capture_name);
                free(defproxy.monitor_uri);
                free(defproxy.defbe.name);