From: Paul Querna Date: Tue, 13 Nov 2007 04:20:50 +0000 (+0000) Subject: Add mod_serf, a reverse proxy module, which uses serf[1] as its http client library. X-Git-Tag: 2.3.0~1275 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=95ac1252e408c07b43adb029af02796c77fb9391;p=apache Add mod_serf, a reverse proxy module, which uses serf[1] as its http client library. To enable, pass something like this to configure: --enable-serf --with-serf=/usr/local/serf/0.1.2 To try it out, put something like this in your httpd.conf: SerfPass http://httpd.apache.org/ LocationMatch and all related directives can also be used, magical eh? [1] - http://code.google.com/p/serf/ git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@594425 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index 93973866dc..32481c3205 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,8 @@ Changes with Apache 2.3.0 [ When backported to 2.2.x, remove entry from this file ] + *) mod_serf: New module for Reverse Proxying. [Paul Querna] + *) mod_autoindex: Generate valid XHTML output by adding the xhtml namespace. PR 43649 [Jose Kahan ] diff --git a/modules/proxy/config.m4 b/modules/proxy/config.m4 index 84c7675140..70e283d54f 100644 --- a/modules/proxy/config.m4 +++ b/modules/proxy/config.m4 @@ -40,6 +40,42 @@ APACHE_MODULE(proxy_fcgi, Apache proxy FastCGI module, $proxy_fcgi_objs, , $prox APACHE_MODULE(proxy_ajp, Apache proxy AJP module, $proxy_ajp_objs, , $proxy_mods_enable) APACHE_MODULE(proxy_balancer, Apache proxy BALANCER module, $proxy_balancer_objs, , $proxy_mods_enable) +AC_DEFUN([CHECK_SERF], [ + AC_MSG_CHECKING(for serf) + serf_found="no" + AC_ARG_WITH(serf, APACHE_HELP_STRING([--with-serf=PREFIX], + [Serf client library]), + [ + if test "$withval" = "yes" ; then + AC_MSG_ERROR([--with-serf requires an argument.]) + else + AC_MSG_NOTICE([serf library configuration]) + serf_prefix=$withval + save_cppflags="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $APR_INCLUDES $APU_INCLUDES -I$serf_prefix/include/serf-0" + AC_CHECK_HEADERS(serf.h,[ + save_ldflags="$LDFLAGS" + LDFLAGS="$LDFLAGS -L$serf_prefix/lib" + AC_CHECK_LIB(serf-0, serf_context_create,[serf_found="yes"]) + LDFLAGS="$save_ldflags"]) + CPPFLAGS="$save_cppflags" + fi + ]) + + if test "$serf_found" = "yes"; then + APR_ADDTO(LDFLAGS, ["-L$serf_prefix/lib"]) + APR_ADDTO(LIBS, ["-lserf-0"]) + APR_ADDTO(INCLUDES, ["-I$serf_prefix/include/serf-0"]) + else + AC_MSG_ERROR(unable to find serf) + fi +]) + +serf_objects="mod_serf.lo" +APACHE_MODULE(serf, [Reverse proxy module using Serf], $serf_objects, , yes, [ + CHECK_SERF +]) + APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current/../generators]) APACHE_MODPATH_FINISH diff --git a/modules/proxy/mod_serf.c b/modules/proxy/mod_serf.c new file mode 100644 index 0000000000..7dfabb9ac0 --- /dev/null +++ b/modules/proxy/mod_serf.c @@ -0,0 +1,439 @@ +/* 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 "httpd.h" +#include "http_core.h" +#include "http_config.h" +#include "http_protocol.h" +#include "http_log.h" + +#include "serf.h" +#include "apr_uri.h" + +module AP_MODULE_DECLARE_DATA serf_module; + +typedef struct { + int on; + apr_uri_t url; +} serf_config_rec; + +typedef struct { + int rstatus; + int want_ssl; + int done_headers; + int keep_reading; + request_rec *r; + serf_config_rec *conf; + serf_ssl_context_t *ssl_ctx; + serf_bucket_alloc_t *bkt_alloc; +} s_baton_t; + + +static void closed_connection(serf_connection_t *conn, + void *closed_baton, + apr_status_t why, + apr_pool_t *pool) +{ + s_baton_t *ctx = closed_baton; + + if (why) { + /* justin says that error handling isn't done yet. hah. */ + /* XXXXXX: review */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, why, ctx->r, "Closed Connection Error"); + ctx->rstatus = HTTP_INTERNAL_SERVER_ERROR; + return; + } +} + +static serf_bucket_t* conn_setup(apr_socket_t *sock, + void *setup_baton, + apr_pool_t *pool) +{ + serf_bucket_t *c; + s_baton_t *ctx = setup_baton; + + c = serf_bucket_socket_create(sock, ctx->bkt_alloc); + if (ctx->want_ssl) { + c = serf_bucket_ssl_decrypt_create(c, ctx->ssl_ctx, ctx->bkt_alloc); + } + + return c; +} + +int copy_headers_in(void *vbaton, const char *key, const char *value) +{ + serf_bucket_t *hdrs_bkt = (serf_bucket_t *)vbaton; + + /* XXXXX: List of headers not to copy to serf. serf's serf_bucket_headers_setn, + * doesn't actually overwrite a header if we set it once, so we need to ignore anything + * we might want to toggle or combine. + */ + switch (key[0]) { + case 'a': + case 'A': + if (strcasecmp("Accept-Encoding", key) == 0) { + return 0; + } + break; + case 'c': + case 'C': + if (strcasecmp("Connection", key) == 0) { + return 0; + } + break; + case 'h': + case 'H': + if (strcasecmp("Host", key) == 0) { + return 0; + } + break; + case 'k': + case 'K': + if (strcasecmp("Keep-Alive", key) == 0) { + return 0; + } + break; + case 't': + case 'T': + if (strcasecmp("TE", key) == 0) { + return 0; + } + if (strcasecmp("Trailer", key) == 0) { + return 0; + } + break; + case 'u': + case 'U': + if (strcasecmp("Upgrade", key) == 0) { + return 0; + } + break; + default: + break; + } + + serf_bucket_headers_setn(hdrs_bkt, key, value); + return 0; +} + +int copy_headers_out(void *vbaton, const char *key, const char *value) +{ + s_baton_t *ctx = vbaton; + int done = 0; + + /* XXXXX: Special Treatment required for MANY other headers. fixme.*/ + switch (key[0]) { + case 'c': + case 'C': + if (strcasecmp("Content-Type", key) == 0) { + ap_set_content_type(ctx->r, value); + done = 1; + break; + } + else if (strcasecmp("Connection", key) == 0) { + done = 1; + break; + } + else if (strcasecmp("Content-Encoding", key) == 0) { + done = 1; + break; + } + else if (strcasecmp("Content-Length", key) == 0) { + done = 1; + break; + } + break; + case 't': + case 'T': + if (strcasecmp("Transfer-Encoding", key) == 0) { + done = 1; + break; + } + break; + default: + break; + } + + if (!done) { + apr_table_addn(ctx->r->headers_out, key, value); + } + + return 0; +} + +static serf_bucket_t* accept_response(serf_request_t *request, + serf_bucket_t *stream, + void *acceptor_baton, + apr_pool_t *pool) +{ + serf_bucket_t *c; + serf_bucket_alloc_t *bkt_alloc; + + /* get the per-request bucket allocator */ + bkt_alloc = serf_request_get_alloc(request); + + /* Create a barrier so the response doesn't eat us! */ + c = serf_bucket_barrier_create(stream, bkt_alloc); + + return serf_bucket_response_create(c, bkt_alloc); +} + +static apr_status_t handle_response(serf_request_t *request, + serf_bucket_t *response, + void *vbaton, + apr_pool_t *pool) +{ + apr_status_t rv; + s_baton_t *ctx = vbaton; + const char *data; + apr_size_t len; + serf_status_line sl; + + /* XXXXXXX: Create better error message. */ + rv = serf_bucket_response_status(response, &sl); + if (rv) { + if (APR_STATUS_IS_EAGAIN(rv)) { + return APR_SUCCESS; + } + + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, ctx->r, "serf_bucket_response_status..."); + + ctx->rstatus = HTTP_INTERNAL_SERVER_ERROR; + + return rv; + } + + /** + * XXXXX: If I understood serf buckets better, it might be possible to not + * copy all of the data here, and better stream it to the client. + **/ + + do { + rv = serf_bucket_read(response, AP_IOBUFSIZE, &data, &len); + + if (SERF_BUCKET_READ_ERROR(rv)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, ctx->r, "serf_bucket_read(response)"); + return rv; + } + + if (!ctx->done_headers) { + serf_bucket_t *hdrs; + hdrs = serf_bucket_response_get_headers(response); + serf_bucket_headers_do(hdrs, copy_headers_out, ctx); + ctx->done_headers = 1; + } + + /* XXXX: write to brigades and stuff. meh */ + ap_rwrite(data, len, ctx->r); + + if (APR_STATUS_IS_EOF(rv)) { + ctx->keep_reading = 0; + return APR_EOF; + } + + /* XXXX: Should we send a flush now? */ + if (APR_STATUS_IS_EAGAIN(rv)) { + return APR_SUCCESS; + } + + } while (1); +} + + +static apr_status_t setup_request(serf_request_t *request, + void *vbaton, + serf_bucket_t **req_bkt, + serf_response_acceptor_t *acceptor, + void **acceptor_baton, + serf_response_handler_t *handler, + void **handler_baton, + apr_pool_t *pool) +{ + s_baton_t *ctx = vbaton; + serf_bucket_t *hdrs_bkt; + serf_bucket_t *body_bkt = NULL; + + + /* XXXXX: handle incoming request bodies */ + *req_bkt = serf_bucket_request_create(ctx->r->method, ctx->r->unparsed_uri, body_bkt, + serf_request_get_alloc(request)); + + hdrs_bkt = serf_bucket_request_get_headers(*req_bkt); + + apr_table_do(copy_headers_in, hdrs_bkt, ctx->r->headers_in, NULL); + + /* XXXXXX: SerfPreserveHost on */ + serf_bucket_headers_setn(hdrs_bkt, "Host", ctx->conf->url.hostname); + + serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip"); + + if (ctx->want_ssl) { + serf_bucket_alloc_t *req_alloc; + + req_alloc = serf_request_get_alloc(request); + + if (ctx->ssl_ctx == NULL) { + *req_bkt = serf_bucket_ssl_encrypt_create(*req_bkt, NULL, + ctx->bkt_alloc); + ctx->ssl_ctx = serf_bucket_ssl_encrypt_context_get(*req_bkt); + } + else { + *req_bkt = serf_bucket_ssl_encrypt_create(*req_bkt, ctx->ssl_ctx, + ctx->bkt_alloc); + } + } + + *acceptor = accept_response; + *acceptor_baton = ctx; + *handler = handle_response; + *handler_baton = ctx; + + return APR_SUCCESS; +} + +static int drive_serf(request_rec *r, serf_config_rec *conf) +{ + apr_status_t rv; + apr_pool_t *pool = r->pool; + apr_sockaddr_t *address; + s_baton_t baton; + /* XXXXX: make persistent/per-process or something.*/ + serf_context_t *serfme; + serf_connection_t *conn; + serf_request_t *srequest; + + /* XXXXX: cache dns? */ + rv = apr_sockaddr_info_get(&address, conf->url.hostname, + APR_UNSPEC, conf->url.port, 0, + pool); + + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Unable to resolve: %s", conf->url.hostname); + return HTTP_INTERNAL_SERVER_ERROR; + } + + serfme = serf_context_create(pool); + + baton.r = r; + baton.conf = conf; + baton.bkt_alloc = serf_bucket_allocator_create(pool, NULL, NULL); + baton.ssl_ctx = NULL; + baton.rstatus = OK; + + baton.done_headers = 0; + baton.keep_reading = 1; + + if (strcasecmp(conf->url.scheme, "https") == 0) { + baton.want_ssl = 1; + } + else { + baton.want_ssl = 0; + } + + conn = serf_connection_create(serfme, address, + conn_setup, &baton, + closed_connection, &baton, + pool); + + srequest = serf_connection_request_create(conn, setup_request, + &baton); + + do { + rv = serf_context_run(serfme, SERF_DURATION_FOREVER, pool); + + /* XXXX: Handle timeouts */ + if (APR_STATUS_IS_TIMEUP(rv)) { + continue; + } + + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "serf_context_run()"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + serf_debug__closed_conn(baton.bkt_alloc); + } while (baton.keep_reading); + + return baton.rstatus; +} + +static int serf_handler(request_rec *r) +{ + serf_config_rec *conf = ap_get_module_config(r->per_dir_config, + &serf_module); + + if (conf->on == 0) { + return DECLINED; + } + + return drive_serf(r, conf); +} + +static const char *add_pass(cmd_parms *cmd, void *vconf, + const char *vdest) +{ + apr_status_t rv; + serf_config_rec *conf = (serf_config_rec *) vconf; + + rv = apr_uri_parse(cmd->pool, vdest, &conf->url); + + if (rv != APR_SUCCESS) { + return "mod_serf: Unable to parse SerfPass url."; + } + + /* XXXX: These are bugs in apr_uri_parse. Fixme. */ + if (!conf->url.port) { + conf->url.port = apr_uri_port_of_scheme(conf->url.scheme); + } + + if (!conf->url.path) { + conf->url.path = "/"; + } + + conf->on = 1; + + return NULL; +} + +static void *create_config(apr_pool_t *p, char *dummy) +{ + serf_config_rec *new = (serf_config_rec *) apr_pcalloc(p, sizeof(serf_config_rec)); + new->on = 0; + return new; +} + +static const command_rec serf_cmds[] = +{ + AP_INIT_TAKE1("SerfPass", add_pass, NULL, OR_INDEXES/*making shit up*/, + "A prefix and destination"), + {NULL} +}; + +static void register_hooks(apr_pool_t *p) +{ + ap_hook_handler(serf_handler, NULL, NULL, APR_HOOK_FIRST); +} + +module AP_MODULE_DECLARE_DATA serf_module = +{ + STANDARD20_MODULE_STUFF, + create_config, + NULL, + NULL, + NULL, + serf_cmds, + register_hooks +};