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 "scoreboard.h"
23 #include "apr_version.h"
25 module AP_MODULE_DECLARE_DATA proxy_balancer_module;
28 #define PROXY_BALANCER_LOCK(b) apr_thread_mutex_lock((b)->mutex)
29 #define PROXY_BALANCER_UNLOCK(b) apr_thread_mutex_unlock((b)->mutex)
31 #define PROXY_BALANCER_LOCK(b) APR_SUCCESS
32 #define PROXY_BALANCER_UNLOCK(b) APR_SUCCESS
35 static int init_runtime_score(apr_pool_t *pool, proxy_balancer *balancer)
38 double median, ffactor = 0.0;
39 proxy_runtime_worker *workers;
40 #if PROXY_HAS_SCOREBOARD
47 workers = (proxy_runtime_worker *)balancer->workers->elts;
49 for (i = 0; i < balancer->workers->nelts; i++) {
50 #if PROXY_HAS_SCOREBOARD
51 ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &mpm_daemons);
52 /* Check if we are prefork or single child */
53 if (workers[i].w->hmax && mpm_daemons > 1) {
54 score = ap_get_scoreboard_lb(getpid(), workers[i].id);
59 /* Use the plain memory */
60 score = apr_pcalloc(pool, sizeof(proxy_runtime_stat));
62 workers[i].s = (proxy_runtime_stat *)score;
65 /* Recalculate lbfactors */
66 for (i = 0; i < balancer->workers->nelts; i++) {
67 /* Set to the original configuration */
68 workers[i].s->lbfactor = workers[i].w->lbfactor;
69 ffactor += workers[i].s->lbfactor;
71 if (ffactor < 100.0) {
73 for (i = 0; i < balancer->workers->nelts; i++) {
74 if (workers[i].s->lbfactor == 0.0)
78 median = (100.0 - ffactor) / z;
79 for (i = 0; i < balancer->workers->nelts; i++) {
80 if (workers[i].s->lbfactor == 0.0)
81 workers[i].s->lbfactor = median;
85 median = (100.0 - ffactor) / balancer->workers->nelts;
86 for (i = 0; i < balancer->workers->nelts; i++)
87 workers[i].s->lbfactor += median;
90 else if (ffactor > 100.0) {
91 median = (ffactor - 100.0) / balancer->workers->nelts;
92 for (i = 0; i < balancer->workers->nelts; i++) {
93 if (workers[i].s->lbfactor > median)
94 workers[i].s->lbfactor -= median;
97 for (i = 0; i < balancer->workers->nelts; i++) {
98 /* Update the status entires */
99 workers[i].s->lbstatus = workers[i].s->lbfactor;
101 balancer->status = 1;
105 /* Retrieve the parameter with the given name */
106 static char *get_path_param(apr_pool_t *pool, char *url,
111 for (path = strstr(url, name); path; path = strstr(path + 1, name)) {
112 path += (strlen(name) + 1);
115 * Session path was found, get it's value
120 path = apr_pstrdup(pool, path);
121 if ((q = strchr(path, '?')))
130 static char *get_cookie_param(request_rec *r, const char *name)
133 const char *start_cookie;
135 if ((cookies = apr_table_get(r->headers_in, "Cookie"))) {
136 for (start_cookie = ap_strstr_c(cookies, name); start_cookie;
137 start_cookie = ap_strstr_c(start_cookie + 1, name)) {
138 if (start_cookie == cookies ||
139 start_cookie[-1] == ';' ||
140 start_cookie[-1] == ',' ||
141 isspace(start_cookie[-1])) {
143 start_cookie += strlen(name);
144 while(*start_cookie && isspace(*start_cookie))
146 if (*start_cookie == '=' && start_cookie[1]) {
148 * Session cookie was found, get it's value
150 char *end_cookie, *cookie;
152 cookie = apr_pstrdup(r->pool, start_cookie);
153 if ((end_cookie = strchr(cookie, ';')) != NULL)
155 if((end_cookie = strchr(cookie, ',')) != NULL)
165 static proxy_runtime_worker *find_route_worker(proxy_balancer *balancer,
169 proxy_runtime_worker *worker = (proxy_runtime_worker *)balancer->workers->elts;
170 for (i = 0; i < balancer->workers->nelts; i++) {
171 if (worker->w->route && strcmp(worker->w->route, route) == 0) {
179 static proxy_runtime_worker *find_session_route(proxy_balancer *balancer,
184 if (!balancer->sticky)
186 /* Try to find the sticky route inside url */
187 *route = get_path_param(r->pool, *url, balancer->sticky);
189 *route = get_cookie_param(r, balancer->sticky);
191 proxy_runtime_worker *worker = find_route_worker(balancer, *route);
192 /* TODO: make worker status codes */
193 /* See if we have a redirection route */
194 if (worker && !PROXY_WORKER_IS_USABLE(worker->w)) {
195 if (worker->w->redirect)
196 worker = find_route_worker(balancer, worker->w->redirect);
197 /* Check if the redirect worker is usable */
198 if (worker && !PROXY_WORKER_IS_USABLE(worker->w))
209 static proxy_runtime_worker *find_best_worker(proxy_balancer *balancer,
213 double total_factor = 0.0;
214 proxy_runtime_worker *worker = (proxy_runtime_worker *)balancer->workers->elts;
215 proxy_runtime_worker *candidate = NULL;
217 /* First try to see if we have available candidate */
218 for (i = 0; i < balancer->workers->nelts; i++) {
219 /* See if the retry timeout is ellapsed
220 * for the workers flagged as IN_ERROR
222 if (!PROXY_WORKER_IS_USABLE(worker->w))
223 ap_proxy_retry_worker("BALANCER", worker->w, r->server);
224 /* If the worker is not in error state
227 if (PROXY_WORKER_IS_USABLE(worker->w)) {
231 /* See if the worker has a larger number of free channels */
232 if (worker->w->cp->nfree > candidate->w->cp->nfree)
235 /* Total factor should allways be 100.
236 * This is for cases when worker is in error state.
237 * It will force the even request distribution
239 total_factor += worker->s->lbfactor;
244 /* All the workers are in error state or disabled.
245 * If the balancer has a timeout wait.
248 if (balancer->timeout) {
249 /* XXX: This can perhaps be build using some
250 * smarter mechanism, like tread_cond.
251 * But since the statuses can came from
252 * different childs, use the provided algo.
254 apr_interval_time_t timeout = balancer->timeout;
255 apr_interval_time_t step, tval = 0;
256 balancer->timeout = 0;
257 step = timeout / 100;
258 while (tval < timeout) {
261 if ((candidate = find_best_worker(balancer, r)))
265 /* restore the timeout */
266 balancer->timeout = timeout;
271 /* We have at least one candidate that is not in
272 * error state or disabled.
273 * Now calculate the appropriate one
275 worker = (proxy_runtime_worker *)balancer->workers->elts;
276 for (i = 0; i < balancer->workers->nelts; i++) {
277 /* If the worker is not error state
278 * or not in disabled mode
280 if (PROXY_WORKER_IS_USABLE(worker->w)) {
281 /* 1. Find the worker with higher lbstatus.
282 * Lbstatus is of higher importance then
283 * the number of empty slots.
285 if (worker->s->lbstatus > candidate->s->lbstatus) {
291 worker = (proxy_runtime_worker *)balancer->workers->elts;
292 for (i = 0; i < balancer->workers->nelts; i++) {
293 /* If the worker is not error state
294 * or not in disabled mode
296 if (PROXY_WORKER_IS_USABLE(worker->w)) {
297 /* XXX: The lbfactor can be update using bytes transfered
298 * Right now, use the round-robin scheme
300 worker->s->lbstatus += worker->s->lbfactor;
301 if (worker->s->lbstatus >= total_factor)
302 worker->s->lbstatus = worker->s->lbfactor;
310 static int rewrite_url(request_rec *r, proxy_worker *worker,
313 const char *scheme = strstr(*url, "://");
314 const char *path = NULL;
317 path = ap_strchr_c(scheme + 3, '/');
319 /* we break the URL into host, port, uri */
321 return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
322 "missing worker. URI cannot be parsed: ", *url,
326 *url = apr_pstrcat(r->pool, worker->name, path, NULL);
331 static int proxy_balancer_pre_request(proxy_worker **worker,
332 proxy_balancer **balancer,
334 proxy_server_conf *conf, char **url)
337 proxy_runtime_worker *runtime;
342 /* Step 1: check if the url is for us */
343 if (!(*balancer = ap_proxy_get_balancer(r->pool, conf, *url)))
346 /* Initialize shared scoreboard data */
347 if (!((*balancer)->status))
348 init_runtime_score(conf->pool, *balancer);
350 /* Step 2: find the session route */
352 runtime = find_session_route(*balancer, r, &route, url);
354 if (route && (*balancer)->sticky_force) {
355 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
356 "proxy: BALANCER: (%s). All workers are in error state for route (%s)",
357 (*balancer)->name, route);
358 return HTTP_SERVICE_UNAVAILABLE;
363 proxy_runtime_worker *workers;
364 /* We have a sticky load balancer */
365 runtime->s->elected++;
366 *worker = runtime->w;
367 /* Update the workers status
368 * so that even session routes get
371 workers = (proxy_runtime_worker *)(*balancer)->workers->elts;
372 for (i = 0; i < (*balancer)->workers->nelts; i++) {
373 /* For now assume that all workers are OK */
374 workers->s->lbstatus += workers->s->lbfactor;
375 if (workers->s->lbstatus >= 100.0)
376 workers->s->lbstatus = workers->s->lbfactor;
380 /* Lock the LoadBalancer
381 * XXX: perhaps we need the process lock here
383 if ((rv = PROXY_BALANCER_LOCK(*balancer)) != APR_SUCCESS) {
384 ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
385 "proxy: BALANCER: lock");
389 runtime = find_best_worker(*balancer, r);
391 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
392 "proxy: BALANCER: (%s). All workers are in error state",
395 PROXY_BALANCER_UNLOCK(*balancer);
396 return HTTP_SERVICE_UNAVAILABLE;
398 runtime->s->elected++;
399 *worker = runtime->w;
401 /* Decrease the free channels number */
402 /* XXX: This should be the function of apr_reslist */
403 --(*worker)->cp->nfree;
405 PROXY_BALANCER_UNLOCK(*balancer);
407 access_status = rewrite_url(r, *worker, url);
408 /* Add the session route to request notes if present */
410 apr_table_setn(r->notes, "session-sticky", (*balancer)->sticky);
411 apr_table_setn(r->notes, "session-route", route);
413 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
414 "proxy_balancer_pre_request rewriting to %s", *url);
415 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
416 "proxy_balancer_pre_request worker (%s) free %d",
418 (*worker)->cp->nfree);
420 return access_status;
423 static int proxy_balancer_post_request(proxy_worker *worker,
424 proxy_balancer *balancer,
426 proxy_server_conf *conf)
430 if ((rv = PROXY_BALANCER_LOCK(balancer)) != APR_SUCCESS) {
431 ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
432 "proxy: BALANCER: lock");
433 return HTTP_INTERNAL_SERVER_ERROR;
435 /* increase the free channels number */
437 /* TODO: calculate the bytes transfered */
439 /* TODO: update the scoreboard status */
441 PROXY_BALANCER_UNLOCK(balancer);
442 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
443 "proxy_balancer_post_request for (%s)", balancer->name);
448 static void recalc_factors(proxy_balancer *balancer,
449 proxy_runtime_worker *fixed)
452 double median, ffactor = 0.0;
453 proxy_runtime_worker *workers;
456 /* Recalculate lbfactors */
457 workers = (proxy_runtime_worker *)balancer->workers->elts;
458 /* Special case if there is only one worker it's
459 * load factor will always be 100
461 if (balancer->workers->nelts == 1) {
462 workers->s->lbstatus = workers->s->lbfactor = 100.0;
465 for (i = 0; i < balancer->workers->nelts; i++) {
466 if (workers[i].s->lbfactor > 100.0)
467 workers[i].s->lbfactor = 100.0;
468 ffactor += workers[i].s->lbfactor;
470 if (ffactor < 100.0) {
471 median = (100.0 - ffactor) / (balancer->workers->nelts - 1);
472 for (i = 0; i < balancer->workers->nelts; i++) {
473 if (&(workers[i]) != fixed)
474 workers[i].s->lbfactor += median;
477 else if (fixed->s->lbfactor < 100.0) {
478 median = (ffactor - 100.0) / (balancer->workers->nelts - 1);
479 for (i = 0; i < balancer->workers->nelts; i++) {
480 if (workers[i].s->lbfactor > median &&
481 &(workers[i]) != fixed)
482 workers[i].s->lbfactor -= median;
486 median = (ffactor - 100.0) / balancer->workers->nelts;
487 for (i = 0; i < balancer->workers->nelts; i++) {
488 workers[i].s->lbfactor -= median;
491 for (i = 0; i < balancer->workers->nelts; i++) {
492 /* Update the status entires */
493 workers[i].s->lbstatus = workers[i].s->lbfactor;
498 static int balancer_handler(request_rec *r)
500 void *sconf = r->server->module_config;
501 proxy_server_conf *conf = (proxy_server_conf *)
502 ap_get_module_config(sconf, &proxy_module);
503 proxy_balancer *balancer, *bsel = NULL;
504 proxy_runtime_worker *worker, *wsel = NULL;
505 apr_table_t *params = apr_table_make(r->pool, 10);
510 /* is this for us? */
511 if (strcmp(r->handler, "balancer-manager"))
513 r->allowed = (AP_METHOD_BIT << M_GET);
514 if (r->method_number != M_GET)
518 char *args = apr_pstrdup(r->pool, r->args);
520 while (args && *args) {
521 if ((val = ap_strchr(args, '='))) {
523 if ((tok = ap_strchr(val, '&')))
525 if ((access_status = ap_unescape_url(val)) != OK)
526 return access_status;
527 apr_table_setn(params, args, val);
531 return HTTP_BAD_REQUEST;
534 if ((name = apr_table_get(params, "b")))
535 bsel = ap_proxy_get_balancer(r->pool, conf,
536 apr_pstrcat(r->pool, "balancer://", name, NULL));
537 if ((name = apr_table_get(params, "w"))) {
538 const char *sc = apr_table_get(params, "s");
540 proxy_worker *ws = NULL;
542 asname = apr_pstrcat(r->pool, sc, "://", name, NULL);
543 ws = ap_proxy_get_worker(r->pool, conf, asname);
546 worker = (proxy_runtime_worker *)bsel->workers->elts;
547 for (n = 0; n < bsel->workers->nelts; n++) {
548 if (strcasecmp(worker->w->name, ws->name) == 0) {
556 /* First set the params */
560 init_runtime_score(conf->pool, bsel);
562 if ((val = apr_table_get(params, "ss"))) {
564 bsel->sticky = apr_pstrdup(conf->pool, val);
568 if ((val = apr_table_get(params, "tm"))) {
569 int ival = atoi(val);
571 bsel->timeout = apr_time_from_sec(ival);
576 if ((val = apr_table_get(params, "lf"))) {
578 double dval = strtod(val, &ep);
580 wsel->s->lbfactor = dval;
582 recalc_factors(bsel, wsel);
585 if ((val = apr_table_get(params, "wr"))) {
587 wsel->w->route = apr_pstrdup(conf->pool, val);
589 wsel->w->route = NULL;
591 if ((val = apr_table_get(params, "rr"))) {
593 wsel->w->redirect = apr_pstrdup(conf->pool, val);
595 wsel->w->redirect = NULL;
597 if ((val = apr_table_get(params, "dw")))
598 wsel->w->status |= PROXY_WORKER_DISABLED;
600 wsel->w->status &= ~PROXY_WORKER_DISABLED;
603 if (apr_table_get(params, "xml")) {
604 ap_set_content_type(r, "text/xml");
605 ap_rputs("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n", r);
606 ap_rputs("<httpd:manager xmlns:httpd=\"http://httpd.apache.org\">\n", r);
607 ap_rputs(" <httpd:balancers>\n", r);
608 balancer = (proxy_balancer *)conf->balancers->elts;
609 for (i = 0; i < conf->balancers->nelts; i++) {
610 ap_rputs(" <httpd:balancer>\n", r);
611 ap_rvputs(r, " <httpd:name>", balancer->name, "</httpd:name>\n", NULL);
612 ap_rputs(" <httpd:workers>\n", r);
613 worker = (proxy_runtime_worker *)balancer->workers->elts;
614 for (n = 0; n < balancer->workers->nelts; n++) {
615 ap_rputs(" <httpd:worker>\n", r);
616 ap_rvputs(r, " <httpd:scheme>", worker->w->scheme,
617 "</httpd:scheme>\n", NULL);
618 ap_rvputs(r, " <httpd:hostname>", worker->w->hostname,
619 "</httpd:hostname>\n", NULL);
620 ap_rprintf(r, " <httpd:loadfactor>%.2f</httpd:loadfactor>\n",
621 worker->s->lbfactor);
622 ap_rputs(" </httpd:worker>\n", r);
625 ap_rputs(" </httpd:workers>\n", r);
626 ap_rputs(" </httpd:balancer>\n", r);
629 ap_rputs(" </httpd:balancers>\n", r);
630 ap_rputs("</httpd:manager>", r);
633 ap_set_content_type(r, "text/html");
634 ap_rputs(DOCTYPE_HTML_3_2
635 "<html><head><title>Balancer Manager</title></head>\n", r);
636 ap_rputs("<body><h1>Load Balancer Manager for ", r);
637 ap_rvputs(r, ap_get_server_name(r), "</h1>\n\n", NULL);
638 ap_rvputs(r, "<dl><dt>Server Version: ",
639 ap_get_server_version(), "</dt>\n", NULL);
640 ap_rvputs(r, "<dt>Server Built: ",
641 ap_get_server_built(), "\n</dt></dl>\n", NULL);
642 balancer = (proxy_balancer *)conf->balancers->elts;
643 for (i = 0; i < conf->balancers->nelts; i++) {
644 if (!balancer->status)
645 init_runtime_score(conf->pool, balancer);
647 ap_rputs("<hr />\n<h3>LoadBalancer Status for ", r);
648 ap_rvputs(r, "<a href=\"", r->uri, "?b=",
649 balancer->name + sizeof("balancer://") - 1,
651 ap_rvputs(r, balancer->name, "</a></h3>\n\n", NULL);
652 ap_rputs("\n\n<table border=\"0\"><tr>"
653 "<th>StickySesion</th><th>Timeout</th>"
655 ap_rvputs(r, "<td>", balancer->sticky, NULL);
656 ap_rprintf(r, "</td><td>%" APR_TIME_T_FMT "</td>\n",
657 apr_time_sec(balancer->timeout));
658 ap_rputs("</table>\n", r);
659 ap_rputs("\n\n<table border=\"0\"><tr>"
660 "<th>Scheme</th><th>Host</th>"
661 "<th>Route</th><th>RouteRedir</th>"
662 "<th>Factor</th><th>Status</th>"
665 worker = (proxy_runtime_worker *)balancer->workers->elts;
666 for (n = 0; n < balancer->workers->nelts; n++) {
668 ap_rvputs(r, "<tr>\n<td>", worker->w->scheme, "</td><td>", NULL);
669 ap_rvputs(r, "<a href=\"", r->uri, "?b=",
670 balancer->name + sizeof("balancer://") - 1,
671 "&s=", worker->w->scheme, "&w=", worker->w->hostname,
673 ap_rvputs(r, worker->w->hostname, "</a></td>", NULL);
674 ap_rvputs(r, "<td>", worker->w->route, NULL);
675 ap_rvputs(r, "</td><td>", worker->w->redirect, NULL);
676 ap_rprintf(r, "</td><td>%.2f</td><td>", worker->s->lbfactor);
677 if (worker->w->status & PROXY_WORKER_DISABLED)
679 else if (worker->w->status & PROXY_WORKER_IN_ERROR)
681 else if (worker->w->status & PROXY_WORKER_INITIALIZED)
685 ap_rputs("</td></tr>\n", r);
689 ap_rputs("</table>\n", r);
692 ap_rputs("<hr />\n", r);
694 ap_rputs("<h3>Edit worker settings for ", r);
695 ap_rvputs(r, wsel->w->name, "</h3>\n", NULL);
696 ap_rvputs(r, "<form method=\"GET\" action=\"", NULL);
697 ap_rvputs(r, r->uri, "\">\n<dl>", NULL);
698 ap_rputs("<table><tr><td>Load factor:</td><td><input name=\"lf\" type=text ", r);
699 ap_rprintf(r, "value=\"%.2f\"></td><tr>\n", wsel->s->lbfactor);
700 ap_rputs("<tr><td>Route:</td><td><input name=\"wr\" type=text ", r);
701 ap_rvputs(r, "value=\"", wsel->w->route, NULL);
702 ap_rputs("\"></td><tr>\n", r);
703 ap_rputs("<tr><td>Route Redirect:</td><td><input name=\"rr\" type=text ", r);
704 ap_rvputs(r, "value=\"", wsel->w->redirect, NULL);
705 ap_rputs("\"></td><tr>\n", r);
706 ap_rputs("<tr><td>Disabled:</td><td><input name=\"dw\" type=checkbox", r);
707 if (wsel->w->status & PROXY_WORKER_DISABLED)
708 ap_rputs(" checked", r);
709 ap_rputs("></td><tr>\n", r);
710 ap_rputs("<tr><td colspan=2><input type=submit value=\"Submit\"></td></tr>\n", r);
711 ap_rvputs(r, "</table>\n<input type=hidden name=\"s\" ", NULL);
712 ap_rvputs(r, "value=\"", wsel->w->scheme, "\">\n", NULL);
713 ap_rvputs(r, "<input type=hidden name=\"w\" ", NULL);
714 ap_rvputs(r, "value=\"", wsel->w->hostname, "\">\n", NULL);
715 ap_rvputs(r, "<input type=hidden name=\"b\" ", NULL);
716 ap_rvputs(r, "value=\"", bsel->name + sizeof("balancer://") - 1,
717 "\">\n</form>\n", NULL);
718 ap_rputs("<hr />\n", r);
721 ap_rputs("<h3>Edit balancer settings for ", r);
722 ap_rvputs(r, bsel->name, "</h3>\n", NULL);
723 ap_rvputs(r, "<form method=\"GET\" action=\"", NULL);
724 ap_rvputs(r, r->uri, "\">\n<dl>", NULL);
725 ap_rputs("<table><tr><td>StickySession Identifier:</td><td><input name=\"ss\" type=text ", r);
727 ap_rvputs(r, "value=\"", bsel->sticky, "\"", NULL);
728 ap_rputs("></td><tr>\n<tr><td>Timeout:</td><td><input name=\"tm\" type=text ", r);
729 ap_rprintf(r, "value=\"%" APR_TIME_T_FMT "\"></td></tr>\n",
730 apr_time_sec(bsel->timeout));
731 ap_rputs("<tr><td colspan=2><input type=submit value=\"Submit\"></td></tr>\n", r);
732 ap_rvputs(r, "</table>\n<input type=hidden name=\"b\" ", NULL);
733 ap_rvputs(r, "value=\"", bsel->name + sizeof("balancer://") - 1,
734 "\">\n</form>\n", NULL);
735 ap_rputs("<hr />\n", r);
737 ap_rputs(ap_psignature("",r), r);
738 ap_rputs("</body></html>\n", r);
743 static void ap_proxy_balancer_register_hook(apr_pool_t *p)
745 /* manager handler */
746 ap_hook_handler(balancer_handler, NULL, NULL, APR_HOOK_FIRST);
747 proxy_hook_pre_request(proxy_balancer_pre_request, NULL, NULL, APR_HOOK_FIRST);
748 proxy_hook_post_request(proxy_balancer_post_request, NULL, NULL, APR_HOOK_FIRST);
751 module AP_MODULE_DECLARE_DATA proxy_balancer_module = {
752 STANDARD20_MODULE_STUFF,
753 NULL, /* create per-directory config structure */
754 NULL, /* merge per-directory config structures */
755 NULL, /* create per-server config structure */
756 NULL, /* merge per-server config structures */
757 NULL, /* command apr_table_t */
758 ap_proxy_balancer_register_hook /* register hooks */