Index: server/util_cookies.c =================================================================== --- server/util_cookies.c (Revision 1417803) +++ server/util_cookies.c (Arbeitskopie) @@ -274,7 +274,83 @@ } +/* Iterate through the cookies, isolate our cookie and then remove it. + * + * If our cookie appears two or more times, but with different values, + * remove it twice and set the duplicated flag to true. Remove any + * $path or other attributes following our cookie if present. If we end + * up with an empty cookie, remove the whole header. + * + * Only RfC 6265 based cookies are supported. But most other cookie works, too. + * TODO: support RfC 2109 & 2965 styled cookie + */ +static int extract_set_cookie_value(ap_cookie_do * v, const char *key, const char *val) +{ + char *cookie; + char *cookie_pair, *pair_next; + char *name, *value; + + /* breaks, when already duplicated */ + if (v->duplicated) + return 1; + + /* get first part of cookie, must be NAME=VALUE */ + cookie = apr_pstrdup(v->r->pool, val); + cookie_pair = apr_strtok(cookie, ";", &pair_next); + if (!cookie_pair) + return 1; + + /* get name */ + name = apr_strtok(cookie_pair, "=", &value); + while (*name == ' ') + name++; + if (*name == '$') + name++; + if (!name || strcasecmp(v->name, name)) + return 1; + + /* check duplication */ + if (v->encoded && strcmp(v->encoded, value) != 0) { + v->duplicated = TRUE; + return 1; + } + + /* save cookie value */ + v->encoded = value; + + return 1; +} + /** + * Read a set cookie called name, placing its value in val. This function implent + * the RfC 6265, only. + * The Set-Cookie2 Headers are scanned, too. (Should valid in most cases) + * + * If the cookie is duplicated, this function returns APR_EGENERAL. + */ +AP_DECLARE(apr_status_t) ap_set_cookie_read(request_rec * r, const char *name, const char **val) +{ + ap_cookie_do v; + v.r = r; + v.encoded = NULL; + v.new_cookies = apr_table_make(r->pool, 10); + v.duplicated = 0; + v.name = name; + + apr_table_do((int (*) (void *, const char *, const char *)) + extract_set_cookie_value, (void *) &v, r->headers_out, + "Set-Cookie", "Set-Cookie2", NULL); + if (v.duplicated) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO() LOG_PREFIX + "server setted cookie '%s' more than once: %s", v.name, r->uri); + return APR_EGENERAL; + } + + *val = v.encoded; + return APR_SUCCESS; +} + +/** * Sanity check a given string that it exists, is not empty, * and does not contain the special characters '=', ';' and '&'. * Index: modules/proxy/proxy_util.c =================================================================== --- modules/proxy/proxy_util.c (Revision 1417803) +++ modules/proxy/proxy_util.c (Arbeitskopie) @@ -1185,6 +1185,9 @@ bshared->sticky_separator = '.'; *bshared->nonce = PROXY_UNSET_NONCE; /* impossible valid input */ + bshared->autoroute_type = PROXY_AUTOROUTE_TYPE_DISABLE; + *bshared->autoroute_field = '\0'; + (*balancer)->s = bshared; (*balancer)->sconf = conf; Index: modules/proxy/mod_proxy.c =================================================================== --- modules/proxy/mod_proxy.c (Revision 1417803) +++ modules/proxy/mod_proxy.c (Arbeitskopie) @@ -263,6 +263,42 @@ return NULL; } +PROXY_DECLARE(char *) ap_set_balancer_autoroute(proxy_balancer *balancer, + const char *val) +{ + if (!strncasecmp(val, "header:", 7)) { + val = val + 7; + balancer->s->autoroute_type = PROXY_AUTOROUTE_TYPE_HEADER; + } + else if (!strncasecmp(val, "header-remove:", 14)) { + val = val + 14; + balancer->s->autoroute_type = PROXY_AUTOROUTE_TYPE_HEADER_REMOVE; + } + else if (!strncasecmp(val, "notes:", 6)) { + val = val + 6; + balancer->s->autoroute_type = PROXY_AUTOROUTE_TYPE_NOTES; + } + else if (!strncasecmp(val, "cookie:", 7)) { + val = val + 7; + balancer->s->autoroute_type = PROXY_AUTOROUTE_TYPE_COOKIE; + } + else if (!strncasecmp(val, "env:", 4)) { + val = val + 4; + balancer->s->autoroute_type = PROXY_AUTOROUTE_TYPE_ENV; + } + else { + balancer->s->autoroute_type = PROXY_AUTOROUTE_TYPE_HEADER_REMOVE; + } + + if (strlen(val) >= PROXY_BALANCER_MAX_STICKY_SIZE) { + balancer->s->autoroute_type = PROXY_AUTOROUTE_TYPE_DISABLE; + return "autoroute field length must be < 64 characters"; + } + PROXY_STRNCPY(balancer->s->autoroute_field, val); + + return NULL; +} + static const char *set_balancer_param(proxy_server_conf *conf, apr_pool_t *p, proxy_balancer *balancer, @@ -271,6 +307,7 @@ { int ival; + char *ret; if (!strcasecmp(key, "stickysession")) { char *path; /* Balancer sticky session name. @@ -407,6 +444,10 @@ else return "forcerecovery must be On|Off"; } + else if (!strcasecmp(key, "autoroute")) { + if ((ret = ap_set_balancer_autoroute( balancer, val))) + return ret; + } else { return "unknown Balancer parameter"; } Index: modules/proxy/mod_proxy.h =================================================================== --- modules/proxy/mod_proxy.h (Revision 1417803) +++ modules/proxy/mod_proxy.h (Arbeitskopie) @@ -312,6 +312,15 @@ #define PROXY_MAX_PROVIDER_NAME_SIZE 16 +/* autoroute types */ +#define PROXY_AUTOROUTE_TYPE_DISABLE 0 +#define PROXY_AUTOROUTE_TYPE_HEADER 1 +#define PROXY_AUTOROUTE_TYPE_NOTES 2 +#define PROXY_AUTOROUTE_TYPE_COOKIE 3 +#define PROXY_AUTOROUTE_TYPE_ENV 4 +#define PROXY_AUTOROUTE_TYPE_HEADER_REMOVE 5 + + #define PROXY_STRNCPY(dst, src) ap_proxy_strncpy((dst), (src), (sizeof(dst))) #define PROXY_COPY_CONF_PARAMS(w, c) \ @@ -428,6 +437,8 @@ unsigned int vhosted:1; unsigned int inactive:1; unsigned int forcerecovery:1; + unsigned int autoroute_type; + char autoroute_field[PROXY_BALANCER_MAX_STICKY_SIZE]; } proxy_balancer_shared; #define ALIGNED_PROXY_BALANCER_SHARED_SIZE (APR_ALIGN_DEFAULT(sizeof(proxy_balancer_shared))) @@ -903,6 +914,15 @@ */ int ap_proxy_lb_workers(void); +/** + * set the autoroute of balancer + * @param balancer balancer + * @param val string with new config + * @return error message, or NULL + */ +PROXY_DECLARE(char *) ap_set_balancer_autoroute(proxy_balancer *balancer, + const char *val); + extern module PROXY_DECLARE_DATA proxy_module; #endif /*MOD_PROXY_H*/ Index: modules/proxy/mod_proxy_balancer.c =================================================================== --- modules/proxy/mod_proxy_balancer.c (Revision 1417803) +++ modules/proxy/mod_proxy_balancer.c (Arbeitskopie) @@ -22,6 +22,7 @@ #include "apr_version.h" #include "ap_hooks.h" #include "apr_date.h" +#include "util_cookies.h" static const char *balancer_mutex_type = "proxy-balancer-shm"; ap_slotmem_provider_t *storage = NULL; @@ -614,6 +615,12 @@ "%s: worker (%s) rewritten to %s", (*balancer)->s->name, (*worker)->s->name, *url); + /* store autoroute fieldname in notes for the output filter */ + if ((*balancer)->s->autoroute_field == PROXY_AUTOROUTE_TYPE_HEADER_REMOVE && + (*balancer)->s->autoroute_field[0] != '\0') { + apr_table_set(r->notes, "proxy-balancer-ar-field", (*balancer)->s->autoroute_field); + } + return access_status; } @@ -624,6 +631,8 @@ { apr_status_t rv; + const char *new_route = NULL; + char *session = NULL; if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01173) @@ -632,6 +641,74 @@ return HTTP_INTERNAL_SERVER_ERROR; } + /* set new route id */ + if (balancer->s->autoroute_type != PROXY_AUTOROUTE_TYPE_DISABLE && *(balancer->s->autoroute_field) != '\0') { + switch (balancer->s->autoroute_type) { + case PROXY_AUTOROUTE_TYPE_HEADER: + new_route = apr_table_get(r->headers_out, balancer->s->autoroute_field); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO() + "%s %s: get route (%s) from header (%s)", + balancer->s->name, worker->s->name, new_route, balancer->s->autoroute_field); + break; + case PROXY_AUTOROUTE_TYPE_HEADER_REMOVE: + new_route = apr_table_get(r->notes, "proxy-balancer-ar-newroute"); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO() + "%s %s: get route (%s) from removed header (%s)", + balancer->s->name, worker->s->name, new_route, balancer->s->autoroute_field); + break; + case PROXY_AUTOROUTE_TYPE_NOTES: + new_route = apr_table_get(r->notes, balancer->s->autoroute_field); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO() + "%s %s: get route (%s) from notes (%s)", + balancer->s->name, worker->s->name, new_route, balancer->s->autoroute_field); + break; + case PROXY_AUTOROUTE_TYPE_ENV: + new_route = apr_table_get(r->subprocess_env, balancer->s->autoroute_field); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO() + "%s %s: get route (%s) from enviroment variable (%s)", + balancer->s->name, worker->s->name, new_route, balancer->s->autoroute_field); + break; + case PROXY_AUTOROUTE_TYPE_COOKIE: + if (*(balancer->s->autoroute_field) != '\0') { + if ((rv = ap_set_cookie_read(r, balancer->s->autoroute_field, (const char**)&session)) != APR_SUCCESS) + session = NULL; + } + else { + if ((rv = ap_set_cookie_read(r, balancer->s->sticky, (const char**)&session)) == APR_SUCCESS) { + /* look for session seperators */ + if (balancer->s->sticky_separator && (session = ap_strchr(session, balancer->s->sticky_separator))) + session++; + if (session && balancer->s->sticky_separator && (session = ap_strchr(session, balancer->s->sticky_separator))) + *session = '\0'; + } + else + session = NULL; + } + new_route = session; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO() + "%s %s: get route (%s) from cookie (%s)", + balancer->s->name, worker->s->name, new_route, balancer->s->autoroute_field); + break; + } + + if (new_route != NULL) { + /* update route id */ + if (strcmp(worker->s->route, new_route) != 0) { + if (strlen(new_route) >= PROXY_WORKER_MAX_ROUTE_SIZE) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO() + "%s %s: route length must be < 64 characters", + balancer->s->name, worker->s->name); + } + else { + PROXY_STRNCPY(worker->s->route, new_route); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO() + "%s: new route id (%s) for worker (%s)", + balancer->s->name, worker->s->name, new_route); + } + } + } + } + if (!apr_is_empty_array(balancer->errstatuses)) { int i; for (i = 0; i < balancer->errstatuses->nelts; i++) { @@ -1136,6 +1213,15 @@ } } } + if ((val = apr_table_get(params, "b_autoroute")) && *val) { + if (strcasecmp(val, "-")) { + ap_set_balancer_autoroute(bsel, val); + } + else { + bsel->s->autoroute_type = PROXY_AUTOROUTE_TYPE_DISABLE; + *bsel->s->autoroute_field = '\0'; + } + } if ((val = apr_table_get(params, "b_wyes")) && (*val == '1' && *(val+1) == '\0') && (val = apr_table_get(params, "b_nwrkr"))) { @@ -1233,6 +1319,16 @@ ap_rprintf(r, " %" APR_TIME_T_FMT "", apr_time_sec(balancer->s->timeout)); + if (balancer->s->autoroute_type != PROXY_AUTOROUTE_TYPE_DISABLE) { + ap_rprintf(r, + " %s:%s", + balancer->s->autoroute_type == PROXY_AUTOROUTE_TYPE_COOKIE ? "cookie" : + balancer->s->autoroute_type == PROXY_AUTOROUTE_TYPE_ENV ? "env" : + balancer->s->autoroute_type == PROXY_AUTOROUTE_TYPE_HEADER ? "header" : + balancer->s->autoroute_type == PROXY_AUTOROUTE_TYPE_HEADER_REMOVE ? "header-remove" : + balancer->s->autoroute_type == PROXY_AUTOROUTE_TYPE_NOTES ? "notes" : "unknown", + balancer->s->autoroute_field); + } if (balancer->s->max_attempts_set) { ap_rprintf(r, " %d\n", @@ -1444,7 +1540,7 @@ "\">", NULL); ap_rvputs(r, balancer->s->name, " [",balancer->s->sname, "]\n", NULL); ap_rputs("\n\n" - "" + "" "\n", r); /* the below is a safe cast, since the number of slots total will * never be more than max_workers, which is restricted to int */ @@ -1466,6 +1562,19 @@ balancer->s->sticky_force ? "On" : "Off"); ap_rprintf(r, "", apr_time_sec(balancer->s->timeout)); + if (balancer->s->autoroute_type == PROXY_AUTOROUTE_TYPE_DISABLE) { + ap_rprintf(r, "\n"); + } + else { + ap_rprintf(r, + "", + balancer->s->autoroute_type == PROXY_AUTOROUTE_TYPE_COOKIE ? "cookie" : + balancer->s->autoroute_type == PROXY_AUTOROUTE_TYPE_ENV ? "env" : + balancer->s->autoroute_type == PROXY_AUTOROUTE_TYPE_HEADER ? "header" : + balancer->s->autoroute_type == PROXY_AUTOROUTE_TYPE_HEADER_REMOVE ? "header-remove" : + balancer->s->autoroute_type == PROXY_AUTOROUTE_TYPE_NOTES ? "notes" : "unknown", + balancer->s->autoroute_field); + } ap_rprintf(r, "\n", balancer->s->max_attempts); ap_rprintf(r, "\n", balancer->s->lbpname); @@ -1588,6 +1697,19 @@ ap_rvputs(r, "value ='", bsel->s->sticky, NULL); } ap_rputs("'>    (Use '-' to delete)\n", r); + ap_rputs("", r); + if (bsel->s->autoroute_type == PROXY_AUTOROUTE_TYPE_DISABLE) { + ap_rprintf(r, ""); + } + else { + ap_rprintf(r, "", + bsel->s->autoroute_type == PROXY_AUTOROUTE_TYPE_COOKIE ? "cookie" : + bsel->s->autoroute_type == PROXY_AUTOROUTE_TYPE_ENV ? "env" : + bsel->s->autoroute_type == PROXY_AUTOROUTE_TYPE_HEADER ? "header" : + bsel->s->autoroute_type == PROXY_AUTOROUTE_TYPE_HEADER_REMOVE ? "header-remove" : + bsel->s->autoroute_type == PROXY_AUTOROUTE_TYPE_NOTES ? "notes" : "unknown", + bsel->s->autoroute_field); + } if (storage->num_free_slots(bsel->wslot) != 0) { ap_rputs(" + + +
MaxMembersStickySessionDisableFailoverTimeoutFailoverAttemptsMethodMaxMembersStickySessionDisableFailoverTimeoutAutorouteFailoverAttemptsMethodPathActive
%" APR_TIME_T_FMT " (None) %s:%s%d%s
AutoRoute    (Use '-' to delete)
    (Use '-' to delete)
Add New Worker:" "    Are you sure? " @@ -1645,6 +1767,30 @@ } +static apr_status_t proxy_balancer_fixup_headers_out(ap_filter_t *f, + apr_bucket_brigade *in) +{ + /* copy new route id to notes, and remove it from response */ + const char *field; + if ((field = apr_table_get(f->r->notes, "proxy-balancer-ar-field")) != NULL) { + const char *value; + if ((value = apr_table_get(f->r->headers_out, field)) != NULL) { + apr_table_set( f->r->notes, "proxy-balancer-ar-newroute", value); + apr_table_unset( f->r->headers_out, field); + } + } + + /* remove ourselves from the filter chain */ + /* send the data up the stack */ + ap_remove_output_filter(f); + return ap_pass_brigade(f->next,in); +} + +static void proxy_balancer_insert_output_filter(request_rec *r) +{ + ap_add_output_filter("FIXUP_PROXY_BALANCER_OUT", NULL, r, r->connection); +} + static void ap_proxy_balancer_register_hook(apr_pool_t *p) { /* Only the mpm_winnt has child init hook handler. @@ -1657,9 +1803,12 @@ ap_hook_pre_config(balancer_pre_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(balancer_handler, NULL, NULL, APR_HOOK_FIRST); ap_hook_child_init(balancer_child_init, aszPred, NULL, APR_HOOK_MIDDLE); + ap_hook_insert_filter(proxy_balancer_insert_output_filter, NULL, NULL, APR_HOOK_FIRST); proxy_hook_pre_request(proxy_balancer_pre_request, NULL, NULL, APR_HOOK_FIRST); proxy_hook_post_request(proxy_balancer_post_request, NULL, NULL, APR_HOOK_FIRST); proxy_hook_canon_handler(proxy_balancer_canon, NULL, NULL, APR_HOOK_FIRST); + ap_register_output_filter("FIXUP_PROXY_BALANCER_OUT", proxy_balancer_fixup_headers_out, + NULL, AP_FTYPE_CONTENT_SET); } AP_DECLARE_MODULE(proxy_balancer) = { Index: include/util_cookies.h =================================================================== --- include/util_cookies.h (Revision 1417803) +++ include/util_cookies.h (Arbeitskopie) @@ -128,6 +128,15 @@ int remove); /** + * Read a set cookie called name, placing its value in val. + * + * Both the Set-Cookie and Set-Cookie2 headers are scanned for the cookie. + * + * If the cookie is duplicated, this function returns APR_EGENERAL. + */ +AP_DECLARE(apr_status_t) ap_set_cookie_read(request_rec * r, const char *name, const char **val); + +/** * Sanity check a given string that it exists, is not empty, * and does not contain the special characters '=', ';' and '&'. * Index: docs/manual/mod/mod_proxy.xml =================================================================== --- docs/manual/mod/mod_proxy.xml (Revision 1417803) +++ docs/manual/mod/mod_proxy.xml (Arbeitskopie) @@ -1143,6 +1143,22 @@ without considering the retry parameter of each worker. In this case set to Off.
autorouteSet the route id automatically. The format of the value is + [method]:[field name]. The autoroute support different methods to get the + new route id. +
    +
  • header reads the given fieldname from the response header.
  • +
  • header-remove same as header, but removes the field from response.
  • +
  • env reads the vaue from the environment variable
  • +
  • notes get the route from an other module, via the notes (the other + module must support it)
  • +
  • cookie reads it from a cookie, set by the backend. If the no field + name is given, the stickysession-cookie is used.
  • +
+ If only a field name is given, the header-remove method is used. +

A sample balancer setup