You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by mi...@apache.org on 2009/09/24 00:20:01 UTC

svn commit: r818285 - in /httpd/httpd/branches/2.2.x: CHANGES STATUS docs/manual/mod/mod_proxy_scgi.xml modules/proxy/NWGNUproxyscgi modules/proxy/config.m4 modules/proxy/mod_proxy_scgi.c

Author: minfrin
Date: Wed Sep 23 22:20:00 2009
New Revision: 818285

URL: http://svn.apache.org/viewvc?rev=818285&view=rev
Log:
...and promote.

Added:
    httpd/httpd/branches/2.2.x/docs/manual/mod/mod_proxy_scgi.xml
    httpd/httpd/branches/2.2.x/modules/proxy/NWGNUproxyscgi
    httpd/httpd/branches/2.2.x/modules/proxy/mod_proxy_scgi.c
Modified:
    httpd/httpd/branches/2.2.x/CHANGES
    httpd/httpd/branches/2.2.x/STATUS
    httpd/httpd/branches/2.2.x/modules/proxy/config.m4

Modified: httpd/httpd/branches/2.2.x/CHANGES
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/CHANGES?rev=818285&r1=818284&r2=818285&view=diff
==============================================================================
--- httpd/httpd/branches/2.2.x/CHANGES [utf-8] (original)
+++ httpd/httpd/branches/2.2.x/CHANGES [utf-8] Wed Sep 23 22:20:00 2009
@@ -9,6 +9,8 @@
      mod_proxy_ftp: NULL pointer dereference on error paths.
      [Stefan Fritsch <sf fritsch.de>, Joe Orton]
 
+  *) mod_proxy_scgi: Backport from trunk. [André Malo]
+
   *) mod_ldap: Don't try to resolve file-based user ids to a DN when AuthLDAPURL
      has been defined at a very high level.  PR 45946.  [Eric Covener]
 

Modified: httpd/httpd/branches/2.2.x/STATUS
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/STATUS?rev=818285&r1=818284&r2=818285&view=diff
==============================================================================
--- httpd/httpd/branches/2.2.x/STATUS (original)
+++ httpd/httpd/branches/2.2.x/STATUS Wed Sep 23 22:20:00 2009
@@ -106,12 +106,6 @@
    2.2.x Patch: http://people.apache.org/~minfrin/ssl-env.diff
    +1: minfrin
 
- * mod_proxy_scgi: Drop in as-is from trunk. Works like a charm for years
-   now in production.
-   source: trunk/modules/proxy/mod_proxy_scgi.c
-   docs: trunk/docs/manual/mod/mod_proxy_scgi.xml
-   +1: nd, jim, minfrin
-
  * mod_mime: Detect invalid use of MultiviewsMatch inside Location and
    LocationMatch sections.  
    PR47754.

