~ubuntu-branches/ubuntu/feisty/apache2/feisty

« back to all changes in this revision

Viewing changes to modules/proxy/mod_proxy_balancer.c

  • Committer: Bazaar Package Importer
  • Author(s): Andreas Barth
  • Date: 2006-12-09 21:05:45 UTC
  • mfrom: (0.6.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061209210545-h70s0xaqc2v8vqr2
Tags: 2.2.3-3.2
* Non-maintainer upload.
* 043_ajp_connection_reuse: Patch from upstream Bugzilla, fixing a critical
  issue with regard to connection reuse in mod_proxy_ajp.
  Closes: #396265

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
#define CORE_PRIVATE
 
20
 
 
21
#include "mod_proxy.h"
 
22
#include "ap_mpm.h"
 
23
#include "apr_version.h"
 
24
#include "apr_hooks.h"
 
25
 
 
26
module AP_MODULE_DECLARE_DATA proxy_balancer_module;
 
27
 
 
28
static int proxy_balancer_canon(request_rec *r, char *url)
 
29
{
 
30
    char *host, *path, *search;
 
31
    const char *err;
 
32
    apr_port_t port = 0;
 
33
 
 
34
    if (strncasecmp(url, "balancer:", 9) == 0) {
 
35
        url += 9;
 
36
    }
 
37
    else {
 
38
        return DECLINED;
 
39
    }
 
40
 
 
41
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
 
42
             "proxy: BALANCER: canonicalising URL %s", url);
 
43
 
 
44
    /* do syntatic check.
 
45
     * We break the URL into host, port, path, search
 
46
     */
 
47
    err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
 
48
    if (err) {
 
49
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
50
                      "error parsing URL %s: %s",
 
51
                      url, err);
 
52
        return HTTP_BAD_REQUEST;
 
53
    }
 
54
    /* now parse path/search args, according to rfc1738 */
 
55
    /* N.B. if this isn't a true proxy request, then the URL _path_
 
56
     * has already been decoded.  True proxy requests have r->uri
 
57
     * == r->unparsed_uri, and no others have that property.
 
58
     */
 
59
    if (r->uri == r->unparsed_uri) {
 
60
        search = strchr(url, '?');
 
61
        if (search != NULL)
 
62
            *(search++) = '\0';
 
63
    }
 
64
    else
 
65
        search = r->args;
 
66
 
 
67
    /* process path */
 
68
    path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, r->proxyreq);
 
69
    if (path == NULL)
 
70
        return HTTP_BAD_REQUEST;
 
71
 
 
72
    r->filename = apr_pstrcat(r->pool, "proxy:balancer://", host,
 
73
            "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
 
74
    return OK;
 
75
}
 
76
 
 
77
static int init_balancer_members(proxy_server_conf *conf, server_rec *s,
 
78
                                 proxy_balancer *balancer)
 
79
{
 
80
    int i;
 
81
    proxy_worker *workers;
 
82
 
 
83
    workers = (proxy_worker *)balancer->workers->elts;
 
84
 
 
85
    for (i = 0; i < balancer->workers->nelts; i++) {
 
86
        ap_proxy_initialize_worker_share(conf, workers, s);
 
87
        ap_proxy_initialize_worker(workers, s);
 
88
        ++workers;
 
89
    }
 
90
 
 
91
    workers = (proxy_worker *)balancer->workers->elts;
 
92
    for (i = 0; i < balancer->workers->nelts; i++) {
 
93
        /* Set to the original configuration */
 
94
        workers[i].s->lbstatus = workers[i].s->lbfactor =
 
95
          (workers[i].lbfactor ? workers[i].lbfactor : 1);
 
96
    }
 
97
    /* Set default number of attempts to the number of
 
98
     * workers.
 
99
     */
 
100
    if (!balancer->max_attempts_set && balancer->workers->nelts > 1) {
 
101
        balancer->max_attempts = balancer->workers->nelts - 1;
 
102
        balancer->max_attempts_set = 1;
 
103
    }
 
104
    return 0;
 
105
}
 
106
 
 
107
/* Retrieve the parameter with the given name
 
108
 * Something like 'JSESSIONID=12345...N'
 
109
 */
 
110
static char *get_path_param(apr_pool_t *pool, char *url,
 
111
                            const char *name)
 
112
{
 
113
    char *path = NULL;
 
114
 
 
115
    for (path = strstr(url, name); path; path = strstr(path + 1, name)) {
 
116
        path += strlen(name);
 
117
        if (*path == '=') {
 
118
            /*
 
119
             * Session path was found, get it's value
 
120
             */
 
121
            ++path;
 
122
            if (strlen(path)) {
 
123
                char *q;
 
124
                path = apr_pstrdup(pool, path);
 
125
                if ((q = strchr(path, '?')))
 
126
                    *q = '\0';
 
127
                return path;
 
128
            }
 
129
        }
 
130
    }
 
131
    return NULL;
 
132
}
 
133
 
 
134
static char *get_cookie_param(request_rec *r, const char *name)
 
