mod_proxy_balancer.c revision c64fb33e0c4634fd352c4a6c143cd1a087c09b13
181e56d8b348d301d615ccf5465ae600fee2867berikabele/* Licensed to the Apache Software Foundation (ASF) under one or more
181e56d8b348d301d615ccf5465ae600fee2867berikabele * contributor license agreements. See the NOTICE file distributed with
fd9abdda70912b99b24e3bf1a38f26fde908a74cnd * this work for additional information regarding copyright ownership.
fd9abdda70912b99b24e3bf1a38f26fde908a74cnd * The ASF licenses this file to You under the Apache License, Version 2.0
fd9abdda70912b99b24e3bf1a38f26fde908a74cnd * (the "License"); you may not use this file except in compliance with
e4e4d8f25022f178ceb567b9f2b37be34b729f08slive * the License. You may obtain a copy of the License at
96ad5d81ee4a2cc66a4ae19893efc8aa6d06fae7jailletc * Unless required by applicable law or agreed to in writing, software
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim * distributed under the License is distributed on an "AS IS" BASIS,
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
d29d9ab4614ff992b0e8de6e2b88d52b6f1f153erbowen * See the License for the specific language governing permissions and
2e545ce2450a9953665f701bb05350f0d3f26275nd * limitations under the License.
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim/* Load balancer module for Apache proxy */
f086b4b402fa9a2fefc7dda85de2a3cc1cd0a654rjungstatic int proxy_balancer_canon(request_rec *r, char *url)
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen const char *err;
51db0c8d97d68b94230ba4972c7a62b55314acd3slive /* do syntatic check.
5a58787efeb02a1c3f06569d019ad81fd2efa06end * We break the URL into host, port, path, search
5a58787efeb02a1c3f06569d019ad81fd2efa06end err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim "error parsing URL %s: %s",
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim /* now parse path/search args, according to rfc1738 */
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim /* N.B. if this isn't a true proxy request, then the URL _path_
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim * has already been decoded. True proxy requests have r->uri
0fd8d4360a2d123a9db5bad1b505b6be405abbd4igalic * == r->unparsed_uri, and no others have that property.
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen /* process path */
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, r->proxyreq);
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen r->filename = apr_pstrcat(r->pool, "proxy:balancer://", host,
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen r->path_info = apr_pstrcat(r->pool, "/", path, NULL);
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowenstatic int init_balancer_members(proxy_server_conf *conf, server_rec *s,
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen /* Set to the original configuration */
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen /* Set default number of attempts to the number of
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen if (!balancer->max_attempts_set && balancer->workers->nelts > 1) {
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen balancer->max_attempts = balancer->workers->nelts - 1;
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen/* Retrieve the parameter with the given name
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen * Something like 'JSESSIONID=12345...N'
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen const char *name)
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen for (path = strstr(url, name); path; path = strstr(path + 1, name)) {
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim * Session path was found, get it's value
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen path = apr_strtok(apr_pstrdup(pool, path), "?&", &q);
7859c11126b92a9ff7ca9ebf9ec0c7dee8ddb70fcovenerstatic char *get_cookie_param(request_rec *r, const char *name)
203a32ea2557b7e1abd50c6bb1ce3bb69cd1570frbowen const char *cookies;
a99c5d4cc3cab6a62b04d52000dbc22ce1fa2d94coar const char *start_cookie;
203a32ea2557b7e1abd50c6bb1ce3bb69cd1570frbowen if ((cookies = apr_table_get(r->headers_in, "Cookie"))) {
203a32ea2557b7e1abd50c6bb1ce3bb69cd1570frbowen for (start_cookie = ap_strstr_c(cookies, name); start_cookie;
203a32ea2557b7e1abd50c6bb1ce3bb69cd1570frbowen start_cookie = ap_strstr_c(start_cookie + 1, name)) {
f915b3725d6bce16d5fa02403601fb9e6872cf49kess * Session cookie was found, get it's value
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen/* Find the worker that has the 'route' defined
f915b3725d6bce16d5fa02403601fb9e6872cf49kessstatic proxy_worker *find_route_worker(proxy_balancer *balancer,
9a58dc6a2b26ec128b1270cf48810e705f1a90dbsf for (i = 0; i < balancer->workers->nelts; i++, worker++) {
9a58dc6a2b26ec128b1270cf48810e705f1a90dbsf if ( (checking_standby ? !PROXY_WORKER_IS_STANDBY(worker) : PROXY_WORKER_IS_STANDBY(worker)) )
9a58dc6a2b26ec128b1270cf48810e705f1a90dbsf if (*(worker->s->route) && strcmp(worker->s->route, route) == 0) {
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim * If the worker is in error state run
5a58787efeb02a1c3f06569d019ad81fd2efa06end * retry on that worker. It will be marked as
5a58787efeb02a1c3f06569d019ad81fd2efa06end * operational if the retry timeout is elapsed.
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen * The worker might still be unusable, but we try
9ed703ab1543b3300f4b60c0405fc1a212b601c8slive * We have a worker that is unusable.
9ed703ab1543b3300f4b60c0405fc1a212b601c8slive * It can be in error or disabled, but in case
645a5920d9fdd53a7f75a6a16e87ff27781b0133slive * it has a redirection set use that redirection worker.
9ed703ab1543b3300f4b60c0405fc1a212b601c8slive * This enables to safely remove the member from the
9ed703ab1543b3300f4b60c0405fc1a212b601c8slive * balancer. Of course you will need some kind of
9ed703ab1543b3300f4b60c0405fc1a212b601c8slive * session replication between those two remote.
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim rworker = find_route_worker(balancer, worker->s->redirect, r);
5a58787efeb02a1c3f06569d019ad81fd2efa06end /* Check if the redirect worker is usable */
654d8eb036bedc99e90e11910ee02d3421417697rbowen * If the worker is in error state run
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen * retry on that worker. It will be marked as
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen * operational if the retry timeout is elapsed.
181e56d8b348d301d615ccf5465ae600fee2867berikabele * The worker might still be unusable, but we try
181e56d8b348d301d615ccf5465ae600fee2867berikabele ap_proxy_retry_worker("BALANCER", rworker, r->server);
e4ca72aa494fed7b6948012734b9c9c098fbba07ndstatic proxy_worker *find_session_route(proxy_balancer *balancer,
9ed703ab1543b3300f4b60c0405fc1a212b601c8slive const char **sticky_used,
645a5920d9fdd53a7f75a6a16e87ff27781b0133slive /* Try to find the sticky route inside url */
645a5920d9fdd53a7f75a6a16e87ff27781b0133slive *route = get_path_param(r->pool, *url, balancer->sticky_path);
9ed703ab1543b3300f4b60c0405fc1a212b601c8slive "proxy: BALANCER: Found value %s for "
c7ed811e89712261aaa4432198e331389044e1f8rjung "proxy: BALANCER: Found value %s for "
c7ed811e89712261aaa4432198e331389044e1f8rjung * If we found a value for sticksession, find the first '.' within.
9ed703ab1543b3300f4b60c0405fc1a212b601c8slive * Everything after '.' (if present) is our route.
f87c874dc2012c5e410ea0d25ffda6a61c6c2917slive if ((*route) && ((*route = strchr(*route, '.')) != NULL ))
9ed703ab1543b3300f4b60c0405fc1a212b601c8slive /* We have a route in path or in cookie
9ed703ab1543b3300f4b60c0405fc1a212b601c8slive * Find the worker that has this route defined.
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen * Notice that the route of the worker chosen is different from
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen * the route supplied by the client.
c7ed811e89712261aaa4432198e331389044e1f8rjung apr_table_setn(r->subprocess_env, "BALANCER_ROUTE_CHANGED", "1");
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen "proxy: BALANCER: Route changed from %s to %s",
36a1dcd0eb7ff5a01f23c95d58bf863da1c7045bkessstatic proxy_worker *find_best_worker(proxy_balancer *balancer,
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen "proxy: BALANCER: (%s). Lock failed for find_best_worker()", balancer->name);
181e56d8b348d301d615ccf5465ae600fee2867berikabele candidate = (*balancer->lbmethod->finder)(balancer, r);
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim PROXY_THREAD_UNLOCK(balancer);
5a58787efeb02a1c3f06569d019ad81fd2efa06end return NULL;
51db0c8d97d68b94230ba4972c7a62b55314acd3slive if ((rv = PROXY_THREAD_UNLOCK(balancer)) != APR_SUCCESS) {
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen "proxy: BALANCER: (%s). Unlock failed for find_best_worker()", balancer->name);
847155534c9678f68502de3809d8d6d2623ac32erbowen /* All the workers are in error state or disabled.
5e7452e356ccbbc11d7a19368f40ae54a1d6c3d3rbowen * If the balancer has a timeout sleep for a while
5e7452e356ccbbc11d7a19368f40ae54a1d6c3d3rbowen * and try again to find the worker. The chances are
5e7452e356ccbbc11d7a19368f40ae54a1d6c3d3rbowen * that some other thread will release a connection.
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen * By default the timeout is not set, and the server
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen * returns SERVER_BUSY.
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen /* XXX: This can perhaps be build using some
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen * smarter mechanism, like tread_cond.
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen * But since the statuses can came from
738504ae90e2233e22f4fd0da1d8ccf0b96e579end * different childs, use the provided algo.
9a58dc6a2b26ec128b1270cf48810e705f1a90dbsf /* Set the timeout to 0 so that we don't
5e7452e356ccbbc11d7a19368f40ae54a1d6c3d3rbowen * end in infinite loop
57d0156f7bbd9ea3a72342cf9912aba61d118702rbowen /* Try again */
5e7452e356ccbbc11d7a19368f40ae54a1d6c3d3rbowen /* restore the timeout */
7add1372edb1ee95a2c4d1314df4c7567bda7c62jimstatic int rewrite_url(request_rec *r, proxy_worker *worker,
51db0c8d97d68b94230ba4972c7a62b55314acd3slive /* we break the URL into host, port, uri */
51db0c8d97d68b94230ba4972c7a62b55314acd3slive return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
51db0c8d97d68b94230ba4972c7a62b55314acd3slive *url = apr_pstrcat(r->pool, worker->name, path, NULL);
51db0c8d97d68b94230ba4972c7a62b55314acd3slivestatic void force_recovery(proxy_balancer *balancer, server_rec *s)
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim for (i = 0; i < balancer->workers->nelts; i++, worker++) {
51db0c8d97d68b94230ba4972c7a62b55314acd3slive /* If all workers are in error state force the recovery.
e4ca72aa494fed7b6948012734b9c9c098fbba07nd for (i = 0; i < balancer->workers->nelts; i++, worker++) {
51db0c8d97d68b94230ba4972c7a62b55314acd3slive "proxy: BALANCER: (%s). Forcing recovery for worker (%s)",
5e7452e356ccbbc11d7a19368f40ae54a1d6c3d3rbowenstatic int proxy_balancer_pre_request(proxy_worker **worker,
c6063a3218284e4373e5e6436f420aa10fa5b7dbrbowen /* Step 1: check if the url is for us
c6063a3218284e4373e5e6436f420aa10fa5b7dbrbowen * The url we can handle starts with 'balancer://'
c6063a3218284e4373e5e6436f420aa10fa5b7dbrbowen * If balancer is already provided skip the search
c6063a3218284e4373e5e6436f420aa10fa5b7dbrbowen * for balancer, because this is failover attempt.
c6063a3218284e4373e5e6436f420aa10fa5b7dbrbowen !(*balancer = ap_proxy_get_balancer(r->pool, conf, *url)))
c6063a3218284e4373e5e6436f420aa10fa5b7dbrbowen /* Step 2: Lock the LoadBalancer
c6063a3218284e4373e5e6436f420aa10fa5b7dbrbowen * XXX: perhaps we need the process lock here
c6063a3218284e4373e5e6436f420aa10fa5b7dbrbowen if ((rv = PROXY_THREAD_LOCK(*balancer)) != APR_SUCCESS) {
c6063a3218284e4373e5e6436f420aa10fa5b7dbrbowen "proxy: BALANCER: (%s). Lock failed for pre_request",
c6063a3218284e4373e5e6436f420aa10fa5b7dbrbowen /* Step 3: force recovery */
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim /* Step 4: find the session route */
7add1372edb1ee95a2c4d1314df4c7567bda7c62jim runtime = find_session_route(*balancer, r, &route, &sticky, url);
fac8c35bfb158112226ab43ddf84d59daca5dc30nd /* We have a sticky load balancer
f086b4b402fa9a2fefc7dda85de2a3cc1cd0a654rjung * Update the workers status
727872d18412fc021f03969b8641810d8896820bhumbedooh * so that even session routes get
0d0ba3a410038e179b695446bb149cce6264e0abnd * into account.
cc7e1025de9ac63bd4db6fe7f71c158b2cf09fe4humbedooh workers = (proxy_worker *)(*balancer)->workers->elts;
cc7e1025de9ac63bd4db6fe7f71c158b2cf09fe4humbedooh /* Take into calculation only the workers that are
727872d18412fc021f03969b8641810d8896820bhumbedooh * not in error state or not disabled.
0d0ba3a410038e179b695446bb149cce6264e0abnd * TODO: Abstract the below, since this is dependent
0d0ba3a410038e179b695446bb149cce6264e0abnd * on the LB implementation
7fec19672a491661b2fe4b29f685bc7f4efa64d4nd int i, member_of = 0;
workers++;
if (member_of) {
return HTTP_SERVICE_UNAVAILABLE;
if (!*worker) {
if (!runtime) {
return HTTP_SERVICE_UNAVAILABLE;
if (route) {
return access_status;
request_rec *r,
return HTTP_INTERNAL_SERVER_ERROR;
return OK;
int access_status;
const char *name;
return DECLINED;
return DECLINED;
if (r->args) {
return access_status;
return HTTP_BAD_REQUEST;
if (ws) {
++worker;
if (wsel) {
const char *val;
if (bsel)
++worker;
++balancer;
++worker;
++balancer;
return OK;
balancer++;
s = s->next;
request_rec *r)
int total_factor = 0;
int cur_lbset = 0;
int max_lbset = 0;
int checking_standby;
int checked_standby;
cur_lbset++;
if (mycandidate) {
return mycandidate;
request_rec *r)
int cur_lbset = 0;
int max_lbset = 0;
int checking_standby;
int checked_standby;
cur_lbset++;
return mycandidate;