[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]
This commit is contained in:
Benoit 2009-03-25 13:02:10 +01:00 committed by Willy Tarreau
parent 946ba59190
commit affb481f1a
5 changed files with 141 additions and 2 deletions

View File

@ -937,6 +937,17 @@ balance url_param <param> [check_post [<max_wait>]]
backend. This algorithm is static, which means that changing a backend. This algorithm is static, which means that changing a
server's weight on the fly will have no effect. 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 <arguments> is an optional list of arguments which may be needed by some
algorithms. Right now, only "url_param" and "uri" support an algorithms. Right now, only "url_param" and "uri" support an
optional argument. optional argument.
@ -952,6 +963,9 @@ balance url_param <param> [check_post [<max_wait>]]
balance roundrobin balance roundrobin
balance url_param userid balance url_param userid
balance url_param session_id check_post 64 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" Note: the following caveats and limitations on using the "check_post"
extension with "url_param" must be considered : extension with "url_param" must be considered :

View File

@ -42,7 +42,8 @@
#define BE_LB_ALGO_SH (BE_LB_PROP_L4 | 0x02) /* balance on source IP hash */ #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_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_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 */ /* various constants */

View File

@ -199,6 +199,9 @@ struct proxy {
unsigned url_param_post_limit; /* if checking POST body for URI parameter, max body to wait for */ 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_len_limit; /* character limit for uri balancing algorithm */
int uri_dirs_depth1; /* directories+1 (slashes) 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 */ char *appsession_name; /* name of the cookie to look for */
int appsession_name_len; /* strlen(appsession_name), computed only once */ int appsession_name_len; /* strlen(appsession_name), computed only once */
int appsession_len; /* length of the appsession cookie value to be used */ int appsession_len; /* length of the appsession cookie value to be used */

View 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 * 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 * 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.sol + s->txn.req.sl.rq.u,
s->txn.req.sl.rq.u_l); 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) { if (!s->srv) {
/* parameter not found, fall back to round robin on the map */ /* parameter not found, fall back to round robin on the map */
s->srv = get_server_rr_with_conns(s->be, s->prev_srv); 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& */ 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 { 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 -1;
} }
return 0; return 0;

View 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_name = strdup(defproxy.url_param_name);
curproxy->url_param_len = defproxy.url_param_len; 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) if (defproxy.iface_name)
curproxy->iface_name = strdup(defproxy.iface_name); curproxy->iface_name = strdup(defproxy.iface_name);
curproxy->iface_len = defproxy.iface_len; 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.check_req);
free(defproxy.cookie_name); free(defproxy.cookie_name);
free(defproxy.url_param_name); free(defproxy.url_param_name);
free(defproxy.hh_name);
free(defproxy.capture_name); free(defproxy.capture_name);
free(defproxy.monitor_uri); free(defproxy.monitor_uri);
free(defproxy.defbe.name); free(defproxy.defbe.name);