]> granicus.if.org Git - apache/blob - modules/proxy/mod_proxy_balancer.c
mod_proxy: Rename erroronstatus to failonstatus...
[apache] / modules / proxy / mod_proxy_balancer.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /* Load balancer module for Apache proxy */
18
19 #include "mod_proxy.h"
20 #include "scoreboard.h"
21 #include "ap_mpm.h"
22 #include "apr_version.h"
23 #include "apr_hooks.h"
24 #include "apr_uuid.h"
25 #include "apr_date.h"
26
27 module AP_MODULE_DECLARE_DATA proxy_balancer_module;
28
29 static char balancer_nonce[APR_UUID_FORMATTED_LENGTH + 1];
30
31 #if 0
32 extern void proxy_update_members(proxy_balancer **balancer, request_rec *r,
33                                   proxy_server_conf *conf);
34 #endif
35
36 static int proxy_balancer_canon(request_rec *r, char *url)
37 {
38     char *host, *path;
39     char *search = NULL;
40     const char *err;
41     apr_port_t port = 0;
42
43     if (strncasecmp(url, "balancer:", 9) == 0) {
44         url += 9;
45     }
46     else {
47         return DECLINED;
48     }
49
50     ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, r->server,
51              "proxy: BALANCER: canonicalising URL %s", url);
52
53     /* do syntatic check.
54      * We break the URL into host, port, path, search
55      */
56     err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
57     if (err) {
58         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
59                       "error parsing URL %s: %s",
60                       url, err);
61         return HTTP_BAD_REQUEST;
62     }
63     /*
64      * now parse path/search args, according to rfc1738:
65      * process the path. With proxy-noncanon set (by
66      * mod_proxy) we use the raw, unparsed uri
67      */
68     if (apr_table_get(r->notes, "proxy-nocanon")) {
69         path = url;   /* this is the raw path */
70     }
71     else {
72         path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
73                                  r->proxyreq);
74         search = r->args;
75     }
76     if (path == NULL)
77         return HTTP_BAD_REQUEST;
78
79     r->filename = apr_pstrcat(r->pool, "proxy:balancer://", host,
80             "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
81
82     r->path_info = apr_pstrcat(r->pool, "/", path, NULL);
83
84     return OK;
85 }
86
87 static int init_balancer_members(proxy_server_conf *conf, server_rec *s,
88                                  proxy_balancer *balancer)
89 {
90     int i;
91     proxy_worker **workers;
92
93     workers = (proxy_worker **)balancer->workers->elts;
94
95     for (i = 0; i < balancer->workers->nelts; i++) {
96         int worker_is_initialized;
97         worker_is_initialized = PROXY_WORKER_IS_INITIALIZED(*workers);
98         if (!worker_is_initialized) {
99             proxy_worker_stat *slot;
100             /*
101              * If the worker is not initialized check whether its scoreboard
102              * slot is already initialized.
103              */
104             slot = (proxy_worker_stat *) ap_get_scoreboard_lb((*workers)->id);
105             if (slot) {
106                 worker_is_initialized = slot->status & PROXY_WORKER_INITIALIZED;
107             }
108             else {
109                 worker_is_initialized = 0;
110             }
111         }
112         ap_proxy_initialize_worker_share(conf, *workers, s);
113         ap_proxy_initialize_worker(*workers, s, conf->pool);
114         if (!worker_is_initialized) {
115             /* Set to the original configuration */
116             (*workers)->s->lbstatus = (*workers)->s->lbfactor =
117             ((*workers)->lbfactor ? (*workers)->lbfactor : 1);
118             (*workers)->s->lbset = (*workers)->lbset;
119         }
120         ++workers;
121     }
122
123     /* Set default number of attempts to the number of
124      * workers.
125      */
126     if (!balancer->max_attempts_set && balancer->workers->nelts > 1) {
127         balancer->max_attempts = balancer->workers->nelts - 1;
128         balancer->max_attempts_set = 1;
129     }
130     return 0;
131 }
132
133 /* Retrieve the parameter with the given name
134  * Something like 'JSESSIONID=12345...N'
135  */
136 static char *get_path_param(apr_pool_t *pool, char *url,
137                             const char *name, int scolon_sep)
138 {
139     char *path = NULL;
140     char *pathdelims = "?&";
141
142     if (scolon_sep) {
143         pathdelims = ";?&";
144     }
145     for (path = strstr(url, name); path; path = strstr(path + 1, name)) {
146         path += strlen(name);
147         if (*path == '=') {
148             /*
149              * Session path was found, get it's value
150              */
151             ++path;
152             if (*path) {
153                 char *q;
154                 path = apr_strtok(apr_pstrdup(pool, path), pathdelims, &q);
155                 return path;
156             }
157         }
158     }
159     return NULL;
160 }
161
162 static char *get_cookie_param(request_rec *r, const char *name)
163 {
164     const char *cookies;
165     const char *start_cookie;
166
167     if ((cookies = apr_table_get(r->headers_in, "Cookie"))) {
168         for (start_cookie = ap_strstr_c(cookies, name); start_cookie;
169              start_cookie = ap_strstr_c(start_cookie + 1, name)) {
170             if (start_cookie == cookies ||
171                 start_cookie[-1] == ';' ||
172                 start_cookie[-1] == ',' ||
173                 isspace(start_cookie[-1])) {
174
175                 start_cookie += strlen(name);
176                 while(*start_cookie && isspace(*start_cookie))
177                     ++start_cookie;
178                 if (*start_cookie == '=' && start_cookie[1]) {
179                     /*
180                      * Session cookie was found, get it's value
181                      */
182                     char *end_cookie, *cookie;
183                     ++start_cookie;
184                     cookie = apr_pstrdup(r->pool, start_cookie);
185                     if ((end_cookie = strchr(cookie, ';')) != NULL)
186                         *end_cookie = '\0';
187                     if((end_cookie = strchr(cookie, ',')) != NULL)
188                         *end_cookie = '\0';
189                     return cookie;
190                 }
191             }
192         }
193     }
194     return NULL;
195 }
196
197 /* Find the worker that has the 'route' defined
198  */
199 static proxy_worker *find_route_worker(proxy_balancer *balancer,
200                                        const char *route, request_rec *r)
201 {
202     int i;
203     int checking_standby;
204     int checked_standby;
205     
206     proxy_worker **workers;
207     proxy_worker *worker;
208
209     checking_standby = checked_standby = 0;
210     while (!checked_standby) {
211         workers = (proxy_worker **)balancer->workers->elts;
212         for (i = 0; i < balancer->workers->nelts; i++, workers++) {
213             worker = *workers;
214             if ( (checking_standby ? !PROXY_WORKER_IS_STANDBY(worker) : PROXY_WORKER_IS_STANDBY(worker)) )
215                 continue;
216             if (*(worker->s->route) && strcmp(worker->s->route, route) == 0) {
217                 if (worker && PROXY_WORKER_IS_USABLE(worker)) {
218                     return worker;
219                 } else {
220                     /*
221                      * If the worker is in error state run
222                      * retry on that worker. It will be marked as
223                      * operational if the retry timeout is elapsed.
224                      * The worker might still be unusable, but we try
225                      * anyway.
226                      */
227                     ap_proxy_retry_worker("BALANCER", worker, r->server);
228                     if (PROXY_WORKER_IS_USABLE(worker)) {
229                             return worker;
230                     } else {
231                         /*
232                          * We have a worker that is unusable.
233                          * It can be in error or disabled, but in case
234                          * it has a redirection set use that redirection worker.
235                          * This enables to safely remove the member from the
236                          * balancer. Of course you will need some kind of
237                          * session replication between those two remote.
238                          */
239                         if (*worker->s->redirect) {
240                             proxy_worker *rworker = NULL;
241                             rworker = find_route_worker(balancer, worker->s->redirect, r);
242                             /* Check if the redirect worker is usable */
243                             if (rworker && !PROXY_WORKER_IS_USABLE(rworker)) {
244                                 /*
245                                  * If the worker is in error state run
246                                  * retry on that worker. It will be marked as
247                                  * operational if the retry timeout is elapsed.
248                                  * The worker might still be unusable, but we try
249                                  * anyway.
250                                  */
251                                 ap_proxy_retry_worker("BALANCER", rworker, r->server);
252                             }
253                             if (rworker && PROXY_WORKER_IS_USABLE(rworker))
254                                 return rworker;
255                         }
256                     }
257                 }
258             }
259         }
260         checked_standby = checking_standby++;
261     }
262     return NULL;
263 }
264
265 static proxy_worker *find_session_route(proxy_balancer *balancer,
266                                         request_rec *r,
267                                         char **route,
268                                         const char **sticky_used,
269                                         char **url)
270 {
271     proxy_worker *worker = NULL;
272
273     if (!balancer->sticky)
274         return NULL;
275     /* Try to find the sticky route inside url */
276     *route = get_path_param(r->pool, *url, balancer->sticky_path, balancer->scolonsep);
277     if (*route) {
278         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
279                      "proxy: BALANCER: Found value %s for "
280                      "stickysession %s", *route, balancer->sticky_path);
281         *sticky_used =  balancer->sticky_path;
282     }
283     else {
284         *route = get_cookie_param(r, balancer->sticky);
285         if (*route) {
286             *sticky_used =  balancer->sticky;
287             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
288                          "proxy: BALANCER: Found value %s for "
289                          "stickysession %s", *route, balancer->sticky);
290         }
291     }
292     /*
293      * If we found a value for sticksession, find the first '.' within.
294      * Everything after '.' (if present) is our route.
295      */
296     if ((*route) && ((*route = strchr(*route, '.')) != NULL ))
297         (*route)++;
298     if ((*route) && (**route)) {
299         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
300                                   "proxy: BALANCER: Found route %s", *route);
301         /* We have a route in path or in cookie
302          * Find the worker that has this route defined.
303          */
304         worker = find_route_worker(balancer, *route, r);
305         if (worker && strcmp(*route, worker->s->route)) {
306             /*
307              * Notice that the route of the worker chosen is different from
308              * the route supplied by the client.
309              */
310             apr_table_setn(r->subprocess_env, "BALANCER_ROUTE_CHANGED", "1");
311             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
312                          "proxy: BALANCER: Route changed from %s to %s",
313                          *route, worker->s->route);
314         }
315         return worker;
316     }
317     else
318         return NULL;
319 }
320
321 static proxy_worker *find_best_worker(proxy_balancer *balancer,
322                                       request_rec *r)
323 {
324     proxy_worker *candidate = NULL;
325     apr_status_t rv;
326
327     if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
328         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
329         "proxy: BALANCER: (%s). Lock failed for find_best_worker()", balancer->name);
330         return NULL;
331     }
332
333     candidate = (*balancer->lbmethod->finder)(balancer, r);
334
335     if (candidate)
336         candidate->s->elected++;
337
338 /*
339         PROXY_THREAD_UNLOCK(balancer);
340         return NULL;
341 */
342
343     if ((rv = PROXY_THREAD_UNLOCK(balancer)) != APR_SUCCESS) {
344         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
345         "proxy: BALANCER: (%s). Unlock failed for find_best_worker()", balancer->name);
346     }
347
348     if (candidate == NULL) {
349         /* All the workers are in error state or disabled.
350          * If the balancer has a timeout sleep for a while
351          * and try again to find the worker. The chances are
352          * that some other thread will release a connection.
353          * By default the timeout is not set, and the server
354          * returns SERVER_BUSY.
355          */
356 #if APR_HAS_THREADS
357         if (balancer->timeout) {
358             /* XXX: This can perhaps be build using some
359              * smarter mechanism, like tread_cond.
360              * But since the statuses can came from
361              * different childs, use the provided algo.
362              */
363             apr_interval_time_t timeout = balancer->timeout;
364             apr_interval_time_t step, tval = 0;
365             /* Set the timeout to 0 so that we don't
366              * end in infinite loop
367              */
368             balancer->timeout = 0;
369             step = timeout / 100;
370             while (tval < timeout) {
371                 apr_sleep(step);
372                 /* Try again */
373                 if ((candidate = find_best_worker(balancer, r)))
374                     break;
375                 tval += step;
376             }
377             /* restore the timeout */
378             balancer->timeout = timeout;
379         }
380 #endif
381     }
382
383     return candidate;
384
385 }
386
387 static int rewrite_url(request_rec *r, proxy_worker *worker,
388                         char **url)
389 {
390     const char *scheme = strstr(*url, "://");
391     const char *path = NULL;
392
393     if (scheme)
394         path = ap_strchr_c(scheme + 3, '/');
395
396     /* we break the URL into host, port, uri */
397     if (!worker) {
398         return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
399                              "missing worker. URI cannot be parsed: ", *url,
400                              NULL));
401     }
402
403     *url = apr_pstrcat(r->pool, worker->name, path, NULL);
404
405     return OK;
406 }
407
408 static void force_recovery(proxy_balancer *balancer, server_rec *s)
409 {
410     int i;
411     int ok = 0;
412     proxy_worker **worker;
413
414     worker = (proxy_worker **)balancer->workers->elts;
415     for (i = 0; i < balancer->workers->nelts; i++, worker++) {
416         if (!((*worker)->s->status & PROXY_WORKER_IN_ERROR)) {
417             ok = 1;
418             break;
419         }
420         else {
421             /* Try if we can recover */
422             ap_proxy_retry_worker("BALANCER", *worker, s);
423             if (!((*worker)->s->status & PROXY_WORKER_IN_ERROR)) {
424                 ok = 1;
425                 break;
426             }
427         }
428     }
429     if (!ok) {
430         /* If all workers are in error state force the recovery.
431          */
432         worker = (proxy_worker **)balancer->workers->elts;
433         for (i = 0; i < balancer->workers->nelts; i++, worker++) {
434             ++(*worker)->s->retries;
435             (*worker)->s->status &= ~PROXY_WORKER_IN_ERROR;
436             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
437                          "proxy: BALANCER: (%s). Forcing recovery for worker (%s)",
438                          balancer->name, (*worker)->hostname);
439         }
440     }
441 }
442
443 static int proxy_balancer_pre_request(proxy_worker **worker,
444                                       proxy_balancer **balancer,
445                                       request_rec *r,
446                                       proxy_server_conf *conf, char **url)
447 {
448     int access_status;
449     proxy_worker *runtime;
450     char *route = NULL;
451     const char *sticky = NULL;
452     apr_status_t rv;
453
454     *worker = NULL;
455     /* Step 1: check if the url is for us
456      * The url we can handle starts with 'balancer://'
457      * If balancer is already provided skip the search
458      * for balancer, because this is failover attempt.
459      */
460     if (!*balancer &&
461         !(*balancer = ap_proxy_get_balancer(r->pool, conf, *url)))
462         return DECLINED;
463
464     /* Step 2: Lock the LoadBalancer
465      * XXX: perhaps we need the process lock here
466      */
467     if ((rv = PROXY_THREAD_LOCK(*balancer)) != APR_SUCCESS) {
468         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
469                      "proxy: BALANCER: (%s). Lock failed for pre_request",
470                      (*balancer)->name);
471         return DECLINED;
472     }
473
474     /* Step 3: force recovery */
475     force_recovery(*balancer, r->server);
476     
477     /* Step 3.5: Update member list for the balancer */
478     /* TODO: Implement as provider! */
479     /* proxy_update_members(balancer, r, conf); */
480
481     /* Step 4: find the session route */
482     runtime = find_session_route(*balancer, r, &route, &sticky, url);
483     if (runtime) {
484         if ((*balancer)->lbmethod && (*balancer)->lbmethod->updatelbstatus) {
485             /* Call the LB implementation */
486             (*balancer)->lbmethod->updatelbstatus(*balancer, runtime, r->server);
487         }
488         else { /* Use the default one */
489             int i, total_factor = 0;
490             proxy_worker **workers;
491             /* We have a sticky load balancer
492              * Update the workers status
493              * so that even session routes get
494              * into account.
495              */
496             workers = (proxy_worker **)(*balancer)->workers->elts;
497             for (i = 0; i < (*balancer)->workers->nelts; i++) {
498                 /* Take into calculation only the workers that are
499                  * not in error state or not disabled.
500                  */
501                 if (PROXY_WORKER_IS_USABLE(*workers)) {
502                     (*workers)->s->lbstatus += (*workers)->s->lbfactor;
503                     total_factor += (*workers)->s->lbfactor;
504                 }
505                 workers++;
506             }
507             runtime->s->lbstatus -= total_factor;
508         }
509         runtime->s->elected++;
510
511         *worker = runtime;
512     }
513     else if (route && (*balancer)->sticky_force) {
514         int i, member_of = 0;
515         proxy_worker **workers;
516         /*
517          * We have a route provided that doesn't match the
518          * balancer name. See if the provider route is the
519          * member of the same balancer in which case return 503
520          */
521         workers = (proxy_worker **)(*balancer)->workers->elts;
522         for (i = 0; i < (*balancer)->workers->nelts; i++) {
523             if (*((*workers)->s->route) && strcmp((*workers)->s->route, route) == 0) {
524                 member_of = 1;
525                 break;
526             }
527             workers++;
528         }
529         if (member_of) {
530             ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
531                          "proxy: BALANCER: (%s). All workers are in error state for route (%s)",
532                          (*balancer)->name, route);
533             if ((rv = PROXY_THREAD_UNLOCK(*balancer)) != APR_SUCCESS) {
534                 ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
535                              "proxy: BALANCER: (%s). Unlock failed for pre_request",
536                              (*balancer)->name);
537             }
538             return HTTP_SERVICE_UNAVAILABLE;
539         }
540     }
541
542     if ((rv = PROXY_THREAD_UNLOCK(*balancer)) != APR_SUCCESS) {
543         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
544                      "proxy: BALANCER: (%s). Unlock failed for pre_request",
545                      (*balancer)->name);
546     }
547     if (!*worker) {
548         runtime = find_best_worker(*balancer, r);
549         if (!runtime) {
550             if ((*balancer)->workers->nelts) {
551                 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
552                             "proxy: BALANCER: (%s). All workers are in error state",
553                             (*balancer)->name);
554             } else {
555                 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
556                             "proxy: BALANCER: (%s). No workers in balancer",
557                             (*balancer)->name);
558             }
559
560             return HTTP_SERVICE_UNAVAILABLE;
561         }
562         if ((*balancer)->sticky && runtime) {
563             /*
564              * This balancer has sticky sessions and the client either has not
565              * supplied any routing information or all workers for this route
566              * including possible redirect and hotstandby workers are in error
567              * state, but we have found another working worker for this
568              * balancer where we can send the request. Thus notice that we have
569              * changed the route to the backend.
570              */
571             apr_table_setn(r->subprocess_env, "BALANCER_ROUTE_CHANGED", "1");
572         }
573         *worker = runtime;
574     }
575
576     (*worker)->s->busy++;
577
578     /* Add balancer/worker info to env. */
579     apr_table_setn(r->subprocess_env,
580                    "BALANCER_NAME", (*balancer)->name);
581     apr_table_setn(r->subprocess_env,
582                    "BALANCER_WORKER_NAME", (*worker)->name);
583     apr_table_setn(r->subprocess_env,
584                    "BALANCER_WORKER_ROUTE", (*worker)->s->route);
585
586     /* Rewrite the url from 'balancer://url'
587      * to the 'worker_scheme://worker_hostname[:worker_port]/url'
588      * This replaces the balancers fictional name with the
589      * real hostname of the elected worker.
590      */
591     access_status = rewrite_url(r, *worker, url);
592     /* Add the session route to request notes if present */
593     if (route) {
594         apr_table_setn(r->notes, "session-sticky", sticky);
595         apr_table_setn(r->notes, "session-route", route);
596
597         /* Add session info to env. */
598         apr_table_setn(r->subprocess_env,
599                        "BALANCER_SESSION_STICKY", sticky);
600         apr_table_setn(r->subprocess_env,
601                        "BALANCER_SESSION_ROUTE", route);
602     }
603     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
604                  "proxy: BALANCER (%s) worker (%s) rewritten to %s",
605                  (*balancer)->name, (*worker)->name, *url);
606
607     return access_status;
608 }
609
610 static int proxy_balancer_post_request(proxy_worker *worker,
611                                        proxy_balancer *balancer,
612                                        request_rec *r,
613                                        proxy_server_conf *conf)
614 {
615
616     apr_status_t rv;
617
618     if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
619         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
620             "proxy: BALANCER: (%s). Lock failed for post_request",
621             balancer->name);
622         return HTTP_INTERNAL_SERVER_ERROR;
623     }
624
625     if (!apr_is_empty_array(balancer->errstatuses)) {
626         int i;
627         for (i = 0; i < balancer->errstatuses->nelts; i++) {
628             int val = ((int *)balancer->errstatuses->elts)[i];
629             if (r->status == val) {
630                 ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
631                              "proxy: BALANCER: (%s).  Forcing recovery for worker (%s), failonstatus %d",
632                              balancer->name, worker->name, val);
633                 worker->s->status |= PROXY_WORKER_IN_ERROR;
634                 worker->s->error_time = apr_time_now();
635                 break;
636             }
637         }
638     }
639
640     if ((rv = PROXY_THREAD_UNLOCK(balancer)) != APR_SUCCESS) {
641         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
642             "proxy: BALANCER: (%s). Unlock failed for post_request",
643             balancer->name);
644     }
645     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
646                  "proxy_balancer_post_request for (%s)", balancer->name);
647
648     if (worker && worker->s->busy)
649         worker->s->busy--;
650
651     return OK;
652
653 }
654
655 static void recalc_factors(proxy_balancer *balancer)
656 {
657     int i;
658     proxy_worker **workers;
659
660
661     /* Recalculate lbfactors */
662     workers = (proxy_worker **)balancer->workers->elts;
663     /* Special case if there is only one worker it's
664      * load factor will always be 1
665      */
666     if (balancer->workers->nelts == 1) {
667         (*workers)->s->lbstatus = (*workers)->s->lbfactor = 1;
668         return;
669     }
670     for (i = 0; i < balancer->workers->nelts; i++) {
671         /* Update the status entries */
672         workers[i]->s->lbstatus = workers[i]->s->lbfactor;
673     }
674 }
675
676 /* post_config hook: */
677 static int balancer_init(apr_pool_t *p, apr_pool_t *plog,
678                          apr_pool_t *ptemp, server_rec *s)
679 {
680     apr_uuid_t uuid;
681     void *data;
682     const char *userdata_key = "mod_proxy_balancer_init";
683
684     /* balancer_init() will be called twice during startup.  So, only
685      * set up the static data the 1st time through. */
686     apr_pool_userdata_get(&data, userdata_key, s->process->pool);
687     if (!data) {
688         /* Retrieve a UUID and store the nonce for the lifetime of
689          * the process. */
690         apr_uuid_get(&uuid);
691         apr_uuid_format(balancer_nonce, &uuid);
692         apr_pool_userdata_set((const void *)1, userdata_key,
693                                apr_pool_cleanup_null, s->process->pool);
694     }
695     return OK;
696 }
697
698 /* Manages the loadfactors and member status
699  */
700 static int balancer_handler(request_rec *r)
701 {
702     void *sconf = r->server->module_config;
703     proxy_server_conf *conf = (proxy_server_conf *)
704         ap_get_module_config(sconf, &proxy_module);
705     proxy_balancer *balancer, *bsel = NULL;
706     proxy_worker *worker, *wsel = NULL;
707     proxy_worker **workers = NULL;
708     apr_table_t *params = apr_table_make(r->pool, 10);
709     int access_status;
710     int i, n;
711     const char *name;
712
713     /* is this for us? */
714     if (strcmp(r->handler, "balancer-manager"))
715         return DECLINED;
716     r->allowed = (AP_METHOD_BIT << M_GET);
717     if (r->method_number != M_GET)
718         return DECLINED;
719
720     if (r->args) {
721         char *args = apr_pstrdup(r->pool, r->args);
722         char *tok, *val;
723         while (args && *args) {
724             if ((val = ap_strchr(args, '='))) {
725                 *val++ = '\0';
726                 if ((tok = ap_strchr(val, '&')))
727                     *tok++ = '\0';
728                 /*
729                  * Special case: workers are allowed path information
730                  */
731                 if ((access_status = ap_unescape_url(val)) != OK)
732                     if (strcmp(args, "w") || (access_status !=  HTTP_NOT_FOUND))
733                         return access_status;
734                 apr_table_setn(params, args, val);
735                 args = tok;
736             }
737             else
738                 return HTTP_BAD_REQUEST;
739         }
740     }
741     
742     /* Check that the supplied nonce matches this server's nonce;
743      * otherwise ignore all parameters, to prevent a CSRF attack. */
744     if (*balancer_nonce &&
745         ((name = apr_table_get(params, "nonce")) == NULL 
746         || strcmp(balancer_nonce, name) != 0)) {
747         apr_table_clear(params);
748     }
749
750     if ((name = apr_table_get(params, "b")))
751         bsel = ap_proxy_get_balancer(r->pool, conf,
752             apr_pstrcat(r->pool, "balancer://", name, NULL));
753     if ((name = apr_table_get(params, "w"))) {
754         proxy_worker *ws;
755
756         ws = ap_proxy_get_worker(r->pool, conf, name);
757         if (bsel && ws) {
758             workers = (proxy_worker **)bsel->workers->elts;
759             for (n = 0; n < bsel->workers->nelts; n++) {
760                 worker = *workers;
761                 if (strcasecmp(worker->name, ws->name) == 0) {
762                     wsel = worker;
763                     break;
764                 }
765                 ++workers;
766             }
767         }
768     }
769     /* First set the params */
770     /*
771      * Note that it is not possible set the proxy_balancer because it is not
772      * in shared memory.
773      */
774     if (wsel) {
775         const char *val;
776         if ((val = apr_table_get(params, "lf"))) {
777             int ival = atoi(val);
778             if (ival >= 1 && ival <= 100) {
779                 wsel->s->lbfactor = ival;
780                 if (bsel)
781                     recalc_factors(bsel);
782             }
783         }
784         if ((val = apr_table_get(params, "wr"))) {
785             if (strlen(val) && strlen(val) < PROXY_WORKER_MAX_ROUTE_SIZ)
786                 strcpy(wsel->s->route, val);
787             else
788                 *wsel->s->route = '\0';
789         }
790         if ((val = apr_table_get(params, "rr"))) {
791             if (strlen(val) && strlen(val) < PROXY_WORKER_MAX_ROUTE_SIZ)
792                 strcpy(wsel->s->redirect, val);
793             else
794                 *wsel->s->redirect = '\0';
795         }
796         if ((val = apr_table_get(params, "dw"))) {
797             if (!strcasecmp(val, "Disable"))
798                 wsel->s->status |= PROXY_WORKER_DISABLED;
799             else if (!strcasecmp(val, "Enable"))
800                 wsel->s->status &= ~PROXY_WORKER_DISABLED;
801         }
802         if ((val = apr_table_get(params, "ls"))) {
803             int ival = atoi(val);
804             if (ival >= 0 && ival <= 99) {
805                 wsel->s->lbset = ival;
806              }
807         }
808
809     }
810     if (apr_table_get(params, "xml")) {
811         ap_set_content_type(r, "text/xml");
812         ap_rputs("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n", r);
813         ap_rputs("<httpd:manager xmlns:httpd=\"http://httpd.apache.org\">\n", r);
814         ap_rputs("  <httpd:balancers>\n", r);
815         balancer = (proxy_balancer *)conf->balancers->elts;
816         for (i = 0; i < conf->balancers->nelts; i++) {
817             ap_rputs("    <httpd:balancer>\n", r);
818             ap_rvputs(r, "      <httpd:name>", balancer->name, "</httpd:name>\n", NULL);
819             ap_rputs("      <httpd:workers>\n", r);
820             workers = (proxy_worker **)balancer->workers->elts;
821             for (n = 0; n < balancer->workers->nelts; n++) {
822                 worker = *workers;
823                 ap_rputs("        <httpd:worker>\n", r);
824                 ap_rvputs(r, "          <httpd:scheme>", worker->scheme,
825                           "</httpd:scheme>\n", NULL);
826                 ap_rvputs(r, "          <httpd:hostname>", worker->hostname,
827                           "</httpd:hostname>\n", NULL);
828                ap_rprintf(r, "          <httpd:loadfactor>%d</httpd:loadfactor>\n",
829                           worker->s->lbfactor);
830                 ap_rputs("        </httpd:worker>\n", r);
831                 ++workers;
832             }
833             ap_rputs("      </httpd:workers>\n", r);
834             ap_rputs("    </httpd:balancer>\n", r);
835             ++balancer;
836         }
837         ap_rputs("  </httpd:balancers>\n", r);
838         ap_rputs("</httpd:manager>", r);
839     }
840     else {
841         ap_set_content_type(r, "text/html; charset=ISO-8859-1");
842         ap_rputs(DOCTYPE_HTML_3_2
843                  "<html><head><title>Balancer Manager</title></head>\n", r);
844         ap_rputs("<body><h1>Load Balancer Manager for ", r);
845         ap_rvputs(r, ap_get_server_name(r), "</h1>\n\n", NULL);
846         ap_rvputs(r, "<dl><dt>Server Version: ",
847                   ap_get_server_description(), "</dt>\n", NULL);
848         ap_rvputs(r, "<dt>Server Built: ",
849                   ap_get_server_built(), "\n</dt></dl>\n", NULL);
850         balancer = (proxy_balancer *)conf->balancers->elts;
851         for (i = 0; i < conf->balancers->nelts; i++) {
852
853             ap_rputs("<hr />\n<h3>LoadBalancer Status for ", r);
854             ap_rvputs(r, balancer->name, "</h3>\n\n", NULL);
855             ap_rputs("\n\n<table border=\"0\" style=\"text-align: left;\"><tr>"
856                 "<th>StickySession</th><th>Timeout</th><th>FailoverAttempts</th><th>Method</th>"
857                 "</tr>\n<tr>", r);
858             if (balancer->sticky) {
859                 if (strcmp(balancer->sticky, balancer->sticky_path)) {
860                     ap_rvputs(r, "<td>", balancer->sticky, " | ",
861                               balancer->sticky_path, NULL);
862                 }
863                 else {
864                     ap_rvputs(r, "<td>", balancer->sticky, NULL);
865                 }
866             }
867             else {
868                 ap_rputs("<td> - ", r);
869             }
870             ap_rprintf(r, "</td><td>%" APR_TIME_T_FMT "</td>",
871                 apr_time_sec(balancer->timeout));
872             ap_rprintf(r, "<td>%d</td>\n", balancer->max_attempts);
873             ap_rprintf(r, "<td>%s</td>\n",
874                        balancer->lbmethod->name);
875             ap_rputs("</table>\n<br />", r);
876             ap_rputs("\n\n<table border=\"0\" style=\"text-align: left;\"><tr>"
877                 "<th>Worker URL</th>"
878                 "<th>Route</th><th>RouteRedir</th>"
879                 "<th>Factor</th><th>Set</th><th>Status</th>"
880                 "<th>Elected</th><th>To</th><th>From</th>"
881                 "</tr>\n", r);
882
883             workers = (proxy_worker **)balancer->workers->elts;
884             for (n = 0; n < balancer->workers->nelts; n++) {
885                 char fbuf[50];
886                 worker = *workers;
887                 ap_rvputs(r, "<tr>\n<td><a href=\"", r->uri, "?b=",
888                           balancer->name + sizeof("balancer://") - 1, "&w=",
889                           ap_escape_uri(r->pool, worker->name),
890                           "&nonce=", balancer_nonce, 
891                           "\">", NULL);
892                 ap_rvputs(r, worker->name, "</a></td>", NULL);
893                 ap_rvputs(r, "<td>", ap_escape_html(r->pool, worker->s->route),
894                           NULL);
895                 ap_rvputs(r, "</td><td>",
896                           ap_escape_html(r->pool, worker->s->redirect), NULL);
897                 ap_rprintf(r, "</td><td>%d</td>", worker->s->lbfactor);
898                 ap_rprintf(r, "<td>%d</td><td>", worker->s->lbset);
899                 if (worker->s->status & PROXY_WORKER_DISABLED)
900                    ap_rputs("Dis ", r);
901                 if (worker->s->status & PROXY_WORKER_IN_ERROR)
902                    ap_rputs("Err ", r);
903                 if (worker->s->status & PROXY_WORKER_STOPPED)
904                    ap_rputs("Stop ", r);
905                 if (worker->s->status & PROXY_WORKER_HOT_STANDBY)
906                    ap_rputs("Stby ", r);
907                 if (PROXY_WORKER_IS_USABLE(worker))
908                     ap_rputs("Ok", r);
909                 if (!PROXY_WORKER_IS_INITIALIZED(worker))
910                     ap_rputs("-", r);
911                 ap_rputs("</td>", r);
912                 ap_rprintf(r, "<td>%" APR_SIZE_T_FMT "</td><td>", worker->s->elected);
913                 ap_rputs(apr_strfsize(worker->s->transferred, fbuf), r);
914                 ap_rputs("</td><td>", r);
915                 ap_rputs(apr_strfsize(worker->s->read, fbuf), r);
916                 ap_rputs("</td></tr>\n", r);
917
918                 ++workers;
919             }
920             ap_rputs("</table>\n", r);
921             ++balancer;
922         }
923         ap_rputs("<hr />\n", r);
924         if (wsel && bsel) {
925             ap_rputs("<h3>Edit worker settings for ", r);
926             ap_rvputs(r, wsel->name, "</h3>\n", NULL);
927             ap_rvputs(r, "<form method=\"GET\" action=\"", NULL);
928             ap_rvputs(r, r->uri, "\">\n<dl>", NULL);
929             ap_rputs("<table><tr><td>Load factor:</td><td><input name=\"lf\" type=text ", r);
930             ap_rprintf(r, "value=\"%d\"></td></tr>\n", wsel->s->lbfactor);
931             ap_rputs("<tr><td>LB Set:</td><td><input name=\"ls\" type=text ", r);
932             ap_rprintf(r, "value=\"%d\"></td></tr>\n", wsel->s->lbset);
933             ap_rputs("<tr><td>Route:</td><td><input name=\"wr\" type=text ", r);
934             ap_rvputs(r, "value=\"", ap_escape_html(r->pool, wsel->s->route),
935                       NULL);
936             ap_rputs("\"></td></tr>\n", r);
937             ap_rputs("<tr><td>Route Redirect:</td><td><input name=\"rr\" type=text ", r);
938             ap_rvputs(r, "value=\"", ap_escape_html(r->pool, wsel->s->redirect),
939                       NULL);
940             ap_rputs("\"></td></tr>\n", r);
941             ap_rputs("<tr><td>Status:</td><td>Disabled: <input name=\"dw\" value=\"Disable\" type=radio", r);
942             if (wsel->s->status & PROXY_WORKER_DISABLED)
943                 ap_rputs(" checked", r);
944             ap_rputs("> | Enabled: <input name=\"dw\" value=\"Enable\" type=radio", r);
945             if (!(wsel->s->status & PROXY_WORKER_DISABLED))
946                 ap_rputs(" checked", r);
947             ap_rputs("></td></tr>\n", r);
948             ap_rputs("<tr><td colspan=2><input type=submit value=\"Submit\"></td></tr>\n", r);
949             ap_rvputs(r, "</table>\n<input type=hidden name=\"w\" ",  NULL);
950             ap_rvputs(r, "value=\"", ap_escape_uri(r->pool, wsel->name), "\">\n", NULL);
951             ap_rvputs(r, "<input type=hidden name=\"b\" ", NULL);
952             ap_rvputs(r, "value=\"", bsel->name + sizeof("balancer://") - 1,
953                       "\">\n", NULL);
954             ap_rvputs(r, "<input type=hidden name=\"nonce\" value=\"", 
955                       balancer_nonce, "\">\n", NULL);
956             ap_rvputs(r, "</form>\n", NULL);
957             ap_rputs("<hr />\n", r);
958         }
959         ap_rputs(ap_psignature("",r), r);
960         ap_rputs("</body></html>\n", r);
961     }
962     return OK;
963 }
964
965 static void child_init(apr_pool_t *p, server_rec *s)
966 {
967     while (s) {
968         void *sconf = s->module_config;
969         proxy_server_conf *conf;
970         proxy_balancer *balancer;
971         int i;
972         conf = (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module);
973
974         /* Initialize shared scoreboard data */
975         balancer = (proxy_balancer *)conf->balancers->elts;
976         for (i = 0; i < conf->balancers->nelts; i++) {
977             if (balancer->lbmethod && balancer->lbmethod->reset)
978                balancer->lbmethod->reset(balancer, s);
979             init_balancer_members(conf, s, balancer);
980             balancer++;
981         }
982         s = s->next;
983     }
984
985 }
986
987 static const char *set_balancer_nonce (cmd_parms *cmd, void *dummy, const char *arg,
988                                        const char *val)
989 {
990     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
991     if (err != NULL) {
992         return err;
993     }
994
995     if (!strcasecmp(arg, "None")) {
996         *balancer_nonce = '\0';
997     } else if (!strcasecmp(arg, "Set")) {
998         if (val) {
999             apr_cpystrn(balancer_nonce, val, sizeof(balancer_nonce));
1000         } else {
1001             return "BalancerNonce Set requires an argument";
1002         }
1003     } else if (strcasecmp(arg, "Default")) {
1004         return "Bad argument for BalancerNonce: Must be 'Set', 'None' or 'Default'";
1005     }
1006     return NULL;
1007 }
1008
1009 static const command_rec balancer_cmds[] =
1010 {
1011     AP_INIT_TAKE12("BalancerNonce", set_balancer_nonce, NULL,
1012        RSRC_CONF, "Set value for balancer-manager nonce"),
1013     {NULL}
1014 };
1015
1016 static void ap_proxy_balancer_register_hook(apr_pool_t *p)
1017 {
1018     /* Only the mpm_winnt has child init hook handler.
1019      * make sure that we are called after the mpm
1020      * initializes
1021      */
1022     static const char *const aszPred[] = { "mpm_winnt.c", NULL};
1023      /* manager handler */
1024     ap_hook_post_config(balancer_init, NULL, NULL, APR_HOOK_MIDDLE);
1025     ap_hook_handler(balancer_handler, NULL, NULL, APR_HOOK_FIRST);
1026     ap_hook_child_init(child_init, aszPred, NULL, APR_HOOK_MIDDLE);
1027     proxy_hook_pre_request(proxy_balancer_pre_request, NULL, NULL, APR_HOOK_FIRST);
1028     proxy_hook_post_request(proxy_balancer_post_request, NULL, NULL, APR_HOOK_FIRST);
1029     proxy_hook_canon_handler(proxy_balancer_canon, NULL, NULL, APR_HOOK_FIRST);
1030 }
1031
1032 AP_DECLARE_MODULE(proxy_balancer) = {
1033     STANDARD20_MODULE_STUFF,
1034     NULL,       /* create per-directory config structure */
1035     NULL,       /* merge per-directory config structures */
1036     NULL,       /* create per-server config structure */
1037     NULL,       /* merge per-server config structures */
1038     balancer_cmds,       /* command apr_table_t */
1039     ap_proxy_balancer_register_hook /* register hooks */
1040 };