From: Benoit Date: Wed, 25 Mar 2009 12:02:10 +0000 (+0100) Subject: [MEDIUM] add support for "balance hdr(name)" X-Git-Tag: v1.3.18~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=affb481f1a09b36688fc67356bec314e544fd238;p=thirdparty%2Fhaproxy.git [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 3545b29583..454bbd395f 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -937,6 +937,17 @@ balance url_param [check_post []] 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 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. + 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 [check_post []] 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 6d576b8dbf..9f15c08d75 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 48d0a82cc4..3cb60c0f41 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -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 */ diff --git a/src/backend.c b/src/backend.c index 6ca5059573..321c8a85d3 100644 --- a/src/backend.c +++ b/src/backend.c @@ -1278,6 +1278,80 @@ struct server *get_server_ph_post(struct session *s) } +/* + * This function tries to find a running server for the proxy 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; diff --git a/src/cfgparse.c b/src/cfgparse.c index 6a4744e531..3a71df2506 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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);