Commit ca74ed3d authored by Jim Jagielski's avatar Jim Jagielski
Browse files

Fold in ALv2 licensed UWSGI proxy (sub)module as acknowledged via

parent 0ac11f8a
Loading
Loading
Loading
Loading
+78 −0
Original line number Diff line number Diff line
<?xml version="1.0"?>
<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd">
<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?>
<!-- $LastChangedRevision: 1755335 $ -->

<!--
 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_uwsgi.xml.meta">

<name>mod_proxy_uwsgi</name>
<description>UWSGI gateway module for <module>mod_proxy</module></description>
<status>Extension</status>
<sourcefile>mod_proxy_uwsgi.c</sourcefile>
<identifier>proxy_uwsgi_module</identifier>

<summary>
    <p>This module <em>requires</em> the service of <module
    >mod_proxy</module>. It provides support for the
    <a href="http://uwsgi-docs.readthedocs.io/en/latest/index.html">UWSGI protocol</a>.</p>

    <p>Thus, in order to get the ability of handling the UWSGI protocol,
    <module>mod_proxy</module> and <module>mod_proxy_uwsgi</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_uwsgi</module>.</p>

    <example><title>Simple gateway</title>
    <highlight language="config">
ProxyPass "/uwsgi-bin/" "uwsgi://localhost:4000/"
      </highlight>
    </example>

    <p>The balanced gateway needs <module>mod_proxy_balancer</module> and
    at least one load balancer algorithm module, such as
    <module>mod_lbmethod_byrequests</module>, in addition to the proxy
    modules listed above.  <module>mod_lbmethod_byrequests</module> is the
    default, and will be used for this example configuration.</p>

    <example><title>Balanced gateway</title>
    <highlight language="config">
ProxyPass "/uwsgi-bin/" "balancer://somecluster/"
&lt;Proxy balancer://somecluster&gt;
    BalancerMember uwsgi://localhost:4000
    BalancerMember uwsgi://localhost:4001
&lt;/Proxy&gt;
    </highlight>
    </example>
</section>

</modulesynopsis>
+12 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="UTF-8" ?>
<!-- GENERATED FROM XML: DO NOT EDIT -->

<metafile reference="mod_proxy_uwsgi.xml">
  <basename>mod_proxy_uwsgi</basename>
  <path>/mod/</path>
  <relpath>..</relpath>

  <variants>
    <variant>en</variant>
  </variants>
</metafile>
+3 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ proxy_ftp_objs="mod_proxy_ftp.lo"
proxy_http_objs="mod_proxy_http.lo"
proxy_fcgi_objs="mod_proxy_fcgi.lo"
proxy_scgi_objs="mod_proxy_scgi.lo"
proxy_uwsgi_objs="mod_proxy_uwsgi.lo"
proxy_fdpass_objs="mod_proxy_fdpass.lo"
proxy_ajp_objs="mod_proxy_ajp.lo ajp_header.lo ajp_link.lo ajp_msg.lo ajp_utils.lo"
proxy_wstunnel_objs="mod_proxy_wstunnel.lo"
@@ -24,6 +25,7 @@ case "$host" in
    proxy_http_objs="$proxy_http_objs mod_proxy.la"
    proxy_fcgi_objs="$proxy_fcgi_objs mod_proxy.la"
    proxy_scgi_objs="$proxy_scgi_objs mod_proxy.la"
    proxy_uwsgi_objs="$proxy_uwsgi_objs mod_proxy.la"
    proxy_fdpass_objs="$proxy_fdpass_objs mod_proxy.la"
    proxy_ajp_objs="$proxy_ajp_objs mod_proxy.la"
    proxy_wstunnel_objs="$proxy_wstunnel_objs mod_proxy.la"
