Skip to content

Instantly share code, notes, and snippets.

@oneguynick
Forked from bodgit/mod_proxy_wstunnel.c
Created November 20, 2015 18:43
Show Gist options
  • Save oneguynick/767c60fb4297f3804c86 to your computer and use it in GitHub Desktop.
Save oneguynick/767c60fb4297f3804c86 to your computer and use it in GitHub Desktop.

Revisions

  1. @bodgit bodgit revised this gist Aug 6, 2014. 2 changed files with 823 additions and 1 deletion.
    766 changes: 766 additions & 0 deletions mod_proxy_wstunnel.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,766 @@
    /* 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 "mod_proxy.h"

    module AP_MODULE_DECLARE_DATA proxy_wstunnel_module;

    /**
    * Create a HTTP request header brigade, old_cl_val and old_te_val as required.
    * @parama p pool
    * @param header_brigade header brigade to use/fill
    * @param r request
    * @param p_conn proxy connection rec
    * @param worker selected worker
    * @param conf per-server proxy config
    * @param uri uri
    * @param url url
    * @param server_portstr port as string
    * @param old_cl_val stored old content-len val
    * @param old_te_val stored old TE val
    * @return OK or HTTP_EXPECTATION_FAILED
    */
    PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
    apr_bucket_brigade *header_brigade,
    request_rec *r,
    proxy_conn_rec *p_conn,
    proxy_worker *worker,
    proxy_server_conf *conf,
    apr_uri_t *uri,
    char *url, char *server_portstr,
    char **old_cl_val,
    char **old_te_val);

    /**
    * @param bucket_alloc bucket allocator
    * @param r request
    * @param p_conn proxy connection
    * @param origin connection rec of origin
    * @param bb brigade to send to origin
    * @param flush flush
    * @return status (OK)
    */
    PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucket_alloc_t *bucket_alloc,
    request_rec *r, proxy_conn_rec *p_conn,
    conn_rec *origin, apr_bucket_brigade *bb,
    int flush);

    /*
    * Canonicalise http-like URLs.
    * scheme is the scheme for the URL
    * url is the URL starting with the first '/'
    * def_port is the default port for this scheme.
    */
    static int proxy_wstunnel_canon(request_rec *r, char *url)
    {
    char *host, *path, sport[7];
    char *search = NULL;
    const char *err;
    char *scheme;
    apr_port_t port, def_port;

    /* ap_port_of_scheme() */
    if (strncasecmp(url, "ws:", 3) == 0) {
    url += 3;
    scheme = "ws:";
    def_port = apr_uri_port_of_scheme("http");
    }
    else if (strncasecmp(url, "wss:", 4) == 0) {
    url += 4;
    scheme = "wss:";
    def_port = apr_uri_port_of_scheme("https");
    }
    else {
    return DECLINED;
    }

    port = def_port;
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "canonicalising URL %s", url);

    /*
    * do syntactic check.
    * We break the URL into host, port, path, search
    */
    err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
    if (err) {
    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "AH02439: " "error parsing URL %s: %s",
    url, err);
    return HTTP_BAD_REQUEST;
    }

    /*
    * now parse path/search args, according to rfc1738:
    * process the path. With proxy-nocanon set (by
    * mod_proxy) we use the raw, unparsed uri
    */
    if (apr_table_get(r->notes, "proxy-nocanon")) {
    path = url; /* this is the raw path */
    }
    else {
    path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
    r->proxyreq);
    search = r->args;
    }
    if (path == NULL)
    return HTTP_BAD_REQUEST;

    apr_snprintf(sport, sizeof(sport), ":%d", port);

    if (ap_strchr_c(host, ':')) {
    /* if literal IPv6 address */
    host = apr_pstrcat(r->pool, "[", host, "]", NULL);
    }
    r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "//", host, sport,
    "/", path, (search) ? "?" : "",
    (search) ? search : "", NULL);
    return OK;
    }


    static int proxy_wstunnel_transfer(request_rec *r, conn_rec *c_i, conn_rec *c_o,
    apr_bucket_brigade *bb, char *name)
    {
    int rv;
    #ifdef DEBUGGING
    apr_off_t len;
    #endif

    do {
    apr_brigade_cleanup(bb);
    rv = ap_get_brigade(c_i->input_filters, bb, AP_MODE_READBYTES,
    APR_NONBLOCK_READ, AP_IOBUFSIZE);
    if (rv == APR_SUCCESS) {
    if (c_o->aborted)
    return APR_EPIPE;
    if (APR_BRIGADE_EMPTY(bb))
    break;
    #ifdef DEBUGGING
    len = -1;
    apr_brigade_length(bb, 0, &len);
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "AH02440: "
    "read %" APR_OFF_T_FMT
    " bytes from %s", len, name);
    #endif
    rv = ap_pass_brigade(c_o->output_filters, bb);
    if (rv == APR_SUCCESS) {
    ap_fflush(c_o->output_filters, bb);
    }
    else {
    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "AH02441: "
    "error on %s - ap_pass_brigade",
    name);
    }
    } else if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) {
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, "AH02442: "
    "error on %s - ap_get_brigade",
    name);
    }
    } while (rv == APR_SUCCESS);

    if (APR_STATUS_IS_EAGAIN(rv)) {
    rv = APR_SUCCESS;
    }
    return rv;
    }

    /* Search thru the input filters and remove the reqtimeout one */
    static void remove_reqtimeout(ap_filter_t *next)
    {
    ap_filter_t *reqto = NULL;
    ap_filter_rec_t *filter;

    filter = ap_get_input_filter_handle("reqtimeout");
    if (!filter) {
    return;
    }

    while (next) {
    if (next->frec == filter) {
    reqto = next;
    break;
    }
    next = next->next;
    }
    if (reqto) {
    ap_remove_input_filter(reqto);
    }
    }

    /*
    * process the request and write the response.
    */
    static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r,
    proxy_conn_rec *conn,
    proxy_worker *worker,
    proxy_server_conf *conf,
    apr_uri_t *uri,
    char *url, char *server_portstr)
    {
    apr_status_t rv = APR_SUCCESS;
    apr_pollset_t *pollset;
    apr_pollfd_t pollfd;
    const apr_pollfd_t *signalled;
    apr_int32_t pollcnt, pi;
    apr_int16_t pollevent;
    conn_rec *c = r->connection;
    apr_socket_t *sock = conn->sock;
    conn_rec *backconn = conn->connection;
    int client_error = 0;
    char *buf;
    apr_bucket_brigade *header_brigade;
    apr_bucket *e;
    char *old_cl_val = NULL;
    char *old_te_val = NULL;
    apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
    apr_socket_t *client_socket = ap_get_module_config(c->conn_config, &core_module);

    header_brigade = apr_brigade_create(p, backconn->bucket_alloc);

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "sending request");

    rv = ap_proxy_create_hdrbrgd(p, header_brigade, r, conn,
    worker, conf, uri, url, server_portstr,
    &old_cl_val, &old_te_val);
    if (rv != OK) {
    return rv;
    }

    buf = apr_pstrcat(p, "Upgrade: WebSocket", CRLF, "Connection: Upgrade", CRLF, CRLF, NULL);
    ap_xlate_proto_to_ascii(buf, strlen(buf));
    e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(header_brigade, e);

    if ((rv = ap_proxy_pass_brigade(c->bucket_alloc, r, conn, backconn,
    header_brigade, 1)) != OK)
    return rv;

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "setting up poll()");

    if ((rv = apr_pollset_create(&pollset, 2, p, 0)) != APR_SUCCESS) {
    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "AH02443: "
    "error apr_pollset_create()");
    return HTTP_INTERNAL_SERVER_ERROR;
    }

    #if 0
    apr_socket_opt_set(sock, APR_SO_NONBLOCK, 1);
    apr_socket_opt_set(sock, APR_SO_KEEPALIVE, 1);
    apr_socket_opt_set(client_socket, APR_SO_NONBLOCK, 1);
    apr_socket_opt_set(client_socket, APR_SO_KEEPALIVE, 1);
    #endif

    pollfd.p = p;
    pollfd.desc_type = APR_POLL_SOCKET;
    pollfd.reqevents = APR_POLLIN;
    pollfd.desc.s = sock;
    pollfd.client_data = NULL;
    apr_pollset_add(pollset, &pollfd);

    pollfd.desc.s = client_socket;
    apr_pollset_add(pollset, &pollfd);


    r->output_filters = c->output_filters;
    r->proto_output_filters = c->output_filters;
    r->input_filters = c->input_filters;
    r->proto_input_filters = c->input_filters;

    remove_reqtimeout(r->input_filters);

    while (1) { /* Infinite loop until error (one side closes the connection) */
    if ((rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled))
    != APR_SUCCESS) {
    if (APR_STATUS_IS_EINTR(rv)) {
    continue;
    }
    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "AH02444: " "error apr_poll()");
    return HTTP_INTERNAL_SERVER_ERROR;
    }
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "AH02445: "
    "woke from poll(), i=%d", pollcnt);

    for (pi = 0; pi < pollcnt; pi++) {
    const apr_pollfd_t *cur = &signalled[pi];

    if (cur->desc.s == sock) {
    pollevent = cur->rtnevents;
    if (pollevent & APR_POLLIN) {
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "AH02446: "
    "sock was readable");
    rv = proxy_wstunnel_transfer(r, backconn, c, bb, "sock");
    }
    else if ((pollevent & APR_POLLERR)
    || (pollevent & APR_POLLHUP)) {
    rv = APR_EPIPE;
    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "AH02447: "
    "err/hup on backconn");
    }
    if (rv != APR_SUCCESS)
    client_error = 1;
    }
    else if (cur->desc.s == client_socket) {
    pollevent = cur->rtnevents;
    if (pollevent & APR_POLLIN) {
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "AH02448: "
    "client was readable");
    rv = proxy_wstunnel_transfer(r, c, backconn, bb, "client");
    }
    }
    else {
    rv = APR_EBADF;
    ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "AH02449: "
    "unknown socket in pollset");
    }

    }
    if (rv != APR_SUCCESS) {
    break;
    }
    }

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
    "finished with poll() - cleaning up");

    if (client_error) {
    return HTTP_INTERNAL_SERVER_ERROR;
    }
    return OK;
    }

    /*
    */
    static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker,
    proxy_server_conf *conf,
    char *url, const char *proxyname,
    apr_port_t proxyport)
    {
    int status;
    char server_portstr[32];
    proxy_conn_rec *backend = NULL;
    char *scheme;
    int retry;
    conn_rec *c = r->connection;
    apr_pool_t *p = r->pool;
    apr_uri_t *uri;

    if (strncasecmp(url, "wss:", 4) == 0) {
    scheme = "WSS";
    }
    else if (strncasecmp(url, "ws:", 3) == 0) {
    scheme = "WS";
    }
    else {
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "AH02450: " "declining URL %s", url);
    return DECLINED;
    }

    uri = apr_palloc(p, sizeof(*uri));
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "AH02451: " "serving URL %s", url);

    /* create space for state information */
    status = ap_proxy_acquire_connection(scheme, &backend, worker,
    r->server);
    if (status != OK) {
    if (backend) {
    backend->close = 1;
    ap_proxy_release_connection(scheme, backend, r->server);
    }
    return status;
    }

    backend->is_ssl = 0;
    backend->close = 0;

    retry = 0;
    while (retry < 2) {
    char *locurl = url;
    /* Step One: Determine Who To Connect To */
    status = ap_proxy_determine_connection(p, r, conf, worker, backend,
    uri, &locurl, proxyname, proxyport,
    server_portstr,
    sizeof(server_portstr));

    if (status != OK)
    break;

    /* Step Two: Make the Connection */
    if (ap_proxy_connect_backend(scheme, backend, worker, r->server)) {
    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "AH02452: "
    "failed to make connection to backend: %s",
    backend->hostname);
    status = HTTP_SERVICE_UNAVAILABLE;
    break;
    }
    /* Step Three: Create conn_rec */
    if (!backend->connection) {
    if ((status = ap_proxy_connection_create(scheme, backend,
    c, r->server)) != OK)
    break;
    }

    /* Step Three: Process the Request */
    status = ap_proxy_wstunnel_request(p, r, backend, worker, conf, uri, locurl,
    server_portstr);
    break;
    }

    /* Do not close the socket */
    ap_proxy_release_connection(scheme, backend, r->server);
    return status;
    }

    static void ap_proxy_http_register_hook(apr_pool_t *p)
    {
    proxy_hook_scheme_handler(proxy_wstunnel_handler, NULL, NULL, APR_HOOK_FIRST);
    proxy_hook_canon_handler(proxy_wstunnel_canon, NULL, NULL, APR_HOOK_FIRST);
    }

    APLOG_USE_MODULE(proxy_wstunnel);
    module AP_MODULE_DECLARE_DATA proxy_wstunnel_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 apr_table_t */
    ap_proxy_http_register_hook /* register hooks */
    };

    /* Clear all connection-based headers from the incoming headers table */
    typedef struct header_dptr {
    apr_pool_t *pool;
    apr_table_t *table;
    apr_time_t time;
    } header_dptr;

    static int clear_conn_headers(void *data, const char *key, const char *val)
    {
    apr_table_t *headers = ((header_dptr*)data)->table;
    apr_pool_t *pool = ((header_dptr*)data)->pool;
    const char *name;
    char *next = apr_pstrdup(pool, val);
    while (*next) {
    name = next;
    while (*next && !apr_isspace(*next) && (*next != ',')) {
    ++next;
    }
    while (*next && (apr_isspace(*next) || (*next == ','))) {
    *next++ = '\0';
    }
    apr_table_unset(headers, name);
    }
    return 1;
    }

    static void proxy_clear_connection(apr_pool_t *p, apr_table_t *headers)
    {
    header_dptr x;
    x.pool = p;
    x.table = headers;
    apr_table_unset(headers, "Proxy-Connection");
    apr_table_do(clear_conn_headers, &x, headers, "Connection", NULL);
    apr_table_unset(headers, "Connection");
    }

    PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
    apr_bucket_brigade *header_brigade,
    request_rec *r,
    proxy_conn_rec *p_conn,
    proxy_worker *worker,
    proxy_server_conf *conf,
    apr_uri_t *uri,
    char *url, char *server_portstr,
    char **old_cl_val,
    char **old_te_val)
    {
    conn_rec *c = r->connection;
    int counter;
    char *buf;
    const apr_array_header_t *headers_in_array;
    const apr_table_entry_t *headers_in;
    apr_table_t *headers_in_copy;
    apr_bucket *e;
    int do_100_continue;
    conn_rec *origin = p_conn->connection;
    proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &proxy_module);

    /*
    * To be compliant, we only use 100-Continue for requests with bodies.
    * We also make sure we won't be talking HTTP/1.0 as well.
    */
    do_100_continue = (worker->ping_timeout_set
    && !r->header_only
    && (apr_table_get(r->headers_in, "Content-Length")
    || apr_table_get(r->headers_in, "Transfer-Encoding"))
    && (PROXYREQ_REVERSE == r->proxyreq)
    && !(apr_table_get(r->subprocess_env, "force-proxy-request-1.0")));

    if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
    /*
    * According to RFC 2616 8.2.3 we are not allowed to forward an
    * Expect: 100-continue to an HTTP/1.0 server. Instead we MUST return
    * a HTTP_EXPECTATION_FAILED
    */
    if (r->expecting_100) {
    return HTTP_EXPECTATION_FAILED;
    }
    buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.0" CRLF, NULL);
    p_conn->close = 1;
    } else {
    buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.1" CRLF, NULL);
    }
    if (apr_table_get(r->subprocess_env, "proxy-nokeepalive")) {
    origin->keepalive = AP_CONN_CLOSE;
    p_conn->close = 1;
    }
    ap_xlate_proto_to_ascii(buf, strlen(buf));
    e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(header_brigade, e);
    if (conf->preserve_host == 0) {
    if (ap_strchr_c(uri->hostname, ':')) { /* if literal IPv6 address */
    if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) {
    buf = apr_pstrcat(p, "Host: [", uri->hostname, "]:",
    uri->port_str, CRLF, NULL);
    } else {
    buf = apr_pstrcat(p, "Host: [", uri->hostname, "]", CRLF, NULL);
    }
    } else {
    if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) {
    buf = apr_pstrcat(p, "Host: ", uri->hostname, ":",
    uri->port_str, CRLF, NULL);
    } else {
    buf = apr_pstrcat(p, "Host: ", uri->hostname, CRLF, NULL);
    }
    }
    }
    else {
    /* don't want to use r->hostname, as the incoming header might have a
    * port attached
    */
    const char* hostname = apr_table_get(r->headers_in,"Host");
    if (!hostname) {
    hostname = r->server->server_hostname;
    ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "AH01092: "
    "no HTTP 0.9 request (with no host line) "
    "on incoming request and preserve host set "
    "forcing hostname to be %s for uri %s",
    hostname, r->uri);
    }
    buf = apr_pstrcat(p, "Host: ", hostname, CRLF, NULL);
    }
    ap_xlate_proto_to_ascii(buf, strlen(buf));
    e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(header_brigade, e);

    /* handle Via */
    if (conf->viaopt == via_block) {
    /* Block all outgoing Via: headers */
    apr_table_unset(r->headers_in, "Via");
    } else if (conf->viaopt != via_off) {
    const char *server_name = ap_get_server_name(r);
    /* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host,
    * then the server name returned by ap_get_server_name() is the
    * origin server name (which does make too much sense with Via: headers)
    * so we use the proxy vhost's name instead.
    */
    if (server_name == r->hostname)
    server_name = r->server->server_hostname;
    /* Create a "Via:" request header entry and merge it */
    /* Generate outgoing Via: header with/without server comment: */
    apr_table_mergen(r->headers_in, "Via",
    (conf->viaopt == via_full)
    ? apr_psprintf(p, "%d.%d %s%s (%s)",
    HTTP_VERSION_MAJOR(r->proto_num),
    HTTP_VERSION_MINOR(r->proto_num),
    server_name, server_portstr,
    AP_SERVER_BASEVERSION)
    : apr_psprintf(p, "%d.%d %s%s",
    HTTP_VERSION_MAJOR(r->proto_num),
    HTTP_VERSION_MINOR(r->proto_num),
    server_name, server_portstr)
    );
    }

    /* Use HTTP/1.1 100-Continue as quick "HTTP ping" test
    * to backend
    */
    if (do_100_continue) {
    apr_table_mergen(r->headers_in, "Expect", "100-Continue");
    r->expecting_100 = 1;
    }

    /* X-Forwarded-*: handling
    *
    * XXX Privacy Note:
    * -----------------
    *
    * These request headers are only really useful when the mod_proxy
    * is used in a reverse proxy configuration, so that useful info
    * about the client can be passed through the reverse proxy and on
    * to the backend server, which may require the information to
    * function properly.
    *
    * In a forward proxy situation, these options are a potential
    * privacy violation, as information about clients behind the proxy
    * are revealed to arbitrary servers out there on the internet.
    *
    * The HTTP/1.1 Via: header is designed for passing client
    * information through proxies to a server, and should be used in
    * a forward proxy configuation instead of X-Forwarded-*. See the
    * ProxyVia option for details.
    */
    if (PROXYREQ_REVERSE == r->proxyreq) {
    const char *buf;

    /* Add X-Forwarded-For: so that the upstream has a chance to
    * determine, where the original request came from.
    */
    apr_table_mergen(r->headers_in, "X-Forwarded-For",
    c->remote_ip);

    /* Add X-Forwarded-Host: so that upstream knows what the
    * original request hostname was.
    */
    if ((buf = apr_table_get(r->headers_in, "Host"))) {
    apr_table_mergen(r->headers_in, "X-Forwarded-Host", buf);
    }

    /* Add X-Forwarded-Server: so that upstream knows what the
    * name of this proxy server is (if there are more than one)
    * XXX: This duplicates Via: - do we strictly need it?
    */
    apr_table_mergen(r->headers_in, "X-Forwarded-Server",
    r->server->server_hostname);
    }

    proxy_run_fixups(r);
    /*
    * Make a copy of the headers_in table before clearing the connection
    * headers as we need the connection headers later in the http output
    * filter to prepare the correct response headers.
    *
    * Note: We need to take r->pool for apr_table_copy as the key / value
    * pairs in r->headers_in have been created out of r->pool and
    * p might be (and actually is) a longer living pool.
    * This would trigger the bad pool ancestry abort in apr_table_copy if
    * apr is compiled with APR_POOL_DEBUG.
    */
    headers_in_copy = apr_table_copy(r->pool, r->headers_in);
    proxy_clear_connection(p, headers_in_copy);
    /* send request headers */
    headers_in_array = apr_table_elts(headers_in_copy);
    headers_in = (const apr_table_entry_t *) headers_in_array->elts;
    for (counter = 0; counter < headers_in_array->nelts; counter++) {
    if (headers_in[counter].key == NULL
    || headers_in[counter].val == NULL

    /* Already sent */
    || !strcasecmp(headers_in[counter].key, "Host")

    /* Clear out hop-by-hop request headers not to send
    * RFC2616 13.5.1 says we should strip these headers
    */
    || !strcasecmp(headers_in[counter].key, "Keep-Alive")
    || !strcasecmp(headers_in[counter].key, "TE")
    || !strcasecmp(headers_in[counter].key, "Trailer")
    || !strcasecmp(headers_in[counter].key, "Upgrade")

    ) {
    continue;
    }
    /* Do we want to strip Proxy-Authorization ?
    * If we haven't used it, then NO
    * If we have used it then MAYBE: RFC2616 says we MAY propagate it.
    * So let's make it configurable by env.
    */
    if (!strcasecmp(headers_in[counter].key,"Proxy-Authorization")) {
    if (r->user != NULL) { /* we've authenticated */
    if (!apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) {
    continue;
    }
    }
    }

    /* Skip Transfer-Encoding and Content-Length for now.
    */
    if (!strcasecmp(headers_in[counter].key, "Transfer-Encoding")) {
    *old_te_val = headers_in[counter].val;
    continue;
    }
    if (!strcasecmp(headers_in[counter].key, "Content-Length")) {
    *old_cl_val = headers_in[counter].val;
    continue;
    }

    /* for sub-requests, ignore freshness/expiry headers */
    if (r->main) {
    if ( !strcasecmp(headers_in[counter].key, "If-Match")
    || !strcasecmp(headers_in[counter].key, "If-Modified-Since")
    || !strcasecmp(headers_in[counter].key, "If-Range")
    || !strcasecmp(headers_in[counter].key, "If-Unmodified-Since")
    || !strcasecmp(headers_in[counter].key, "If-None-Match")) {
    continue;
    }
    }

    buf = apr_pstrcat(p, headers_in[counter].key, ": ",
    headers_in[counter].val, CRLF,
    NULL);
    ap_xlate_proto_to_ascii(buf, strlen(buf));
    e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(header_brigade, e);
    }
    return OK;
    }

    PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucket_alloc_t *bucket_alloc,
    request_rec *r, proxy_conn_rec *p_conn,
    conn_rec *origin, apr_bucket_brigade *bb,
    int flush)
    {
    apr_status_t status;
    apr_off_t transferred;

    if (flush) {
    apr_bucket *e = apr_bucket_flush_create(bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(bb, e);
    }
    apr_brigade_length(bb, 0, &transferred);
    if (transferred != -1)
    p_conn->worker->s->transferred += transferred;
    status = ap_pass_brigade(origin->output_filters, bb);
    if (status != APR_SUCCESS) {
    ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, "AH01084: "
    "pass request body failed to %pI (%s)",
    p_conn->addr, p_conn->hostname);
    if (origin->aborted) {
    const char *ssl_note;

    if (((ssl_note = apr_table_get(origin->notes, "SSL_connect_rv"))
    != NULL) && (strcmp(ssl_note, "err") == 0)) {
    return ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR,
    "Error during SSL Handshake with"
    " remote server");
    }
    return APR_STATUS_IS_TIMEUP(status) ? HTTP_GATEWAY_TIME_OUT : HTTP_BAD_GATEWAY;
    }
    else {
    return HTTP_BAD_REQUEST;
    }
    }
    apr_brigade_cleanup(bb);
    return OK;
    }
    58 changes: 57 additions & 1 deletion mod_proxy_wstunnel.spec
    Original file line number Diff line number Diff line change
    @@ -1 +1,57 @@
    FIXME
    %{!?_httpd_apxs: %{expand: %%global _httpd_apxs %%{_sbindir}/apxs}}
    %{!?_httpd_mmn: %{expand: %%global _httpd_mmn %%(cat %{_includedir}/httpd/.mmn || echo missing-httpd-devel)}}
    %{!?_httpd_confdir: %{expand: %%global _httpd_confdir %%{_sysconfdir}/httpd/conf.d}}
    %{!?_httpd_moddir: %{expand: %%global _httpd_moddir %%{_libdir}/httpd/modules}}

    Name: mod_proxy_wstunnel
    Version: 0.1
    Release: 1%{?dist}
    Summary: Websockets proxy module for Apache

    Group: System Environment/Daemons
    License: Apache
    URL: https://gist.github.com/bodgit/80a2fb09170596589a03
    Source0: mod_proxy_wstunnel.c
    BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)

    BuildRequires: httpd-devel
    Requires: httpd-mmn = %{_httpd_mmn}

    %description
    Websockets proxy module for Apache


    %prep
    %setup -q -c -T
    %{__install} %{SOURCE0} .


    %build
    %{_httpd_apxs} -Wc,-Wall -c %{name}.c


    %install
    %{__rm} -rf %{buildroot}
    %{__install} -D -p -m 0755 .libs/%{name}.so %{buildroot}%{_httpd_moddir}/%{name}.so

    cat << EOF >proxy_wstunnel.conf
    LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
    EOF

    %{__install} -D -p -m 644 proxy_wstunnel.conf %{buildroot}%{_httpd_confdir}/proxy_wstunnel.conf


    %clean
    rm -rf %{buildroot}


    %files
    %defattr(-,root,root,-)
    %doc
    %{_httpd_moddir}/%{name}.so
    %config(noreplace) %{_httpd_confdir}/proxy_wstunnel.conf


    %changelog
    * Wed Aug 06 2014 Matt Dainty <[email protected]> 0.1-1
    - Initial version 0.1.
  2. @bodgit bodgit created this gist Aug 6, 2014.
    1 change: 1 addition & 0 deletions mod_proxy_wstunnel.spec
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    FIXME