135
{
 
136
    const char *cookies;
 
137
    const char *start_cookie;
 
138
 
 
139
    if ((cookies = apr_table_get(r->headers_in, "Cookie"))) {
 
140
        for (start_cookie = ap_strstr_c(cookies, name); start_cookie;
 
141
             start_cookie = ap_strstr_c(start_cookie + 1, name)) {
 
142
            if (start_cookie == cookies ||
 
143
                start_cookie[-1] == ';' ||
 
144
                start_cookie[-1] == ',' ||
 
145
                isspace(start_cookie[-1])) {
 
146
 
 
147
                start_cookie += strlen(name);
 
148
                while(*start_cookie && isspace(*start_cookie))
 
149
                    ++start_cookie;
 
150
                if (*start_cookie == '=' && start_cookie[1]) {
 
151
                    /*
 
152
                     * Session cookie was found, get it's value
 
153
                     */
 
154
                    char *end_cookie, *cookie;
 
155
                    ++start_cookie;
 
156
                    cookie = apr_pstrdup(r->pool, start_cookie);
 
157
                    if ((end_cookie = strchr(cookie, ';')) != NULL)
 
158
                        *end_cookie = '\0';
 
159
                    if((end_cookie = strchr(cookie, ',')) != NULL)
 
160
                        *end_cookie = '\0';
 
161
                    return cookie;
 
162
                }
 
163
            }
 
164
        }
 
165
    }
 
166
    return NULL;
 
167
}
 
168
 
 
169
/* Find the worker that has the 'route' defined
 
170
 */
 
171
static proxy_worker *find_route_worker(proxy_balancer *balancer,
 
172
                                       const char *route)
 
173
{
 
174
    int i;
 
175
    proxy_worker *worker = (proxy_worker *)balancer->workers->elts;
 
176
    for (i = 0; i < balancer->workers->nelts; i++) {
 
177
        if (*(worker->s->route) && strcmp(worker->s->route, route) == 0) {
 
178
            return worker;
 
179
        }
 
180
        worker++;
 
181
    }
 
182
    return NULL;
 
183
}
 
184
 
 
185
static proxy_worker *find_session_route(proxy_balancer *balancer,
 
186
                                        request_rec *r,
 
187
                                        char **route,
 
188
                                        char **url)
 
189
{
 
190
    proxy_worker *worker = NULL;
 
191
 
 
192
    if (!balancer->sticky)
 
193
        return NULL;
 
194
    /* Try to find the sticky route inside url */
 
195
    *route = get_path_param(r->pool, *url, balancer->sticky);
 
196
    if (!*route)
 
197
        *route = get_cookie_param(r, balancer->sticky);
 
198
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
 
199
                            "proxy: BALANCER: Found value %s for "
 
200
                            "stickysession %s", *route, balancer->sticky);
 
201
    /*
 
202
     * If we found a value for sticksession, find the first '.' within.
 
203
     * Everything after '.' (if present) is our route.
 
204
     */
 
205
    if ((*route) && ((*route = strchr(*route, '.')) != NULL ))
 
206
        (*route)++;
 
207
    if ((*route) && (**route)) {
 
208
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
 
209
                                  "proxy: BALANCER: Found route %s", *route);
 
210
        /* We have a route in path or in cookie
 
211
         * Find the worker that has this route defined.
 
212
         */
 
213
        worker = find_route_worker(balancer, *route);
 
214
        if (worker && !PROXY_WORKER_IS_USABLE(worker)) {
 
215
            /* We have a worker that is unusable.
 
216
             * It can be in error or disabled, but in case
 
217
             * it has a redirection set use that redirection worker.
 
218
             * This enables to safely remove the member from the
 
219
             * balancer. Of course you will need a some kind of
 
220
             * session replication between those two remote.
 
221
             */
 
222
            if (*worker->s->redirect)
 
223
                worker = find_route_worker(balancer, worker->s->redirect);
 
224
            /* Check if the redirect worker is usable */
 
225
            if (worker && !PROXY_WORKER_IS_USABLE(worker))
 
226
                worker = NULL;
 
227
        }
 
228
        return worker;
 
229
    }
 
230
    else
 
231
        return NULL;
 
232
}
 
233
 
 
234
static proxy_worker *find_best_worker(proxy_balancer *balancer,
 
235
                                      request_rec *r)
 
236
{
 
237
    proxy_worker *candidate = NULL;
 
238
 
 
239
    if (PROXY_THREAD_LOCK(balancer) != APR_SUCCESS)
 
240
        return NULL;
 
241
 
 
242
    candidate = (*balancer->lbmethod->finder)(balancer, r);
 
243
 
 
244
/*
 
245
        PROXY_THREAD_UNLOCK(balancer);
 
246
        return NULL;
 
247
*/
 
248
 
 
249
    PROXY_THREAD_UNLOCK(balancer);
 
250
 
 
251
    if (candidate == NULL) {
 
252
        /* All the workers are in error state or disabled.
 
253
         * If the balancer has a timeout sleep for a while
 
254
         * and try again to find the worker. The chances are
 
255
         * that some other thread will release a connection.
 
256
         * By default the timeout is not set, and the server
 
257
         * returns SERVER_BUSY.
 
258
         */
 
259
#if APR_HAS_THREADS
 
260
        if (balancer->timeout) {
 
261
            /* XXX: This can perhaps be build using some
 
262
             * smarter mechanism, like tread_cond.
 
263
             * But since the statuses can came from
 
264
             * different childs, use the provided algo.
 
265
             */
 
266
            apr_interval_time_t timeout = balancer->timeout;
 
267
            apr_interval_time_t step, tval = 0;
 
268
            /* Set the timeout to 0 so that we don't
 
269
             * end in infinite loop
 
270
             */
 
271
            balancer->timeout = 0;
 
272
            step = timeout / 100;
 
273
            while (tval < timeout) {
 
274
                apr_sleep(step);
 
275
                /* Try again */
 
276
                if ((candidate = find_best_worker(balancer, r)))
 
277
                    break;
 
278
                tval += step;
 
279
            }
 
280
            /* restore the timeout */
 
281
            balancer->timeout = timeout;
 
282
        }
 
283
#endif
 
284
    }
 
285
    return candidate;
 
286
}
 
