[MEDIUM] add support for "balance hdr(name)"
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]
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 3545b29..454bbd3 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -937,6 +937,17 @@
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 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 :
diff --git a/include/types/backend.h b/include/types/backend.h
index 6d576b8..9f15c08 100644
--- a/include/types/backend.h
+++ b/include/types/backend.h
@@ -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 */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 48d0a82..3cb60c0 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -199,6 +199,9 @@
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 */
diff --git a/src/backend.c b/src/backend.c
index 6ca5059..321c8a8 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1279,6 +1279,80 @@
/*
+ * 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
* 'assigned'.
@@ -1396,6 +1470,19 @@
}
}
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);
+ if (!s->srv) {
+ err = SRV_STATUS_FULL;
+ goto out;
+ }
+ }
+ break;
default:
/* unknown balancing algorithm */
err = SRV_STATUS_INTERNAL;
@@ -2022,9 +2109,37 @@
else if ( curproxy->url_param_post_limit < 3 )
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;
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 6a4744e..3a71df2 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -803,6 +803,11 @@
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 @@
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);