[PATCH] Goofing with proxy load balancing

[PATCH] Goofing with proxy load balancing

am 23.07.2003 05:27:49 von Bill Stoddard

This is a multi-part message in MIME format.
--------------080106050808090204010701
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

Here is some nasty nasty code but it works well enough for me to test
some ideas. I'll refine it as I have time.

Quick overview of the implementation:
This code uses a single optional hook, the load_balance hook, which is
defined by mod_proxy, implemented in mod_proxy_lb.c and is called by
ap_proxy_http_create_connection in proxy_http.c. The hook takes three
input arguments:
request_rec *r,
char *load_balancer_module_id,
r->unparsed_uri,

and returns a 'trylist'. The trylist contains pointers to a couple of
callback functions defined by the lb module identified by
'load_balancer_module_id'. One callback is to fetch an ip address
(actually a proxy_conn_t*) to use to route a request. The other callback
is to report a failure to establish a connection with that
proxy_conn_t). The trylist also contains a void * pointer to an opaque
datatype specific to a particular load_balancer module. I did it this
way to reduce unnecessary coupling between the load balancers and
mod_proxy/proxy_http.

mod_proxy_lb.c is a braindead round robin router. Hopefully it is
sufficient to demonstrate where I'm heading... Load balancer modules can
be arbitraily complex in how they decide to route requests.

To use...
1. apply the patch from directory modules/proxy

2. add mod_proxy_lb.c to your compile environment (compile it as
loadable module)

3. Update httpd.conf with something like this:

LoadModule proxy_lb_module modules/mod_proxy_lb.so

LBServerCluster cluster1 http://downstreamserver1:port
LBServerCluster cluster1 http://downstreamserver2:port
LBProxyPass /file500.html cluster1
LBProxyPass /snoop cluster1


Where 'downstreamserver#' is the name of a backend system to route
requests to. 'cluster1' is the name of a logical group of backend
machines pointed to by LBProxyPass directives (which are coded exactly
like ProxyPass directives, except the 'real' name is replaced by a
symbol representing the backend cluster.)

If I send a request for /snoop, the request will be routed to the
servers defined in LBServerCluster in the order they appear in the
config file. Rerouting to the next server will only occur if either the
DNS lookup or connection establishment to the server fails. Simple
failure to find a resource on the target server will not cause a reroute.

My fingers are tired of typing so here is the patch. Comments welcome.

Bill

--------------080106050808090204010701
Content-Type: text/plain;
name="proxy_lb.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="proxy_lb.patch"

? load_balance_071803
? load_bal_071803.tar
? mod_proxy_lb.c
? mod_proxy_lb.dsp
? proxy_http.c.v1
? proxy_lb.patch
Index: mod_proxy.c
============================================================ =======
RCS file: /home/cvs/httpd-2.0/modules/proxy/mod_proxy.c,v
retrieving revision 1.90.2.2
diff -u -u -r1.90.2.2 mod_proxy.c
--- mod_proxy.c 22 Feb 2003 18:38:13 -0000 1.90.2.2
+++ mod_proxy.c 23 Jul 2003 02:48:29 -0000
@@ -1142,6 +1142,7 @@
APR_HOOK_STRUCT(
APR_HOOK_LINK(scheme_handler)
APR_HOOK_LINK(canon_handler)
+ APR_HOOK_LINK(load_balance)
)

APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, scheme_handler,
@@ -1155,3 +1156,6 @@
APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(proxy, PROXY, int, fixups,
(request_rec *r), (r),
OK, DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, load_balance,
+ (request_rec *r, const char *type, const char *url, proxy_conn_list_t **trylist),
+ (r,type, url, trylist),DECLINED)
Index: mod_proxy.dsp
============================================================ =======
RCS file: /home/cvs/httpd-2.0/modules/proxy/mod_proxy.dsp,v
retrieving revision 1.19.2.5
diff -u -u -r1.19.2.5 mod_proxy.dsp
--- mod_proxy.dsp 11 Mar 2003 01:08:07 -0000 1.19.2.5
+++ mod_proxy.dsp 23 Jul 2003 02:48:29 -0000
@@ -53,7 +53,7 @@
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /machine:I386 /out:"Release/mod_proxy.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy.so
-# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /machine:I386 /out:"Release/mod_proxy.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy.so /opt:ref
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /debug /machine:I386 /out:"Release/mod_proxy.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy.so /opt:ref