287
 
 
288
static int rewrite_url(request_rec *r, proxy_worker *worker,
 
289
                        char **url)
 
290
{
 
291
    const char *scheme = strstr(*url, "://");
 
292
    const char *path = NULL;
 
293
 
 
294
    if (scheme)
 
295
        path = ap_strchr_c(scheme + 3, '/');
 
296
 
 
297
    /* we break the URL into host, port, uri */
 
298
    if (!worker) {
 
299
        return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
 
300
                             "missing worker. URI cannot be parsed: ", *url,
 
301
                             NULL));
 
302
    }
 
303
 
 
304
    *url = apr_pstrcat(r->pool, worker->name, path, NULL);
 
305
 
 
306
    return OK;
 
307
}
 
308
 
 
309
static int proxy_balancer_pre_request(proxy_worker **worker,
 
310
                                      proxy_balancer **balancer,
 
311
                                      request_rec *r,
 
312
                                      proxy_server_conf *conf, char **url)
 
313
{
 
314
    int access_status;
 
315
    proxy_worker *runtime;
 
316
    char *route = NULL;
 
317
    apr_status_t rv;
 
318
 
 
319
    *worker = NULL;
 
320
    /* Step 1: check if the url is for us
 
321
     * The url we can handle starts with 'balancer://'
 
322
     * If balancer is already provided skip the search
 
323
     * for balancer, because this is failover attempt.
 
324
     */
 
325
    if (!*balancer &&
 
326
        !(*balancer = ap_proxy_get_balancer(r->pool, conf, *url)))
 
327
        return DECLINED;
 
328
 
 
329
    /* Step 2: find the session route */
 
330
 
 
331
    runtime = find_session_route(*balancer, r, &route, url);
 
332
    /* Lock the LoadBalancer
 
333
     * XXX: perhaps we need the process lock here
 
334
     */
 
335
    if ((rv = PROXY_THREAD_LOCK(*balancer)) != APR_SUCCESS) {
 
336
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
 
337
                     "proxy: BALANCER: lock");
 
338
        return DECLINED;
 
339
    }
 
340
    if (runtime) {
 
341
        int i, total_factor = 0;
 
342
        proxy_worker *workers;
 
343
        /* We have a sticky load balancer
 
344
         * Update the workers status
 
345
         * so that even session routes get
 
346
         * into account.
 
347
         */
 
348
        workers = (proxy_worker *)(*balancer)->workers->elts;
 
349
        for (i = 0; i < (*balancer)->workers->nelts; i++) {
 
350
            /* Take into calculation only the workers that are
 
351
             * not in error state or not disabled.
 
352
             */
 
353
            if (PROXY_WORKER_IS_USABLE(workers)) {
 
354
                workers->s->lbstatus += workers->s->lbfactor;
 
355
                total_factor += workers->s->lbfactor;
 
356
            }
 
357
            workers++;
 
358
        }
 
359
        runtime->s->lbstatus -= total_factor;
 
360
        runtime->s->elected++;
 
361
 
 
362
        *worker = runtime;
 
363
    }
 
364
    else if (route && (*balancer)->sticky_force) {
 
365
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
 
366
                     "proxy: BALANCER: (%s). All workers are in error state for route (%s)",
 
367
                     (*balancer)->name, route);
 
368
        PROXY_THREAD_UNLOCK(*balancer);
 
369
        return HTTP_SERVICE_UNAVAILABLE;
 
370
    }
 
371
 
 
372
    PROXY_THREAD_UNLOCK(*balancer);
 
373
    if (!*worker) {
 
374
        runtime = find_best_worker(*balancer, r);
 
375
        if (!runtime) {
 
376
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
 
377
                         "proxy: BALANCER: (%s). All workers are in error state",
 
378
                         (*balancer)->name);
 
379
 
 
380
            return HTTP_SERVICE_UNAVAILABLE;
 
381
        }
 
382
        *worker = runtime;
 
383
    }
 
384
 
 
385
    /* Rewrite the url from 'balancer://url'
 
386
     * to the 'worker_scheme://worker_hostname[:worker_port]/url'
 
387
     * This replaces the balancers fictional name with the
 
388
     * real hostname of the elected worker.
 
389
     */
 
390
    access_status = rewrite_url(r, *worker, url);
 
391
    /* Add the session route to request notes if present */
 
392
    if (route) {
 
393
        apr_table_setn(r->notes, "session-sticky", (*balancer)->sticky);
 
394
        apr_table_setn(r->notes, "session-route", route);
 
395
    }
 
396
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
 
397
                 "proxy: BALANCER (%s) worker (%s) rewritten to %s",
 
398
                 (*balancer)->name, (*worker)->name, *url);
 
399
 
 
400
    return access_status;
 
401
}
 
402
 
 
403
static int proxy_balancer_post_request(proxy_worker *worker,
 
404
                                       proxy_balancer *balancer,
 
405
                                       request_rec *r,
 
406
                                       proxy_server_conf *conf)
 
