mirror of
https://github.com/apache/httpd.git
synced 2025-08-05 16:55:50 +03:00
Define a new proxy_address struct holding the current/latest sockaddr in use by each proxy worker and conn. Since backend addresses can be updated when their TTL expires and while connections are being processed, each address is refcounted and freed only when the last worker (or conn) using it grabs the new one. The lifetime of the addresses is handled at a single place by the new ap_proxy_determine_address() function. It guarantees to bind the current/latest backend address to the passed in conn (or do nothing if it's up to date already). The function is called indirectly by ap_proxy_determine_connection() for the proxy modules that use it, or directly by mod_proxy_ftp and mod_proxy_hcheck. It also is called eventually by ap_proxy_connect_backend() when connect()ing all the current addresses fails, to check (PROXY_DETERMINE_ADDRESS_CHECK) if some new addrs are available. This commit is also a rework of the lifetime of conn->addr, conn->hostname and conn->forward, using the conn->uds_pool and conn->fwd_pool for the cases where the backend is connected through a UDS socket and a remote CONNECT proxy respectively. * include/ap_mmn.h: Minor bump for new function/fields. * modules/proxy/mod_proxy.h (struct proxy_address, ap_proxy_determine_addresss()): Declare ap_proxy_determine_addresss() and opaque struct proxy_address, new fields to structs proxy_conn_rec/proxy_worker_shared/proxy_worker. * modules/proxy/mod_proxy.c (set_worker_param): Parse/set the new worker->address_ttl parameter. * modules/proxy/proxy_util.c (proxy_util_register_hooks(), ap_proxy_initialize_worker(), ap_proxy_connection_reusable(), ap_proxyerror(), proxyerror_core(), init_conn_pool(), make_conn_subpool(), connection_make(), connection_cleanup(), connection_constructor()): Initialize *proxy_start_time in proxy_util_register_hooks() as the epoch from which expiration times are relative (i.e. seconds stored in an uint32_t for atomic changes). Make sure worker->s->is_address_reusable and worker->s->disablereuse are consistant in ap_proxy_initialize_worker(), thus no need to check for both in ap_proxy_connection_reusable(). New proxyerror_core() helper taking an apr_status_t to log, wrap in ap_proxyerror(). New make_conn_subpool() to create worker->cp->{pool,dns} with their own allocator. New connection_make() helper to factorize code in connection_cleanup() and connection_constructor(). * modules/proxy/proxy_util.c (proxy_address_inc(), proxy_address_dec(), proxy_address_cleanup(), proxy_address_set_expired(), worker_address_get(), worker_address_set(), worker_address_resolve(), proxy_addrs_equal(), ap_proxy_determine_address(), ap_proxy_determine_connection(), ap_proxy_connect_backend()): Implement ap_proxy_determine_address() using the above helpers for atomic changes, and call it from ap_proxy_determine_connection() and ap_proxy_connect_backend(). * modules/proxy/mod_proxy_ftp.c (proxy_ftp_handler): Use ap_proxy_determine_address() and use the returned backend->addr. * modules/proxy/mod_proxy_hcheck.c (hc_determine_connection, hc_get_backend, hc_init_worker, hc_watchdog_callback): Use ap_proxy_determine_address() in hc_determine_connection() and call the latter from hc_get_backend(), replace hc_init_worker() by hc_init_baton() which now calls hc_get_hcworker() and hc_get_backend() to resolve the first address at init time. * modules/proxy/mod_proxy_http.c (proxy_http_handler): Use backend->addr and ->hostname instead of worker->cp->addr and worker->s->hostname_ex respectively. * modules/proxy/mod_proxy_ajp.c (ap_proxy_ajp_request): Use backend->addr and ->hostname instead of worker->cp->addr and worker->s->hostname_ex respectively. Closes #367 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1912459 13f79535-47bb-0310-9956-ffa450edef68
1365 lines
47 KiB
C
1365 lines
47 KiB
C
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
* (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#include "mod_proxy.h"
|
|
#include "mod_watchdog.h"
|
|
#include "ap_slotmem.h"
|
|
#include "ap_expr.h"
|
|
#if APR_HAS_THREADS
|
|
#include "apr_thread_pool.h"
|
|
#endif
|
|
#include "http_ssl.h"
|
|
|
|
module AP_MODULE_DECLARE_DATA proxy_hcheck_module;
|
|
|
|
#define HCHECK_WATHCHDOG_NAME ("_proxy_hcheck_")
|
|
#define HC_THREADPOOL_SIZE (16)
|
|
|
|
/* Why? So we can easily set/clear HC_USE_THREADS during dev testing */
|
|
#if APR_HAS_THREADS
|
|
#ifndef HC_USE_THREADS
|
|
#define HC_USE_THREADS 1
|
|
#endif
|
|
#else
|
|
#define HC_USE_THREADS 0
|
|
#endif
|
|
|
|
typedef struct {
|
|
char *name;
|
|
hcmethod_t method;
|
|
int passes;
|
|
int fails;
|
|
apr_interval_time_t interval;
|
|
char *hurl;
|
|
char *hcexpr;
|
|
} hc_template_t;
|
|
|
|
typedef struct {
|
|
char *expr;
|
|
ap_expr_info_t *pexpr; /* parsed expression */
|
|
} hc_condition_t;
|
|
|
|
typedef struct {
|
|
apr_pool_t *p;
|
|
apr_array_header_t *templates;
|
|
apr_table_t *conditions;
|
|
apr_hash_t *hcworkers;
|
|
server_rec *s;
|
|
} sctx_t;
|
|
|
|
/* Used in the HC worker via the context field */
|
|
typedef struct {
|
|
const char *path; /* The path of the original worker URL */
|
|
const char *method; /* Method string for the HTTP/AJP request */
|
|
const char *req; /* pre-formatted HTTP/AJP request */
|
|
proxy_worker *w; /* Pointer to the actual worker */
|
|
const char *protocol; /* HTTP 1.0 or 1.1? */
|
|
} wctx_t;
|
|
|
|
typedef struct {
|
|
apr_pool_t *ptemp;
|
|
sctx_t *ctx;
|
|
proxy_balancer *balancer;
|
|
proxy_worker *worker;
|
|
proxy_worker *hc;
|
|
apr_time_t *now;
|
|
} baton_t;
|
|
|
|
static APR_OPTIONAL_FN_TYPE(ajp_handle_cping_cpong) *ajp_handle_cping_cpong = NULL;
|
|
|
|
static void *hc_create_config(apr_pool_t *p, server_rec *s)
|
|
{
|
|
sctx_t *ctx = apr_pcalloc(p, sizeof(sctx_t));
|
|
ctx->s = s;
|
|
apr_pool_create(&ctx->p, p);
|
|
apr_pool_tag(ctx->p, "proxy_hcheck");
|
|
ctx->templates = apr_array_make(p, 10, sizeof(hc_template_t));
|
|
ctx->conditions = apr_table_make(p, 10);
|
|
ctx->hcworkers = apr_hash_make(p);
|
|
return ctx;
|
|
}
|
|
|
|
static ap_watchdog_t *watchdog;
|
|
#if HC_USE_THREADS
|
|
static apr_thread_pool_t *hctp;
|
|
static int tpsize;
|
|
#endif
|
|
|
|
/*
|
|
* This serves double duty by not only validating (and creating)
|
|
* the health-check template, but also ties into set_worker_param()
|
|
* which does the actual setting of worker params in shm.
|
|
*/
|
|
static const char *set_worker_hc_param(apr_pool_t *p,
|
|
server_rec *s,
|
|
proxy_worker *worker,
|
|
const char *key,
|
|
const char *val,
|
|
void *v)
|
|
{
|
|
int ival;
|
|
hc_template_t *temp;
|
|
sctx_t *ctx = (sctx_t *) ap_get_module_config(s->module_config,
|
|
&proxy_hcheck_module);
|
|
if (!worker && !v) {
|
|
return "Bad call to set_worker_hc_param()";
|
|
}
|
|
if (!ctx) {
|
|
ctx = hc_create_config(p, s);
|
|
ap_set_module_config(s->module_config, &proxy_hcheck_module, ctx);
|
|
}
|
|
temp = (hc_template_t *)v;
|
|
if (!strcasecmp(key, "hctemplate")) {
|
|
hc_template_t *template;
|
|
template = (hc_template_t *)ctx->templates->elts;
|
|
for (ival = 0; ival < ctx->templates->nelts; ival++, template++) {
|
|
if (!ap_cstr_casecmp(template->name, val)) {
|
|
if (worker) {
|
|
worker->s->method = template->method;
|
|
worker->s->interval = template->interval;
|
|
worker->s->passes = template->passes;
|
|
worker->s->fails = template->fails;
|
|
PROXY_STRNCPY(worker->s->hcuri, template->hurl);
|
|
PROXY_STRNCPY(worker->s->hcexpr, template->hcexpr);
|
|
} else {
|
|
temp->method = template->method;
|
|
temp->interval = template->interval;
|
|
temp->passes = template->passes;
|
|
temp->fails = template->fails;
|
|
temp->hurl = apr_pstrdup(p, template->hurl);
|
|
temp->hcexpr = apr_pstrdup(p, template->hcexpr);
|
|
}
|
|
return NULL;
|
|
}
|
|
}
|
|
return apr_psprintf(p, "Unknown ProxyHCTemplate name: %s", val);
|
|
}
|
|
else if (!strcasecmp(key, "hcmethod")) {
|
|
proxy_hcmethods_t *method = proxy_hcmethods;
|
|
for (; method->name; method++) {
|
|
if (!ap_cstr_casecmp(val, method->name)) {
|
|
if (!method->implemented) {
|
|
return apr_psprintf(p, "Health check method %s not (yet) implemented",
|
|
val);
|
|
}
|
|
if (worker) {
|
|
worker->s->method = method->method;
|
|
} else {
|
|
temp->method = method->method;
|
|
}
|
|
return NULL;
|
|
}
|
|
}
|
|
return "Unknown method";
|
|
}
|
|
else if (!strcasecmp(key, "hcinterval")) {
|
|
apr_interval_time_t hci;
|
|
apr_status_t rv;
|
|
rv = ap_timeout_parameter_parse(val, &hci, "s");
|
|
if (rv != APR_SUCCESS)
|
|
return "Unparse-able hcinterval setting";
|
|
if (hci < AP_WD_TM_SLICE)
|
|
return apr_psprintf(p, "Interval must be a positive value greater than %"
|
|
APR_TIME_T_FMT "ms", apr_time_as_msec(AP_WD_TM_SLICE));
|
|
if (worker) {
|
|
worker->s->interval = hci;
|
|
} else {
|
|
temp->interval = hci;
|
|
}
|
|
}
|
|
else if (!strcasecmp(key, "hcpasses")) {
|
|
ival = atoi(val);
|
|
if (ival < 0)
|
|
return "Passes must be a positive value";
|
|
if (worker) {
|
|
worker->s->passes = ival;
|
|
} else {
|
|
temp->passes = ival;
|
|
}
|
|
}
|
|
else if (!strcasecmp(key, "hcfails")) {
|
|
ival = atoi(val);
|
|
if (ival < 0)
|
|
return "Fails must be a positive value";
|
|
if (worker) {
|
|
worker->s->fails = ival;
|
|
} else {
|
|
temp->fails = ival;
|
|
}
|
|
}
|
|
else if (!strcasecmp(key, "hcuri")) {
|
|
if (strlen(val) >= sizeof(worker->s->hcuri))
|
|
return apr_psprintf(p, "Health check uri length must be < %d characters",
|
|
(int)sizeof(worker->s->hcuri));
|
|
if (worker) {
|
|
PROXY_STRNCPY(worker->s->hcuri, val);
|
|
} else {
|
|
temp->hurl = apr_pstrdup(p, val);
|
|
}
|
|
}
|
|
else if (!strcasecmp(key, "hcexpr")) {
|
|
hc_condition_t *cond;
|
|
cond = (hc_condition_t *)apr_table_get(ctx->conditions, val);
|
|
if (!cond) {
|
|
return apr_psprintf(p, "Unknown health check condition expr: %s", val);
|
|
}
|
|
/* This check is wonky... a known expr can't be this big. Check anyway */
|
|
if (strlen(val) >= sizeof(worker->s->hcexpr))
|
|
return apr_psprintf(p, "Health check uri length must be < %d characters",
|
|
(int)sizeof(worker->s->hcexpr));
|
|
if (worker) {
|
|
PROXY_STRNCPY(worker->s->hcexpr, val);
|
|
} else {
|
|
temp->hcexpr = apr_pstrdup(p, val);
|
|
}
|
|
}
|
|
else {
|
|
return "unknown Worker hcheck parameter";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const char *set_hc_condition(cmd_parms *cmd, void *dummy, const char *arg)
|
|
{
|
|
char *name = NULL;
|
|
char *expr;
|
|
sctx_t *ctx;
|
|
hc_condition_t *cond;
|
|
|
|
const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
|
|
if (err)
|
|
return err;
|
|
ctx = (sctx_t *) ap_get_module_config(cmd->server->module_config,
|
|
&proxy_hcheck_module);
|
|
|
|
name = ap_getword_conf(cmd->pool, &arg);
|
|
if (!*name) {
|
|
return apr_pstrcat(cmd->temp_pool, "Missing expression name for ",
|
|
cmd->cmd->name, NULL);
|
|
}
|
|
if (strlen(name) > (PROXY_WORKER_MAX_SCHEME_SIZE - 1)) {
|
|
return apr_psprintf(cmd->temp_pool, "Expression name limited to %d characters",
|
|
(PROXY_WORKER_MAX_SCHEME_SIZE - 1));
|
|
}
|
|
/* get expr. Allow fancy new {...} quoting style */
|
|
expr = ap_getword_conf2(cmd->temp_pool, &arg);
|
|
if (!*expr) {
|
|
return apr_pstrcat(cmd->temp_pool, "Missing expression for ",
|
|
cmd->cmd->name, NULL);
|
|
}
|
|
cond = apr_palloc(cmd->pool, sizeof(hc_condition_t));
|
|
cond->pexpr = ap_expr_parse_cmd(cmd, expr, 0, &err, NULL);
|
|
if (err) {
|
|
return apr_psprintf(cmd->temp_pool, "Could not parse expression \"%s\": %s",
|
|
expr, err);
|
|
}
|
|
cond->expr = apr_pstrdup(cmd->pool, expr);
|
|
apr_table_setn(ctx->conditions, name, (void *)cond);
|
|
expr = ap_getword_conf(cmd->temp_pool, &arg);
|
|
if (*expr) {
|
|
return "error: extra parameter(s)";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const char *set_hc_template(cmd_parms *cmd, void *dummy, const char *arg)
|
|
{
|
|
char *name = NULL;
|
|
char *word, *val;
|
|
hc_template_t *template;
|
|
sctx_t *ctx;
|
|
|
|
const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
|
|
if (err)
|
|
return err;
|
|
ctx = (sctx_t *) ap_get_module_config(cmd->server->module_config,
|
|
&proxy_hcheck_module);
|
|
|
|
name = ap_getword_conf(cmd->temp_pool, &arg);
|
|
if (!*name) {
|
|
return apr_pstrcat(cmd->temp_pool, "Missing template name for ",
|
|
cmd->cmd->name, NULL);
|
|
}
|
|
|
|
template = (hc_template_t *)apr_array_push(ctx->templates);
|
|
|
|
template->name = apr_pstrdup(cmd->pool, name);
|
|
template->method = template->passes = template->fails = 1;
|
|
template->interval = apr_time_from_sec(HCHECK_WATHCHDOG_DEFAULT_INTERVAL);
|
|
template->hurl = NULL;
|
|
template->hcexpr = NULL;
|
|
while (*arg) {
|
|
word = ap_getword_conf(cmd->pool, &arg);
|
|
val = strchr(word, '=');
|
|
if (!val) {
|
|
return "Invalid ProxyHCTemplate parameter. Parameter must be "
|
|
"in the form 'key=value'";
|
|
}
|
|
else
|
|
*val++ = '\0';
|
|
err = set_worker_hc_param(cmd->pool, ctx->s, NULL, word, val, template);
|
|
|
|
if (err) {
|
|
/* get rid of recently pushed (bad) template */
|
|
apr_array_pop(ctx->templates);
|
|
return apr_pstrcat(cmd->temp_pool, "ProxyHCTemplate: ", err, " ", word, "=", val, "; ", name, NULL);
|
|
}
|
|
/* No error means we have a valid template */
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#if HC_USE_THREADS
|
|
static const char *set_hc_tpsize (cmd_parms *cmd, void *dummy, const char *arg)
|
|
{
|
|
const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
|
|
if (err)
|
|
return err;
|
|
|
|
tpsize = atoi(arg);
|
|
if (tpsize < 0)
|
|
return "Invalid ProxyHCTPsize parameter. Parameter must be "
|
|
">= 0";
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Create a dummy request rec, simply so we can use ap_expr.
|
|
* Use our short-lived pool for bucket_alloc so that we can simply move
|
|
* buckets and use them after the backend connection is released.
|
|
*/
|
|
static request_rec *create_request_rec(apr_pool_t *p, server_rec *s,
|
|
proxy_balancer *balancer,
|
|
const char *method,
|
|
const char *protocol)
|
|
{
|
|
request_rec *r;
|
|
|
|
r = apr_pcalloc(p, sizeof(request_rec));
|
|
r->pool = p;
|
|
r->server = s;
|
|
|
|
r->per_dir_config = r->server->lookup_defaults;
|
|
if (balancer->section_config) {
|
|
r->per_dir_config = ap_merge_per_dir_configs(r->pool,
|
|
r->per_dir_config,
|
|
balancer->section_config);
|
|
}
|
|
|
|
r->proxyreq = PROXYREQ_RESPONSE;
|
|
|
|
r->user = NULL;
|
|
r->ap_auth_type = NULL;
|
|
|
|
r->allowed_methods = ap_make_method_list(p, 2);
|
|
|
|
r->headers_in = apr_table_make(r->pool, 1);
|
|
r->trailers_in = apr_table_make(r->pool, 1);
|
|
r->subprocess_env = apr_table_make(r->pool, 25);
|
|
r->headers_out = apr_table_make(r->pool, 12);
|
|
r->err_headers_out = apr_table_make(r->pool, 5);
|
|
r->trailers_out = apr_table_make(r->pool, 1);
|
|
r->notes = apr_table_make(r->pool, 5);
|
|
|
|
r->request_config = ap_create_request_config(r->pool);
|
|
/* Must be set before we run create request hook */
|
|
|
|
r->sent_bodyct = 0; /* bytect isn't for body */
|
|
|
|
r->read_length = 0;
|
|
r->read_body = REQUEST_NO_BODY;
|
|
|
|
r->status = HTTP_OK; /* Until further notice */
|
|
r->the_request = NULL;
|
|
|
|
/* Begin by presuming any module can make its own path_info assumptions,
|
|
* until some module interjects and changes the value.
|
|
*/
|
|
r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
|
|
|
|
|
|
/* Time to populate r with the data we have. */
|
|
r->method = method;
|
|
/* Provide quick information about the request method as soon as known */
|
|
r->method_number = ap_method_number_of(r->method);
|
|
if (r->method_number == M_OPTIONS
|
|
|| (r->method_number == M_GET && r->method[0] == 'H')) {
|
|
r->header_only = 1;
|
|
}
|
|
else {
|
|
r->header_only = 0;
|
|
}
|
|
r->protocol = "HTTP/1.0";
|
|
r->proto_num = HTTP_VERSION(1, 0);
|
|
if ( protocol && (protocol[7] == '1') ) {
|
|
r->protocol = "HTTP/1.1";
|
|
r->proto_num = HTTP_VERSION(1, 1);
|
|
}
|
|
r->hostname = NULL;
|
|
|
|
return r;
|
|
}
|
|
|
|
static void set_request_connection(request_rec *r, conn_rec *conn)
|
|
{
|
|
conn->bucket_alloc = apr_bucket_alloc_create(r->pool);
|
|
r->connection = conn;
|
|
|
|
r->kept_body = apr_brigade_create(r->pool, conn->bucket_alloc);
|
|
r->output_filters = r->proto_output_filters = conn->output_filters;
|
|
r->input_filters = r->proto_input_filters = conn->input_filters;
|
|
|
|
r->useragent_addr = conn->client_addr;
|
|
r->useragent_ip = conn->client_ip;
|
|
}
|
|
|
|
static void create_hcheck_req(wctx_t *wctx, proxy_worker *hc,
|
|
apr_pool_t *p)
|
|
{
|
|
char *req = NULL;
|
|
const char *method = NULL;
|
|
const char *protocol = NULL;
|
|
|
|
/* TODO: Fold into switch/case below? This seems more obvious */
|
|
if ( (hc->s->method == OPTIONS11) || (hc->s->method == HEAD11) || (hc->s->method == GET11) ) {
|
|
protocol = "HTTP/1.1";
|
|
} else {
|
|
protocol = "HTTP/1.0";
|
|
}
|
|
switch (hc->s->method) {
|
|
case OPTIONS:
|
|
case OPTIONS11:
|
|
method = "OPTIONS";
|
|
req = apr_psprintf(p,
|
|
"OPTIONS * %s\r\n"
|
|
"Host: %s:%d\r\n"
|
|
"\r\n", protocol,
|
|
hc->s->hostname_ex, (int)hc->s->port);
|
|
break;
|
|
|
|
case HEAD:
|
|
case HEAD11:
|
|
method = "HEAD";
|
|
/* fallthru */
|
|
case GET:
|
|
case GET11:
|
|
if (!method) { /* did we fall thru? If not, we are GET */
|
|
method = "GET";
|
|
}
|
|
req = apr_psprintf(p,
|
|
"%s %s%s%s %s\r\n"
|
|
"Host: %s:%d\r\n"
|
|
"\r\n",
|
|
method,
|
|
(wctx->path ? wctx->path : ""),
|
|
(wctx->path && *hc->s->hcuri ? "/" : "" ),
|
|
(*hc->s->hcuri ? hc->s->hcuri : ""),
|
|
protocol,
|
|
hc->s->hostname_ex, (int)hc->s->port);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
wctx->req = req;
|
|
wctx->method = method;
|
|
wctx->protocol = protocol;
|
|
}
|
|
|
|
static proxy_worker *hc_get_hcworker(sctx_t *ctx, proxy_worker *worker,
|
|
apr_pool_t *p)
|
|
{
|
|
proxy_worker *hc = NULL;
|
|
apr_port_t port;
|
|
|
|
hc = (proxy_worker *)apr_hash_get(ctx->hcworkers, &worker, sizeof worker);
|
|
if (!hc) {
|
|
apr_uri_t uri;
|
|
apr_status_t rv;
|
|
const char *url = worker->s->name;
|
|
wctx_t *wctx = apr_pcalloc(ctx->p, sizeof(wctx_t));
|
|
|
|
port = (worker->s->port ? worker->s->port
|
|
: ap_proxy_port_of_scheme(worker->s->scheme));
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(03248)
|
|
"Creating hc worker %pp for %s://%s:%d",
|
|
worker, worker->s->scheme, worker->s->hostname_ex,
|
|
(int)port);
|
|
|
|
ap_proxy_define_worker(ctx->p, &hc, NULL, NULL, worker->s->name, 0);
|
|
apr_snprintf(hc->s->name, sizeof hc->s->name, "%pp", worker);
|
|
PROXY_STRNCPY(hc->s->hostname, worker->s->hostname); /* for compatibility */
|
|
PROXY_STRNCPY(hc->s->hostname_ex, worker->s->hostname_ex);
|
|
PROXY_STRNCPY(hc->s->scheme, worker->s->scheme);
|
|
PROXY_STRNCPY(hc->s->hcuri, worker->s->hcuri);
|
|
PROXY_STRNCPY(hc->s->hcexpr, worker->s->hcexpr);
|
|
hc->hash.def = hc->s->hash.def = ap_proxy_hashfunc(hc->s->name, PROXY_HASHFUNC_DEFAULT);
|
|
hc->hash.fnv = hc->s->hash.fnv = ap_proxy_hashfunc(hc->s->name, PROXY_HASHFUNC_FNV);
|
|
hc->s->port = port;
|
|
hc->s->conn_timeout_set = worker->s->conn_timeout_set;
|
|
hc->s->conn_timeout = worker->s->conn_timeout;
|
|
hc->s->ping_timeout_set = worker->s->ping_timeout_set;
|
|
hc->s->ping_timeout = worker->s->ping_timeout;
|
|
hc->s->timeout_set = worker->s->timeout_set;
|
|
hc->s->timeout = worker->s->timeout;
|
|
/* Do not disable worker in case of errors */
|
|
hc->s->status |= PROXY_WORKER_IGNORE_ERRORS;
|
|
/* Mark as the "generic" worker */
|
|
hc->s->status |= PROXY_WORKER_GENERIC;
|
|
ap_proxy_initialize_worker(hc, ctx->s, ctx->p);
|
|
hc->s->is_address_reusable = worker->s->is_address_reusable;
|
|
hc->s->disablereuse = worker->s->disablereuse;
|
|
hc->s->method = worker->s->method;
|
|
rv = apr_uri_parse(p, url, &uri);
|
|
if (rv == APR_SUCCESS) {
|
|
wctx->path = apr_pstrdup(ctx->p, uri.path);
|
|
}
|
|
wctx->w = worker;
|
|
create_hcheck_req(wctx, hc, ctx->p);
|
|
hc->context = wctx;
|
|
apr_hash_set(ctx->hcworkers, &worker, sizeof worker, hc);
|
|
}
|
|
/* This *could* have changed via the Balancer Manager */
|
|
/* TODO */
|
|
if (hc->s->method != worker->s->method) {
|
|
wctx_t *wctx = hc->context;
|
|
port = (worker->s->port ? worker->s->port
|
|
: ap_proxy_port_of_scheme(worker->s->scheme));
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(03311)
|
|
"Updating hc worker %pp for %s://%s:%d",
|
|
worker, worker->s->scheme, worker->s->hostname_ex,
|
|
(int)port);
|
|
hc->s->method = worker->s->method;
|
|
create_hcheck_req(wctx, hc, ctx->p);
|
|
}
|
|
return hc;
|
|
}
|
|
|
|
static int hc_determine_connection(const char *proxy_function,
|
|
proxy_conn_rec *backend,
|
|
server_rec *s)
|
|
{
|
|
proxy_worker *worker = backend->worker;
|
|
apr_status_t rv;
|
|
|
|
/*
|
|
* normally, this is done in ap_proxy_determine_connection().
|
|
* TODO: Look at using ap_proxy_determine_connection() with a
|
|
* fake request_rec
|
|
*/
|
|
rv = ap_proxy_determine_address(proxy_function, backend,
|
|
worker->s->hostname_ex, worker->s->port,
|
|
0, NULL, s);
|
|
if (rv != APR_SUCCESS) {
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(03249)
|
|
"DNS lookup failure for: %s:%hu",
|
|
worker->s->hostname_ex, worker->s->port);
|
|
return !OK;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
static apr_status_t backend_cleanup(const char *proxy_function, proxy_conn_rec *backend,
|
|
server_rec *s, int status)
|
|
{
|
|
if (backend) {
|
|
backend->close = 1;
|
|
ap_proxy_release_connection(proxy_function, backend, s);
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03251)
|
|
"Health check %s Status (%d) for %s.",
|
|
ap_proxy_show_hcmethod(backend->worker->s->method),
|
|
status,
|
|
backend->worker->s->name);
|
|
}
|
|
if (status != OK) {
|
|
return APR_EGENERAL;
|
|
}
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
static int hc_get_backend(const char *proxy_function, proxy_conn_rec **backend,
|
|
proxy_worker *hc, sctx_t *ctx)
|
|
{
|
|
int status;
|
|
|
|
status = ap_proxy_acquire_connection(proxy_function, backend, hc, ctx->s);
|
|
if (status != OK) {
|
|
return status;
|
|
}
|
|
|
|
if (strcmp(hc->s->scheme, "https") == 0 || strcmp(hc->s->scheme, "wss") == 0 ) {
|
|
if (!ap_ssl_has_outgoing_handlers()) {
|
|
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ctx->s, APLOGNO(03252)
|
|
"mod_ssl not configured?");
|
|
return !OK;
|
|
}
|
|
(*backend)->is_ssl = 1;
|
|
}
|
|
|
|
return hc_determine_connection(proxy_function, *backend, ctx->s);
|
|
}
|
|
|
|
static apr_status_t hc_init_baton(baton_t *baton)
|
|
{
|
|
sctx_t *ctx = baton->ctx;
|
|
proxy_worker *worker = baton->worker, *hc;
|
|
apr_status_t rv = APR_SUCCESS;
|
|
int once = 0;
|
|
|
|
/*
|
|
* Since this is the watchdog, workers never actually handle a
|
|
* request here, and so the local data isn't initialized (of
|
|
* course, the shared memory is). So we need to bootstrap
|
|
* worker->cp. Note, we only need do this once.
|
|
*/
|
|
if (!worker->cp) {
|
|
rv = ap_proxy_initialize_worker(worker, ctx->s, ctx->p);
|
|
if (rv != APR_SUCCESS) {
|
|
ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ctx->s, APLOGNO(03250) "Cannot init worker");
|
|
return rv;
|
|
}
|
|
once = 1;
|
|
}
|
|
|
|
baton->hc = hc = hc_get_hcworker(ctx, worker, baton->ptemp);
|
|
|
|
/* Try to resolve the worker address once if it's reusable */
|
|
if (once && worker->s->is_address_reusable) {
|
|
proxy_conn_rec *backend = NULL;
|
|
if (hc_get_backend("HCHECK", &backend, hc, ctx)) {
|
|
rv = APR_EGENERAL;
|
|
}
|
|
if (backend) {
|
|
backend->close = 1;
|
|
ap_proxy_release_connection("HCHECK", backend, ctx->s);
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
static apr_status_t hc_check_cping(baton_t *baton, apr_thread_t *thread)
|
|
{
|
|
int status;
|
|
sctx_t *ctx = baton->ctx;
|
|
proxy_worker *hc = baton->hc;
|
|
proxy_conn_rec *backend = NULL;
|
|
apr_pool_t *ptemp = baton->ptemp;
|
|
request_rec *r;
|
|
apr_interval_time_t timeout;
|
|
|
|
if (!ajp_handle_cping_cpong) {
|
|
return APR_ENOTIMPL;
|
|
}
|
|
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, baton->ctx->s, "HCCPING starting");
|
|
if ((status = hc_get_backend("HCCPING", &backend, hc, ctx)) != OK) {
|
|
return backend_cleanup("HCCPING", backend, ctx->s, status);
|
|
}
|
|
if ((status = ap_proxy_connect_backend("HCCPING", backend, hc, ctx->s)) != OK) {
|
|
return backend_cleanup("HCCPING", backend, ctx->s, status);
|
|
}
|
|
r = create_request_rec(ptemp, ctx->s, baton->balancer, "CPING", NULL);
|
|
if ((status = ap_proxy_connection_create_ex("HCCPING", backend, r)) != OK) {
|
|
return backend_cleanup("HCCPING", backend, ctx->s, status);
|
|
}
|
|
set_request_connection(r, backend->connection);
|
|
backend->connection->current_thread = thread;
|
|
|
|
if (hc->s->ping_timeout_set) {
|
|
timeout = hc->s->ping_timeout;
|
|
} else if ( hc->s->conn_timeout_set) {
|
|
timeout = hc->s->conn_timeout;
|
|
} else if ( hc->s->timeout_set) {
|
|
timeout = hc->s->timeout;
|
|
} else {
|
|
/* default to socket timeout */
|
|
apr_socket_timeout_get(backend->sock, &timeout);
|
|
}
|
|
status = ajp_handle_cping_cpong(backend->sock, r, timeout);
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, baton->ctx->s, "HCCPING done %d", status);
|
|
return backend_cleanup("HCCPING", backend, ctx->s, status);
|
|
}
|
|
|
|
static apr_status_t hc_check_tcp(baton_t *baton)
|
|
{
|
|
int status;
|
|
sctx_t *ctx = baton->ctx;
|
|
proxy_worker *hc = baton->hc;
|
|
proxy_conn_rec *backend = NULL;
|
|
|
|
status = hc_get_backend("HCTCP", &backend, hc, ctx);
|
|
if (status == OK) {
|
|
status = ap_proxy_connect_backend("HCTCP", backend, hc, ctx->s);
|
|
/* does an unconditional ap_proxy_is_socket_connected() */
|
|
}
|
|
return backend_cleanup("HCTCP", backend, ctx->s, status);
|
|
}
|
|
|
|
static int hc_send(request_rec *r, const char *out, apr_bucket_brigade *bb)
|
|
{
|
|
apr_status_t rv;
|
|
conn_rec *c = r->connection;
|
|
apr_bucket_alloc_t *ba = c->bucket_alloc;
|
|
ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, r->server, "%s", out);
|
|
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(out, strlen(out),
|
|
r->pool, ba));
|
|
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_flush_create(ba));
|
|
rv = ap_pass_brigade(c->output_filters, bb);
|
|
apr_brigade_cleanup(bb);
|
|
return (rv) ? !OK : OK;
|
|
}
|
|
|
|
static int hc_read_headers(request_rec *r)
|
|
{
|
|
char buffer[HUGE_STRING_LEN];
|
|
int len;
|
|
const char *ct;
|
|
|
|
len = ap_getline(buffer, sizeof(buffer), r, 1);
|
|
if (len <= 0) {
|
|
return !OK;
|
|
}
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(03254)
|
|
"%.*s", len, buffer);
|
|
/* for the below, see ap_proxy_http_process_response() */
|
|
if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
|
|
int major;
|
|
char keepchar;
|
|
int proxy_status = OK;
|
|
const char *proxy_status_line = NULL;
|
|
|
|
major = buffer[5] - '0';
|
|
if ((major != 1) || (len >= sizeof(buffer)-1)) {
|
|
return !OK;
|
|
}
|
|
|
|
keepchar = buffer[12];
|
|
buffer[12] = '\0';
|
|
proxy_status = atoi(&buffer[9]);
|
|
if (keepchar != '\0') {
|
|
buffer[12] = keepchar;
|
|
} else {
|
|
buffer[12] = ' ';
|
|
buffer[13] = '\0';
|
|
}
|
|
proxy_status_line = apr_pstrdup(r->pool, &buffer[9]);
|
|
r->status = proxy_status;
|
|
r->status_line = proxy_status_line;
|
|
} else {
|
|
return !OK;
|
|
}
|
|
|
|
/* OK, 1st line is OK... scarf in the headers */
|
|
while ((len = ap_getline(buffer, sizeof(buffer), r, 1)) > 0) {
|
|
char *value, *end;
|
|
ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, r->server, "%.*s",
|
|
len, buffer);
|
|
if (!(value = strchr(buffer, ':'))) {
|
|
return !OK;
|
|
}
|
|
*value = '\0';
|
|
++value;
|
|
while (apr_isspace(*value))
|
|
++value; /* Skip to start of value */
|
|
for (end = &value[strlen(value)-1]; end > value && apr_isspace(*end); --end)
|
|
*end = '\0';
|
|
apr_table_add(r->headers_out, buffer, value);
|
|
}
|
|
|
|
/* Set the Content-Type for the request if set */
|
|
if ((ct = apr_table_get(r->headers_out, "Content-Type")) != NULL)
|
|
ap_set_content_type(r, ct);
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int hc_read_body(request_rec *r, apr_bucket_brigade *bb)
|
|
{
|
|
apr_status_t rv = APR_SUCCESS;
|
|
int seen_eos = 0;
|
|
|
|
do {
|
|
apr_size_t len = HUGE_STRING_LEN;
|
|
|
|
apr_brigade_cleanup(bb);
|
|
rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_READBYTES,
|
|
APR_BLOCK_READ, len);
|
|
|
|
if (rv != APR_SUCCESS) {
|
|
if (APR_STATUS_IS_EOF(rv)) {
|
|
rv = APR_SUCCESS;
|
|
break;
|
|
}
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, APLOGNO(03300)
|
|
"Error reading response body");
|
|
break;
|
|
}
|
|
|
|
while (!APR_BRIGADE_EMPTY(bb)) {
|
|
apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
|
|
if (APR_BUCKET_IS_EOS(bucket)) {
|
|
seen_eos = 1;
|
|
break;
|
|
}
|
|
if (APR_BUCKET_IS_FLUSH(bucket)) {
|
|
apr_bucket_delete(bucket);
|
|
continue;
|
|
}
|
|
APR_BUCKET_REMOVE(bucket);
|
|
APR_BRIGADE_INSERT_TAIL(r->kept_body, bucket);
|
|
}
|
|
}
|
|
while (!seen_eos);
|
|
apr_brigade_cleanup(bb);
|
|
return (rv == APR_SUCCESS ? OK : !OK);
|
|
}
|
|
|
|
/*
|
|
* Send the HTTP OPTIONS, HEAD or GET request to the backend
|
|
* server associated w/ worker. If we have Conditions,
|
|
* then apply those to the resulting response, otherwise
|
|
* any status code 2xx or 3xx is considered "passing"
|
|
*/
|
|
static apr_status_t hc_check_http(baton_t *baton, apr_thread_t *thread)
|
|
{
|
|
int status;
|
|
proxy_conn_rec *backend = NULL;
|
|
sctx_t *ctx = baton->ctx;
|
|
proxy_worker *hc = baton->hc;
|
|
proxy_worker *worker = baton->worker;
|
|
apr_pool_t *ptemp = baton->ptemp;
|
|
request_rec *r;
|
|
wctx_t *wctx;
|
|
hc_condition_t *cond;
|
|
apr_bucket_brigade *bb;
|
|
|
|
wctx = (wctx_t *)hc->context;
|
|
if (!wctx->req || !wctx->method) {
|
|
return APR_ENOTIMPL;
|
|
}
|
|
|
|
if ((status = hc_get_backend("HCOH", &backend, hc, ctx)) != OK) {
|
|
return backend_cleanup("HCOH", backend, ctx->s, status);
|
|
}
|
|
if ((status = ap_proxy_connect_backend("HCOH", backend, hc, ctx->s)) != OK) {
|
|
return backend_cleanup("HCOH", backend, ctx->s, status);
|
|
}
|
|
|
|
r = create_request_rec(ptemp, ctx->s, baton->balancer, wctx->method, wctx->protocol);
|
|
if ((status = ap_proxy_connection_create_ex("HCOH", backend, r)) != OK) {
|
|
return backend_cleanup("HCOH", backend, ctx->s, status);
|
|
}
|
|
set_request_connection(r, backend->connection);
|
|
backend->connection->current_thread = thread;
|
|
|
|
bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
|
|
|
|
if ((status = hc_send(r, wctx->req, bb)) != OK) {
|
|
return backend_cleanup("HCOH", backend, ctx->s, status);
|
|
}
|
|
if ((status = hc_read_headers(r)) != OK) {
|
|
return backend_cleanup("HCOH", backend, ctx->s, status);
|
|
}
|
|
if (!r->header_only) {
|
|
apr_table_t *saved_headers_in = r->headers_in;
|
|
r->headers_in = apr_table_copy(r->pool, r->headers_out);
|
|
ap_proxy_pre_http_request(backend->connection, r);
|
|
status = hc_read_body(r, bb);
|
|
r->headers_in = saved_headers_in;
|
|
if (status != OK) {
|
|
return backend_cleanup("HCOH", backend, ctx->s, status);
|
|
}
|
|
r->trailers_out = apr_table_copy(r->pool, r->trailers_in);
|
|
}
|
|
|
|
if (*worker->s->hcexpr &&
|
|
(cond = (hc_condition_t *)apr_table_get(ctx->conditions, worker->s->hcexpr)) != NULL) {
|
|
const char *err;
|
|
int ok = ap_expr_exec(r, cond->pexpr, &err);
|
|
if (ok > 0) {
|
|
ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
|
|
"Condition %s for %s (%s): passed", worker->s->hcexpr,
|
|
hc->s->name, worker->s->name);
|
|
} else if (ok < 0 || err) {
|
|
ap_log_error(APLOG_MARK, APLOG_INFO, 0, ctx->s, APLOGNO(03301)
|
|
"Error on checking condition %s for %s (%s): %s", worker->s->hcexpr,
|
|
hc->s->name, worker->s->name, err);
|
|
status = !OK;
|
|
} else {
|
|
ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
|
|
"Condition %s for %s (%s) : failed", worker->s->hcexpr,
|
|
hc->s->name, worker->s->name);
|
|
status = !OK;
|
|
}
|
|
} else if (r->status < 200 || r->status > 399) {
|
|
ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
|
|
"Response status %i for %s (%s): failed", r->status,
|
|
hc->s->name, worker->s->name);
|
|
status = !OK;
|
|
}
|
|
return backend_cleanup("HCOH", backend, ctx->s, status);
|
|
}
|
|
|
|
static void * APR_THREAD_FUNC hc_check(apr_thread_t *thread, void *b)
|
|
{
|
|
baton_t *baton = (baton_t *)b;
|
|
server_rec *s = baton->ctx->s;
|
|
proxy_worker *worker = baton->worker;
|
|
proxy_worker *hc = baton->hc;
|
|
apr_time_t now;
|
|
apr_status_t rv;
|
|
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03256)
|
|
"%sHealth checking %s", (thread ? "Threaded " : ""),
|
|
worker->s->name);
|
|
|
|
if (hc->s->method == TCP) {
|
|
rv = hc_check_tcp(baton);
|
|
}
|
|
else if (hc->s->method == CPING) {
|
|
rv = hc_check_cping(baton, thread);
|
|
}
|
|
else {
|
|
rv = hc_check_http(baton, thread);
|
|
}
|
|
|
|
now = apr_time_now();
|
|
if (rv == APR_ENOTIMPL) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(03257)
|
|
"Somehow tried to use unimplemented hcheck method: %d",
|
|
(int)hc->s->method);
|
|
}
|
|
/* what state are we in ? */
|
|
else if (PROXY_WORKER_IS_HCFAILED(worker) || PROXY_WORKER_IS_ERROR(worker)) {
|
|
if (rv == APR_SUCCESS) {
|
|
worker->s->pcount += 1;
|
|
if (worker->s->pcount >= worker->s->passes) {
|
|
ap_proxy_set_wstatus(PROXY_WORKER_HC_FAIL_FLAG, 0, worker);
|
|
ap_proxy_set_wstatus(PROXY_WORKER_IN_ERROR_FLAG, 0, worker);
|
|
worker->s->pcount = 0;
|
|
ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03302)
|
|
"%sHealth check ENABLING %s", (thread ? "Threaded " : ""),
|
|
worker->s->name);
|
|
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (rv != APR_SUCCESS) {
|
|
worker->s->error_time = now;
|
|
worker->s->fcount += 1;
|
|
if (worker->s->fcount >= worker->s->fails) {
|
|
ap_proxy_set_wstatus(PROXY_WORKER_HC_FAIL_FLAG, 1, worker);
|
|
worker->s->fcount = 0;
|
|
ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03303)
|
|
"%sHealth check DISABLING %s", (thread ? "Threaded " : ""),
|
|
worker->s->name);
|
|
}
|
|
}
|
|
}
|
|
if (baton->now) {
|
|
*baton->now = now;
|
|
}
|
|
apr_pool_destroy(baton->ptemp);
|
|
worker->s->updated = now;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static apr_status_t hc_watchdog_callback(int state, void *data,
|
|
apr_pool_t *pool)
|
|
{
|
|
apr_status_t rv = APR_SUCCESS;
|
|
proxy_balancer *balancer;
|
|
sctx_t *ctx = (sctx_t *)data;
|
|
server_rec *s = ctx->s;
|
|
proxy_server_conf *conf;
|
|
|
|
switch (state) {
|
|
case AP_WATCHDOG_STATE_STARTING:
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03258)
|
|
"%s watchdog started.",
|
|
HCHECK_WATHCHDOG_NAME);
|
|
#if HC_USE_THREADS
|
|
if (tpsize && hctp == NULL) {
|
|
rv = apr_thread_pool_create(&hctp, tpsize,
|
|
tpsize, ctx->p);
|
|
if (rv != APR_SUCCESS) {
|
|
ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(03312)
|
|
"apr_thread_pool_create() with %d threads failed",
|
|
tpsize);
|
|
/* we can continue on without the threadpools */
|
|
hctp = NULL;
|
|
} else {
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(03313)
|
|
"apr_thread_pool_create() with %d threads succeeded",
|
|
tpsize);
|
|
}
|
|
} else {
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(03314)
|
|
"Skipping apr_thread_pool_create()");
|
|
hctp = NULL;
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case AP_WATCHDOG_STATE_RUNNING:
|
|
/* loop thru all workers */
|
|
if (s) {
|
|
int i;
|
|
conf = (proxy_server_conf *) ap_get_module_config(s->module_config, &proxy_module);
|
|
balancer = (proxy_balancer *)conf->balancers->elts;
|
|
ctx->s = s;
|
|
for (i = 0; i < conf->balancers->nelts; i++, balancer++) {
|
|
int n;
|
|
apr_time_t now;
|
|
proxy_worker **workers;
|
|
proxy_worker *worker;
|
|
/* Have any new balancers or workers been added dynamically? */
|
|
ap_proxy_sync_balancer(balancer, s, conf);
|
|
workers = (proxy_worker **)balancer->workers->elts;
|
|
now = apr_time_now();
|
|
for (n = 0; n < balancer->workers->nelts; n++) {
|
|
worker = *workers;
|
|
if (!PROXY_WORKER_IS(worker, PROXY_WORKER_STOPPED) &&
|
|
(worker->s->method != NONE) &&
|
|
(worker->s->updated != 0) &&
|
|
(now > worker->s->updated + worker->s->interval)) {
|
|
baton_t *baton;
|
|
apr_pool_t *ptemp;
|
|
|
|
ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
|
|
"Checking %s worker: %s [%d] (%pp)", balancer->s->name,
|
|
worker->s->name, worker->s->method, worker);
|
|
|
|
/* This pool has the lifetime of the check */
|
|
apr_pool_create(&ptemp, ctx->p);
|
|
apr_pool_tag(ptemp, "hc_request");
|
|
baton = apr_pcalloc(ptemp, sizeof(baton_t));
|
|
baton->ctx = ctx;
|
|
baton->balancer = balancer;
|
|
baton->worker = worker;
|
|
baton->ptemp = ptemp;
|
|
if ((rv = hc_init_baton(baton))) {
|
|
worker->s->updated = now;
|
|
apr_pool_destroy(ptemp);
|
|
return rv;
|
|
}
|
|
worker->s->updated = 0;
|
|
#if HC_USE_THREADS
|
|
if (hctp) {
|
|
apr_thread_pool_push(hctp, hc_check, (void *)baton,
|
|
APR_THREAD_TASK_PRIORITY_NORMAL,
|
|
NULL);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
baton->now = &now;
|
|
hc_check(NULL, baton);
|
|
}
|
|
}
|
|
workers++;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AP_WATCHDOG_STATE_STOPPING:
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03261)
|
|
"stopping %s watchdog.",
|
|
HCHECK_WATHCHDOG_NAME);
|
|
#if HC_USE_THREADS
|
|
if (hctp) {
|
|
rv = apr_thread_pool_destroy(hctp);
|
|
if (rv != APR_SUCCESS) {
|
|
ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(03315)
|
|
"apr_thread_pool_destroy() failed");
|
|
}
|
|
hctp = NULL;
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
return rv;
|
|
}
|
|
static int hc_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
|
|
apr_pool_t *ptemp)
|
|
{
|
|
#if HC_USE_THREADS
|
|
hctp = NULL;
|
|
tpsize = HC_THREADPOOL_SIZE;
|
|
#endif
|
|
|
|
ajp_handle_cping_cpong = APR_RETRIEVE_OPTIONAL_FN(ajp_handle_cping_cpong);
|
|
if (ajp_handle_cping_cpong) {
|
|
proxy_hcmethods_t *method = proxy_hcmethods;
|
|
for (; method->name; method++) {
|
|
if (method->method == CPING) {
|
|
method->implemented = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
static int hc_post_config(apr_pool_t *p, apr_pool_t *plog,
|
|
apr_pool_t *ptemp, server_rec *main_s)
|
|
{
|
|
apr_status_t rv;
|
|
server_rec *s = main_s;
|
|
|
|
APR_OPTIONAL_FN_TYPE(ap_watchdog_get_instance) *hc_watchdog_get_instance;
|
|
APR_OPTIONAL_FN_TYPE(ap_watchdog_register_callback) *hc_watchdog_register_callback;
|
|
|
|
if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
|
|
return OK;
|
|
}
|
|
hc_watchdog_get_instance = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_get_instance);
|
|
hc_watchdog_register_callback = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_register_callback);
|
|
if (!hc_watchdog_get_instance || !hc_watchdog_register_callback) {
|
|
ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(03262)
|
|
"mod_watchdog is required");
|
|
return !OK;
|
|
}
|
|
rv = hc_watchdog_get_instance(&watchdog,
|
|
HCHECK_WATHCHDOG_NAME,
|
|
0, 1, p);
|
|
if (rv) {
|
|
ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03263)
|
|
"Failed to create watchdog instance (%s)",
|
|
HCHECK_WATHCHDOG_NAME);
|
|
return !OK;
|
|
}
|
|
while (s) {
|
|
sctx_t *ctx = ap_get_module_config(s->module_config,
|
|
&proxy_hcheck_module);
|
|
|
|
if (s != ctx->s) {
|
|
ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, s, APLOGNO(10019)
|
|
"Missing unique per-server context: %s (%pp:%pp) (no hchecks)",
|
|
s->server_hostname, s, ctx->s);
|
|
s = s->next;
|
|
continue;
|
|
}
|
|
rv = hc_watchdog_register_callback(watchdog,
|
|
AP_WD_TM_SLICE,
|
|
ctx,
|
|
hc_watchdog_callback);
|
|
if (rv) {
|
|
ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03264)
|
|
"Failed to register watchdog callback (%s)",
|
|
HCHECK_WATHCHDOG_NAME);
|
|
return !OK;
|
|
}
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03265)
|
|
"watchdog callback registered (%s for %s)", HCHECK_WATHCHDOG_NAME, s->server_hostname);
|
|
s = s->next;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
static void hc_show_exprs(request_rec *r)
|
|
{
|
|
const apr_table_entry_t *elts;
|
|
const apr_array_header_t *hdr;
|
|
int i;
|
|
sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
|
|
&proxy_hcheck_module);
|
|
if (!ctx)
|
|
return;
|
|
if (apr_is_empty_table(ctx->conditions))
|
|
return;
|
|
|
|
ap_rputs("\n\n<table>"
|
|
"<tr><th colspan='2'>Health check cond. expressions:</th></tr>\n"
|
|
"<tr><th>Expr name</th><th>Expression</th></tr>\n", r);
|
|
|
|
hdr = apr_table_elts(ctx->conditions);
|
|
elts = (const apr_table_entry_t *) hdr->elts;
|
|
for (i = 0; i < hdr->nelts; ++i) {
|
|
hc_condition_t *cond;
|
|
if (!elts[i].key) {
|
|
continue;
|
|
}
|
|
cond = (hc_condition_t *)elts[i].val;
|
|
ap_rprintf(r, "<tr><td>%s</td><td>%s</td></tr>\n",
|
|
ap_escape_html(r->pool, elts[i].key),
|
|
ap_escape_html(r->pool, cond->expr));
|
|
}
|
|
ap_rputs("</table><hr/>\n", r);
|
|
}
|
|
|
|
static void hc_select_exprs(request_rec *r, const char *expr)
|
|
{
|
|
const apr_table_entry_t *elts;
|
|
const apr_array_header_t *hdr;
|
|
int i;
|
|
sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
|
|
&proxy_hcheck_module);
|
|
if (!ctx)
|
|
return;
|
|
if (apr_is_empty_table(ctx->conditions))
|
|
return;
|
|
|
|
hdr = apr_table_elts(ctx->conditions);
|
|
elts = (const apr_table_entry_t *) hdr->elts;
|
|
for (i = 0; i < hdr->nelts; ++i) {
|
|
if (!elts[i].key) {
|
|
continue;
|
|
}
|
|
ap_rprintf(r, "<option value='%s' %s >%s</option>\n",
|
|
ap_escape_html(r->pool, elts[i].key),
|
|
(!strcmp(elts[i].key, expr)) ? "selected" : "",
|
|
ap_escape_html(r->pool, elts[i].key));
|
|
}
|
|
}
|
|
|
|
static int hc_valid_expr(request_rec *r, const char *expr)
|
|
{
|
|
const apr_table_entry_t *elts;
|
|
const apr_array_header_t *hdr;
|
|
int i;
|
|
sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
|
|
&proxy_hcheck_module);
|
|
if (!ctx)
|
|
return 0;
|
|
if (apr_is_empty_table(ctx->conditions))
|
|
return 0;
|
|
|
|
hdr = apr_table_elts(ctx->conditions);
|
|
elts = (const apr_table_entry_t *) hdr->elts;
|
|
for (i = 0; i < hdr->nelts; ++i) {
|
|
if (!elts[i].key) {
|
|
continue;
|
|
}
|
|
if (!strcmp(elts[i].key, expr))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const char *hc_get_body(request_rec *r)
|
|
{
|
|
apr_off_t length;
|
|
apr_size_t len;
|
|
apr_status_t rv;
|
|
char *buf;
|
|
|
|
if (!r || !r->kept_body)
|
|
return "";
|
|
|
|
rv = apr_brigade_length(r->kept_body, 1, &length);
|
|
len = (apr_size_t)length;
|
|
if (rv != APR_SUCCESS || len == 0)
|
|
return "";
|
|
|
|
buf = apr_palloc(r->pool, len + 1);
|
|
rv = apr_brigade_flatten(r->kept_body, buf, &len);
|
|
if (rv != APR_SUCCESS)
|
|
return "";
|
|
buf[len] = '\0'; /* ensure */
|
|
return (const char*)buf;
|
|
}
|
|
|
|
static const char *hc_expr_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
|
|
{
|
|
char *var = (char *)data;
|
|
|
|
if (var && *var && ctx->r && ap_cstr_casecmp(var, "BODY") == 0) {
|
|
return hc_get_body(ctx->r);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const char *hc_expr_func_fn(ap_expr_eval_ctx_t *ctx, const void *data,
|
|
const char *arg)
|
|
{
|
|
char *var = (char *)arg;
|
|
|
|
if (var && *var && ctx->r && ap_cstr_casecmp(var, "BODY") == 0) {
|
|
return hc_get_body(ctx->r);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int hc_expr_lookup(ap_expr_lookup_parms *parms)
|
|
{
|
|
switch (parms->type) {
|
|
case AP_EXPR_FUNC_VAR:
|
|
/* for now, we just handle everything that starts with HC_.
|
|
*/
|
|
if (strncasecmp(parms->name, "HC_", 3) == 0) {
|
|
*parms->func = hc_expr_var_fn;
|
|
*parms->data = parms->name + 3;
|
|
return OK;
|
|
}
|
|
break;
|
|
case AP_EXPR_FUNC_STRING:
|
|
/* Function HC() is implemented by us.
|
|
*/
|
|
if (strcasecmp(parms->name, "HC") == 0) {
|
|
*parms->func = hc_expr_func_fn;
|
|
*parms->data = parms->arg;
|
|
return OK;
|
|
}
|
|
break;
|
|
}
|
|
return DECLINED;
|
|
}
|
|
|
|
static const command_rec command_table[] = {
|
|
AP_INIT_RAW_ARGS("ProxyHCTemplate", set_hc_template, NULL, OR_FILEINFO,
|
|
"Health check template"),
|
|
AP_INIT_RAW_ARGS("ProxyHCExpr", set_hc_condition, NULL, OR_FILEINFO,
|
|
"Define a health check condition ruleset expression"),
|
|
#if HC_USE_THREADS
|
|
AP_INIT_TAKE1("ProxyHCTPsize", set_hc_tpsize, NULL, RSRC_CONF,
|
|
"Set size of health check thread pool"),
|
|
#endif
|
|
{ NULL }
|
|
};
|
|
|
|
static void hc_register_hooks(apr_pool_t *p)
|
|
{
|
|
static const char *const aszPre[] = { "mod_proxy_balancer.c", "mod_proxy.c", NULL};
|
|
static const char *const aszSucc[] = { "mod_watchdog.c", NULL};
|
|
APR_REGISTER_OPTIONAL_FN(set_worker_hc_param);
|
|
APR_REGISTER_OPTIONAL_FN(hc_show_exprs);
|
|
APR_REGISTER_OPTIONAL_FN(hc_select_exprs);
|
|
APR_REGISTER_OPTIONAL_FN(hc_valid_expr);
|
|
ap_hook_pre_config(hc_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
|
|
ap_hook_post_config(hc_post_config, aszPre, aszSucc, APR_HOOK_LAST);
|
|
ap_hook_expr_lookup(hc_expr_lookup, NULL, NULL, APR_HOOK_MIDDLE);
|
|
}
|
|
|
|
/* the main config structure */
|
|
|
|
AP_DECLARE_MODULE(proxy_hcheck) =
|
|
{
|
|
STANDARD20_MODULE_STUFF,
|
|
NULL, /* create per-dir config structures */
|
|
NULL, /* merge per-dir config structures */
|
|
hc_create_config, /* create per-server config structures */
|
|
NULL, /* merge per-server config structures */
|
|
command_table, /* table of config file commands */
|
|
hc_register_hooks /* register hooks */
|
|
};
|