Added: httpd/httpd/branches/2.2.x/docs/manual/mod/mod_proxy_scgi.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/docs/manual/mod/mod_proxy_scgi.xml?rev=818285&view=auto
==============================================================================
--- httpd/httpd/branches/2.2.x/docs/manual/mod/mod_proxy_scgi.xml (added)
+++ httpd/httpd/branches/2.2.x/docs/manual/mod/mod_proxy_scgi.xml Wed Sep 23 22:20:00 2009
@@ -0,0 +1,145 @@
+<?xml version="1.0"?>
+<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd">
+<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?>
+<!-- $LastChangedRevision: 729541 $ -->
+
+<!--
+ 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.
+-->
+
+<modulesynopsis metafile="mod_proxy_scgi.xml.meta">
+
+<name>mod_proxy_scgi</name>
+<description>SCGI gateway module for <module>mod_proxy</module></description>
+<status>Extension</status>
+<sourcefile>mod_proxy_scgi.c</sourcefile>
+<identifier>proxy_scgi_module</identifier>
+<compatibility>Available in version 2.3 and later</compatibility>
+
+<summary>
+    <p>This module <em>requires</em> the service of <module
+    >mod_proxy</module>. It provides support for the
+    <a href="http://python.ca/scgi/protocol.txt">SCGI protocol, version
+    1</a>.</p>
+
+    <p>Thus, in order to get the ability of handling the SCGI protocol,
+    <module>mod_proxy</module> and <module>mod_proxy_scgi</module> have to
+    be present in the server.</p>
+
+    <note type="warning"><title>Warning</title>
+      <p>Do not enable proxying until you have <a
+      href="mod_proxy.html#access">secured your server</a>. Open proxy
+      servers are dangerous both to your network and to the Internet at
+      large.</p>
+    </note>
+</summary>
+
+<seealso><module>mod_proxy</module></seealso>
+<seealso><module>mod_proxy_balancer</module></seealso>
+
+<section id="examples"><title>Examples</title>
+    <p>Remember, in order to make the following examples work, you have to
+    enable <module>mod_proxy</module> and <module>mod_proxy_scgi</module>.</p>
+
+    <example><title>Simple gateway</title>
+      ProxyPass /scgi-bin/ scgi://localhost:4000/
+    </example>
+
+    <p>The balanced gateway needs <module>mod_proxy_balancer</module> in
+    addition to the already mentioned proxy modules.</p>
+
+    <example><title>Balanced gateway</title>
+    ProxyPass /scgi-bin/ balancer://somecluster/<br />
+    &lt;Proxy balancer://somecluster/&gt;<br />
+    <indent>
+        BalancerMember scgi://localhost:4000/<br />
+        BalancerMember scgi://localhost:4001/<br />
+    </indent>
+    &lt;/Proxy&gt;
+    </example>
+</section>
+
+<directivesynopsis>
+<name>ProxySCGISendfile</name>
+<description>Enable evaluation of <var>X-Sendfile</var> pseudo response
+header</description>
+<syntax>ProxySCGISendfile On|Off|<var>Headername</var></syntax>
+<default>ProxySCGISendfile Off</default>
+<contextlist><context>server config</context><context>virtual host</context>
+<context>directory</context></contextlist>
+
+<usage>
+    <p>The <directive>ProxySCGISendfile</directive> directive enables the
+    SCGI backend to let files serve directly by the gateway. This is useful
+    performance purposes -- the httpd can use <code>sendfile</code> or other
+    optimizations, which are not possible if the file comes over the backend
+    socket.</p>
+    <p>The <directive>ProxySCGISendfile</directive> argument determines the
+    gateway behaviour:</p>
+    <dl>
+    <dt><code>Off</code></dt>
+    <dd>No special handling takes place.</dd>
+
+    <dt><code>On</code></dt>
+    <dd>The gateway looks for a backend response header called
+    <code>X-Sendfile</code> and interprets the value as filename to serve. The
+    header is removed from the final response headers. This is equivalent to
+    <code>ProxySCGIRequest X-Sendfile</code>.</dd>
+
+    <dt>anything else</dt>
+    <dd>Similar to <code>On</code>, but instead of the hardcoded header name
+    the argument is applied as header name.</dd>
+    </dl>
+
+    <example><title>Example</title>
+    # Use the default header (X-Sendfile)<br />
+    ProxySCGISendfile On<br />
+    <br />
+    # Use a different header<br />
+    ProxySCGISendfile X-Send-Static
+    </example>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>ProxySCGIInternalRedirect</name>
+<description>Enable or disable internal redirect responses from the
+backend</description>
+<syntax>ProxySCGIInternalRedirect On|Off</syntax>
+<default>ProxySCGIInternalRedirect On</default>
+<contextlist><context>server config</context><context>virtual host</context>
+<context>directory</context></contextlist>
+
+<usage>
+    <p>The <directive>ProxySCGIInternalRedirect</directive> enables the backend
+    to internally redirect the gateway to a different URL. This feature
+    origins in <module>mod_cgi</module>, which internally redirects the
+    response, if the response status is <code>OK</code> (<code>200</code>) and
+    the response contains a <code>Location</code> header and its value starts
+    with a slash (<code>/</code>). This value is interpreted as a new local
+    URL the apache internally redirects to.</p>
+
+    <p><module>mod_proxy_scgi</module> does the same as
+    <module>mod_cgi</module> in this regard, except that you can turn off the
+    feature.</p>
+
+    <example><title>Example</title>
+    ProxySCGIInternalRedirect Off
+    </example>
+</usage>
+</directivesynopsis>
+
+</modulesynopsis>