407
{
 
408
    apr_status_t rv;
 
409
 
 
410
    if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
 
411
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
 
412
            "proxy: BALANCER: lock");
 
413
        return HTTP_INTERNAL_SERVER_ERROR;
 
414
    }
 
415
    /* TODO: calculate the bytes transferred
 
416
     * This will enable to elect the worker that has
 
417
     * the lowest load.
 
418
     * The bytes transferred depends on the protocol
 
419
     * used, so each protocol handler should keep the
 
420
     * track on that.
 
421
     */
 
422
 
 
423
    PROXY_THREAD_UNLOCK(balancer);
 
424
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
 
425
                 "proxy_balancer_post_request for (%s)", balancer->name);
 
426
 
 
427
    return OK;
 
428
}
 
429
 
 
430
static void recalc_factors(proxy_balancer *balancer)
 
431
{
 
432
    int i;
 
433
    proxy_worker *workers;
 
434
 
 
435
 
 
436
    /* Recalculate lbfactors */
 
437
    workers = (proxy_worker *)balancer->workers->elts;
 
438
    /* Special case if there is only one worker it's
 
439
     * load factor will always be 1
 
440
     */
 
441
    if (balancer->workers->nelts == 1) {
 
442
        workers->s->lbstatus = workers->s->lbfactor = 1;
 
443
        return;
 
444
    }
 
445
    for (i = 0; i < balancer->workers->nelts; i++) {
 
446
        /* Update the status entries */
 
447
        workers[i].s->lbstatus = workers[i].s->lbfactor;
 
448
    }
 
449
}
 
450
 
 
451
/* Manages the loadfactors and member status
 
452
 */
 
453
static int balancer_handler(request_rec *r)
 