!ELSEIF "$(CFG)" == "mod_proxy - Win32 Debug"

Index: mod_proxy.h
============================================================ =======
RCS file: /home/cvs/httpd-2.0/modules/proxy/mod_proxy.h,v
retrieving revision 1.85.2.1
diff -u -u -r1.85.2.1 mod_proxy.h
--- mod_proxy.h 3 Feb 2003 17:31:49 -0000 1.85.2.1
+++ mod_proxy.h 23 Jul 2003 02:48:29 -0000
@@ -223,6 +223,7 @@
} proxy_completion;


+
/* hooks */

/* Create a set of PROXY_DECLARE(type), PROXY_DECLARE_NONSTD(type) and
@@ -262,8 +263,41 @@
APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, create_req, (request_rec *r, request_rec *pr))
APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, fixups, (request_rec *r))

-/* proxy_util.c */
+/*
+ * Define the interface to optional load balancer modules. The interface
+ * consists of an optional hook, load_balance, implemented by the load
+ * balancer module and two structures that load balancers and mod_proxy
+ * share in common. proxy_conn_t and proxt_conn_list_t are the only
+ * two data types that are required to be shared between mod_proxy and
+ * load balancers. All load balancer modules are required to implement
+ * the two callback functions in proxy_conn_list_t.
+ * lb_cb_get_conn() takes as its argument the proxy_conn_list_t returned
+ * by the load_balance hook and returns a proxy_conn_t for the desired
+ * backend connection.
+ */
+typedef struct {
+ const char *name;
+ apr_port_t port;
+ apr_sockaddr_t *addr;
+ apr_socket_t *sock;
+ const char *scheme;
+} proxy_conn_t;
+
+typedef struct proxy_conn_list_t proxy_conn_list_t;
+struct proxy_conn_list_t {
+ proxy_conn_t* (*lb_cb_get_conn)(proxy_conn_list_t *cl);
+ void (*lb_cb_mark_conn_down)(proxy_conn_list_t *cl, proxy_conn_t* pc);
+ /* Pointer to stuff only the specific lb module that allocated this struct knows about */
+ void *lb_private;
+};
+/**
+ *
+ */
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, load_balance,
+ (request_rec *r, const char *type, const char *url, proxy_conn_list_t **trylist))
+

+/* proxy_util.c */
PROXY_DECLARE(request_rec *)ap_proxy_make_fake_req(conn_rec *c, request_rec *r);
PROXY_DECLARE(int) ap_proxy_hex2c(const char *x);
PROXY_DECLARE(void) ap_proxy_c2hex(int ch, char *x);
Index: proxy_http.c
============================================================ =======
RCS file: /home/cvs/httpd-2.0/modules/proxy/proxy_http.c,v
retrieving revision 1.164.2.3
diff -u -u -r1.164.2.3 proxy_http.c
--- proxy_http.c 15 Apr 2003 16:36:16 -0000 1.164.2.3
+++ proxy_http.c 23 Jul 2003 02:48:30 -0000
@@ -190,6 +190,26 @@
apr_table_unset(headers, "Connection");
}

+/*
+ * arguments:
+ * p (in) - upstream connection pool (in the future, this might be a pool specific to a backend host if
+ * we ever want to do connection pooling).
+ * r (in) - request_rec
+ ***
+ ** This is the most important output from the routine.
+ * p_conn (out) - allocated by caller out of the upstream connection pool. pc_conn->addr is
+ * returned by the call to apr_sockaddr_info_get. If the address passed in is an ipaddress,
+ * DNS lookup is not performed.
+ ****
+ * c (in) - upstream connection
+ * conf (in) - proxy_server_conf
+ * uri (out) - ap_uri_t allocated out of the upstream connection pool by caller. initialized here
+ * url (in|out) - request url (e.g. http://foo/). May be reallocated out of the connection pool
+ * proxyname (in) - forward proxy name
+ * proxyport (in) - forward proxy port
+ * server_portstr (out) -
+ * server_portstr_size (in) -
+ */
static
apr_status_t ap_proxy_http_determine_connection(apr_pool_t *p, request_rec *r,
proxy_http_conn_t *p_conn,
@@ -200,10 +220,12 @@
const char *proxyname,
apr_port_t proxyport,
char *server_portstr,
- int server_portstr_size) {
+ int server_portstr_size)
+{
int server_port;
apr_status_t err;
apr_sockaddr_t *uri_addr;
+
/*
* Break up the URL to determine the host to connect to
*/
@@ -272,142 +294,168 @@
return OK;
}