@@ -36,6 +38,7 @@ APACHE_MODULE(proxy_ftp, Apache proxy FTP module. Requires --enable-proxy., $pr
APACHE_MODULE(proxy_http, Apache proxy HTTP module.  Requires --enable-proxy., $proxy_http_objs, , most, , proxy)
APACHE_MODULE(proxy_fcgi, Apache proxy FastCGI module.  Requires --enable-proxy., $proxy_fcgi_objs, , most, , proxy)
APACHE_MODULE(proxy_scgi, Apache proxy SCGI module.  Requires --enable-proxy., $proxy_scgi_objs, , most, , proxy)
APACHE_MODULE(proxy_uwsgi, Apache proxy UWSGI module.  Requires --enable-proxy., $proxy_uwsgi_objs, , most, , proxy)
APACHE_MODULE(proxy_fdpass, Apache proxy to Unix Daemon Socket module.  Requires --enable-proxy., $proxy_fdpass_objs, , most, [
  AC_CHECK_DECL(CMSG_DATA,,, [
    #include <sys/types.h>
+520 −0
Original line number Diff line number Diff line
/* 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_uwsgi ***

Copyright 2009-2017 Unbit S.a.s. <info@unbit.it>
     
Licensed 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.

*/

#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 UWSGI_SCHEME "uwsgi"
#define UWSGI_DEFAULT_PORT 3031

module AP_MODULE_DECLARE_DATA proxy_uwsgi_module;


static int uwsgi_canon(request_rec *r, char *url)
{
    char *host, sport[sizeof(":65535")];
    const char *err, *path;
    apr_port_t port = UWSGI_DEFAULT_PORT;

    if (strncasecmp(url, UWSGI_SCHEME "://", sizeof(UWSGI_SCHEME) + 2)) {
        return DECLINED;
    }
    url += sizeof(UWSGI_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;
    }

    if (port != UWSGI_DEFAULT_PORT)
        apr_snprintf(sport, sizeof(sport), ":%u", port);
    else
        sport[0] = '\0';

    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:" UWSGI_SCHEME "://", host, sport, "/",
                              path, NULL);

    return OK;
}


static int uwsgi_send(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,
                          "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 uwsgi header block
 */
static int uwsgi_send_headers(request_rec *r, proxy_conn_rec *conn)
{
    char *buf, *ptr;

    const apr_array_header_t *env_table;
    const apr_table_entry_t *env;

    int j;

    apr_size_t headerlen = 4;
    uint16_t pktsize, keylen, vallen;

    ap_add_common_vars(r);
    ap_add_cgi_vars(r);

    // this is not a security problem (in Linux) as uWSGI destroy the env memory area readable in /proc
    // and generally if you host untrusted apps in your server and allows them to read others uid /proc/<pid>
    // files you have higher problems...
    const char *auth = apr_table_get(r->headers_in, "Authorization");
    if (auth) {
        apr_table_setn(r->subprocess_env, "HTTP_AUTHORIZATION", auth); 
    }

    const char *script_name = apr_table_get(r->subprocess_env, "SCRIPT_NAME");
    const char *path_info = apr_table_get(r->subprocess_env, "PATH_INFO");

    if (script_name && path_info) {
        if (strcmp(path_info, "/")) {
            apr_table_set(r->subprocess_env, "SCRIPT_NAME", apr_pstrndup(r->pool, script_name, strlen(script_name)-strlen(path_info)));
	}
        else {
            if (!strcmp(script_name, "/")) {
                apr_table_set(r->subprocess_env, "SCRIPT_NAME", "");
            }
        }
    }

    env_table = apr_table_elts(r->subprocess_env);
    env = (apr_table_entry_t *)env_table->elts;

    for (j = 0; j < env_table->nelts; ++j) {
        headerlen += 2 + strlen(env[j].key) + 2 + strlen(env[j].val) ;
    }

    ptr = buf = apr_palloc(r->pool, headerlen);

    ptr+=4;

    for (j = 0; j < env_table->nelts; ++j) {
	keylen = strlen(env[j].key);
	*ptr++= (uint8_t) (keylen & 0xff);
	*ptr++= (uint8_t) ((keylen >> 8)  & 0xff);
	memcpy(ptr, env[j].key, keylen) ; ptr+=keylen;

	vallen = strlen(env[j].val);
	*ptr++= (uint8_t) (vallen & 0xff);
	*ptr++= (uint8_t) ((vallen >> 8)  & 0xff);
	memcpy(ptr, env[j].val, vallen) ; ptr+=vallen;
    }

    pktsize = headerlen-4;

    buf[0] = 0;
    buf[1] = (uint8_t) (pktsize & 0xff);
    buf[2] = (uint8_t) ((pktsize >> 8) & 0xff);
    buf[3] = 0;

    return uwsgi_send(conn, buf, headerlen, r);
}


static int uwsgi_send_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 = uwsgi_send(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,
                          "receiving request body failed");
            return HTTP_INTERNAL_SERVER_ERROR;
        }
    }

    return OK;
}

static request_rec *make_fake_req(conn_rec *c, request_rec *r)
{
    apr_pool_t *pool;
    request_rec *rp;

    apr_pool_create(&pool, c->pool);

    rp = apr_pcalloc(pool, sizeof(*r));

    rp->pool            = pool;
    rp->status          = HTTP_OK;

    rp->headers_in      = apr_table_make(pool, 50);
    rp->subprocess_env  = apr_table_make(pool, 50);
    rp->headers_out     = apr_table_make(pool, 12);
    rp->err_headers_out = apr_table_make(pool, 5);
    rp->notes           = apr_table_make(pool, 5);

    rp->server = r->server;
    rp->log = r->log;
    rp->proxyreq = r->proxyreq;
    rp->request_time = r->request_time;
    rp->connection      = c;
    rp->output_filters  = c->output_filters;
    rp->input_filters   = c->input_filters;
    rp->proto_output_filters  = c->output_filters;
    rp->proto_input_filters   = c->input_filters;
    rp->useragent_ip = c->client_ip;
    rp->useragent_addr = c->client_addr;

    rp->request_config  = ap_create_request_config(pool);
    proxy_run_create_req(r, rp);

    return rp;
}

static int uwsgi_response(request_rec *r, proxy_conn_rec *backend, proxy_server_conf *conf)
{

	char buffer[HUGE_STRING_LEN];
	const char *buf;
	char *value, *end;
	int len;
	int backend_broke = 0;
	apr_status_t rc;
	conn_rec *c = r->connection;
	apr_off_t readbytes;
	apr_status_t rv;
	apr_bucket *e;
	apr_read_type_e mode = APR_NONBLOCK_READ;

	request_rec *rp = make_fake_req(backend->connection, r);
	rp->proxyreq = PROXYREQ_RESPONSE;

	apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
	apr_bucket_brigade *pass_bb = apr_brigade_create(r->pool, c->bucket_alloc);

	len = ap_getline(buffer, sizeof(buffer), rp, 1);

	if (len <= 0) {
		// oops
		return HTTP_INTERNAL_SERVER_ERROR;
	}

	backend->worker->s->read += len;

	if (len >= sizeof(buffer)-1) {
		// oops
		return HTTP_INTERNAL_SERVER_ERROR;
	}
	/* Position of http status code */
	int status_start;
	if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
		status_start = 9;
	} else if (apr_date_checkmask(buffer, "HTTP/# ###*")) {
		status_start = 7;
	} else {
		// oops
		return HTTP_INTERNAL_SERVER_ERROR;
	}
	int status_end = status_start + 3;

	char keepchar = buffer[status_end];
	buffer[status_end] = '\0';
	r->status = atoi(&buffer[status_start]);

	if (keepchar != '\0') {
		buffer[status_end] = keepchar;
	} else {
		/* 2616 requires the space in Status-Line; the origin
		* server may have sent one but ap_rgetline_core will
		* have stripped it. */
		buffer[status_end] = ' ';
		buffer[status_end+1] = '\0';
	}
	r->status_line = apr_pstrdup(r->pool, &buffer[status_start]);

	// start parsing headers;
	while ((len = ap_getline(buffer, sizeof(buffer), rp, 1)) > 0) {
		value = strchr(buffer, ':');
		// invalid header skip
		if (!value) continue;
		*value = '\0';
		++value;
		while (apr_isspace(*value)) ++value; 
		for (end = &value[strlen(value)-1]; end > value && apr_isspace(*end); --end) *end = '\0';
		apr_table_add(r->headers_out, buffer, value);
	}

	if ((buf = apr_table_get(r->headers_out, "Content-Type"))) {
		ap_set_content_type(r, apr_pstrdup(r->pool, buf));
	}

    // honor ProxyErrorOverride and ErrorDocument
#if AP_MODULE_MAGIC_AT_LEAST(20101106,0)
    proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
    if (dconf->error_override && ap_is_HTTP_ERROR(r->status)) {
#else
    if (conf->error_override && ap_is_HTTP_ERROR(r->status)) {
#endif
        int status = r->status;
        r->status = HTTP_OK;
        r->status_line = NULL;

        apr_brigade_cleanup(bb);
               apr_brigade_cleanup(pass_bb);

        return status;
    }

	int finish = 0;
	while(!finish) {
		rv = ap_get_brigade(rp->input_filters, bb,
                                        AP_MODE_READBYTES, mode,
                                        conf->io_buffer_size);
		if (APR_STATUS_IS_EAGAIN(rv)
                        || (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb)) ) {
			e = apr_bucket_flush_create(c->bucket_alloc);
			APR_BRIGADE_INSERT_TAIL(bb, e);
			if (ap_pass_brigade(r->output_filters, bb) || c->aborted) {
				break;
			}
			apr_brigade_cleanup(bb);
			mode = APR_BLOCK_READ;
			continue;
		}
		else if (rv == APR_EOF) {
			break;
		}
		else if (rv != APR_SUCCESS) {
			ap_proxy_backend_broke(r, bb);
			ap_pass_brigade(r->output_filters, bb);
			backend_broke = 1;
			break;
		}

		mode = APR_NONBLOCK_READ;
		apr_brigade_length(bb, 0, &readbytes);
		backend->worker->s->read += readbytes;

		if (APR_BRIGADE_EMPTY(bb)) {
                        apr_brigade_cleanup(bb);
                        break;
                }

		ap_proxy_buckets_lifetime_transform(r, bb, pass_bb);

		// found the last brigade?
		if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) finish = 1;

		// do not pass chunk if it is zero_sized
		apr_brigade_length(pass_bb, 0, &readbytes);

		if ((readbytes > 0 && ap_pass_brigade(r->output_filters, pass_bb) != APR_SUCCESS) || c->aborted) {
			finish = 1;
		}

		apr_brigade_cleanup(bb);
		apr_brigade_cleanup(pass_bb);
	}

	e = apr_bucket_eos_create(c->bucket_alloc);
	APR_BRIGADE_INSERT_TAIL(bb, e);
        ap_pass_brigade(r->output_filters, bb);

	apr_brigade_cleanup(bb);

	if (c->aborted || backend_broke) {
        	return DONE;
        }

	return OK;
}