454
{
 
455
    void *sconf = r->server->module_config;
 
456
    proxy_server_conf *conf = (proxy_server_conf *)
 
457
        ap_get_module_config(sconf, &proxy_module);
 
458
    proxy_balancer *balancer, *bsel = NULL;
 
459
    proxy_worker *worker, *wsel = NULL;
 
460
    apr_table_t *params = apr_table_make(r->pool, 10);
 
461
    int access_status;
 
462
    int i, n;
 
463
    const char *name;
 
464
 
 
465
    /* is this for us? */
 
466
    if (strcmp(r->handler, "balancer-manager"))
 
467
        return DECLINED;
 
468
    r->allowed = (AP_METHOD_BIT << M_GET);
 
469
    if (r->method_number != M_GET)
 
470
        return DECLINED;
 
471
 
 
472
    if (r->args) {
 
473
        char *args = apr_pstrdup(r->pool, r->args);
 
474
        char *tok, *val;
 
475
        while (args && *args) {
 
476
            if ((val = ap_strchr(args, '='))) {
 
477
                *val++ = '\0';
 
478
                if ((tok = ap_strchr(val, '&')))
 
479
                    *tok++ = '\0';
 
480
                /*
 
481
                 * Special case: workers are allowed path information
 
482
                 */
 
483
                if ((access_status = ap_unescape_url(val)) != OK)
 
484
                    if (strcmp(args, "w") || (access_status !=  HTTP_NOT_FOUND))
 
485
                        return access_status;
 
486
                apr_table_setn(params, args, val);
 
487
                args = tok;
 
488
            }
 
489
            else
 
490
                return HTTP_BAD_REQUEST;
 
491
        }
 
492
    }
 
493
    if ((name = apr_table_get(params, "b")))
 
494
        bsel = ap_proxy_get_balancer(r->pool, conf,
 
495
            apr_pstrcat(r->pool, "balancer://", name, NULL));
 
496
    if ((name = apr_table_get(params, "w"))) {
 
497
        proxy_worker *ws;
 
498
 
 
499
        ws = ap_proxy_get_worker(r->pool, conf, name);
 
500
        if (ws) {
 
501
            worker = (proxy_worker *)bsel->workers->elts;
 
502
            for (n = 0; n < bsel->workers->nelts; n++) {
 
503
                if (strcasecmp(worker->name, ws->name) == 0) {
 
504
                    wsel = worker;
 
505
                    break;
 
506
                }
 
507
                ++worker;
 
508
            }
 
509
        }
 
510
    }
 
511
    /* First set the params */
 
512
    if (bsel) {
 
513
        const char *val;
 
514
        if ((val = apr_table_get(params, "ss"))) {
 
515
            if (strlen(val))
 
516
                bsel->sticky = apr_pstrdup(conf->pool, val);
 
517
            else
 
518
                bsel->sticky = NULL;
 
519
        }
 
520
        if ((val = apr_table_get(params, "tm"))) {
 
521
            int ival = atoi(val);
 
522
            if (ival >= 0)
 
523
                bsel->timeout = apr_time_from_sec(ival);
 
524
        }
 
525
        if ((val = apr_table_get(params, "fa"))) {
 
526
            int ival = atoi(val);
 
527
            if (ival >= 0)
 
528
                bsel->max_attempts = ival;
 
529
            bsel->max_attempts_set = 1;
 
530
        }
 
531
        if ((val = apr_table_get(params, "lm"))) {
 
532
            proxy_balancer_method *provider;
 
533
            provider = ap_lookup_provider(PROXY_LBMETHOD, val, "0");
 
534
            if (provider) {
 
535
                bsel->lbmethod = provider;
 
536
            }
 
537
        }
 
538
    }
 
539
    if (wsel) {
 
540
        const char *val;
 
541
        if ((val = apr_table_get(params, "lf"))) {
 
542
            int ival = atoi(val);
 
543
            if (ival >= 1 && ival <= 100) {
 
544
                wsel->s->lbfactor = ival;
 
545
                if (bsel)
 
546
                    recalc_factors(bsel);
 
547
            }
 
548
        }
 
549
        if ((val = apr_table_get(params, "wr"))) {
 
550
            if (strlen(val) && strlen(val) < PROXY_WORKER_MAX_ROUTE_SIZ)
 
551
                strcpy(wsel->s->route, val);
 
552
            else
 
553
                *wsel->s->route = '\0';
 
554
        }
 
555
        if ((val = apr_table_get(params, "rr"))) {
 
556
            if (strlen(val) && strlen(val) < PROXY_WORKER_MAX_ROUTE_SIZ)
 
557
                strcpy(wsel->s->redirect, val);
 
558
            else
 
559
                *wsel->s->redirect = '\0';
 
560
        }
 
561
        if ((val = apr_table_get(params, "dw"))) {
 
562
            if (!strcasecmp(val, "Disable"))
 
563
                wsel->s->status |= PROXY_WORKER_DISABLED;
 
564
            else if (!strcasecmp(val, "Enable"))
 
565
                wsel->s->status &= ~PROXY_WORKER_DISABLED;
 
566
        }
 
567
 
 
568
    }
 
569
    if (apr_table_get(params, "xml")) {
 
570
        ap_set_content_type(r, "text/xml");
 
571
        ap_rputs("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n", r);
 
572
        ap_rputs("<httpd:manager xmlns:httpd=\"http://httpd.apache.org\">\n", r);
 
573
        ap_rputs("  <httpd:balancers>\n", r);
 
574
        balancer = (proxy_balancer *)conf->balancers->elts;
 
575
        for (i = 0; i < conf->balancers->nelts; i++) {
 
576
            ap_rputs("    <httpd:balancer>\n", r);
 
577
            ap_rvputs(r, "      <httpd:name>", balancer->name, "</httpd:name>\n", NULL);
 
578
            ap_rputs("      <httpd:workers>\n", r);
 
579
            worker = (proxy_worker *)balancer->workers->elts;
 
580
            for (n = 0; n < balancer->workers->nelts; n++) {
 
581
                ap_rputs("        <httpd:worker>\n", r);
 
582
                ap_rvputs(r, "          <httpd:scheme>", worker->scheme,
 
583
                          "</httpd:scheme>\n", NULL);
 
584
                ap_rvputs(r, "          <httpd:hostname>", worker->hostname,
 
585
                          "</httpd:hostname>\n", NULL);
 
586
               ap_rprintf(r, "          <httpd:loadfactor>%d</httpd:loadfactor>\n",
 
587
                          worker->s->lbfactor);
 
588
                ap_rputs("        </httpd:worker>\n", r);
 
589
                ++worker;
 
590
            }
 
591
            ap_rputs("      </httpd:workers>\n", r);
 
592
            ap_rputs("    </httpd:balancer>\n", r);
 
593
            ++balancer;
 
594
        }
 
595
        ap_rputs("  </httpd:balancers>\n", r);
 
596
        ap_rputs("</httpd:manager>", r);
 
597
    }
 
598
    else {
 
599
        ap_set_content_type(r, "text/html");
 
600
        ap_rputs(DOCTYPE_HTML_3_2
 
601
                 "<html><head><title>Balancer Manager</title></head>\n", r);
 
602
        ap_rputs("<body><h1>Load Balancer Manager for ", r);
 
603
        ap_rvputs(r, ap_get_server_name(r), "</h1>\n\n", NULL);
 
604
        ap_rvputs(r, "<dl><dt>Server Version: ",
 
605
                  ap_get_server_version(), "</dt>\n", NULL);
 
606
        ap_rvputs(r, "<dt>Server Built: ",
 
607
                  ap_get_server_built(), "\n</dt></dl>\n", NULL);
 
608
        balancer = (proxy_balancer *)conf->balancers->elts;
 
609
        for (i = 0; i < conf->balancers->nelts; i++) {
 
610
 
 
611
            ap_rputs("<hr />\n<h3>LoadBalancer Status for ", r);
 
612
            ap_rvputs(r, "<a href=\"", r->uri, "?b=",
 
613
                      balancer->name + sizeof("balancer://") - 1,
 
614
                      "\">", NULL);
 
615
            ap_rvputs(r, balancer->name, "</a></h3>\n\n", NULL);
 
616
            ap_rputs("\n\n<table border=\"0\" style=\"text-align: left;\"><tr>"
 
617
                "<th>StickySession</th><th>Timeout</th><th>FailoverAttempts</th><th>Method</th>"
 
618
                "</tr>\n<tr>", r);
 
619
            ap_rvputs(r, "<td>", balancer->sticky, NULL);
 
620
            ap_rprintf(r, "</td><td>%" APR_TIME_T_FMT "</td>",
 
621
                apr_time_sec(balancer->timeout));
 
622
            ap_rprintf(r, "<td>%d</td>\n", balancer->max_attempts);
 
623
            ap_rprintf(r, "<td>%s</td>\n",
 
624
                       balancer->lbmethod->name);
 
625
            ap_rputs("</table>\n<br />", r);
 
626
            ap_rputs("\n\n<table border=\"0\" style=\"text-align: left;\"><tr>"
 
627
                "<th>Worker URL</th>"
 
628
                "<th>Route</th><th>RouteRedir</th>"
 
629
                "<th>Factor</th><th>Status</th>"
 
630
                "</tr>\n", r);
 
631
 
 
632
            worker = (proxy_worker *)balancer->workers->elts;
 
633
            for (n = 0; n < balancer->workers->nelts; n++) {
 
634
 
 
635
                ap_rvputs(r, "<tr>\n<td><a href=\"", r->uri, "?b=",
 
636
                          balancer->name + sizeof("balancer://") - 1, "&w=",
 
637
                          ap_escape_uri(r->pool, worker->name),
 
638
                          "\">", NULL);
 
639
                ap_rvputs(r, worker->name, "</a></td>", NULL);
 
640
                ap_rvputs(r, "<td>", worker->s->route, NULL);
 
641
                ap_rvputs(r, "</td><td>", worker->s->redirect, NULL);
 
642
                ap_rprintf(r, "</td><td>%d</td><td>", worker->s->lbfactor);
 
643
                if (worker->s->status & PROXY_WORKER_DISABLED)
 
644
                    ap_rputs("Dis", r);
 
645
                else if (worker->s->status & PROXY_WORKER_IN_ERROR)
 
646
                    ap_rputs("Err", r);
 
647
                else if (worker->s->status & PROXY_WORKER_INITIALIZED)
 
648
                    ap_rputs("Ok", r);
 
649
                else
 
650
                    ap_rputs("-", r);
 
651
                ap_rputs("</td></tr>\n", r);
 
652
 
 
653
                ++worker;
 
654
            }
 
655
            ap_rputs("</table>\n", r);
 
656
            ++balancer;
 
657
        }
 
658
        ap_rputs("<hr />\n", r);
 
659
        if (wsel && bsel) {
 
660
            ap_rputs("<h3>Edit worker settings for ", r);
 
661
            ap_rvputs(r, wsel->name, "</h3>\n", NULL);
 
662
            ap_rvputs(r, "<form method=\"GET\" action=\"", NULL);
 
663
            ap_rvputs(r, r->uri, "\">\n<dl>", NULL);
 
664
            ap_rputs("<table><tr><td>Load factor:</td><td><input name=\"lf\" type=text ", r);
 
665
            ap_rprintf(r, "value=\"%d\"></td><tr>\n", wsel->s->lbfactor);
 
666
            ap_rputs("<tr><td>Route:</td><td><input name=\"wr\" type=text ", r);
 
667
            ap_rvputs(r, "value=\"", wsel->route, NULL);
 
668
            ap_rputs("\"></td><tr>\n", r);
 
669
            ap_rputs("<tr><td>Route Redirect:</td><td><input name=\"rr\" type=text ", r);
 
670
            ap_rvputs(r, "value=\"", wsel->redirect, NULL);
 
671
            ap_rputs("\"></td><tr>\n", r);
 
672
            ap_rputs("<tr><td>Status:</td><td>Disabled: <input name=\"dw\" value=\"Disable\" type=radio", r);
 
673
            if (wsel->s->status & PROXY_WORKER_DISABLED)
 
674
                ap_rputs(" checked", r);
 
675
            ap_rputs("> | Enabled: <input name=\"dw\" value=\"Enable\" type=radio", r);
 
676
            if (!(wsel->s->status & PROXY_WORKER_DISABLED))
 
677
                ap_rputs(" checked", r);
 
678
            ap_rputs("></td><tr>\n", r);
 
679
            ap_rputs("<tr><td colspan=2><input type=submit value=\"Submit\"></td></tr>\n", r);
 
680
            ap_rvputs(r, "</table>\n<input type=hidden name=\"w\" ",  NULL);
 
681
            ap_rvputs(r, "value=\"", ap_escape_uri(r->pool, wsel->name), "\">\n", NULL);
 
682
            ap_rvputs(r, "<input type=hidden name=\"b\" ", NULL);
 
683
            ap_rvputs(r, "value=\"", bsel->name + sizeof("balancer://") - 1,
 
684
                      "\">\n</form>\n", NULL);
 
685
            ap_rputs("<hr />\n", r);
 
686
        }
 
687
        else if (bsel) {
 
688
            ap_rputs("<h3>Edit balancer settings for ", r);
 
689
            ap_rvputs(r, bsel->name, "</h3>\n", NULL);
 
690
            ap_rvputs(r, "<form method=\"GET\" action=\"", NULL);
 
691
            ap_rvputs(r, r->uri, "\">\n<dl>", NULL);
 
692
            ap_rputs("<table><tr><td>StickySession Identifier:</td><td><input name=\"ss\" type=text ", r);
 
693
            if (bsel->sticky)
 
694
                ap_rvputs(r, "value=\"", bsel->sticky, "\"", NULL);
 
695
            ap_rputs("></td><tr>\n<tr><td>Timeout:</td><td><input name=\"tm\" type=text ", r);
 
696
            ap_rprintf(r, "value=\"%" APR_TIME_T_FMT "\"></td></tr>\n",
 
697
                       apr_time_sec(bsel->timeout));
 
698
            ap_rputs("<tr><td>Failover Attempts:</td><td><input name=\"fa\" type=text ", r);
 
699
            ap_rprintf(r, "value=\"%d\"></td></tr>\n",
 
700
                       bsel->max_attempts);
 
701
            ap_rputs("<tr><td>LB Method:</td><td><select name=\"lm\">", r);
 
702
            {
 
703
                apr_array_header_t *methods;
 
704
                ap_list_provider_names_t *method;
 
705
                int i;
 
706
                methods = ap_list_provider_names(r->pool, PROXY_LBMETHOD, "0");
 
707
                method = (ap_list_provider_names_t *)methods->elts;
 
708
                for (i = 0; i < methods->nelts; i++) {
 
709
                    ap_rprintf(r, "<option value=\"%s\" %s>%s</option>", method->provider_name,
 
710
                       (!strcasecmp(bsel->lbmethod->name, method->provider_name)) ? "selected" : "",
 
711
                       method->provider_name);
 
712
                    method++;
 
713
                }
 
714
            }
 
715
            ap_rputs("</select></td></tr>\n", r);
 
716
            ap_rputs("<tr><td colspan=2><input type=submit value=\"Submit\"></td></tr>\n", r);
 
717
            ap_rvputs(r, "</table>\n<input type=hidden name=\"b\" ", NULL);
 
718
            ap_rvputs(r, "value=\"", bsel->name + sizeof("balancer://") - 1,
 
719
                      "\">\n</form>\n", NULL);
 
720
            ap_rputs("<hr />\n", r);
 
721
        }
 
722
        ap_rputs(ap_psignature("",r), r);
 
723
        ap_rputs("</body></html>\n", r);
 
724
    }
 
725
    return OK;
 
726
}
 