+/*
+ * url is passed in because it may need to be rewritten if the destination
+ * host changes due to load balancing. Only the hostname (and possibly the
+ * port) will change.
+ */
static
apr_status_t ap_proxy_http_create_connection(apr_pool_t *p, request_rec *r,
- proxy_http_conn_t *p_conn,
- conn_rec *c, conn_rec **origin,
- proxy_conn_rec *backend,
+ conn_rec *c,
proxy_server_conf *conf,
- const char *proxyname) {
- int failed=0, new=0;
- apr_socket_t *client_socket = NULL;
+ const char *proxyname,
+ apr_port_t proxyport,
+ apr_uri_t **pp_uri,
+ char **url,
+ proxy_http_conn_t **pp_conn,
+ conn_rec **origin)
+{
+ apr_sockaddr_t *uri_addr;
+ apr_uri_t *uri;
+ proxy_conn_list_t *trylist = NULL;
+ apr_status_t rv;
+ int rc = OK;
+ proxy_conn_t *pc;
+ proxy_http_conn_t *p_conn;
+ int n = 0;
+ (*pp_conn) = p_conn = apr_pcalloc(r->connection->pool, sizeof(*p_conn));

- /* We have determined who to connect to. Now make the connection, supporting
- * a KeepAlive connection.
- */
+ *pp_uri = uri = apr_palloc(r->connection->pool, sizeof(*uri));

- /* get all the possible IP addresses for the destname and loop through them
- * until we get a successful connection
+ /* Break the URL into host, port, uri. We may need to change the host field
+ * if the host changes during load balancing
*/
+ if (APR_SUCCESS != apr_uri_parse(p, *url, uri)) {
+ return ap_proxyerror(r, HTTP_BAD_REQUEST,
+ apr_pstrcat(p,"URI cannot be parsed: ", *url,
+ NULL));
+ }
+ if (!uri->port) {
+ uri->port = apr_uri_port_of_scheme(uri->scheme);
+ }

- /* if a keepalive socket is already open, check whether it must stay
- * open, or whether it should be closed and a new socket created.
+ /*
+ * Gather the list of downstream hosts to try.
+ * ToDo:
+ * - load balance forward proxy requests
+ * - Add mod_proxy config directive to specify the load balancing
+ * algorithm (more than just 'roundrobin')
*/
- /* see memory note above */
- if (backend->connection) {
- client_socket = ap_get_module_config(backend->connection->conn_config, &core_module);
- if ((backend->connection->id == c->id) &&
- (backend->port == p_conn->port) &&
- (backend->hostname) &&
- (!apr_strnatcasecmp(backend->hostname, p_conn->name))) {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
- "proxy: keepalive address match (keep original socket)");
- } else {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
- "proxy: keepalive address mismatch / connection has"
- " changed (close old socket (%s/%s, %d/%d))",
- p_conn->name, backend->hostname, p_conn->port,
- backend->port);
- apr_socket_close(client_socket);
- backend->connection = NULL;
- }
+ if (!proxyname) {
+ rc = proxy_run_load_balance(r, "roundrobin", r->unparsed_uri, &trylist);
}
+ if (trylist && rc == OK) {
+ while (trylist) {
+ pc = trylist->lb_cb_get_conn(trylist);
+ if (!pc) {
+ rc = HTTP_BAD_GATEWAY; /* Humm, is this the right rc? */
+ break;
+ }
+ p_conn->name = apr_pstrdup(r->connection->pool, pc->name);
+ p_conn->port = pc->port;

- /* get a socket - either a keepalive one, or a new one */
- new = 1;
- if ((backend->connection) && (backend->connection->id == c->id)) {
- apr_size_t buffer_len = 1;
- char test_buffer[1];
- apr_status_t socket_status;
- apr_interval_time_t current_timeout;
-
- /* use previous keepalive socket */
- *origin = backend->connection;
- p_conn->sock = client_socket;
- new = 0;
-
- /* save timeout */
- apr_socket_timeout_get(p_conn->sock, ¤t_timeout);
- /* set no timeout */
- apr_socket_timeout_set(p_conn->sock, 0);
- socket_status = apr_recv(p_conn->sock, test_buffer, &buffer_len);
- /* put back old timeout */
- apr_socket_timeout_set(p_conn->sock, current_timeout);
- if ( APR_STATUS_IS_EOF(socket_status) ) {
- ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
- "proxy: HTTP: previous connection is closed");
- new = 1;
+ /* Resolve the name */
+ rv = apr_sockaddr_info_get(&p_conn->addr, p_conn->name, APR_UNSPEC, p_conn->port, 0, c->pool);
+ if (rv == APR_SUCCESS) {
+ rc = ap_proxy_connect_to_backend(&p_conn->sock, "HTTP", p_conn->addr, p_conn->name,
+ conf, r->server, c->pool);
+ }
+ if (rv == APR_SUCCESS && rc == OK) {
+ /* We have a working backend connection */
+ break;
+ }
+ else {
+ /* Tell the load balancer module this connection is toasted... */
+ trylist->lb_cb_mark_conn_down(trylist, pc);
+ }
+ }
+ if (rc != OK) {
+ return rc;
}
+ /* Update the uri->hostname and uri->hostinfo fields with the new value */
+ uri->hostname = p_conn->name;
}
- if (new) {
-
- /* create a new socket */
- backend->connection = NULL;
-
- /*
- * At this point we have a list of one or more IP addresses of
- * the machine to connect to. If configured, reorder this
- * list so that the "best candidate" is first try. "best
- * candidate" could mean the least loaded server, the fastest
- * responding server, whatever.
- *
- * For now we do nothing, ie we get DNS round robin.
- * XXX FIXME
+ else {
+ /* Not doing load balancing
+ * do a DNS lookup for the destination host
*/
- failed = ap_proxy_connect_to_backend(&p_conn->sock, "HTTP",
- p_conn->addr, p_conn->name,
- conf, r->server, c->pool);
-
- /* handle a permanent error on the connect */
- if (failed) {
- if (proxyname) {
- return DECLINED;
- } else {
- return HTTP_BAD_GATEWAY;
- }
+ rv = apr_sockaddr_info_get(&uri_addr, apr_pstrdup(c->pool, uri->hostname),
+ APR_UNSPEC, uri->port, 0, c->pool);
+
+ /* allocate these out of the connection pool - the check on
+ * r->connection->id makes sure that this string does not get accessed
+ * past the connection lifetime
+ */
+ /* are we connecting directly, or via a proxy? */
+ if (proxyname) {
+ p_conn->name = apr_pstrdup(c->pool, proxyname);
+ p_conn->port = proxyport;
+ /* see memory note above */
+ rv = apr_sockaddr_info_get(&p_conn->addr, p_conn->name, APR_UNSPEC,
+ p_conn->port, 0, c->pool);
}
+ else {
+ p_conn->name = apr_pstrdup(c->pool, uri->hostname);
+ p_conn->port = uri->port;
+ p_conn->addr = uri_addr;

- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
- "proxy: socket is connected");
-
- /* the socket is now open, create a new backend server connection */
- *origin = ap_run_create_connection(c->pool, r->server, p_conn->sock,
- r->connection->id,
- r->connection->sbh, c->bucket_alloc);
- if (!*origin) {
- /* the peer reset the connection already; ap_run_create_connection()
- * closed the socket
- */
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
- r->server, "proxy: an error occurred creating a "
- "new connection to %pI (%s)", p_conn->addr,
- p_conn->name);
- apr_socket_close(p_conn->sock);
- return HTTP_INTERNAL_SERVER_ERROR;
}
- backend->connection = *origin;
- backend->hostname = apr_pstrdup(c->pool, p_conn->name);
- backend->port = p_conn->port;
-
- if (backend->is_ssl) {
- if (!ap_proxy_ssl_enable(backend->connection)) {
- ap_log_error(APLOG_MARK, APLOG_ERR, 0,
- r->server, "proxy: failed to enable ssl support "
- "for %pI (%s)", p_conn->addr, p_conn->name);
- return HTTP_INTERNAL_SERVER_ERROR;
- }
+ if (rv != APR_SUCCESS) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ apr_pstrcat(p, "DNS lookup failure for: ",
+ p_conn->name, NULL));
}
- else {
- ap_proxy_ssl_disable(backend->connection);
+ rc = ap_proxy_connect_to_backend(&p_conn->sock, "HTTP", p_conn->addr, p_conn->name,
+ conf, r->server, c->pool);
+ }
+
+ /* Reconstitute the URL to account for load balancing */
+ if (!proxyname) {
+ *url = apr_pstrcat(p, uri->path, uri->query ? "?" : "",
+ uri->query ? uri->query : "",
+ uri->fragment ? "#" : "",
+ uri->fragment ? uri->fragment : "", NULL);
+ }
+ /* check if ProxyBlock directive on this host */
+ if (OK != ap_proxy_checkproxyblock(r, conf, uri_addr)) {
+ return ap_proxyerror(r, HTTP_FORBIDDEN,
+ "Connect to remote machine blocked");
+ }
+ /*
+ * Handle a permanent error on the connect
+ * Make sure this return code is ok...
+ */
+ if (rv != OK) {
+ if (proxyname) {
+ return DECLINED;
+ } else {
+ return HTTP_BAD_GATEWAY;
}
+ }

- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
- "proxy: connection complete to %pI (%s)",
- p_conn->addr, p_conn->name);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: socket is connected");