Added: httpd/httpd/branches/2.2.x/modules/proxy/NWGNUproxyscgi
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/modules/proxy/NWGNUproxyscgi?rev=818285&view=auto
==============================================================================
--- httpd/httpd/branches/2.2.x/modules/proxy/NWGNUproxyscgi (added)
+++ httpd/httpd/branches/2.2.x/modules/proxy/NWGNUproxyscgi Wed Sep 23 22:20:00 2009
@@ -0,0 +1,263 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary.  This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)\build\NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS	+= \
+			$(AP_WORK)/include \
+			$(NWOS) \
+			$(AP_WORK)/modules/http \
+			$(AP_WORK)/modules/arch/netware \
+			$(APR)/include \
+			$(APRUTIL)/include \
+			$(APR) \
+			$(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS		+= \
+			$(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES	+= \
+			$(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS		+= \
+			$(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS	+= \
+			$(EOLIST)
+
+XCFLAGS		+= \
+			$(EOLIST)
+
+XDEFINES	+= \
+			$(EOLIST)
+
+XLFLAGS		+= \
+			$(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS	+= \
+			$(EOLIST)
+
+XCFLAGS		+= \
+			$(EOLIST)
+
+XDEFINES	+= \
+			$(EOLIST)
+
+XLFLAGS		+= \
+			$(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS	+= \
+			$(EOLIST)
+
+XCFLAGS		+= \
+			$(EOLIST)
+
+XDEFINES	+= \
+			$(EOLIST)
+
+XLFLAGS		+= \
+			$(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm.  If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME	= proxyscg
+
+#
+# This is used by the link '-desc ' directive. 
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION	= Apache $(VERSION_STR) Proxy SCGI Sub-Module
+
+#
+# This is used by the '-threadname' directive.  If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME	= Proxy SCGI Module
+
+#
+# If this is specified, it will override VERSION value in 
+# $(AP_WORK)\build\NWGNUenvironment.inc
+#
+NLM_VERSION	=
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE	= 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM	= _LibCPrelude
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM	= _LibCPostlude
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM	=
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS	= AUTOUNLOAD, PSEUDOPREEMPTION
+
+#
+# If this is specified it will be linked in with the XDCData option in the def 
+# file instead of the default of $(NWOS)/apache.xdc.  XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA		= 
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+	$(OBJDIR)/proxyscgi.nlm \
+	$(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+	$(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+	$(OBJDIR)/mod_proxy_scgi.o \
+	$(OBJDIR)/proxy_util.o \
+	$(OBJDIR)/libprews.o \
+	$(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+	libcpre.o \
+	$(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+	aprlib \
+	libc \
+	proxy \
+	$(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+ 
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+	@$(APR)/aprlib.imp \
+	@$(NWOS)/httpd.imp \
+	@$(OBJDIR)/mod_proxy.imp \
+	@libc.imp \
+	$(EOLIST)
+ 
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+	$(EOLIST)
+endif
+ 
+#   
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+	proxy_scgi_module \
+	$(EOLIST)
+
+#   
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+	$(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the 
+# correct place.  (See $(AP_WORK)\build\NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
+

Modified: httpd/httpd/branches/2.2.x/modules/proxy/config.m4
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/modules/proxy/config.m4?rev=818285&r1=818284&r2=818285&view=diff
==============================================================================
--- httpd/httpd/branches/2.2.x/modules/proxy/config.m4 (original)
+++ httpd/httpd/branches/2.2.x/modules/proxy/config.m4 Wed Sep 23 22:20:00 2009
@@ -16,6 +16,7 @@
 proxy_connect_objs="mod_proxy_connect.lo"
 proxy_ftp_objs="mod_proxy_ftp.lo"
 proxy_http_objs="mod_proxy_http.lo"
+proxy_scgi_objs="mod_proxy_scgi.lo"
 proxy_ajp_objs="mod_proxy_ajp.lo ajp_header.lo ajp_link.lo ajp_msg.lo ajp_utils.lo"
 proxy_balancer_objs="mod_proxy_balancer.lo"
 
@@ -26,6 +27,7 @@
     proxy_connect_objs="$proxy_connect_objs mod_proxy.la"
     proxy_ftp_objs="$proxy_ftp_objs mod_proxy.la"
     proxy_http_objs="$proxy_http_objs mod_proxy.la"
+    proxy_scgi_objs="$proxy_scgi_objs mod_proxy.la"
     proxy_ajp_objs="$proxy_ajp_objs mod_proxy.la"
     proxy_balancer_objs="$proxy_balancer_objs mod_proxy.la"
     ;;
@@ -34,6 +36,7 @@
 APACHE_MODULE(proxy_connect, Apache proxy CONNECT module, $proxy_connect_objs, , $proxy_mods_enable)
 APACHE_MODULE(proxy_ftp, Apache proxy FTP module, $proxy_ftp_objs, , $proxy_mods_enable)
 APACHE_MODULE(proxy_http, Apache proxy HTTP module, $proxy_http_objs, , $proxy_mods_enable)
+APACHE_MODULE(proxy_scgi, Apache proxy SCGI module, $proxy_scgi_objs, , $proxy_mods_enable)
 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)
 

Added: httpd/httpd/branches/2.2.x/modules/proxy/mod_proxy_scgi.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/modules/proxy/mod_proxy_scgi.c?rev=818285&view=auto
==============================================================================
--- httpd/httpd/branches/2.2.x/modules/proxy/mod_proxy_scgi.c (added)
+++ httpd/httpd/branches/2.2.x/modules/proxy/mod_proxy_scgi.c Wed Sep 23 22:20:00 2009
@@ -0,0 +1,627 @@
+/* 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.
+ */
+
+/*
+ * mod_proxy_scgi.c
+ * Proxy backend module for the SCGI protocol
+ * (http://python.ca/scgi/protocol.txt)
+ *
+ * André Malo (nd/perlig.de), August 2007
+ */
+
+#define APR_WANT_MEMFUNC
+#define APR_WANT_STRFUNC
+#include "apr_strings.h"
+#include "apr_hooks.h"
+#include "apr_optional_hooks.h"
+#include "apr_buckets.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_script.h"
+
+#include "mod_proxy.h"
+
+
+#define SCHEME "scgi"
+#define PROXY_FUNCTION "SCGI"
+#define SCGI_MAGIC "SCGI"
+#define SCGI_PROTOCOL_VERSION "1"
+#define SCGI_DEFAULT_PORT (4000)
+
+/* just protect from typos */
+#define CONTENT_LENGTH "CONTENT_LENGTH"
+#define GATEWAY_INTERFACE "GATEWAY_INTERFACE"
+
+module AP_MODULE_DECLARE_DATA proxy_scgi_module;
+
+
+typedef enum {
+    scgi_internal_redirect,
+    scgi_sendfile
+} scgi_request_type;
+
+typedef struct {
+    const char *location;    /* target URL */
+    scgi_request_type type;  /* type of request */
+} scgi_request_config;
+
+const char *scgi_sendfile_off = "off";
+const char *scgi_sendfile_on = "X-Sendfile";
+
+typedef struct {
+    const char *sendfile;
+    int internal_redirect;
+} scgi_config;
+
+
+/*
+ * We create our own bucket type, which is actually derived (c&p) from the
+ * socket bucket.
+ * Maybe some time this should be made more abstract (like passing an
+ * interception function to read or something) and go into the ap_ or
+ * even apr_ namespace.
+ */
+
+typedef struct {
+    apr_socket_t *sock;
+    apr_off_t *counter;
+} socket_ex_data;
+
+static apr_bucket *bucket_socket_ex_create(socket_ex_data *data,
+                                           apr_bucket_alloc_t *list);
+
+
+static apr_status_t bucket_socket_ex_read(apr_bucket *a, const char **str,
+                                          apr_size_t *len,
+                                          apr_read_type_e block)
+{
+    socket_ex_data *data = a->data;
+    apr_socket_t *p = data->sock;
+    char *buf;
+    apr_status_t rv;
+    apr_interval_time_t timeout;
+
+    if (block == APR_NONBLOCK_READ) {
+        apr_socket_timeout_get(p, &timeout);
+        apr_socket_timeout_set(p, 0);
+    }
+
+    *str = NULL;
+    *len = APR_BUCKET_BUFF_SIZE;
+    buf = apr_bucket_alloc(*len, a->list);
+
+    rv = apr_socket_recv(p, buf, len);
+
+    if (block == APR_NONBLOCK_READ) {
+        apr_socket_timeout_set(p, timeout);
+    }
+
+    if (rv != APR_SUCCESS && rv != APR_EOF) {
+        apr_bucket_free(buf);
+        return rv;
+    }
+
+    if (*len > 0) {
+        apr_bucket_heap *h;
+
+        /* count for stats */
+        *data->counter += *len;
+
+        /* Change the current bucket to refer to what we read */
+        a = apr_bucket_heap_make(a, buf, *len, apr_bucket_free);
+        h = a->data;
+        h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */
+        *str = buf;
+        APR_BUCKET_INSERT_AFTER(a, bucket_socket_ex_create(data, a->list));
+    }
+    else {
+        apr_bucket_free(buf);
+        a = apr_bucket_immortal_make(a, "", 0);
+        *str = a->data;
+    }
+    return APR_SUCCESS;
+}
+
+static const apr_bucket_type_t bucket_type_socket_ex = {
+    "SOCKET_EX", 5, APR_BUCKET_DATA,
+    apr_bucket_destroy_noop,
+    bucket_socket_ex_read,
+    apr_bucket_setaside_notimpl, 
+    apr_bucket_split_notimpl,
+    apr_bucket_copy_notimpl
+};
+
+static apr_bucket *bucket_socket_ex_make(apr_bucket *b, socket_ex_data *data)
+{
+    b->type        = &bucket_type_socket_ex;
+    b->length      = (apr_size_t)(-1);
+    b->start       = -1;
+    b->data        = data;
+    return b;
+}
+
+static apr_bucket *bucket_socket_ex_create(socket_ex_data *data,
+                                           apr_bucket_alloc_t *list)
+{
+    apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+
+    APR_BUCKET_INIT(b);
+    b->free = apr_bucket_free;
+    b->list = list;
+    return bucket_socket_ex_make(b, data);
+}
+
+
+/*
+ * Canonicalize scgi-like URLs.
+ */
+static int scgi_canon(request_rec *r, char *url)
+{
+    char *host, sport[sizeof(":65535")];
+    const char *err, *path;
+    apr_port_t port = SCGI_DEFAULT_PORT;
+
+    if (strncasecmp(url, SCHEME "://", sizeof(SCHEME) + 2)) {
+        return DECLINED;
+    }
+    url += sizeof(SCHEME); /* Keep slashes */
+
+    err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+    if (err) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                      "error parsing URL %s: %s", url, err);
+        return HTTP_BAD_REQUEST;
+    }
+        
+    apr_snprintf(sport, sizeof(sport), ":%u", port);
+        
+    if (ap_strchr(host, ':')) { /* if literal IPv6 address */
+        host = apr_pstrcat(r->pool, "[", host, "]", NULL);
+    }
+
+    path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
+                             r->proxyreq);
+    if (!path) {
+        return HTTP_BAD_REQUEST;
+    }
+
+    r->filename = apr_pstrcat(r->pool, "proxy:" SCHEME "://", host, sport, "/",
+                              path, NULL);
+    r->path_info = apr_pstrcat(r->pool, "/", path, NULL);
+    return OK;
+}
+
+
+/*
+ * Send a block of data, ensure, everything is sent
+ */
+static int sendall(proxy_conn_rec *conn, const char *buf, apr_size_t length,
+                   request_rec *r)
+{
+    apr_status_t rv;
+    apr_size_t written;
+
+    while (length > 0) {
+        written = length;
+        if ((rv = apr_socket_send(conn->sock, buf, &written)) != APR_SUCCESS) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+                          "proxy: " PROXY_FUNCTION ": sending data to "
+                          "%s:%u failed", conn->hostname, conn->port);
+            return HTTP_SERVICE_UNAVAILABLE;
+        }
+
+        /* count for stats */
+        conn->worker->s->transferred += written;
+        buf += written;
+        length -= written;
+    }
+
+    return OK;
+}
+
+
+/*
+ * Send SCGI header block
+ */
+static int send_headers(request_rec *r, proxy_conn_rec *conn)
+{
+    char *buf, *cp, *bodylen;
+    const char *ns_len;
+    const apr_array_header_t *env_table;
+    const apr_table_entry_t *env;
+    apr_size_t j, len, bodylen_size;
+    apr_size_t headerlen =   sizeof(CONTENT_LENGTH)
+                           + sizeof(SCGI_MAGIC)
+                           + sizeof(SCGI_PROTOCOL_VERSION);
+
+    ap_add_common_vars(r);
+    ap_add_cgi_vars(r);
+
+    /*
+     * The header blob basically takes the environment and concatenates
+     * keys and values using 0 bytes. There are special treatments here:
+     *   - GATEWAY_INTERFACE and SCGI_MAGIC are dropped
+     *   - CONTENT_LENGTH is always set and must be sent as the very first
+     *     variable
+     *
+     * Additionally it's wrapped into a so-called netstring (see SCGI spec)
+     */
+    env_table = apr_table_elts(r->subprocess_env);
+    env = (apr_table_entry_t *)env_table->elts;
+    for (j=0; j<env_table->nelts; ++j) {
+        if (   (!strcmp(env[j].key, GATEWAY_INTERFACE))
+            || (!strcmp(env[j].key, CONTENT_LENGTH))
+            || (!strcmp(env[j].key, SCGI_MAGIC))) {
+            continue;
+        }
+        headerlen += strlen(env[j].key) + strlen(env[j].val) + 2;
+    }
+    bodylen = apr_psprintf(r->pool, "%" APR_OFF_T_FMT, r->remaining);
+    bodylen_size = strlen(bodylen) + 1;
+    headerlen += bodylen_size;
+
+    ns_len = apr_psprintf(r->pool, "%" APR_SIZE_T_FMT ":", headerlen);
+    len = strlen(ns_len);
+    headerlen += len + 1; /* 1 == , */
+    cp = buf = apr_palloc(r->pool, headerlen);
+    memcpy(cp, ns_len, len);
+    cp += len;
+
+    memcpy(cp, CONTENT_LENGTH, sizeof(CONTENT_LENGTH));
+    cp += sizeof(CONTENT_LENGTH);
+    memcpy(cp, bodylen, bodylen_size);
+    cp += bodylen_size;
+    memcpy(cp, SCGI_MAGIC, sizeof(SCGI_MAGIC));
+    cp += sizeof(SCGI_MAGIC);
+    memcpy(cp, SCGI_PROTOCOL_VERSION, sizeof(SCGI_PROTOCOL_VERSION));
+    cp += sizeof(SCGI_PROTOCOL_VERSION);
+
+    for (j=0; j<env_table->nelts; ++j) {
+        if (   (!strcmp(env[j].key, GATEWAY_INTERFACE))
+            || (!strcmp(env[j].key, CONTENT_LENGTH))
+            || (!strcmp(env[j].key, SCGI_MAGIC))) {
+            continue;
+        }
+        len = strlen(env[j].key) + 1;
+        memcpy(cp, env[j].key, len);
+        cp += len;
+        len = strlen(env[j].val) + 1;
+        memcpy(cp, env[j].val, len);
+        cp += len;
+    }
+    *cp++ = ',';
+
+    return sendall(conn, buf, headerlen, r);
+}
+
+
+/*
+ * Send request body (if any)
+ */
+static int send_request_body(request_rec *r, proxy_conn_rec *conn)
+{
+    if (ap_should_client_block(r)) {
+        char *buf = apr_palloc(r->pool, AP_IOBUFSIZE);
+        int status;
+        apr_size_t readlen;
+
+        readlen = ap_get_client_block(r, buf, AP_IOBUFSIZE);
+        while (readlen > 0) {
+            status = sendall(conn, buf, readlen, r);
+            if (status != OK) {
+                return HTTP_SERVICE_UNAVAILABLE;
+            }
+            readlen = ap_get_client_block(r, buf, AP_IOBUFSIZE);
+        }
+        if (readlen == -1) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                          "proxy: " PROXY_FUNCTION ": receiving request body "
+                          "failed");
+            return HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    return OK;
+}
+
+
+/*
+ * Fetch response from backend and pass back to the front
+ */
+static int pass_response(request_rec *r, proxy_conn_rec *conn)
+{
+    apr_bucket_brigade *bb;
+    apr_bucket *b;
+    const char *location;
+    scgi_config *conf;
+    socket_ex_data *sock_data;
+    int status;
+
+    sock_data = apr_palloc(r->pool, sizeof(*sock_data));
+    sock_data->sock = conn->sock;
+    sock_data->counter = &conn->worker->s->read;
+
+    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+    b = bucket_socket_ex_create(sock_data, r->connection->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(bb, b);
+    b = apr_bucket_eos_create(r->connection->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(bb, b);
+
+    status = ap_scan_script_header_err_brigade(r, bb, NULL);
+    if (status != OK) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                      "proxy: " PROXY_FUNCTION ": error reading response "
+                      "headers from %s:%u", conn->hostname, conn->port);
+        r->status_line = NULL;
+        apr_brigade_destroy(bb);
+        return status;
+    }
+
+    conf = ap_get_module_config(r->per_dir_config, &proxy_scgi_module);
+    if (conf->sendfile && conf->sendfile != scgi_sendfile_off) {
+        short err = 1;
+
+        location = apr_table_get(r->err_headers_out, conf->sendfile);
+        if (!location) {
+            err = 0;
+            location = apr_table_get(r->headers_out, conf->sendfile);
+        }
+        if (location) {
+            scgi_request_config *req_conf = apr_palloc(r->pool,
+                                                       sizeof(*req_conf));
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                          "proxy: " PROXY_FUNCTION ": Found %s: %s - "
+                          "preparing subrequest.",
+                          conf->sendfile, location);
+
+            if (err) {
+                apr_table_unset(r->err_headers_out, conf->sendfile);
+            }
+            else {
+                apr_table_unset(r->headers_out, conf->sendfile);
+            }
+            req_conf->location = location;
+            req_conf->type = scgi_sendfile;
+            ap_set_module_config(r->request_config, &proxy_scgi_module,
+                                 req_conf);
+            apr_brigade_destroy(bb);
+            return OK;
+        }
+    }
+
+    if (conf->internal_redirect && r->status == HTTP_OK) {
+        location = apr_table_get(r->headers_out, "Location");
+        if (location && *location == '/') {
+            scgi_request_config *req_conf = apr_palloc(r->pool,
+                                                       sizeof(*req_conf));
+            req_conf->location = location;
+            req_conf->type = scgi_internal_redirect;
+            ap_set_module_config(r->request_config, &proxy_scgi_module,
+                                 req_conf);
+            apr_brigade_destroy(bb);
+            return OK;
+        }
+    }
+
+    /* XXX: What could we do with that return code? */
+    (void)ap_pass_brigade(r->output_filters, bb);
+
+    return OK;
+}
+
+/*
+ * Internal redirect / subrequest handler, working on request_status hook
+ */
+static int scgi_request_status(int *status, request_rec *r)
+{
+    scgi_request_config *req_conf;
+
+    if (   (*status == OK)
+        && (req_conf = ap_get_module_config(r->request_config,
+                                            &proxy_scgi_module))) {
+        switch (req_conf->type) {
+        case scgi_internal_redirect:
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                          "proxy: " PROXY_FUNCTION ": Internal redirect to %s",
+                          req_conf->location);
+
+            r->status_line = NULL;
+            if (r->method_number != M_GET) {
+                /* keep HEAD, which is passed around as M_GET, too */
+                r->method = "GET";
+                r->method_number = M_GET;
+            }
+            apr_table_unset(r->headers_in, "Content-Length");
+            ap_internal_redirect_handler(req_conf->location, r);
+            return OK;
+            /* break; */
+
+        case scgi_sendfile:
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                          "proxy: " PROXY_FUNCTION ": File subrequest to %s",
+                          req_conf->location);
+            do {
+                request_rec *rr;
+
+                rr = ap_sub_req_lookup_file(req_conf->location, r,
+                                            r->output_filters);
+                if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
+                    /*
+                     * We don't touch Content-Length here. It might be
+                     * borked (there's plenty of room for a race condition).
+                     * Either the backend sets it or it's gonna be chunked.
+                     */
+                    ap_run_sub_req(rr);
+                }
+                else {
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                                  "Subrequest to file '%s' not possible. "
+                                  "(rr->status=%d, rr->finfo.filetype=%d)",
+                                  req_conf->location, rr->status,
+                                  rr->finfo.filetype);
+                    *status = HTTP_INTERNAL_SERVER_ERROR;
+                    return *status;
+                }
+            } while(0);
+
+            return OK;
+            /* break; */
+        }
+    }
+
+    return DECLINED;
+}
+
+
+/*
+ * This handles scgi:(dest) URLs
+ */
+static int scgi_handler(request_rec *r, proxy_worker *worker,
+                        proxy_server_conf *conf, char *url,
+                        const char *proxyname, apr_port_t proxyport)
+{
+    int status;
+    proxy_conn_rec *backend = NULL;
+    apr_pool_t *p = r->pool;
+    apr_uri_t *uri = apr_palloc(r->pool, sizeof(*uri));
+    char dummy;
+
+    if (strncasecmp(url, SCHEME "://", sizeof(SCHEME) + 2)) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                      "proxy: " PROXY_FUNCTION ": declining URL %s", url);
+        return DECLINED;
+    }
+    url += sizeof(SCHEME); /* keep the slashes */
+    
+    /* Create space for state information */
+    status = ap_proxy_acquire_connection(PROXY_FUNCTION, &backend, worker,
+                                         r->server);
+    if (status != OK) {
+        goto cleanup;
+    }
+    backend->is_ssl = 0;
+
+    /* Step One: Determine Who To Connect To */
+    status = ap_proxy_determine_connection(p, r, conf, worker, backend,
+                                           uri, &url, proxyname, proxyport,
+                                           &dummy, 1);
+    if (status != OK) {
+        goto cleanup;
+    }
+
+    /* Step Two: Make the Connection */
+    if (ap_proxy_connect_backend(PROXY_FUNCTION, backend, worker, r->server)) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+                     "proxy: " PROXY_FUNCTION ": failed to make connection "
+                     "to backend: %s:%u", backend->hostname, backend->port);
+        status = HTTP_SERVICE_UNAVAILABLE;
+        goto cleanup;
+    }
+
+    /* Step Three: Process the Request */
+    if (   ((status = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK)
+        || ((status = send_headers(r, backend)) != OK)
+        || ((status = send_request_body(r, backend)) != OK)
+        || ((status = pass_response(r, backend)) != OK)) {
+        goto cleanup;
+    }
+
+cleanup:
+    if (backend) {
+        backend->close = 1; /* always close the socket */
+        ap_proxy_release_connection(PROXY_FUNCTION, backend, r->server);
+    }
+    return status;
+}
+
+
+static void *create_scgi_config(apr_pool_t *p, char *dummy)
+{
+    scgi_config *conf=apr_palloc(p, sizeof(*conf));
+
+    conf->sendfile = NULL;
+    conf->internal_redirect = -1;
+
+    return conf;
+}
+
+
+static void *merge_scgi_config(apr_pool_t *p, void *base_, void *add_)
+{
+    scgi_config *base=base_, *add=add_, *conf=apr_palloc(p, sizeof(*conf));
+
+    conf->sendfile = add->sendfile ? add->sendfile: base->sendfile;
+    conf->internal_redirect =   (add->internal_redirect != -1)
+                              ? add->internal_redirect
+                              : base->internal_redirect;
+    return conf;
+}
+
+
+static const char *scgi_set_send_file(cmd_parms *cmd, void *mconfig,
+                                      const char *arg)
+{
+    scgi_config *conf=mconfig;
+
+    if (!strcasecmp(arg, "Off")) {
+        conf->sendfile = scgi_sendfile_off;
+    }
+    else if (!strcasecmp(arg, "On")) {
+        conf->sendfile = scgi_sendfile_on;
+    }
+    else {
+        conf->sendfile = arg;
+    }
+    return NULL;
+}
+
+
+static const command_rec scgi_cmds[] =
+{
+    AP_INIT_TAKE1("ProxySCGISendfile", scgi_set_send_file, NULL,
+                  RSRC_CONF|ACCESS_CONF,
+                  "The name of the X-Sendfile peudo response header or "
+                  "On or Off"),
+    AP_INIT_FLAG("ProxySCGIInternalRedirect", ap_set_flag_slot,
+                 (void*)APR_OFFSETOF(scgi_config, internal_redirect),
+                 RSRC_CONF|ACCESS_CONF,
+                 "Off if internal redirect responses should not be accepted"),
+    {NULL}
+};
+
+
+static void register_hooks(apr_pool_t *p)
+{
+    proxy_hook_scheme_handler(scgi_handler, NULL, NULL, APR_HOOK_FIRST);
+    proxy_hook_canon_handler(scgi_canon, NULL, NULL, APR_HOOK_FIRST);
+    APR_OPTIONAL_HOOK(proxy, request_status, scgi_request_status, NULL, NULL,
+                      APR_HOOK_MIDDLE);
+}
+
+
+module AP_MODULE_DECLARE_DATA proxy_scgi_module = {
+    STANDARD20_MODULE_STUFF,
+    create_scgi_config,  /* create per-directory config structure */
+    merge_scgi_config,   /* merge per-directory config structures */
+    NULL,                /* create per-server config structure */
+    NULL,                /* merge per-server config structures */
+    scgi_cmds,           /* command table */
+    register_hooks       /* register hooks */
+};