727
 
 
728
static void child_init(apr_pool_t *p, server_rec *s)
 
729
{
 
730
    while (s) {
 
731
        void *sconf = s->module_config;
 
732
        proxy_server_conf *conf;
 
733
        proxy_balancer *balancer;
 
734
        int i;
 
735
        conf = (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module);
 
736
 
 
737
        /* Initialize shared scoreboard data */
 
738
        balancer = (proxy_balancer *)conf->balancers->elts;
 
739
        for (i = 0; i < conf->balancers->nelts; i++) {
 
740
            init_balancer_members(conf, s, balancer);
 
741
            balancer++;
 
742
        }
 
743
        s = s->next;
 
744
    }
 
745
 
 
746
}
 
747
 
 
748
/*
 
749
 * The idea behind the find_best_byrequests scheduler is the following:
 
750
 *
 
751
 * lbfactor is "how much we expect this worker to work", or "the worker's
 
752
 * normalized work quota".
 
753
 *
 
754
 * lbstatus is "how urgent this worker has to work to fulfill its quota
 
755
 * of work".
 
756
 *
 
757
 * We distribute each worker's work quota to the worker, and then look
 
758
 * which of them needs to work most urgently (biggest lbstatus).  This
 
759
 * worker is then selected for work, and its lbstatus reduced by the
 
760
 * total work quota we distributed to all workers.  Thus the sum of all
 
761
 * lbstatus does not change.(*)
 
762
 *
 
763
 * If some workers are disabled, the others will
 
764
 * still be scheduled correctly.
 
765
 *
 
766
 * If a balancer is configured as follows:
 
767
 *
 
768
 * worker     a    b    c    d
 
769
 * lbfactor  25   25   25   25
 
770
 *
 
771
 * And b gets disabled, the following schedule is produced:
 
772
 *
 
773
 *    a c d a c d a c d ...
 
774
 *
 
775
 * Note that the above lbfactor setting is the *exact* same as:
 
776
 *
 
777
 * worker     a    b    c    d
 
778
 * lbfactor   1    1    1    1
 
779
 *
 
780
 * Asymmetric configurations work as one would expect. For
 
781
 * example:
 
782
 *
 
783
 * worker     a    b    c    d
 
784
 * lbfactor   1    1    1    2
 
785
 *
 
786
 * would have a, b and c all handling about the same
 
787
 * amount of load with d handling twice what a or b
 
788
 * or c handles individually. So we could see:
 
789
 *
 
790
 *   b a d c d a c d b d ...
 
791
 *
 
792
 */
 