- /* set up the connection filters */
- ap_run_pre_connection(*origin, p_conn->sock);
+ /* the socket is now open, create a new backend server connection */
+ *origin = ap_run_create_connection(c->pool, r->server, p_conn->sock,
+ r->connection->id,
+ r->connection->sbh, c->bucket_alloc);
+ if (!*origin) {
+ /* the peer reset the connection already; ap_run_create_connection()
+ * closed the socket
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ r->server, "proxy: an error occurred creating a "
+ "new connection to %pI (%s)", p_conn->addr,
+ p_conn->name);
+ apr_socket_close(p_conn->sock);
+ return HTTP_INTERNAL_SERVER_ERROR;
}
+
+ /* By now, the url passed in on our arg list may need to be rewritten
+ * because the host name changed.
+ */
+
return OK;
}

@@ -1001,7 +1049,9 @@

static
apr_status_t ap_proxy_http_cleanup(request_rec *r, proxy_http_conn_t *p_conn,
- proxy_conn_rec *backend) {
+ proxy_conn_rec *backend)
+{
+
/* If there are no KeepAlives, or if the connection has been signalled
* to close, close the socket and clean up
*/
@@ -1027,18 +1077,7 @@
* also, if we have trouble which is clearly specific to the proxy, then
* we return DECLINED so that we can try another proxy. (Or the direct
* route.)
- */
-int ap_proxy_http_handler(request_rec *r, proxy_server_conf *conf,
- char *url, const char *proxyname,
- apr_port_t proxyport)
-{
- int status;
- char server_portstr[32];
- conn_rec *origin = NULL;
- proxy_conn_rec *backend = NULL;
- int is_ssl = 0;
-
- /* Note: Memory pool allocation.
+ * Note: Memory pool allocation.
* A downstream keepalive connection is always connected to the existence
* (or not) of an upstream keepalive connection. If this is not done then
* load balancing against multiple backend servers breaks (one backend
@@ -1050,13 +1089,25 @@
* pool, and when we want to reuse a socket, we check first whether the
* connection ID of the current upstream connection is the same as that
* of the connection when the socket was opened.
- */
+ *
+ */
+int ap_proxy_http_handler(request_rec *r, proxy_server_conf *conf,
+ char *url, const char *proxyname,
+ apr_port_t proxyport)
+{
+ int status;
+ int server_port;
+ char server_portstr[32];
+
+ proxy_http_conn_t *p_conn = NULL;
+ conn_rec *origin = NULL;
+ proxy_conn_rec *backend = NULL;
+ apr_uri_t *uri;
+
+ int is_ssl = 0;
apr_pool_t *p = r->connection->pool;
conn_rec *c = r->connection;
- apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
- apr_uri_t *uri = apr_palloc(r->connection->pool, sizeof(*uri));
- proxy_http_conn_t *p_conn = apr_pcalloc(r->connection->pool,
- sizeof(*p_conn));
+ apr_bucket_brigade *bb;

/* is it for us? */
if (strncasecmp(url, "https:", 6) == 0) {
@@ -1096,31 +1147,69 @@
}

backend->is_ssl = is_ssl;
+ /*
+ * ToDo:
+ * - Handle keep-alive connections to the downstream server, particulary
+ * when handling subrequests (ie, when doing page assembly in the proxy).
+ * We don't want to start a new connection for each subrequest.
+ * - Config directive to specify a load balance module to use (we are
+ * using "roundrobin" here.)
+ * - Handle the case where we want to use native mod_proxy function and
+ * -not- use load balancers.
+ */
+
+ /*
+ * Step 1: Make the Connection to the Upstream Server
+ */

- /* Step One: Determine Who To Connect To */
- status = ap_proxy_http_determine_connection(p, r, p_conn, c, conf, uri,
- &url, proxyname, proxyport,
- server_portstr,
- sizeof(server_portstr));
+ status = ap_proxy_http_create_connection(p, r, c, conf, proxyname, proxyport,
+ &uri, &url,&p_conn, &origin);
if ( status != OK ) {
return status;
}

- /* Step Two: Make the Connection */
- status = ap_proxy_http_create_connection(p, r, p_conn, c, &origin, backend,
- conf, proxyname);
- if ( status != OK ) {
- return status;
+ /* Get the server port for the Via headers */
+ server_port = ap_get_server_port(r);
+ if (ap_is_default_port(server_port, r)) {
+ strcpy(server_portstr,"");
+ } else {
+ apr_snprintf(server_portstr, sizeof(server_portstr), ":%d",
+ server_port);
+ }
+
+ backend->connection = origin;
+ backend->hostname = apr_pstrdup(c->pool, p_conn->name);
+ backend->port = p_conn->port;
+ if (backend->is_ssl) {
+ if (!ap_proxy_ssl_enable(backend->connection)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0,
+ r->server, "proxy: failed to enable ssl support "
+ "for %pI (%s)", p_conn->addr, p_conn->name);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ else {
+ ap_proxy_ssl_disable(backend->connection);
}
-
- /* Step Three: Send the Request */
+
+ /* set up the connection filters */
+ ap_run_pre_connection(origin, p_conn->sock);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "proxy: connection complete to %pI (%s)",
+ p_conn->addr, p_conn->name);
+
+ /*
+ * Step Two: Send the Request
+ */
+ bb = apr_brigade_create(p, c->bucket_alloc);
status = ap_proxy_http_request(p, r, p_conn, origin, conf, uri, url, bb,
server_portstr);
if ( status != OK ) {
return status;
}

- /* Step Four: Receive the Response */
+ /* Step Three: Receive the Response */
status = ap_proxy_http_process_response(p, r, p_conn, origin, backend, conf,
bb, server_portstr);
if ( status != OK ) {
@@ -1129,11 +1218,15 @@
return status;
}

- /* Step Five: Clean Up */
+ /* Step Four: Clean Up */
status = ap_proxy_http_cleanup(r, p_conn, backend);
if ( status != OK ) {
return status;
}
+ /*
+ * Run the ap_run_proxy_cleanup() hook ... register this call as a pool cleanup?
+ ap_run_proxy_cleanup();
+ */

return OK;
}

--------------080106050808090204010701
Content-Type: text/plain;
name="mod_proxy_lb.c"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="mod_proxy_lb.c"

/* ============================================================ ========
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ============================================================ ========
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
* Portions of this software are based upon public domain software
* originally written at the National Center for Supercomputing Applications,
* University of Illinois, Urbana-Champaign.
*/

#include "mod_proxy.h"

module AP_MODULE_DECLARE_DATA proxy_lb_module;

/* LBProxyPass fake_url server_cluster */
typedef struct {
const char* fake;
const char* server_cluster;
} proxy_lb_alias;

typedef struct {
apr_hash_t *cluster; /* Hash table of proxy_conn_t */
apr_array_header_t *aliases; /* Array of proxy_lb_alias types */
} proxy_lb_conf;

/*
* Implement callback functions mod_proxy uses to get at our goods. Every
* load balancer module must implement these two functions.
*/
apr_thread_mutex_t *index_lock;
static int index = 0;

static proxy_conn_t* lb_get_conn(proxy_conn_list_t *trylist)
{
apr_array_header_t *cname;
proxy_conn_t *pc;

cname = (apr_array_header_t *) trylist->lb_private;

apr_thread_mutex_lock(index_lock);
pc = (proxy_conn_t *) cname->elts;
pc += index;
/* Make sure index always references a valid entry in the cname->elts array */
index++;
if (index == cname->nelts) {
/* Start over */
index = 0;
}
apr_thread_mutex_unlock(index_lock);
return pc;
}
static void lb_mark_conn_down(proxy_conn_list_t *trylist, proxy_conn_t *pc)
{
}

static int alias_match(const char *uri, const char *alias_fakename)
{
const char *end_fakename = alias_fakename + strlen(alias_fakename);
const char *aliasp = alias_fakename, *urip = uri;

while (aliasp < end_fakename) {
if (*aliasp == '/') {
/* any number of '/' in the alias matches any number in
* the supplied URI, but there must be at least one...
*/
if (*urip != '/')
return 0;

while (*aliasp == '/')
++aliasp;
while (*urip == '/')
++urip;
}
else {
/* Other characters are compared literally */
if (*urip++ != *aliasp++)
return 0;
}
}

/* Check last alias path component matched all the way */

if (aliasp[-1] != '/' && *urip != '\0' && *urip != '/')
return 0;

/* Return number of characters from URI which matched (may be
* greater than length of alias, since we may have matched
* doubled slashes)
*/

return urip - uri;
}

static int proxy_lb_fixup(request_rec *r)
{
return OK;
}
/*
*
*/
static int proxy_lb_trans(request_rec *r)
{
int i, len;
proxy_conn_t *trylist;
apr_array_header_t *cname;
proxy_lb_conf *conf = ap_get_module_config(r->server->module_config,
&proxy_lb_module);
proxy_lb_alias *ent = (proxy_lb_alias *) conf->aliases->elts;

/* Does this URL match one of the aliases? */
for (i = 0; i < conf->aliases->nelts; i++) {
len = alias_match(r->uri, ent[i].fake);

if (len > 0) {
/* Look up the list of available servers for this url
* todo:
* - Find a better name for cname: cname is an array of
* proxy_conn_t's
*/
cname = apr_hash_get(conf->cluster, ent[i].server_cluster, APR_HASH_KEY_STRING);

if (cname && cname->nelts) {
trylist = (proxy_conn_t *) cname->elts;
/*
* Pick the first entry off the trylist.
*/
r->filename = apr_pstrcat(r->pool, "proxy:",
trylist->scheme,
"://",
trylist->name,
":",
"9080",
r->uri,
NULL);

r->handler = "proxy-server";
r->proxyreq = PROXYREQ_REVERSE;
/* Make the trylist available to the load balancer hook */
ap_set_module_config(r->request_config, &proxy_lb_module, cname);
return OK;
}
}
}
return DECLINED;
}
/*
* Return the trylist
*/
static int proxy_load_balancer(request_rec *r, const char* mod, const char *uri, proxy_conn_list_t **connlist)
{
apr_array_header_t *cname;
proxy_conn_t *trylist;

/* Check to see if this module should attempt to load balance this uri */
cname = ap_get_module_config(r->request_config, &proxy_lb_module);
if (!cname) {
return DECLINED;
}
(*connlist) = apr_pcalloc(r->pool, sizeof(proxy_conn_list_t));

/* Setup callback functions defined in the module for use by mod_proxy
* when iterating over candidate hosts to forward requests to
*/
(*connlist)->lb_cb_get_conn = lb_get_conn;
(*connlist)->lb_cb_mark_conn_down = lb_mark_conn_down;

(*connlist)->lb_private = cname;

return OK;
}
static int proxy_lb_post_config(apr_pool_t *pconf, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
{
// proxy_ssl_enable = APR_RETRIEVE_OPTIONAL_FN(ssl_proxy_enable);
// proxy_ssl_disable = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_disable);

return OK;
}

static void *create_proxy_lb_config(apr_pool_t *p, server_rec *s)
{
proxy_lb_conf *conf = apr_pcalloc(p, sizeof(*conf));
/* cluster contains the list of hostnames */
conf->cluster = apr_hash_make(p);

/* LBProxyPass alias */
conf->aliases = apr_array_make(p, 10, sizeof(proxy_lb_alias));

apr_thread_mutex_create(&index_lock, APR_THREAD_MUTEX_DEFAULT, p);
return conf;
}
/*
* Handle LBServerCluster directive
*/
static const char* lbservercluster(cmd_parms *cmd, void *dummy, const char *arg, const char *arg2)
{
proxy_conn_t *trylist_entry;
server_rec *s = cmd->server;
proxy_lb_conf *conf = ap_get_module_config(s->module_config, &proxy_lb_module);
apr_array_header_t *cname;
apr_uri_t *parsed_uri;

cname = apr_hash_get(conf->cluster, arg, APR_HASH_KEY_STRING);

if (!cname) {
cname = apr_array_make(cmd->pool, 10, sizeof(*trylist_entry));
apr_hash_set(conf->cluster, arg, APR_HASH_KEY_STRING, cname);
}

/* Parse arg2 into it's components and save in the array. Arg 2 looks like this:
* http://name:port
*/
parsed_uri = apr_pcalloc(cmd->pool, sizeof(*parsed_uri));
apr_uri_parse(cmd->pool, arg2, parsed_uri);
trylist_entry = apr_array_push(cname);
trylist_entry->scheme = parsed_uri->scheme;
trylist_entry->name = parsed_uri->hostname;
trylist_entry->port = parsed_uri->port;
trylist_entry->addr = NULL;
trylist_entry->sock = NULL;

return NULL;
}
/*
* Handle LBProxyPass directive
*/
static const char* lbproxypass(cmd_parms *cmd, void *dummy,
const char *a1, const char *a2)
{
server_rec *s = cmd->server;
proxy_lb_conf *conf = ap_get_module_config(s->module_config, &proxy_lb_module);
proxy_lb_alias *alias;
alias = apr_array_push(conf->aliases);
alias->fake = a1;
alias->server_cluster = a2;
return NULL;
}
static const command_rec proxy_lb_cmds[] =
{
AP_INIT_ITERATE2("LBServerCluster", lbservercluster, NULL, RSRC_CONF,
"List of hosts/domains that are members of this cluster"),
AP_INIT_TAKE2("LBProxyPass", lbproxypass, NULL, RSRC_CONF,
"protocol:url cluster_id (http://foo/snoop cluster1"),
(NULL)
};

static void proxy_lb_register_hook(apr_pool_t *p)
{
/* fixup before mod_rewrite, so that the proxied url will not
* escaped accidentally by our fixup.
*/
static const char * const aszSucc[]={ "mod_rewrite.c", NULL };

ap_hook_post_config(proxy_lb_post_config, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_fixups(proxy_lb_fixup, NULL, aszSucc, APR_HOOK_FIRST);
ap_hook_translate_name(proxy_lb_trans, NULL, NULL, APR_HOOK_FIRST);
proxy_hook_load_balance(proxy_load_balancer, NULL, NULL, APR_HOOK_FIRST);
}
module AP_MODULE_DECLARE_DATA proxy_lb_module = {
STANDARD20_MODULE_STUFF,
NULL, /* create per-directory config structure */
NULL, /* merge per-directory config structures */
create_proxy_lb_config, /* create per-server config structure */
NULL, /* merge per-server config structures */
proxy_lb_cmds, /* command apr_table_t */
proxy_lb_register_hook /* register hooks */
};

--------------080106050808090204010701--

Re: [PATCH] Goofing with proxy load balancing

am 23.07.2003 13:36:31 von Graham Leggett

Hi,

> Quick overview of the implementation:
> This code uses a single optional hook, the load_balance hook, which is
> defined by mod_proxy, implemented in mod_proxy_lb.c and is called by
> ap_proxy_http_create_connection in proxy_http.c.

Is it possible to call this hook something more general than "load
balancing", as load balancing is just one possible function of this.

Also, something that would be really useful is for a "load
balancer/backhand" module is the ability for the module hook to return
DECLINED:

Say for example a stateful module that watches JSESSIONID and passes all
common requests to the same backend server, might choose to decline to
perform, based on the cookie being missing, etc. Then, the next module
in line (eg simple-round-robin) gets a go.

There would have to be a second hook in there, after the request is
complete, so that the stateful module could see JSESSIONID when it was
set the first time.

Thoughts?

Regards,
Graham
--
-----------------------------------------
minfrin@sharp.fm "There's a moon
over Bourbon Street
tonight..."

Re: [PATCH] Goofing with proxy load balancing

am 23.07.2003 13:40:03 von Graham Leggett

Bill Stoddard wrote:

> This code uses a single optional hook, the load_balance hook, which is
> defined by mod_proxy, implemented in mod_proxy_lb.c and is called by
> ap_proxy_http_create_connection in proxy_http.c.

One more quick thing - the connection hook should not be in
proxy_http.c, as the functionality is then limited to the HTTP proxy
module only.

The hook should be in mod_proxy.c, or in proxy_util.c, and so be
accessible to proxy_ftp and proxy_connect also.

Regards,
Graham
--
-----------------------------------------
minfrin@sharp.fm "There's a moon
over Bourbon Street
tonight..."