static int uwsgi_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 server_portstr[32];

    if (strncasecmp(url, UWSGI_SCHEME "://", sizeof(UWSGI_SCHEME) + 2)) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                      "declining URL %s", url);
        return DECLINED;
    }

    // ADD PATH_INFO
#if AP_MODULE_MAGIC_AT_LEAST(20111130,0)
    size_t w_len = strlen(worker->s->name);
#else
    size_t w_len = strlen(worker->name);
#endif
    char *u_path_info = r->filename + 6 + w_len;
    int delta = 0;
    if (u_path_info[0] != '/') {
        delta = 1;
    }
    int decode_status = ap_unescape_url(url+w_len-delta);
    if (decode_status) {
       ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                      "unable to decode uri: %s",
                      url+w_len-delta);
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    apr_table_add(r->subprocess_env, "PATH_INFO", url+w_len-delta);


    /* Create space for state information */
    status = ap_proxy_acquire_connection(UWSGI_SCHEME, &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,
                                           server_portstr, sizeof(server_portstr));
    if (status != OK) {
        goto cleanup;
    }


    /* Step Two: Make the Connection */
    if (ap_proxy_connect_backend(UWSGI_SCHEME, backend, worker, r->server)) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                      "failed to make connection to backend: %s:%u",
                      backend->hostname, backend->port);
        status = HTTP_SERVICE_UNAVAILABLE;
        goto cleanup;
    }

    /* Step Three: Create conn_rec */
    if (!backend->connection) {
	if ((status = ap_proxy_connection_create(UWSGI_SCHEME, backend,
						r->connection, r->server)) != OK)
		goto cleanup;
    }

    /* Step Four: Process the Request */
    if (   ((status = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK)
        || ((status = uwsgi_send_headers(r, backend)) != OK)
        || ((status = uwsgi_send_body(r, backend)) != OK)
        || ((status = uwsgi_response(r, backend, conf)) != OK)) {
        goto cleanup;
    }

cleanup:
    if (backend) {
        backend->close = 1; /* always close the socket */
        ap_proxy_release_connection(UWSGI_SCHEME, backend, r->server);
    }
    return status;
}


static void register_hooks(apr_pool_t *p)
{
    proxy_hook_scheme_handler(uwsgi_handler, NULL, NULL, APR_HOOK_FIRST);
    proxy_hook_canon_handler(uwsgi_canon, NULL, NULL, APR_HOOK_FIRST);
}


module AP_MODULE_DECLARE_DATA proxy_uwsgi_module = {
    STANDARD20_MODULE_STUFF,
    NULL,		/* create per-directory config structure */
    NULL,		/* merge per-directory config structures */
    NULL,		/* create per-server config structure */
    NULL,		/* merge per-server config structures */
    NULL,		/* command table */
    register_hooks	/* register hooks */
};
+123 −0

File added.

Preview size limit exceeded, changes collapsed.