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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 #include "mod_proxy.h"
17 #include "mod_watchdog.h"
18 #include "ap_slotmem.h"
21 #include "apr_thread_pool.h"
24 module AP_MODULE_DECLARE_DATA proxy_hcheck_module;
26 #define HCHECK_WATHCHDOG_NAME ("_proxy_hcheck_")
27 #define HC_THREADPOOL_SIZE (16)
29 /* Why? So we can easily set/clear HC_USE_THREADS during dev testing */
31 #define HC_USE_THREADS 1
33 #define HC_USE_THREADS 0
34 typedef void apr_thread_pool_t;
42 apr_interval_time_t interval;
49 ap_expr_info_t *pexpr; /* parsed expression */
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;
64 /* Used in the HC worker via the context field */
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 */
78 static void *hc_create_config(apr_pool_t *p, server_rec *s)
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;
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.
97 static const char *set_worker_hc_param(apr_pool_t *p,
106 sctx_t *ctx = (sctx_t *) ap_get_module_config(s->module_config,
107 &proxy_hcheck_module);
109 return "Bad call to set_worker_hc_param()";
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)) {
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);
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);
135 return apr_psprintf(p, "Unknown ProxyHCTemplate name: %s", val);
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",
146 worker->s->method = method->method;
148 temp->method = method->method;
153 return "Unknown method";
155 else if (!strcasecmp(key, "hcinterval")) {
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);
161 worker->s->interval = apr_time_from_sec(ival);
163 temp->interval = apr_time_from_sec(ival);
166 else if (!strcasecmp(key, "hcpasses")) {
169 return "Passes must be a positive value";
171 worker->s->passes = ival;
176 else if (!strcasecmp(key, "hcfails")) {
179 return "Fails must be a positive value";
181 worker->s->fails = ival;
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));
191 PROXY_STRNCPY(worker->s->hcuri, val);
193 temp->hurl = apr_pstrdup(p, val);
196 else if (!strcasecmp(key, "hcexpr")) {
197 hc_condition_t *cond;
198 cond = (hc_condition_t *)apr_table_get(ctx->conditions, val);
200 return apr_psprintf(p, "Unknown health check condition expr: %s", val);
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));
207 PROXY_STRNCPY(worker->s->hcexpr, val);
209 temp->hcexpr = apr_pstrdup(p, val);
213 return "unknown Worker hcheck parameter";
218 static const char *set_hc_condition(cmd_parms *cmd, void *dummy, const char *arg)
223 hc_condition_t *cond;
225 const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
228 ctx = (sctx_t *) ap_get_module_config(cmd->server->module_config,
229 &proxy_hcheck_module);
231 name = ap_getword_conf(cmd->pool, &arg);
233 return apr_pstrcat(cmd->temp_pool, "Missing expression name for ",
234 cmd->cmd->name, NULL);
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));
240 /* get expr. Allow fancy new {...} quoting style */
241 expr = ap_getword_conf2(cmd->temp_pool, &arg);
243 return apr_pstrcat(cmd->temp_pool, "Missing expression for ",
244 cmd->cmd->name, NULL);
246 cond = apr_palloc(cmd->pool, sizeof(hc_condition_t));
247 cond->pexpr = ap_expr_parse_cmd(cmd, expr, 0, &err, NULL);
249 return apr_psprintf(cmd->temp_pool, "Could not parse expression \"%s\": %s",
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);
256 return "error: extra parameter(s)";
262 static const char *set_hc_template(cmd_parms *cmd, void *dummy, const char *arg)
266 hc_template_t *template;
269 const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
272 ctx = (sctx_t *) ap_get_module_config(cmd->server->module_config,
273 &proxy_hcheck_module);
275 name = ap_getword_conf(cmd->temp_pool, &arg);
277 return apr_pstrcat(cmd->temp_pool, "Missing template name for ",
278 cmd->cmd->name, NULL);
281 template = (hc_template_t *)apr_array_push(ctx->templates);
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;
289 word = ap_getword_conf(cmd->pool, &arg);
290 val = strchr(word, '=');
292 return "Invalid ProxyHCTemplate parameter. Parameter must be "
293 "in the form 'key=value'";
297 err = set_worker_hc_param(cmd->pool, ctx->s, NULL, word, val, template);
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);
304 /* No error means we have a valid template */
311 static const char *set_hc_tpsize (cmd_parms *cmd, void *dummy, const char *arg)
315 const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
318 ctx = (sctx_t *) ap_get_module_config(cmd->server->module_config,
319 &proxy_hcheck_module);
321 ctx->tpsize = atoi(arg);
323 return "Invalid ProxyHCTPsize parameter. Parameter must be "
330 * Create a dummy request rec, simply so we can use ap_expr.
331 * Use our short-lived poll for bucket_alloc
333 static request_rec *create_request_rec(apr_pool_t *p1, conn_rec *conn, const char *method)
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);
343 r->connection = conn;
344 r->connection->bucket_alloc = ba;
345 r->server = conn->base_server;
348 r->ap_auth_type = NULL;
350 r->allowed_methods = ap_make_method_list(p, 2);
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);
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 */
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;
370 r->sent_bodyct = 0; /* bytect isn't for body */
373 r->read_body = REQUEST_NO_BODY;
375 r->status = HTTP_OK; /* Until further notice */
377 r->the_request = NULL;
379 /* Begin by presuming any module can make its own path_info assumptions,
380 * until some module interjects and changes the value.
382 r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
384 r->useragent_addr = conn->client_addr;
385 r->useragent_ip = conn->client_ip;
388 /* Time to populate r with the data we have. */
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') {
396 r->protocol = "HTTP/1.0";
397 r->proto_num = HTTP_VERSION(1, 0);
404 static proxy_worker *hc_get_hcworker(sctx_t *ctx, proxy_worker *worker,
407 proxy_worker *hc = NULL;
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));
417 const char *url = worker->s->name;
418 wctx_t *wctx = apr_pcalloc(ctx->p, sizeof(wctx_t));
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,
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);
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);
446 apr_hash_set(ctx->hcworkers, wptr, APR_HASH_KEY_STRING, hc);
448 /* This *could* have changed via the Balancer Manager */
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,
455 hc->s->method = worker->s->method;
456 apr_hash_set(ctx->hcworkers, wptr, APR_HASH_KEY_STRING, hc);
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;
465 * normally, this is done in ap_proxy_determine_connection().
466 * TODO: Look at using ap_proxy_determine_connection() with a
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);
478 return (rv == APR_SUCCESS ? OK : !OK);
481 static apr_status_t hc_init_worker(sctx_t *ctx, proxy_worker *worker) {
482 apr_status_t rv = APR_SUCCESS;
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.
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");
495 rv = (hc_determine_connection(ctx, worker) == OK ? APR_SUCCESS : APR_EGENERAL);
500 static apr_status_t backend_cleanup(const char *proxy_function, proxy_conn_rec *backend,
501 server_rec *s, int status)
505 ap_proxy_release_connection(proxy_function, backend, s);
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),
511 backend->worker->s->name);
518 static int hc_get_backend(const char *proxy_function, proxy_conn_rec **backend,
519 proxy_worker *hc, sctx_t *ctx)
522 status = ap_proxy_acquire_connection(proxy_function, backend, hc, ctx->s);
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?");
533 (*backend)->is_ssl = 1;
537 status = hc_determine_connection(ctx, hc);
539 (*backend)->addr = hc->cp->addr;
544 static apr_status_t hc_check_tcp(sctx_t *ctx, apr_pool_t *ptemp, proxy_worker *worker)
547 proxy_conn_rec *backend = NULL;
550 hc = hc_get_hcworker(ctx, worker, ptemp);
552 status = hc_get_backend("HCTCP", &backend, hc, ctx);
554 backend->addr = hc->cp->addr;
555 status = ap_proxy_connect_backend("HCTCP", backend, hc, ctx->s);
557 status = (ap_proxy_is_socket_connected(backend->sock) ? OK : !OK);
560 return backend_cleanup("HCTCP", backend, ctx->s, status);
563 static void hc_send(sctx_t *ctx, apr_pool_t *ptemp, const char *out, proxy_conn_rec *backend)
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,
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);
574 static int hc_read_headers(sctx_t *ctx, request_rec *r)
576 char buffer[HUGE_STRING_LEN];
579 len = ap_getline(buffer, sizeof(buffer), r, 1);
583 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(03254)
585 /* for the below, see ap_proxy_http_process_response() */
586 if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
589 int proxy_status = OK;
590 const char *proxy_status_line = NULL;
592 major = buffer[5] - '0';
593 minor = buffer[7] - '0';
594 if ((major != 1) || (len >= sizeof(buffer)-1)) {
598 keepchar = buffer[12];
600 proxy_status = atoi(&buffer[9]);
601 if (keepchar != '\0') {
602 buffer[12] = keepchar;
607 proxy_status_line = apr_pstrdup(r->pool, &buffer[9]);
608 r->status = proxy_status;
609 r->status_line = proxy_status_line;
613 /* OK, 1st line is OK... scarf in the headers */
614 while ((len = ap_getline(buffer, sizeof(buffer), r, 1)) > 0) {
616 if (!(value = strchr(buffer, ':'))) {
619 ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ctx->s, "%s", buffer);
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)
626 apr_table_add(r->headers_out, buffer, value);
631 static int hc_read_body (sctx_t *ctx, request_rec *r)
633 apr_status_t rv = APR_SUCCESS;
634 apr_bucket_brigade *bb;
637 bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
639 apr_bucket *bucket, *cpy;
640 apr_size_t len = HUGE_STRING_LEN;
642 rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_READBYTES,
643 APR_BLOCK_READ, len);
645 if (rv != APR_SUCCESS) {
646 if (APR_STATUS_IS_TIMEUP(rv) || APR_STATUS_IS_EOF(rv)) {
650 ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ctx->s, APLOGNO(03300)
651 "Error reading response body");
655 for (bucket = APR_BRIGADE_FIRST(bb);
656 bucket != APR_BRIGADE_SENTINEL(bb);
657 bucket = APR_BUCKET_NEXT(bucket))
659 if (APR_BUCKET_IS_EOS(bucket)) {
663 if (APR_BUCKET_IS_FLUSH(bucket)) {
666 rv = apr_bucket_copy(bucket, &cpy);
667 if (rv != APR_SUCCESS) {
670 APR_BRIGADE_INSERT_TAIL(r->kept_body, cpy);
672 apr_brigade_cleanup(bb);
675 return (rv == APR_SUCCESS ? OK : !OK);
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"
684 static apr_status_t hc_check_http(sctx_t *ctx, apr_pool_t *ptemp, proxy_worker *worker)
687 proxy_conn_rec *backend = NULL;
692 hc_condition_t *cond;
695 hc = hc_get_hcworker(ctx, worker, ptemp);
696 wctx = (wctx_t *)hc->context;
698 if ((status = hc_get_backend("HCOH", &backend, hc, ctx)) != OK) {
699 return backend_cleanup("HCOH", backend, ctx->s, status);
701 if ((status = ap_proxy_connect_backend("HCOH", backend, hc, ctx->s)) != OK) {
702 return backend_cleanup("HCOH", backend, ctx->s, status);
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);
710 switch (hc->s->method) {
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);
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);
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);
745 return backend_cleanup("HCOH", backend, ctx->s, !OK);
749 hc_send(ctx, ptemp, wctx->req, backend);
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);
755 if (hc->s->method == GET) {
756 if ((status = hc_read_body(ctx, r)) != OK) {
757 return backend_cleanup("HCOH", backend, ctx->s, status);
761 if (*worker->s->hcexpr &&
762 (cond = (hc_condition_t *)apr_table_get(ctx->conditions, worker->s->hcexpr)) != NULL) {
764 int ok = ap_expr_exec(r, cond->pexpr, &err);
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) {
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);
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);
781 } else if (r->status < 200 || r->status > 399) {
784 return backend_cleanup("HCOH", backend, ctx->s, status);
787 static void *hc_check(apr_thread_t *thread, void *b)
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;
796 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03256)
797 "%sHealth checking %s", (thread ? "Threaded " : ""), worker->s->name);
799 switch (worker->s->method) {
801 rv = hc_check_tcp(ctx, ptemp, worker);
807 rv = hc_check_http(ctx, ptemp, worker);
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);
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 " : ""),
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 " : ""),
848 worker->s->updated = now;
849 apr_pool_destroy(ptemp);
853 static apr_status_t hc_watchdog_callback(int state, void *data,
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;
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);
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",
875 /* we can continue on without the threadpools */
878 ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO()
879 "apr_thread_pool_create() with %d threads succeeded",
883 ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO()
884 "Skipping apr_thread_pool_create()");
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);
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++) {
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++) {
909 if (!PROXY_WORKER_IS(worker, PROXY_WORKER_STOPPED) &&
910 (worker->s->method != NONE) &&
911 (now > worker->s->updated + worker->s->interval)) {
913 /* This pool must last the lifetime of the (possible) thread */
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);
920 if ((rv = hc_init_worker(ctx, worker)) != APR_SUCCESS) {
923 baton = apr_palloc(ptemp, sizeof(baton_t));
926 baton->worker = worker;
927 baton->ptemp = ptemp;
931 rv = apr_thread_pool_push(ctx->hctp, hc_check, (void *)baton,
932 APR_THREAD_TASK_PRIORITY_NORMAL, NULL);
936 hc_check(NULL, baton);
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);
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");
963 static int hc_post_config(apr_pool_t *p, apr_pool_t *plog,
964 apr_pool_t *ptemp, server_rec *s)
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;
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");
979 ctx = (sctx_t *) ap_get_module_config(s->module_config,
980 &proxy_hcheck_module);
982 rv = hc_watchdog_get_instance(&ctx->watchdog,
983 HCHECK_WATHCHDOG_NAME,
986 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03263)
987 "Failed to create watchdog instance (%s)",
988 HCHECK_WATHCHDOG_NAME);
991 rv = hc_watchdog_register_callback(ctx->watchdog,
992 apr_time_from_sec(HCHECK_WATHCHDOG_INTERVAL),
994 hc_watchdog_callback);
996 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03264)
997 "Failed to register watchdog callback (%s)",
998 HCHECK_WATHCHDOG_NAME);
1001 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03265)
1002 "watchdog callback registered (%s)", HCHECK_WATHCHDOG_NAME);
1006 static void hc_show_exprs(request_rec *r)
1008 const apr_table_entry_t *elts;
1009 const apr_array_header_t *hdr;
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))
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);
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;
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));
1032 ap_rputs("</table><hr/>\n", r);
1035 static void hc_select_exprs(request_rec *r, const char *expr)
1037 const apr_table_entry_t *elts;
1038 const apr_array_header_t *hdr;
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))
1045 hdr = apr_table_elts(ctx->conditions);
1046 elts = (const apr_table_entry_t *) hdr->elts;
1047 for (i = 0; i < hdr->nelts; ++i) {
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));
1058 static int hc_valid_expr(request_rec *r, const char *expr)
1060 const apr_table_entry_t *elts;
1061 const apr_array_header_t *hdr;
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))
1068 hdr = apr_table_elts(ctx->conditions);
1069 elts = (const apr_table_entry_t *) hdr->elts;
1070 for (i = 0; i < hdr->nelts; ++i) {
1074 if (!strcmp(elts[i].key, expr))
1080 static const char *hc_get_body(request_rec *r)
1087 if (!r || !r->kept_body)
1090 rv = apr_brigade_length(r->kept_body, 1, &length);
1091 len = (apr_size_t)length;
1092 if (rv != APR_SUCCESS || len == 0)
1095 buf = apr_palloc(r->pool, len + 1);
1096 rv = apr_brigade_flatten(r->kept_body, buf, &len);
1097 if (rv != APR_SUCCESS)
1099 buf[len] = '\0'; /* ensure */
1100 return (const char*)buf;
1103 static const char *hc_expr_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
1105 char *var = (char *)data;
1107 if (var && *var && ctx->r && ap_casecmpstr(var, "BODY") == 0) {
1108 return hc_get_body(ctx->r);
1113 static const char *hc_expr_func_fn(ap_expr_eval_ctx_t *ctx, const void *data,
1116 char *var = (char *)arg;
1118 if (var && *var && ctx->r && ap_casecmpstr(var, "BODY") == 0) {
1119 return hc_get_body(ctx->r);
1124 static int hc_expr_lookup(ap_expr_lookup_parms *parms)
1126 switch (parms->type) {
1127 case AP_EXPR_FUNC_VAR:
1128 /* for now, we just handle everything that starts with HC_.
1130 if (strncasecmp(parms->name, "HC_", 3) == 0) {
1131 *parms->func = hc_expr_var_fn;
1132 *parms->data = parms->name + 3;
1136 case AP_EXPR_FUNC_STRING:
1137 /* Function HC() is implemented by us.
1139 if (strcasecmp(parms->name, "HC") == 0) {
1140 *parms->func = hc_expr_func_fn;
1141 *parms->data = parms->arg;
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"),
1155 AP_INIT_TAKE1("ProxyHCTPsize", set_hc_tpsize, NULL, OR_FILEINFO,
1156 "Set size of health check thread pool"),
1161 static void hc_register_hooks(apr_pool_t *p)
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);
1173 /* the main config structure */
1175 AP_DECLARE_MODULE(proxy_hcheck) =
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 */