793
 
 
794
static proxy_worker *find_best_byrequests(proxy_balancer *balancer,
 
795
                                request_rec *r)
 
796
{
 
797
    int i;
 
798
    int total_factor = 0;
 
799
    proxy_worker *worker = (proxy_worker *)balancer->workers->elts;
 
800
    proxy_worker *mycandidate = NULL;
 
801
 
 
802
 
 
803
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
 
804
                 "proxy: Entering byrequests for BALANCER (%s)",
 
805
                 balancer->name);
 
806
 
 
807
    /* First try to see if we have available candidate */
 
808
    for (i = 0; i < balancer->workers->nelts; i++) {
 
809
        /* If the worker is in error state run
 
810
         * retry on that worker. It will be marked as
 
811
         * operational if the retry timeout is elapsed.
 
812
         * The worker might still be unusable, but we try
 
813
         * anyway.
 
814
         */
 
815
        if (!PROXY_WORKER_IS_USABLE(worker))
 
816
            ap_proxy_retry_worker("BALANCER", worker, r->server);
 
817
        /* Take into calculation only the workers that are
 
818
         * not in error state or not disabled.
 
819
         */
 
820
        if (PROXY_WORKER_IS_USABLE(worker)) {
 
821
            worker->s->lbstatus += worker->s->lbfactor;
 
822
            total_factor += worker->s->lbfactor;
 
823
            if (!mycandidate || worker->s->lbstatus > mycandidate->s->lbstatus)
 
824
                mycandidate = worker;
 
825
        }
 
826
        worker++;
 
827
    }
 
