]> granicus.if.org Git - apache/blob - modules/proxy/mod_proxy_hcheck.c
Netwarep proxy makefiles
[apache] / modules / proxy / mod_proxy_hcheck.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 #include "mod_proxy.h"
17 #include "mod_watchdog.h"
18 #include "ap_slotmem.h"
19 #include "ap_expr.h"
20 #if APR_HAS_THREADS
21 #include "apr_thread_pool.h"
22 #endif
23
24 module AP_MODULE_DECLARE_DATA proxy_hcheck_module;
25
26 #define HCHECK_WATHCHDOG_NAME ("_proxy_hcheck_")
27 #define HC_THREADPOOL_SIZE (16)
28
29 /* Why? So we can easily set/clear HC_USE_THREADS during dev testing */
30 #if APR_HAS_THREADS
31 #define HC_USE_THREADS 1
32 #else
33 #define HC_USE_THREADS 0
34 typedef void apr_thread_pool_t;
35 #endif
36
37 typedef struct {
38     char *name;
39     hcmethod_t method;
40     int passes;
41     int fails;
42     apr_interval_time_t interval;
43     char *hurl;
44     char *hcexpr;
45 } hc_template_t;
46
47 typedef struct {
48     char *expr;
49     ap_expr_info_t *pexpr;       /* parsed expression */
50 } hc_condition_t;
51
52 typedef struct {
53     apr_pool_t *p;
54     apr_bucket_alloc_t *ba;
55     apr_array_header_t *templates;
56     apr_table_t *conditions;
57     ap_watchdog_t *watchdog;
58     apr_hash_t *hcworkers;
59     apr_thread_pool_t *hctp;
60     int tpsize;
61     server_rec *s;
62 } sctx_t;
63
64 /* Used in the HC worker via the context field */
65 typedef struct {
66     char *path;      /* The path of the original worker URL */
67     char *req;       /* pre-formatted HTTP/AJP request */
68     proxy_worker *w; /* Pointer to the actual worker */
69 } wctx_t;
70
71 typedef struct {
72     apr_pool_t *ptemp;
73     sctx_t *ctx;
74     proxy_worker *worker;
75     apr_time_t now;
76 } baton_t;
77
78 static void *hc_create_config(apr_pool_t *p, server_rec *s)
79 {
80     sctx_t *ctx = (sctx_t *) apr_palloc(p, sizeof(sctx_t));
81     apr_pool_create(&ctx->p, p);
82     ctx->ba = apr_bucket_alloc_create(p);
83     ctx->templates = apr_array_make(p, 10, sizeof(hc_template_t));
84     ctx->conditions = apr_table_make(p, 10);
85     ctx->hcworkers = apr_hash_make(p);
86     ctx->tpsize = HC_THREADPOOL_SIZE;
87     ctx->s = s;
88
89     return ctx;
90 }
91
92 /*
93  * This serves double duty by not only validating (and creating)
94  * the health-check template, but also ties into set_worker_param()
95  * which does the actual setting of worker params in shm.
96  */
97 static const char *set_worker_hc_param(apr_pool_t *p,
98                                     server_rec *s,
99                                     proxy_worker *worker,
100                                     const char *key,
101                                     const char *val,
102                                     void *v)
103 {
104     int ival;
105     hc_template_t *temp;
106     sctx_t *ctx = (sctx_t *) ap_get_module_config(s->module_config,
107                                                   &proxy_hcheck_module);
108     if (!worker && !v) {
109         return "Bad call to set_worker_hc_param()";
110     }
111     temp = (hc_template_t *)v;
112     if (!strcasecmp(key, "hctemplate")) {
113         hc_template_t *template;
114         template = (hc_template_t *)ctx->templates->elts;
115         for (ival = 0; ival < ctx->templates->nelts; ival++, template++) {
116             if (!ap_casecmpstr(template->name, val)) {
117                 if (worker) {
118                     worker->s->method = template->method;
119                     worker->s->interval = template->interval;
120                     worker->s->passes = template->passes;
121                     worker->s->fails = template->fails;
122                     PROXY_STRNCPY(worker->s->hcuri, template->hurl);
123                     PROXY_STRNCPY(worker->s->hcexpr, template->hcexpr);
124                 } else {
125                     temp->method = template->method;
126                     temp->interval = template->interval;
127                     temp->passes = template->passes;
128                     temp->fails = template->fails;
129                     temp->hurl = apr_pstrdup(p, template->hurl);
130                     temp->hcexpr = apr_pstrdup(p, template->hcexpr);
131                 }
132                 return NULL;
133             }
134         }
135         return apr_psprintf(p, "Unknown ProxyHCTemplate name: %s", val);
136     }
137     else if (!strcasecmp(key, "hcmethod")) {
138         proxy_hcmethods_t *method = proxy_hcmethods;
139         for (; method->name; method++) {
140             if (!ap_casecmpstr(val, method->name)) {
141                 if (!method->implemented) {
142                     return apr_psprintf(p, "Health check method %s not (yet) implemented",
143                                         val);
144                 }
145                 if (worker) {
146                     worker->s->method = method->method;
147                 } else {
148                     temp->method = method->method;
149                 }
150                 return NULL;
151             }
152         }
153         return "Unknown method";
154     }
155     else if (!strcasecmp(key, "hcinterval")) {
156         ival = atoi(val);
157         if (ival < HCHECK_WATHCHDOG_INTERVAL)
158             return apr_psprintf(p, "Interval must be a positive value greater than %d seconds",
159                                 HCHECK_WATHCHDOG_INTERVAL);
160         if (worker) {
161             worker->s->interval = apr_time_from_sec(ival);
162         } else {
163             temp->interval = apr_time_from_sec(ival);
164         }
165     }
166     else if (!strcasecmp(key, "hcpasses")) {
167         ival = atoi(val);
168         if (ival < 0)
169             return "Passes must be a positive value";
170         if (worker) {
171             worker->s->passes = ival;
172         } else {
173             temp->passes = ival;
174         }
175     }
176     else if (!strcasecmp(key, "hcfails")) {
177         ival = atoi(val);
178         if (ival < 0)
179             return "Fails must be a positive value";
180         if (worker) {
181             worker->s->fails = ival;
182         } else {
183             temp->fails = ival;
184         }
185     }
186     else if (!strcasecmp(key, "hcuri")) {
187         if (strlen(val) >= sizeof(worker->s->hcuri))
188             return apr_psprintf(p, "Health check uri length must be < %d characters",
189                     (int)sizeof(worker->s->hcuri));
190         if (worker) {
191             PROXY_STRNCPY(worker->s->hcuri, val);
192         } else {
193             temp->hurl = apr_pstrdup(p, val);
194         }
195     }
196     else if (!strcasecmp(key, "hcexpr")) {
197         hc_condition_t *cond;
198         cond = (hc_condition_t *)apr_table_get(ctx->conditions, val);
199         if (!cond) {
200             return apr_psprintf(p, "Unknown health check condition expr: %s", val);
201         }
202         /* This check is wonky... a known expr can't be this big. Check anyway */
203         if (strlen(val) >= sizeof(worker->s->hcexpr))
204             return apr_psprintf(p, "Health check uri length must be < %d characters",
205                     (int)sizeof(worker->s->hcexpr));
206         if (worker) {
207             PROXY_STRNCPY(worker->s->hcexpr, val);
208         } else {
209             temp->hcexpr = apr_pstrdup(p, val);
210         }
211     }
212   else {
213         return "unknown Worker hcheck parameter";
214     }
215     return NULL;
216 }
217
218 static const char *set_hc_condition(cmd_parms *cmd, void *dummy, const char *arg)
219 {
220     char *name = NULL;
221     char *expr;
222     sctx_t *ctx;
223     hc_condition_t *cond;
224
225     const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
226     if (err)
227         return err;
228     ctx = (sctx_t *) ap_get_module_config(cmd->server->module_config,
229                                           &proxy_hcheck_module);
230
231     name = ap_getword_conf(cmd->pool, &arg);
232     if (!*name) {
233         return apr_pstrcat(cmd->temp_pool, "Missing expression name for ",
234                            cmd->cmd->name, NULL);
235     }
236     if (strlen(name) > (PROXY_WORKER_MAX_SCHEME_SIZE - 1)) {
237         return apr_psprintf(cmd->temp_pool, "Expression name limited to %d characters",
238                            (PROXY_WORKER_MAX_SCHEME_SIZE - 1));
239     }
240     /* get expr. Allow fancy new {...} quoting style */
241     expr = ap_getword_conf2(cmd->temp_pool, &arg);
242     if (!*expr) {
243         return apr_pstrcat(cmd->temp_pool, "Missing expression for ",
244                            cmd->cmd->name, NULL);
245     }
246     cond = apr_palloc(cmd->pool, sizeof(hc_condition_t));
247     cond->pexpr = ap_expr_parse_cmd(cmd, expr, 0, &err, NULL);
248     if (err) {
249         return apr_psprintf(cmd->temp_pool, "Could not parse expression \"%s\": %s",
250                             expr, err);
251     }
252     cond->expr = apr_pstrdup(cmd->pool, expr);
253     apr_table_setn(ctx->conditions, name, (void *)cond);
254     expr = ap_getword_conf(cmd->temp_pool, &arg);
255     if (*expr) {
256         return "error: extra parameter(s)";
257     }
258
259     return NULL;
260 }
261
262 static const char *set_hc_template(cmd_parms *cmd, void *dummy, const char *arg)
263 {
264     char *name = NULL;
265     char *word, *val;
266     hc_template_t *template;
267     sctx_t *ctx;
268
269     const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
270     if (err)
271         return err;
272     ctx = (sctx_t *) ap_get_module_config(cmd->server->module_config,
273                                           &proxy_hcheck_module);
274
275     name = ap_getword_conf(cmd->temp_pool, &arg);
276     if (!*name) {
277         return apr_pstrcat(cmd->temp_pool, "Missing template name for ",
278                            cmd->cmd->name, NULL);
279     }
280
281     template = (hc_template_t *)apr_array_push(ctx->templates);
282
283     template->name = apr_pstrdup(cmd->pool, name);
284     template->method = template->passes = template->fails = 1;
285     template->interval = apr_time_from_sec(HCHECK_WATHCHDOG_DEFAULT_INTERVAL);
286     template->hurl = NULL;
287     template->hcexpr = NULL;
288     while (*arg) {
289         word = ap_getword_conf(cmd->pool, &arg);
290         val = strchr(word, '=');
291         if (!val) {
292             return "Invalid ProxyHCTemplate parameter. Parameter must be "
293                    "in the form 'key=value'";
294         }
295         else
296             *val++ = '\0';
297         err = set_worker_hc_param(cmd->pool, ctx->s, NULL, word, val, template);
298
299         if (err) {
300             /* get rid of recently pushed (bad) template */
301             apr_array_pop(ctx->templates);
302             return apr_pstrcat(cmd->temp_pool, "ProxyHCTemplate: ", err, " ", word, "=", val, "; ", name, NULL);
303         }
304         /* No error means we have a valid template */
305     }
306
307     return NULL;
308 }
309
310 #if HC_USE_THREADS
311 static const char *set_hc_tpsize (cmd_parms *cmd, void *dummy, const char *arg)
312 {
313     sctx_t *ctx;
314
315     const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
316     if (err)
317         return err;
318     ctx = (sctx_t *) ap_get_module_config(cmd->server->module_config,
319                                           &proxy_hcheck_module);
320
321     ctx->tpsize = atoi(arg);
322     if (ctx->tpsize < 0)
323         return "Invalid ProxyHCTPsize parameter. Parameter must be "
324                ">= 0";
325     return NULL;
326 }
327 #endif
328
329 /*
330  * Create a dummy request rec, simply so we can use ap_expr.
331  * Use our short-lived poll for bucket_alloc
332  */
333 static request_rec *create_request_rec(apr_pool_t *p1, conn_rec *conn, const char *method)
334 {
335     request_rec *r;
336     apr_pool_t *p;
337     apr_bucket_alloc_t *ba;
338     apr_pool_create(&p, p1);
339     apr_pool_tag(p, "request");
340     r = apr_pcalloc(p, sizeof(request_rec));
341     ba = apr_bucket_alloc_create(p);
342     r->pool            = p;
343     r->connection      = conn;
344     r->connection->bucket_alloc = ba;
345     r->server          = conn->base_server;
346
347     r->user            = NULL;
348     r->ap_auth_type    = NULL;
349
350     r->allowed_methods = ap_make_method_list(p, 2);
351
352     r->headers_in      = apr_table_make(r->pool, 25);
353     r->trailers_in     = apr_table_make(r->pool, 5);
354     r->subprocess_env  = apr_table_make(r->pool, 25);
355     r->headers_out     = apr_table_make(r->pool, 12);
356     r->err_headers_out = apr_table_make(r->pool, 5);
357     r->trailers_out    = apr_table_make(r->pool, 5);
358     r->notes           = apr_table_make(r->pool, 5);
359
360     r->kept_body       = apr_brigade_create(r->pool, r->connection->bucket_alloc);
361     r->request_config  = ap_create_request_config(r->pool);
362     /* Must be set before we run create request hook */
363
364     r->proto_output_filters = conn->output_filters;
365     r->output_filters  = r->proto_output_filters;
366     r->proto_input_filters = conn->input_filters;
367     r->input_filters   = r->proto_input_filters;
368     r->per_dir_config  = r->server->lookup_defaults;
369
370     r->sent_bodyct     = 0;                      /* bytect isn't for body */
371
372     r->read_length     = 0;
373     r->read_body       = REQUEST_NO_BODY;
374
375     r->status          = HTTP_OK;  /* Until further notice */
376     r->header_only     = 1;
377     r->the_request     = NULL;
378
379     /* Begin by presuming any module can make its own path_info assumptions,
380      * until some module interjects and changes the value.
381      */
382     r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
383
384     r->useragent_addr = conn->client_addr;
385     r->useragent_ip = conn->client_ip;
386
387
388     /* Time to populate r with the data we have. */
389     r->method = method;
390     /* Provide quick information about the request method as soon as known */
391     r->method_number = ap_method_number_of(r->method);
392     if (r->method_number == M_GET && r->method[0] == 'G') {
393         r->header_only = 0;
394     }
395
396     r->protocol = "HTTP/1.0";
397     r->proto_num = HTTP_VERSION(1, 0);
398
399     r->hostname = NULL;
400
401     return r;
402 }
403
404 static proxy_worker *hc_get_hcworker(sctx_t *ctx, proxy_worker *worker,
405                                      apr_pool_t *p)
406 {
407     proxy_worker *hc = NULL;
408     const char* wptr;
409     apr_port_t port;
410
411     wptr = apr_psprintf(ctx->p, "%pp", worker);
412     hc = (proxy_worker *)apr_hash_get(ctx->hcworkers, wptr, APR_HASH_KEY_STRING);
413     port = (worker->s->port ? worker->s->port : ap_proxy_port_of_scheme(worker->s->scheme));
414     if (!hc) {
415         apr_uri_t uri;
416         apr_status_t rv;
417         const char *url = worker->s->name;
418         wctx_t *wctx = apr_pcalloc(ctx->p, sizeof(wctx_t));
419
420         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(03248)
421                      "Creating hc worker %s for %s://%s:%d",
422                      wptr, worker->s->scheme, worker->s->hostname,
423                      (int)port);
424
425         ap_proxy_define_worker(ctx->p, &hc, NULL, NULL, worker->s->name, 0);
426         PROXY_STRNCPY(hc->s->name,     wptr);
427         PROXY_STRNCPY(hc->s->hostname, worker->s->hostname);
428         PROXY_STRNCPY(hc->s->scheme,   worker->s->scheme);
429         hc->hash.def = hc->s->hash.def = ap_proxy_hashfunc(hc->s->name, PROXY_HASHFUNC_DEFAULT);
430         hc->hash.fnv = hc->s->hash.fnv = ap_proxy_hashfunc(hc->s->name, PROXY_HASHFUNC_FNV);
431         hc->s->port = port;
432         /* Do not disable worker in case of errors */
433         hc->s->status |= PROXY_WORKER_IGNORE_ERRORS;
434         /* Mark as the "generic" worker */
435         hc->s->status |= PROXY_WORKER_GENERIC;
436         ap_proxy_initialize_worker(hc, ctx->s, ctx->p);
437         hc->s->is_address_reusable = worker->s->is_address_reusable;
438         hc->s->disablereuse = worker->s->disablereuse;
439         hc->s->method = worker->s->method;
440         rv = apr_uri_parse(p, url, &uri);
441         if (rv == APR_SUCCESS) {
442             wctx->path = apr_pstrdup(ctx->p, uri.path);
443         }
444         wctx->w = worker;
445         hc->context = wctx;
446         apr_hash_set(ctx->hcworkers, wptr, APR_HASH_KEY_STRING, hc);
447     }
448     /* This *could* have changed via the Balancer Manager */
449     /* TODO */
450     if (hc->s->method != worker->s->method) {
451         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO()
452                      "Updating hc worker %s for %s://%s:%d",
453                      wptr, worker->s->scheme, worker->s->hostname,
454                      (int)port);
455         hc->s->method = worker->s->method;
456         apr_hash_set(ctx->hcworkers, wptr, APR_HASH_KEY_STRING, hc);
457     }
458     return hc;
459 }
460
461 static int hc_determine_connection(sctx_t *ctx, proxy_worker *worker) {
462     apr_status_t rv = APR_SUCCESS;
463     int will_reuse = worker->s->is_address_reusable && !worker->s->disablereuse;
464     /*
465      * normally, this is done in ap_proxy_determine_connection().
466      * TODO: Look at using ap_proxy_determine_connection() with a
467      * fake request_rec
468      */
469     if (!worker->cp->addr || !will_reuse) {
470         rv = apr_sockaddr_info_get(&(worker->cp->addr), worker->s->hostname, APR_UNSPEC,
471                                    worker->s->port, 0, ctx->p);
472         if (rv != APR_SUCCESS) {
473             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(03249)
474                          "DNS lookup failure for: %s:%d",
475                          worker->s->hostname, (int)worker->s->port);
476         }
477     }
478     return (rv == APR_SUCCESS ? OK : !OK);
479 }
480
481 static apr_status_t hc_init_worker(sctx_t *ctx, proxy_worker *worker) {
482     apr_status_t rv = APR_SUCCESS;
483     /*
484      * Since this is the watchdog, workers never actually handle a
485      * request here, and so the local data isn't initialized (of
486      * course, the shared memory is). So we need to bootstrap
487      * worker->cp. Note, we only need do this once.
488      */
489     if (!worker->cp) {
490         rv = ap_proxy_initialize_worker(worker, ctx->s, ctx->p);
491         if (rv != APR_SUCCESS) {
492             ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ctx->s, APLOGNO(03250) "Cannot init worker");
493             return rv;
494         }
495         rv = (hc_determine_connection(ctx, worker) == OK ? APR_SUCCESS : APR_EGENERAL);
496     }
497     return rv;
498 }
499
500 static apr_status_t backend_cleanup(const char *proxy_function, proxy_conn_rec *backend,
501                                     server_rec *s, int status)
502 {
503     if (backend) {
504         backend->close = 1;
505         ap_proxy_release_connection(proxy_function, backend, s);
506     }
507     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03251)
508                      "Health check %s Status (%d) for %s.",
509                      ap_proxy_show_hcmethod(backend->worker->s->method),
510                      status,
511                      backend->worker->s->name);
512     if (status != OK) {
513         return APR_EGENERAL;
514     }
515     return APR_SUCCESS;
516 }
517
518 static int hc_get_backend(const char *proxy_function, proxy_conn_rec **backend,
519                           proxy_worker *hc, sctx_t *ctx)
520 {
521     int status;
522     status = ap_proxy_acquire_connection(proxy_function, backend, hc, ctx->s);
523     if (status == OK) {
524         (*backend)->addr = hc->cp->addr;
525         (*backend)->pool = ctx->p;
526         (*backend)->hostname = hc->s->hostname;
527         if (strcmp(hc->s->scheme, "https") == 0) {
528             if (!ap_proxy_ssl_enable(NULL)) {
529                 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ctx->s, APLOGNO(03252)
530                               "mod_ssl not configured?");
531                 return !OK;
532             }
533             (*backend)->is_ssl = 1;
534         }
535
536     }
537     status = hc_determine_connection(ctx, hc);
538     if (status == OK) {
539         (*backend)->addr = hc->cp->addr;
540     }
541     return status;
542 }
543
544 static apr_status_t hc_check_tcp(sctx_t *ctx, apr_pool_t *ptemp, proxy_worker *worker)
545 {
546     int status;
547     proxy_conn_rec *backend = NULL;
548     proxy_worker *hc;
549
550     hc = hc_get_hcworker(ctx, worker, ptemp);
551
552     status = hc_get_backend("HCTCP", &backend, hc, ctx);
553     if (status == OK) {
554         backend->addr = hc->cp->addr;
555         status = ap_proxy_connect_backend("HCTCP", backend, hc, ctx->s);
556         if (status == OK) {
557             status = (ap_proxy_is_socket_connected(backend->sock) ? OK : !OK);
558         }
559     }
560     return backend_cleanup("HCTCP", backend, ctx->s, status);
561 }
562
563 static void hc_send(sctx_t *ctx, apr_pool_t *ptemp, const char *out, proxy_conn_rec *backend)
564 {
565     apr_bucket_brigade *tmp_bb = apr_brigade_create(ptemp, ctx->ba);
566     ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ctx->s, "%s", out);
567     APR_BRIGADE_INSERT_TAIL(tmp_bb, apr_bucket_pool_create(out, strlen(out), ptemp,
568                             ctx->ba));
569     APR_BRIGADE_INSERT_TAIL(tmp_bb, apr_bucket_flush_create(ctx->ba));
570     ap_pass_brigade(backend->connection->output_filters, tmp_bb);
571     apr_brigade_destroy(tmp_bb);
572 }
573
574 static int hc_read_headers(sctx_t *ctx, request_rec *r)
575 {
576     char buffer[HUGE_STRING_LEN];
577     int len;
578
579     len = ap_getline(buffer, sizeof(buffer), r, 1);
580     if (len <= 0) {
581         return !OK;
582     }
583     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(03254)
584             "%s", buffer);
585     /* for the below, see ap_proxy_http_process_response() */
586     if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
587         int major, minor;
588         char keepchar;
589         int proxy_status = OK;
590         const char *proxy_status_line = NULL;
591
592         major = buffer[5] - '0';
593         minor = buffer[7] - '0';
594         if ((major != 1) || (len >= sizeof(buffer)-1)) {
595             return !OK;
596         }
597
598         keepchar = buffer[12];
599         buffer[12] = '\0';
600         proxy_status = atoi(&buffer[9]);
601         if (keepchar != '\0') {
602             buffer[12] = keepchar;
603         } else {
604             buffer[12] = ' ';
605             buffer[13] = '\0';
606         }
607         proxy_status_line = apr_pstrdup(r->pool, &buffer[9]);
608         r->status = proxy_status;
609         r->status_line = proxy_status_line;
610     } else {
611         return !OK;
612     }
613     /* OK, 1st line is OK... scarf in the headers */
614     while ((len = ap_getline(buffer, sizeof(buffer), r, 1)) > 0) {
615         char *value, *end;
616         if (!(value = strchr(buffer, ':'))) {
617             return !OK;
618         }
619         ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ctx->s, "%s", buffer);
620         *value = '\0';
621         ++value;
622         while (apr_isspace(*value))
623             ++value;            /* Skip to start of value   */
624         for (end = &value[strlen(value)-1]; end > value && apr_isspace(*end); --end)
625             *end = '\0';
626         apr_table_add(r->headers_out, buffer, value);
627     }
628     return OK;
629 }
630
631 static int hc_read_body (sctx_t *ctx, request_rec *r)
632 {
633     apr_status_t rv = APR_SUCCESS;
634     apr_bucket_brigade *bb;
635     int seen_eos = 0;
636
637     bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
638     do {
639         apr_bucket *bucket, *cpy;
640         apr_size_t len = HUGE_STRING_LEN;
641
642         rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_READBYTES,
643                             APR_BLOCK_READ, len);
644
645         if (rv != APR_SUCCESS) {
646             if (APR_STATUS_IS_TIMEUP(rv) || APR_STATUS_IS_EOF(rv)) {
647                 rv = APR_SUCCESS;
648                 break;
649             }
650             ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ctx->s, APLOGNO(03300)
651                           "Error reading response body");
652             break;
653         }
654
655         for (bucket = APR_BRIGADE_FIRST(bb);
656              bucket != APR_BRIGADE_SENTINEL(bb);
657              bucket = APR_BUCKET_NEXT(bucket))
658         {
659             if (APR_BUCKET_IS_EOS(bucket)) {
660                 seen_eos = 1;
661                 break;
662             }
663             if (APR_BUCKET_IS_FLUSH(bucket)) {
664                 continue;
665             }
666             rv =  apr_bucket_copy(bucket, &cpy);
667             if (rv != APR_SUCCESS) {
668                 break;
669             }
670             APR_BRIGADE_INSERT_TAIL(r->kept_body, cpy);
671         }
672         apr_brigade_cleanup(bb);
673     }
674     while (!seen_eos);
675     return (rv == APR_SUCCESS ? OK : !OK);
676 }
677
678 /*
679  * Send the HTTP OPTIONS, HEAD or GET request to the backend
680  * server associated w/ worker. If we have Conditions,
681  * then apply those to the resulting response, otherwise
682  * any status code 2xx or 3xx is considered "passing"
683  */
684 static apr_status_t hc_check_http(sctx_t *ctx, apr_pool_t *ptemp, proxy_worker *worker)
685 {
686     int status;
687     proxy_conn_rec *backend = NULL;
688     proxy_worker *hc;
689     conn_rec c;
690     request_rec *r;
691     wctx_t *wctx;
692     hc_condition_t *cond;
693     const char *method;
694
695     hc = hc_get_hcworker(ctx, worker, ptemp);
696     wctx = (wctx_t *)hc->context;
697
698     if ((status = hc_get_backend("HCOH", &backend, hc, ctx)) != OK) {
699         return backend_cleanup("HCOH", backend, ctx->s, status);
700     }
701     if ((status = ap_proxy_connect_backend("HCOH", backend, hc, ctx->s)) != OK) {
702         return backend_cleanup("HCOH", backend, ctx->s, status);
703     }
704
705     if (!backend->connection) {
706         if ((status = ap_proxy_connection_create("HCOH", backend, &c, ctx->s)) != OK) {
707             return backend_cleanup("HCOH", backend, ctx->s, status);
708         }
709     }
710     switch (hc->s->method) {
711         case OPTIONS:
712             if (!wctx->req) {
713                 wctx->req = apr_psprintf(ctx->p,
714                                    "OPTIONS * HTTP/1.0\r\nHost: %s:%d\r\n\r\n",
715                                     hc->s->hostname, (int)hc->s->port);
716             }
717             method = "OPTIONS";
718             break;
719
720         case HEAD:
721             if (!wctx->req) {
722                 wctx->req = apr_psprintf(ctx->p,
723                                    "HEAD %s%s%s HTTP/1.0\r\nHost: %s:%d\r\n\r\n",
724                                    (wctx->path ? wctx->path : ""),
725                                    (wctx->path && *hc->s->hcuri ? "/" : "" ),
726                                    (*hc->s->hcuri ? hc->s->hcuri : ""),
727                                    hc->s->hostname, (int)hc->s->port);
728             }
729             method = "HEAD";
730             break;
731
732         case GET:
733             if (!wctx->req) {
734                 wctx->req = apr_psprintf(ctx->p,
735                                    "GET %s%s%s HTTP/1.0\r\nHost: %s:%d\r\n\r\n",
736                                    (wctx->path ? wctx->path : ""),
737                                    (wctx->path && *hc->s->hcuri ? "/" : "" ),
738                                    (*hc->s->hcuri ? hc->s->hcuri : ""),
739                                    hc->s->hostname, (int)hc->s->port);
740             }
741             method = "GET";
742             break;
743
744         default:
745             return backend_cleanup("HCOH", backend, ctx->s, !OK);
746             break;
747     }
748
749     hc_send(ctx, ptemp, wctx->req, backend);
750
751     r = create_request_rec(ptemp, backend->connection, method);
752     if ((status = hc_read_headers(ctx, r)) != OK) {
753         return backend_cleanup("HCOH", backend, ctx->s, status);
754     }
755     if (hc->s->method == GET) {
756         if ((status = hc_read_body(ctx, r)) != OK) {
757             return backend_cleanup("HCOH", backend, ctx->s, status);
758         }
759     }
760
761     if (*worker->s->hcexpr &&
762             (cond = (hc_condition_t *)apr_table_get(ctx->conditions, worker->s->hcexpr)) != NULL) {
763         const char *err;
764         int ok = ap_expr_exec(r, cond->pexpr, &err);
765         if (ok > 0) {
766             status = OK;
767             ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
768                          "Condition %s for %s (%s): passed", worker->s->hcexpr,
769                          hc->s->name, worker->s->name);
770         } else if (ok < 0 || err) {
771             status = !OK;
772             ap_log_error(APLOG_MARK, APLOG_INFO, 0, ctx->s, APLOGNO(03301)
773                          "Error on checking condition %s for %s (%s): %s", worker->s->hcexpr,
774                          hc->s->name, worker->s->name, err);
775         } else {
776             ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
777                          "Condition %s for %s (%s) : failed", worker->s->hcexpr,
778                          hc->s->name, worker->s->name);
779             status = !OK;
780         }
781     } else if (r->status < 200 || r->status > 399) {
782         status = !OK;
783     }
784     return backend_cleanup("HCOH", backend, ctx->s, status);
785 }
786
787 static void *hc_check(apr_thread_t *thread, void *b)
788 {
789     baton_t *baton = (baton_t *)b;
790     sctx_t *ctx = baton->ctx;
791     apr_time_t now = baton->now;
792     proxy_worker *worker = baton->worker;
793     apr_pool_t *ptemp = baton->ptemp;
794     server_rec *s = ctx->s;
795     apr_status_t rv;
796     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03256)
797                  "%sHealth checking %s", (thread ? "Threaded " : ""), worker->s->name);
798
799     switch (worker->s->method) {
800         case TCP:
801             rv = hc_check_tcp(ctx, ptemp, worker);
802             break;
803
804         case OPTIONS:
805         case HEAD:
806         case GET:
807              rv = hc_check_http(ctx, ptemp, worker);
808              break;
809
810         default:
811             rv = APR_ENOTIMPL;
812             break;
813     }
814     if (rv == APR_ENOTIMPL) {
815         ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(03257)
816                          "Somehow tried to use unimplemented hcheck method: %d",
817                          (int)worker->s->method);
818         apr_pool_destroy(ptemp);
819         return NULL;
820     }
821     /* what state are we in ? */
822     if (PROXY_WORKER_IS_HCFAILED(worker)) {
823         if (rv == APR_SUCCESS) {
824             worker->s->pcount += 1;
825             if (worker->s->pcount >= worker->s->passes) {
826                 ap_proxy_set_wstatus(PROXY_WORKER_HC_FAIL_FLAG, 0, worker);
827                 ap_proxy_set_wstatus(PROXY_WORKER_IN_ERROR_FLAG, 0, worker);
828                 worker->s->pcount = 0;
829                 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03302)
830                              "%sHealth check ENABLING %s", (thread ? "Threaded " : ""),
831                              worker->s->name);
832
833             }
834         }
835     } else {
836         if (rv != APR_SUCCESS) {
837             worker->s->error_time = now;
838             worker->s->fcount += 1;
839             if (worker->s->fcount >= worker->s->fails) {
840                 ap_proxy_set_wstatus(PROXY_WORKER_HC_FAIL_FLAG, 1, worker);
841                 worker->s->fcount = 0;
842                 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03303)
843                              "%sHealth check DISABLING %s", (thread ? "Threaded " : ""),
844                              worker->s->name);
845             }
846         }
847     }
848     worker->s->updated = now;
849     apr_pool_destroy(ptemp);
850     return NULL;
851 }
852
853 static apr_status_t hc_watchdog_callback(int state, void *data,
854                                          apr_pool_t *pool)
855 {
856     apr_status_t rv = APR_SUCCESS;
857     apr_time_t now = apr_time_now();
858     proxy_balancer *balancer;
859     sctx_t *ctx = (sctx_t *)data;
860     server_rec *s = ctx->s;
861     proxy_server_conf *conf;
862     switch (state) {
863         case AP_WATCHDOG_STATE_STARTING:
864             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03258)
865                          "%s watchdog started.",
866                          HCHECK_WATHCHDOG_NAME);
867 #if HC_USE_THREADS
868             if (ctx->tpsize) {
869                 rv =  apr_thread_pool_create(&ctx->hctp, ctx->tpsize,
870                                              ctx->tpsize, ctx->p);
871                 if (rv != APR_SUCCESS) {
872                     ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO()
873                                  "apr_thread_pool_create() with %d threads failed",
874                                  ctx->tpsize);
875                     /* we can continue on without the threadpools */
876                     ctx->hctp = NULL;
877                 } else {
878                     ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO()
879                                  "apr_thread_pool_create() with %d threads succeeded",
880                                  ctx->tpsize);
881                 }
882             } else {
883                 ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO()
884                              "Skipping apr_thread_pool_create()");
885                 ctx->hctp = NULL;
886             }
887
888 #endif
889             break;
890
891         case AP_WATCHDOG_STATE_RUNNING:
892             /* loop thru all workers */
893             ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s,
894                          "Run of %s watchdog.",
895                          HCHECK_WATHCHDOG_NAME);
896             if (s) {
897                 int i;
898                 conf = (proxy_server_conf *) ap_get_module_config(s->module_config, &proxy_module);
899                 balancer = (proxy_balancer *)conf->balancers->elts;
900                 for (i = 0; i < conf->balancers->nelts; i++, balancer++) {
901                     int n;
902                     proxy_worker **workers;
903                     proxy_worker *worker;
904                     /* Have any new balancers or workers been added dynamically? */
905                     ap_proxy_sync_balancer(balancer, s, conf);
906                     workers = (proxy_worker **)balancer->workers->elts;
907                     for (n = 0; n < balancer->workers->nelts; n++) {
908                         worker = *workers;
909                         if (!PROXY_WORKER_IS(worker, PROXY_WORKER_STOPPED) &&
910                            (worker->s->method != NONE) &&
911                            (now > worker->s->updated + worker->s->interval)) {
912                             baton_t *baton;
913                             /* This pool must last the lifetime of the (possible) thread */
914                             apr_pool_t *ptemp;
915                             apr_pool_create(&ptemp, ctx->p);
916                             ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s,
917                                          "Checking %s worker: %s  [%d] (%pp)", balancer->s->name,
918                                          worker->s->name, worker->s->method, worker);
919
920                             if ((rv = hc_init_worker(ctx, worker)) != APR_SUCCESS) {
921                                 return rv;
922                             }
923                             baton = apr_palloc(ptemp, sizeof(baton_t));
924                             baton->ctx = ctx;
925                             baton->now = now;
926                             baton->worker = worker;
927                             baton->ptemp = ptemp;
928
929                             if (ctx->hctp) {
930 #if HC_USE_THREADS
931                                 rv = apr_thread_pool_push(ctx->hctp, hc_check, (void *)baton,
932                                                           APR_THREAD_TASK_PRIORITY_NORMAL, NULL);
933 #endif
934                                 ;
935                             } else {
936                                 hc_check(NULL, baton);
937                             }
938                         }
939                         workers++;
940                     }
941                 }
942                 /* s = s->next; */
943             }
944             break;
945
946         case AP_WATCHDOG_STATE_STOPPING:
947             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03261)
948                          "stopping %s watchdog.",
949                          HCHECK_WATHCHDOG_NAME);
950 #if HC_USE_THREADS
951             rv =  apr_thread_pool_destroy(ctx->hctp);
952             if (rv != APR_SUCCESS) {
953                 ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO()
954                              "apr_thread_pool_destroy() failed");
955             }
956 #endif
957             ctx->hctp = NULL;
958             break;
959     }
960     return rv;
961 }
962
963 static int hc_post_config(apr_pool_t *p, apr_pool_t *plog,
964                        apr_pool_t *ptemp, server_rec *s)
965 {
966     apr_status_t rv;
967     sctx_t *ctx;
968
969     APR_OPTIONAL_FN_TYPE(ap_watchdog_get_instance) *hc_watchdog_get_instance;
970     APR_OPTIONAL_FN_TYPE(ap_watchdog_register_callback) *hc_watchdog_register_callback;
971
972     hc_watchdog_get_instance = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_get_instance);
973     hc_watchdog_register_callback = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_register_callback);
974     if (!hc_watchdog_get_instance || !hc_watchdog_register_callback) {
975         ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(03262)
976                      "mod_watchdog is required");
977         return !OK;
978     }
979     ctx = (sctx_t *) ap_get_module_config(s->module_config,
980                                           &proxy_hcheck_module);
981
982     rv = hc_watchdog_get_instance(&ctx->watchdog,
983                                   HCHECK_WATHCHDOG_NAME,
984                                   0, 1, p);
985     if (rv) {
986         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03263)
987                      "Failed to create watchdog instance (%s)",
988                      HCHECK_WATHCHDOG_NAME);
989         return !OK;
990     }
991     rv = hc_watchdog_register_callback(ctx->watchdog,
992             apr_time_from_sec(HCHECK_WATHCHDOG_INTERVAL),
993             ctx,
994             hc_watchdog_callback);
995     if (rv) {
996         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03264)
997                      "Failed to register watchdog callback (%s)",
998                      HCHECK_WATHCHDOG_NAME);
999         return !OK;
1000     }
1001     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03265)
1002                  "watchdog callback registered (%s)", HCHECK_WATHCHDOG_NAME);
1003     return OK;
1004 }
1005
1006 static void hc_show_exprs(request_rec *r)
1007 {
1008     const apr_table_entry_t *elts;
1009     const apr_array_header_t *hdr;
1010     int i;
1011     sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
1012                                                   &proxy_hcheck_module);
1013     if (apr_is_empty_table(ctx->conditions))
1014         return;
1015
1016     ap_rputs("\n\n<table>"
1017              "<tr><th colspan='2'>Health check cond. expressions:</th></tr>\n"
1018              "<tr><th>Expr name</th><th>Expression</th></tr>\n", r);
1019
1020     hdr = apr_table_elts(ctx->conditions);
1021     elts = (const apr_table_entry_t *) hdr->elts;
1022     for (i = 0; i < hdr->nelts; ++i) {
1023         hc_condition_t *cond;
1024         if (!elts[i].key) {
1025             continue;
1026         }
1027         cond = (hc_condition_t *)elts[i].val;
1028         ap_rprintf(r, "<tr><td>%s</td><td>%s</td></tr>\n",
1029                    ap_escape_html(r->pool, elts[i].key),
1030                    ap_escape_html(r->pool, cond->expr));
1031     }
1032     ap_rputs("</table><hr/>\n", r);
1033 }
1034
1035 static void hc_select_exprs(request_rec *r, const char *expr)
1036 {
1037     const apr_table_entry_t *elts;
1038     const apr_array_header_t *hdr;
1039     int i;
1040     sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
1041                                                   &proxy_hcheck_module);
1042     if (apr_is_empty_table(ctx->conditions))
1043         return;
1044
1045     hdr = apr_table_elts(ctx->conditions);
1046     elts = (const apr_table_entry_t *) hdr->elts;
1047     for (i = 0; i < hdr->nelts; ++i) {
1048         if (!elts[i].key) {
1049             continue;
1050         }
1051         ap_rprintf(r, "<option value='%s' %s >%s</option>\n",
1052                    ap_escape_html(r->pool, elts[i].key),
1053                    (!strcmp(elts[i].key, expr)) ? "selected" : "",
1054                            ap_escape_html(r->pool, elts[i].key));
1055     }
1056 }
1057
1058 static int hc_valid_expr(request_rec *r, const char *expr)
1059 {
1060     const apr_table_entry_t *elts;
1061     const apr_array_header_t *hdr;
1062     int i;
1063     sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
1064                                                   &proxy_hcheck_module);
1065     if (apr_is_empty_table(ctx->conditions))
1066         return 0;
1067
1068     hdr = apr_table_elts(ctx->conditions);
1069     elts = (const apr_table_entry_t *) hdr->elts;
1070     for (i = 0; i < hdr->nelts; ++i) {
1071         if (!elts[i].key) {
1072             continue;
1073         }
1074         if (!strcmp(elts[i].key, expr))
1075             return 1;
1076     }
1077     return 0;
1078 }
1079
1080 static const char *hc_get_body(request_rec *r)
1081 {
1082     apr_off_t length;
1083     apr_size_t len;
1084     apr_status_t rv;
1085     char *buf;
1086
1087     if (!r || !r->kept_body)
1088         return "";
1089
1090     rv = apr_brigade_length(r->kept_body, 1, &length);
1091     len = (apr_size_t)length;
1092     if (rv != APR_SUCCESS || len == 0)
1093         return "";
1094
1095     buf = apr_palloc(r->pool, len + 1);
1096     rv = apr_brigade_flatten(r->kept_body, buf, &len);
1097     if (rv != APR_SUCCESS)
1098         return "";
1099     buf[len] = '\0'; /* ensure */
1100     return (const char*)buf;
1101 }
1102
1103 static const char *hc_expr_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
1104 {
1105     char *var = (char *)data;
1106
1107     if (var && *var && ctx->r && ap_casecmpstr(var, "BODY") == 0) {
1108         return hc_get_body(ctx->r);
1109     }
1110     return NULL;
1111 }
1112
1113 static const char *hc_expr_func_fn(ap_expr_eval_ctx_t *ctx, const void *data,
1114                                 const char *arg)
1115 {
1116     char *var = (char *)arg;
1117
1118     if (var && *var && ctx->r && ap_casecmpstr(var, "BODY") == 0) {
1119         return hc_get_body(ctx->r);
1120     }
1121     return NULL;
1122 }
1123
1124 static int hc_expr_lookup(ap_expr_lookup_parms *parms)
1125 {
1126     switch (parms->type) {
1127     case AP_EXPR_FUNC_VAR:
1128         /* for now, we just handle everything that starts with HC_.
1129          */
1130         if (strncasecmp(parms->name, "HC_", 3) == 0) {
1131             *parms->func = hc_expr_var_fn;
1132             *parms->data = parms->name + 3;
1133             return OK;
1134         }
1135         break;
1136     case AP_EXPR_FUNC_STRING:
1137         /* Function HC() is implemented by us.
1138          */
1139         if (strcasecmp(parms->name, "HC") == 0) {
1140             *parms->func = hc_expr_func_fn;
1141             *parms->data = parms->arg;
1142             return OK;
1143         }
1144         break;
1145     }
1146     return DECLINED;
1147 }
1148
1149 static const command_rec command_table[] = {
1150     AP_INIT_RAW_ARGS("ProxyHCTemplate", set_hc_template, NULL, OR_FILEINFO,
1151                      "Health check template"),
1152     AP_INIT_RAW_ARGS("ProxyHCExpr", set_hc_condition, NULL, OR_FILEINFO,
1153                      "Define a health check condition ruleset expression"),
1154 #if HC_USE_THREADS
1155     AP_INIT_TAKE1("ProxyHCTPsize", set_hc_tpsize, NULL, OR_FILEINFO,
1156                      "Set size of health check thread pool"),
1157 #endif
1158     { NULL }
1159 };
1160
1161 static void hc_register_hooks(apr_pool_t *p)
1162 {
1163     static const char *const aszPre[] = { "mod_proxy_balancer.c", "mod_proxy.c", NULL};
1164     static const char *const aszSucc[] = { "mod_watchdog.c", NULL};
1165     APR_REGISTER_OPTIONAL_FN(set_worker_hc_param);
1166     APR_REGISTER_OPTIONAL_FN(hc_show_exprs);
1167     APR_REGISTER_OPTIONAL_FN(hc_select_exprs);
1168     APR_REGISTER_OPTIONAL_FN(hc_valid_expr);
1169     ap_hook_post_config(hc_post_config, aszPre, aszSucc, APR_HOOK_LAST);
1170     ap_hook_expr_lookup(hc_expr_lookup, NULL, NULL, APR_HOOK_MIDDLE);
1171 }
1172
1173 /* the main config structure */
1174
1175 AP_DECLARE_MODULE(proxy_hcheck) =
1176 {
1177     STANDARD20_MODULE_STUFF,
1178     NULL,              /* create per-dir config structures */
1179     NULL,              /* merge  per-dir config structures */
1180     hc_create_config,  /* create per-server config structures */
1181     NULL,              /* merge  per-server config structures */
1182     command_table,     /* table of config file commands */
1183     hc_register_hooks  /* register hooks */
1184 };