1 /* Copyright 1999-2004 The Apache Software Foundation
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
16 /* Load balancer module for Apache proxy */
20 #include "mod_proxy.h"
22 #include "apr_version.h"
24 module AP_MODULE_DECLARE_DATA proxy_balancer_module;
27 #define PROXY_BALANCER_LOCK(b) apr_thread_mutex_lock((b)->mutex)
28 #define PROXY_BALANCER_UNLOCK(b) apr_thread_mutex_unlock((b)->mutex)
30 #define PROXY_BALANCER_LOCK(b) APR_SUCCESS
31 #define PROXY_BALANCER_UNLOCK(b) APR_SUCCESS
35 /* Retrieve the parameter with the given name */
36 static char *get_path_param(apr_pool_t *pool, char *url,
41 for (path = strstr(url, name); path; path = strstr(path + 1, name)) {
42 path += (strlen(name) + 1);
45 * Session path was found, get it's value
50 path = apr_pstrdup(pool, path);
51 if ((q = strchr(path, '?')))
60 static char *get_cookie_param(request_rec *r, const char *name)
63 const char *start_cookie;
65 if ((cookies = apr_table_get(r->headers_in, "Cookie"))) {
66 for (start_cookie = ap_strstr_c(cookies, name); start_cookie;
67 start_cookie = ap_strstr_c(start_cookie + 1, name)) {
68 if (start_cookie == cookies ||
69 start_cookie[-1] == ';' ||
70 start_cookie[-1] == ',' ||
71 isspace(start_cookie[-1])) {
73 start_cookie += strlen(name);
74 while(*start_cookie && isspace(*start_cookie))
76 if (*start_cookie == '=' && start_cookie[1]) {
78 * Session cookie was found, get it's value
80 char *end_cookie, *cookie;
82 cookie = apr_pstrdup(r->pool, start_cookie);
83 if ((end_cookie = strchr(cookie, ';')) != NULL)
85 if((end_cookie = strchr(cookie, ',')) != NULL)
95 static proxy_runtime_worker *find_route_worker(proxy_balancer *balancer,
99 proxy_runtime_worker *worker = (proxy_runtime_worker *)balancer->workers->elts;
100 for (i = 0; i < balancer->workers->nelts; i++) {
101 if (worker->w->route && strcmp(worker->w->route, route) == 0) {
109 static proxy_runtime_worker *find_session_route(proxy_balancer *balancer,
114 if (!balancer->sticky)
116 /* Try to find the sticky route inside url */
117 *route = get_path_param(r->pool, *url, balancer->sticky);
119 *route = get_cookie_param(r, balancer->sticky);
121 proxy_runtime_worker *worker = find_route_worker(balancer, *route);
122 /* TODO: make worker status codes */
123 /* See if we have a redirection route */
124 if (worker && !PROXY_WORKER_IS_USABLE(worker->w)) {
125 if (worker->w->redirect)
126 worker = find_route_worker(balancer, worker->w->redirect);
127 /* Check if the redirect worker is usable */
128 if (worker && !PROXY_WORKER_IS_USABLE(worker->w))
139 static proxy_runtime_worker *find_best_worker(proxy_balancer *balancer,
143 double total_factor = 0.0;
144 proxy_runtime_worker *worker = (proxy_runtime_worker *)balancer->workers->elts;
145 proxy_runtime_worker *candidate = NULL;
147 /* First try to see if we have available candidate */
148 for (i = 0; i < balancer->workers->nelts; i++) {
149 /* See if the retry timeout is ellapsed
150 * for the workers flagged as IN_ERROR
152 if (!PROXY_WORKER_IS_USABLE(worker->w))
153 ap_proxy_retry_worker("BALANCER", worker->w, r->server);
154 /* If the worker is not in error state
157 if (PROXY_WORKER_IS_USABLE(worker->w)) {
161 /* See if the worker has a larger number of free channels */
162 if (worker->w->cp->nfree > candidate->w->cp->nfree)
165 /* Total factor should allways be 100.
166 * This is for cases when worker is in error state.
167 * It will force the even request distribution
169 total_factor += worker->s->lbfactor;
174 /* All the workers are in error state or disabled.
175 * If the balancer has a timeout wait.
178 if (balancer->timeout) {
179 /* XXX: This can perhaps be build using some
180 * smarter mechanism, like tread_cond.
181 * But since the statuses can came from
182 * different childs, use the provided algo.
184 apr_interval_time_t timeout = balancer->timeout;
185 apr_interval_time_t step, tval = 0;
186 balancer->timeout = 0;
187 step = timeout / 100;
188 while (tval < timeout) {
191 if ((candidate = find_best_worker(balancer, r)))
195 /* restore the timeout */
196 balancer->timeout = timeout;
201 /* We have at least one candidate that is not in
202 * error state or disabled.
203 * Now calculate the appropriate one
205 worker = (proxy_runtime_worker *)balancer->workers->elts;
206 for (i = 0; i < balancer->workers->nelts; i++) {
207 /* If the worker is not error state
208 * or not in disabled mode
210 if (PROXY_WORKER_IS_USABLE(worker->w)) {
211 /* 1. Find the worker with higher lbstatus.
212 * Lbstatus is of higher importance then
213 * the number of empty slots.
215 if (worker->s->lbstatus > candidate->s->lbstatus) {
221 worker = (proxy_runtime_worker *)balancer->workers->elts;
222 for (i = 0; i < balancer->workers->nelts; i++) {
223 /* If the worker is not error state
224 * or not in disabled mode
226 if (PROXY_WORKER_IS_USABLE(worker->w)) {
227 /* XXX: The lbfactor can be update using bytes transfered
228 * Right now, use the round-robin scheme
230 worker->s->lbstatus += worker->s->lbfactor;
231 if (worker->s->lbstatus >= total_factor)
232 worker->s->lbstatus = worker->s->lbfactor;
240 static int rewrite_url(request_rec *r, proxy_worker *worker,
243 const char *scheme = strstr(*url, "://");
244 const char *path = NULL;
247 path = ap_strchr_c(scheme + 3, '/');
249 /* we break the URL into host, port, uri */
251 return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
252 "missing worker. URI cannot be parsed: ", *url,
256 *url = apr_pstrcat(r->pool, worker->name, path, NULL);
261 static int proxy_balancer_pre_request(proxy_worker **worker,
262 proxy_balancer **balancer,
264 proxy_server_conf *conf, char **url)
267 proxy_runtime_worker *runtime;
272 /* Spet 1: check if the url is for us */
273 if (!(*balancer = ap_proxy_get_balancer(r->pool, conf, *url)))
276 /* Step 2: find the session route */
278 runtime = find_session_route(*balancer, r, &route, url);
280 if (route && (*balancer)->sticky_force) {
281 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
282 "proxy: BALANCER: (%s). All workers are in error state for route (%s)",
283 (*balancer)->name, route);
284 return HTTP_SERVICE_UNAVAILABLE;
289 proxy_runtime_worker *workers;
290 /* We have a sticky load balancer */
291 *worker = runtime->w;
292 /* Update the workers status
293 * so that even session routes get
296 workers = (proxy_runtime_worker *)(*balancer)->workers->elts;
297 for (i = 0; i < (*balancer)->workers->nelts; i++) {
298 /* For now assume that all workers are OK */
299 workers->s->lbstatus += workers->s->lbfactor;
300 if (workers->s->lbstatus >= 100.0)
301 workers->s->lbstatus = workers->s->lbfactor;
305 /* Lock the LoadBalancer
306 * XXX: perhaps we need the process lock here
308 if ((rv = PROXY_BALANCER_LOCK(*balancer)) != APR_SUCCESS) {
309 ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
310 "proxy: BALANCER: lock");
314 runtime = find_best_worker(*balancer, r);
316 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
317 "proxy: BALANCER: (%s). All workers are in error state",
320 PROXY_BALANCER_UNLOCK(*balancer);
321 return HTTP_SERVICE_UNAVAILABLE;
323 *worker = runtime->w;
325 /* Decrease the free channels number */
326 if ((*worker)->cp->nfree)
327 --(*worker)->cp->nfree;
329 PROXY_BALANCER_UNLOCK(*balancer);
331 access_status = rewrite_url(r, *worker, url);
332 /* Add the session route to request notes if present */
334 apr_table_setn(r->notes, "session-sticky", (*balancer)->sticky);
335 apr_table_setn(r->notes, "session-route", route);
337 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
338 "proxy_balancer_pre_request rewriting to %s", *url);
339 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
340 "proxy_balancer_pre_request worker (%s) free %d",
342 (*worker)->cp->nfree);
344 return access_status;
347 static int proxy_balancer_post_request(proxy_worker *worker,
348 proxy_balancer *balancer,
350 proxy_server_conf *conf)
354 access_status = DECLINED;
357 if ((rv = PROXY_BALANCER_LOCK(balancer)) != APR_SUCCESS) {
358 ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
359 "proxy: BALANCER: lock");
360 return HTTP_INTERNAL_SERVER_ERROR;
362 /* increase the free channels number */
363 if (worker->cp->nfree)
365 /* TODO: calculate the bytes transfered */
367 /* TODO: update the scoreboard status */
369 PROXY_BALANCER_UNLOCK(balancer);
372 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
373 "proxy_balancer_post_request for (%s)", balancer->name);
375 return access_status;
378 static void ap_proxy_balancer_register_hook(apr_pool_t *p)
380 proxy_hook_pre_request(proxy_balancer_pre_request, NULL, NULL, APR_HOOK_FIRST);
381 proxy_hook_post_request(proxy_balancer_post_request, NULL, NULL, APR_HOOK_FIRST);
384 module AP_MODULE_DECLARE_DATA proxy_balancer_module = {
385 STANDARD20_MODULE_STUFF,
386 NULL, /* create per-directory config structure */
387 NULL, /* merge per-directory config structures */
388 NULL, /* create per-server config structure */
389 NULL, /* merge per-server config structures */
390 NULL, /* command apr_table_t */
391 ap_proxy_balancer_register_hook /* register hooks */