828
 
 
829
    if (mycandidate) {
 
830
        mycandidate->s->lbstatus -= total_factor;
 
831
        mycandidate->s->elected++;
 
832
    }
 
833
 
 
834
    return mycandidate;
 
835
}
 
836
 
 
837
/*
 
838
 * The idea behind the find_best_bytraffic scheduler is the following:
 
839
 *
 
840
 * We know the amount of traffic (bytes in and out) handled by each
 
841
 * worker. We normalize that traffic by each workers' weight. So assuming
 
842
 * a setup as below:
 
843
 *
 
844
 * worker     a    b    c
 
845
 * lbfactor   1    1    3
 
846
 *
 
847
 * the scheduler will allow worker c to handle 3 times the
 
848
 * traffic of a and b. If each request/response results in the
 
849
 * same amount of traffic, then c would be accessed 3 times as
 
850
 * often as a or b. If, for example, a handled a request that
 
851
 * resulted in a large i/o bytecount, then b and c would be
 
852
 * chosen more often, to even things out.
 
853
 */
 
854
static proxy_worker *find_best_bytraffic(proxy_balancer *balancer,
 
855
                                         request_rec *r)
 
856
{
 
857
    int i;
 
858
    apr_off_t mytraffic = 0;
 
859
    apr_off_t curmin = 0;
 
860
    proxy_worker *worker = (proxy_worker *)balancer->workers->elts;
 
861
    proxy_worker *mycandidate = NULL;
 
862
 
 
863
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
 
864
                 "proxy: Entering bytraffic for BALANCER (%s)",
 
865
                 balancer->name);
 
866
 
 
867
    /* First try to see if we have available candidate */
 
868
    for (i = 0; i < balancer->workers->nelts; i++) {
 
869
        /* If the worker is in error state run
 
870
         * retry on that worker. It will be marked as
 
871
         * operational if the retry timeout is elapsed.
 
872
         * The worker might still be unusable, but we try
 
873
         * anyway.
 
874
         */
 
875
        if (!PROXY_WORKER_IS_USABLE(worker))
 
876
            ap_proxy_retry_worker("BALANCER", worker, r->server);
 
877
        /* Take into calculation only the workers that are
 
878
         * not in error state or not disabled.
 
879
         */
 
880
        if (PROXY_WORKER_IS_USABLE(worker)) {
 
881
            mytraffic = (worker->s->transferred/worker->s->lbfactor) +
 
882
                        (worker->s->read/worker->s->lbfactor);
 
883
            if (!mycandidate || mytraffic < curmin) {
 
884
                mycandidate = worker;
 
885
                curmin = mytraffic;
 
886
            }
 
887
        }
 
888
        worker++;
 
889
    }
 
890
 
 
891
    if (mycandidate) {
 
892
        mycandidate->s->elected++;
 
893
    }
 
894
 
 
895
    return mycandidate;
 
896
}
 
897
 
 
898
/*
 
899
 * How to add additional lbmethods:
 
900
 *   1. Create func which determines "best" candidate worker
 
901
 *      (eg: find_best_bytraffic, above)
 
902
 *   2. Register it as a provider.
 
903
 */
 
904
static const proxy_balancer_method byrequests =
 
905
{
 
906
    "byrequests",
 
907
    &find_best_byrequests,
 
908
    NULL
 
909
};
 
910
 
 
911
static const proxy_balancer_method bytraffic =
 
912
{
 
913
    "bytraffic",
 
914
    &find_best_bytraffic,
 
915
    NULL
 
916
};
 
917
 
 
918
static void ap_proxy_balancer_register_hook(apr_pool_t *p)
 
919
{
 
920
    /* Only the mpm_winnt has child init hook handler.
 
921
     * make sure that we are called after the mpm
 
922
     * initializes and after the mod_proxy
 
923
     */
 
924
    static const char *const aszPred[] = { "mpm_winnt.c", "mod_proxy.c", NULL};
 
925
     /* manager handler */
 
926
    ap_hook_handler(balancer_handler, NULL, NULL, APR_HOOK_FIRST);
 
927
    ap_hook_child_init(child_init, aszPred, NULL, APR_HOOK_MIDDLE);
 
928
    proxy_hook_pre_request(proxy_balancer_pre_request, NULL, NULL, APR_HOOK_FIRST);
 
929
    proxy_hook_post_request(proxy_balancer_post_request, NULL, NULL, APR_HOOK_FIRST);
 
930
    proxy_hook_canon_handler(proxy_balancer_canon, NULL, NULL, APR_HOOK_FIRST);
 
931
    ap_register_provider(p, PROXY_LBMETHOD, "bytraffic", "0", &bytraffic);
 
932
    ap_register_provider(p, PROXY_LBMETHOD, "byrequests", "0", &byrequests);
 
933
}
 
934
 
 
935
module AP_MODULE_DECLARE_DATA proxy_balancer_module = {
 
936
    STANDARD20_MODULE_STUFF,
 
937
    NULL,       /* create per-directory config structure */
 
938
    NULL,       /* merge per-directory config structures */
 
939
    NULL,       /* create per-server config structure */
 
940
    NULL,       /* merge per-server config structures */
 
941
    NULL,       /* command apr_table_t */
 
942
    ap_proxy_balancer_register_hook /* register hooks */
 
943
};