| /* Copyright 1999-2004 The Apache Software Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* Load balancer module for Apache proxy */ |
| |
| #define CORE_PRIVATE |
| |
| #include "mod_proxy.h" |
| #include "ap_mpm.h" |
| #include "apr_version.h" |
| |
| module AP_MODULE_DECLARE_DATA proxy_balancer_module; |
| |
| #if APR_HAS_THREADS |
| #define PROXY_BALANCER_LOCK(b) apr_thread_mutex_lock((b)->mutex) |
| #define PROXY_BALANCER_UNLOCK(b) apr_thread_mutex_unlock((b)->mutex) |
| #else |
| #define PROXY_BALANCER_LOCK(b) APR_SUCCESS |
| #define PROXY_BALANCER_UNLOCK(b) APR_SUCCESS |
| #endif |
| |
| |
| /* Retrieve the parameter with the given name */ |
| static char *get_path_param(apr_pool_t *pool, char *url, |
| const char *name) |
| { |
| char *path = NULL; |
| |
| for (path = strstr(url, name); path; path = strstr(path + 1, name)) { |
| path += (strlen(name) + 1); |
| if (*path == '=') { |
| /* |
| * Session path was found, get it's value |
| */ |
| ++path; |
| if (strlen(path)) { |
| char *q; |
| path = apr_pstrdup(pool, path); |
| if ((q = strchr(path, '?'))) |
| *q = '\0'; |
| return path; |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| static char *get_cookie_param(request_rec *r, const char *name) |
| { |
| const char *cookies; |
| const char *start_cookie; |
| |
| if ((cookies = apr_table_get(r->headers_in, "Cookie"))) { |
| for (start_cookie = strstr(cookies, name); start_cookie; |
| start_cookie = strstr(start_cookie + 1, name)) { |
| if (start_cookie == cookies || |
| start_cookie[-1] == ';' || |
| start_cookie[-1] == ',' || |
| isspace(start_cookie[-1])) { |
| |
| start_cookie += strlen(name); |
| while(*start_cookie && isspace(*start_cookie)) |
| ++start_cookie; |
| if (*start_cookie == '=' && start_cookie[1]) { |
| /* |
| * Session cookie was found, get it's value |
| */ |
| char *end_cookie, *cookie; |
| ++start_cookie; |
| cookie = apr_pstrdup(r->pool, start_cookie); |
| if ((end_cookie = strchr(cookie, ';')) != NULL) |
| *end_cookie = '\0'; |
| if((end_cookie = strchr(cookie, ',')) != NULL) |
| *end_cookie = '\0'; |
| return cookie; |
| } |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| static proxy_runtime_worker *find_route_worker(proxy_balancer *balancer, |
| const char *route) |
| { |
| int i; |
| proxy_runtime_worker *worker = (proxy_runtime_worker *)balancer->workers->elts; |
| for (i = 0; i < balancer->workers->nelts; i++) { |
| if (worker->w->route && strcmp(worker->w->route, route) == 0) { |
| return worker; |
| } |
| worker++; |
| } |
| return NULL; |
| } |
| |
| static proxy_runtime_worker *find_session_route(proxy_balancer *balancer, |
| request_rec *r, |
| char **route, |
| char **url) |
| { |
| if (!balancer->sticky) |
| return NULL; |
| /* Try to find the sticky route inside url */ |
| *route = get_path_param(r->pool, *url, balancer->sticky); |
| if (!*route) |
| *route = get_cookie_param(r, balancer->sticky); |
| if (*route) { |
| proxy_runtime_worker *worker = find_route_worker(balancer, *route); |
| /* TODO: make worker status codes */ |
| /* See if we have a redirection route */ |
| if (worker && !PROXY_WORKER_IS_USABLE(worker->w)) { |
| if (worker->w->redirect) |
| worker = find_route_worker(balancer, worker->w->redirect); |
| /* Check if the redirect worker is usable */ |
| if (worker && !PROXY_WORKER_IS_USABLE(worker->w)) |
| worker = NULL; |
| } |
| else |
| worker = NULL; |
| return worker; |
| } |
| else |
| return NULL; |
| } |
| |
| static proxy_runtime_worker *find_best_worker(proxy_balancer *balancer, |
| request_rec *r) |
| { |
| int i; |
| double total_factor = 0.0; |
| proxy_runtime_worker *worker = (proxy_runtime_worker *)balancer->workers->elts; |
| proxy_runtime_worker *candidate = NULL; |
| |
| /* First try to see if we have available candidate */ |
| for (i = 0; i < balancer->workers->nelts; i++) { |
| /* See if the retry timeout is ellapsed |
| * for the workers flagged as IN_ERROR |
| */ |
| if (!PROXY_WORKER_IS_USABLE(worker->w)) |
| ap_proxy_retry_worker("BALANCER", worker->w, r->server); |
| /* If the worker is not in error state |
| * or not disabled. |
| */ |
| if (PROXY_WORKER_IS_USABLE(worker->w)) { |
| if (!candidate) |
| candidate = worker; |
| else { |
| /* See if the worker has a larger number of free channels */ |
| if (worker->w->cp->nfree > candidate->w->cp->nfree) |
| candidate = worker; |
| } |
| /* Total factor should allways be 100. |
| * This is for cases when worker is in error state. |
| * It will force the even request distribution |
| */ |
| total_factor += worker->s->lbfactor; |
| } |
| worker++; |
| } |
| if (!candidate) { |
| /* All the workers are in error state or disabled. |
| * If the balancer has a timeout wait. |
| */ |
| #if APR_HAS_THREADS |
| if (balancer->timeout) { |
| /* XXX: This can perhaps be build using some |
| * smarter mechanism, like tread_cond. |
| * But since the statuses can came from |
| * different childs, use the provided algo. |
| */ |
| apr_interval_time_t timeout = balancer->timeout; |
| apr_interval_time_t step, tval = 0; |
| balancer->timeout = 0; |
| step = timeout / 100; |
| while (tval < timeout) { |
| apr_sleep(step); |
| /* Try again */ |
| if ((candidate = find_best_worker(balancer, r))) |
| break; |
| tval += step; |
| } |
| /* restore the timeout */ |
| balancer->timeout = timeout; |
| } |
| #endif |
| } |
| else { |
| /* We have at least one candidate that is not in |
| * error state or disabled. |
| * Now calculate the appropriate one |
| */ |
| worker = (proxy_runtime_worker *)balancer->workers->elts; |
| for (i = 0; i < balancer->workers->nelts; i++) { |
| /* If the worker is not error state |
| * or not in disabled mode |
| */ |
| if (PROXY_WORKER_IS_USABLE(worker->w)) { |
| /* 1. Find the worker with higher lbstatus. |
| * Lbstatus is of higher importance then |
| * the number of empty slots. |
| */ |
| if (worker->s->lbstatus > candidate->s->lbstatus) { |
| candidate = worker; |
| } |
| } |
| worker++; |
| } |
| worker = (proxy_runtime_worker *)balancer->workers->elts; |
| for (i = 0; i < balancer->workers->nelts; i++) { |
| /* If the worker is not error state |
| * or not in disabled mode |
| */ |
| if (PROXY_WORKER_IS_USABLE(worker->w)) { |
| /* XXX: The lbfactor can be update using bytes transfered |
| * Right now, use the round-robin scheme |
| */ |
| worker->s->lbstatus += worker->s->lbfactor; |
| if (worker->s->lbstatus >= total_factor) |
| worker->s->lbstatus = worker->s->lbfactor; |
| } |
| worker++; |
| } |
| } |
| return candidate; |
| } |
| |
| static int rewrite_url(request_rec *r, proxy_worker *worker, |
| char **url) |
| { |
| const char *scheme = strstr(*url, "://"); |
| const char *path = NULL; |
| |
| if (scheme) |
| path = strchr(scheme + 3, '/'); |
| |
| /* we break the URL into host, port, uri */ |
| if (!worker) { |
| return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool, |
| "missing worker. URI cannot be parsed: ", *url, |
| NULL)); |
| } |
| |
| *url = apr_pstrcat(r->pool, worker->name, path, NULL); |
| |
| return OK; |
| } |
| |
| static int proxy_balancer_pre_request(proxy_worker **worker, |
| proxy_balancer **balancer, |
| request_rec *r, |
| proxy_server_conf *conf, char **url) |
| { |
| int access_status; |
| proxy_runtime_worker *runtime; |
| char *route; |
| apr_status_t rv; |
| |
| *worker = NULL; |
| /* Spet 1: check if the url is for us */ |
| if (!(*balancer = ap_proxy_get_balancer(r->pool, conf, *url))) |
| return DECLINED; |
| |
| /* Step 2: find the session route */ |
| |
| runtime = find_session_route(*balancer, r, &route, url); |
| if (!runtime) { |
| if (route && (*balancer)->sticky_force) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, |
| "proxy: BALANCER: (%s). All workers are in error state for route (%s)", |
| (*balancer)->name, route); |
| return HTTP_SERVICE_UNAVAILABLE; |
| } |
| } |
| else { |
| int i; |
| proxy_runtime_worker *workers; |
| /* We have a sticky load balancer */ |
| *worker = runtime->w; |
| /* Update the workers status |
| * so that even session routes get |
| * into account. |
| */ |
| workers = (proxy_runtime_worker *)(*balancer)->workers->elts; |
| for (i = 0; i < (*balancer)->workers->nelts; i++) { |
| /* For now assume that all workers are OK */ |
| workers->s->lbstatus += workers->s->lbfactor; |
| if (workers->s->lbstatus >= 100.0) |
| workers->s->lbstatus = workers->s->lbfactor; |
| workers++; |
| } |
| } |
| /* Lock the LoadBalancer |
| * XXX: perhaps we need the process lock here |
| */ |
| if ((rv = PROXY_BALANCER_LOCK(*balancer)) != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, |
| "proxy: BALANCER: lock"); |
| return DECLINED; |
| } |
| if (!*worker) { |
| runtime = find_best_worker(*balancer, r); |
| if (!runtime) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, |
| "proxy: BALANCER: (%s). All workers are in error state", |
| (*balancer)->name); |
| |
| PROXY_BALANCER_UNLOCK(*balancer); |
| return HTTP_SERVICE_UNAVAILABLE; |
| } |
| *worker = runtime->w; |
| } |
| /* Decrease the free channels number */ |
| if ((*worker)->cp->nfree) |
| --(*worker)->cp->nfree; |
| |
| PROXY_BALANCER_UNLOCK(*balancer); |
| |
| access_status = rewrite_url(r, *worker, url); |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, |
| "proxy_balancer_pre_request rewriting to %s", *url); |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, |
| "proxy_balancer_pre_request worker (%s) free %d", |
| (*worker)->name, |
| (*worker)->cp->nfree); |
| |
| return access_status; |
| } |
| |
| static int proxy_balancer_post_request(proxy_worker *worker, |
| proxy_balancer *balancer, |
| request_rec *r, |
| proxy_server_conf *conf) |
| { |
| int access_status; |
| if (!balancer) |
| access_status = DECLINED; |
| else { |
| apr_status_t rv; |
| if ((rv = PROXY_BALANCER_LOCK(balancer)) != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, |
| "proxy: BALANCER: lock"); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| /* increase the free channels number */ |
| if (worker->cp->nfree) |
| worker->cp->nfree++; |
| /* TODO: calculate the bytes transfered */ |
| |
| /* TODO: update the scoreboard status */ |
| |
| PROXY_BALANCER_UNLOCK(balancer); |
| access_status = OK; |
| } |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, |
| "proxy_balancer_post_request for (%s)", balancer->name); |
| |
| return access_status; |
| } |
| |
| static void ap_proxy_balancer_register_hook(apr_pool_t *p) |
| { |
| proxy_hook_pre_request(proxy_balancer_pre_request, NULL, NULL, APR_HOOK_FIRST); |
| proxy_hook_post_request(proxy_balancer_post_request, NULL, NULL, APR_HOOK_FIRST); |
| } |
| |
| module AP_MODULE_DECLARE_DATA proxy_balancer_module = { |
| STANDARD20_MODULE_STUFF, |
| NULL, /* create per-directory config structure */ |
| NULL, /* merge per-directory config structures */ |
| NULL, /* create per-server config structure */ |
| NULL, /* merge per-server config structures */ |
| NULL, /* command apr_table_t */ |
| ap_proxy_balancer_register_hook /* register hooks */ |
| }; |