Actually I was working on these lands lately to implement some Upgrade
forwarding.

I ended up with the attached patch (still incomplete, but working)
which moves some mod_proxy_wstunnel parts (TCP-forwarding) to
proxy_util and finally uses the parsing/power of mod_proxy_http for
this achievement.
The patch is based on 2.4.18 and does not apply to trunk or 2.4.x due
latest changes wrt lifetime_transform buckets, but could be easily
adapted...

I think handling Upgrade in mod_proxy_http would "deprecate"
mod_proxy_wstunnel which, as Tijs noticed, can't handle conditional
Upgrade w/o some logic already in mod_proxy_http, and which does not
play well with mod_proxy_http either when the requested (sub)paths
collide.

I'm proposing it now (quite quickly) to see if the approach looks
relevant/suitable/viable (and I could work further on it), or if it is
simply not a good idea :)

Thoughts?
Index: include/http_protocol.h
===================================================================
--- include/http_protocol.h	(revision 1734109)
+++ include/http_protocol.h	(working copy)
@@ -976,10 +976,13 @@ AP_DECLARE(void) ap_set_sub_req_protocol(request_r
  */
 AP_DECLARE(void) ap_finalize_sub_req_protocol(request_rec *sub_r);
 
+#define AP_SEND_INTERIM_KEEP_HEADERS 0xdeadbeef
 /**
  * Send an interim (HTTP 1xx) response immediately.
  * @param r The request
  * @param send_headers Whether to send&clear headers in r->headers_out
+ * @remark send_headers == AP_SEND_INTERIM_KEEP_HEADERS allows to send
+ *         but not clear r->headers_out
  */
 AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers);
 
Index: modules/proxy/mod_proxy.h
===================================================================
--- modules/proxy/mod_proxy.h	(revision 1734109)
+++ modules/proxy/mod_proxy.h	(working copy)
@@ -1004,6 +1004,9 @@ PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucke
                                          conn_rec *origin, apr_bucket_brigade *bb,
                                          int flush);
 
+PROXY_DECLARE(int) ap_proxy_tunnel_request(apr_pool_t *p, request_rec *r,
+                                           proxy_conn_rec *backend);
+
 /**
  * Clear the headers referenced by the Connection header from the given
  * table, and remove the Connection header.
Index: modules/proxy/mod_proxy_http.c
===================================================================
--- modules/proxy/mod_proxy_http.c	(revision 1734109)
+++ modules/proxy/mod_proxy_http.c	(working copy)
@@ -712,6 +712,7 @@ int ap_proxy_http_request(apr_pool_t *p, request_r
     enum rb_methods rb_method = RB_INIT;
     char *old_cl_val = NULL;
     char *old_te_val = NULL;
+    const char *upgrade = NULL;
     apr_off_t bytes_read = 0;
     apr_off_t bytes;
     int force10, rv;
@@ -723,6 +724,9 @@ int ap_proxy_http_request(apr_pool_t *p, request_r
         }
         force10 = 1;
     } else {
+        if (apr_table_get(r->subprocess_env, "proxy-forward-upgrade")) {
+            upgrade = apr_table_get(r->headers_in, "Upgrade");
+        }
         force10 = 0;
     }
 
@@ -925,6 +929,10 @@ skip_body:
         if (!ap_proxy_connection_reusable(p_conn)) {
             buf = apr_pstrdup(p, "Connection: close" CRLF);
         }
+        else if (upgrade) {
+            buf = apr_pstrcat(p, "Connection: Keep-Alive, Upgrade" CRLF
+                                 "Upgrade: ", upgrade, CRLF, NULL);
+        }
         else {
             buf = apr_pstrdup(p, "Connection: Keep-Alive" CRLF);
         }
@@ -1248,7 +1256,6 @@ apr_status_t ap_proxy_http_process_response(apr_po
     static const char *hop_by_hop_hdrs[] =
         {"Keep-Alive", "Proxy-Authenticate", "TE", "Trailer", "Upgrade", NULL};
     int i;
-    const char *te = NULL;
     int original_status = r->status;
     int proxy_status = OK;
     const char *original_status_line = r->status_line;
@@ -1295,6 +1302,7 @@ apr_status_t ap_proxy_http_process_response(apr_po
                    origin->local_addr->port));
     tmp_bb = apr_brigade_create(p, c->bucket_alloc);
     do {
+        const char *te = NULL, *upgrade = NULL;
         apr_status_t rc;
 
         apr_brigade_cleanup(bb);
@@ -1490,6 +1498,11 @@ apr_status_t ap_proxy_http_process_response(apr_po
              */
             te = apr_table_get(r->headers_out, "Transfer-Encoding");
 
+            /* Likewise for Upgrade header */
+            if (r->status == HTTP_SWITCHING_PROTOCOLS) {
+                upgrade = apr_table_get(r->headers_out, "Upgrade");
+            }
+
             /* strip connection listed hop-by-hop headers from response */
             toclose = ap_proxy_clear_connection_fn(r, r->headers_out);
             if (toclose) {
@@ -1583,10 +1596,27 @@ apr_status_t ap_proxy_http_process_response(apr_po
                                                "proxy-interim-response");
             ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
                           "HTTP: received interim %d response", r->status);
-            if (!policy
-                    || (!strcasecmp(policy, "RFC") && ((r->expecting_100 = 1)))) {
+            if (!policy || !strcasecmp(policy, "RFC")) {
+                if (upgrade) {
+                    apr_table_setn(r->headers_out, "Upgrade", upgrade);
+                    apr_table_setn(r->headers_out, "Connection", "upgrade");
+                    
+                    /* Since after the Upgrade this is not HTTP anymore,
+                     * consider the HTTP (final) response is this status 101,
+                     * hence tell ap_send_interim_response() to send but still
+                     * keep (not clear) the headers.
+                     */
+                    ap_send_interim_response(r, AP_SEND_INTERIM_KEEP_HEADERS);
+                }
+                else
                 ap_send_interim_response(r, 1);
             }
+            else if (upgrade) {
+                return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+                                apr_psprintf(p, "Unexpected status %i "
+                                             "returned by remote server",
+                                             proxy_status));
+            }
             /* FIXME: refine this to be able to specify per-response-status
              * policies and maybe also add option to bail out with 502
              */
@@ -1611,31 +1641,6 @@ apr_status_t ap_proxy_http_process_response(apr_po
             }
         }
 
-        r->sent_bodyct = 1;
-        /*
-         * Is it an HTTP/0.9 response or did we maybe preread the 1st line of
-         * the response? If so, load the extra data. These are 2 mutually
-         * exclusive possibilities, that just happen to require very
-         * similar behavior.
-         */
-        if (backasswards || pread_len) {
-            apr_ssize_t cntr = (apr_ssize_t)pread_len;
-            if (backasswards) {
-                /*@@@FIXME:
-                 * At this point in response processing of a 0.9 response,
-                 * we don't know yet whether data is binary or not.
-                 * mod_charset_lite will get control later on, so it cannot
-                 * decide on the conversion of this buffer full of data.
-                 * However, chances are that we are not really talking to an
-                 * HTTP/0.9 server, but to some different protocol, therefore
-                 * the best guess IMHO is to always treat the buffer as "text/x":
-                 */
-                ap_xlate_proto_to_ascii(buffer, len);
-                cntr = (apr_ssize_t)len;
-            }
-            e = apr_bucket_heap_create(buffer, cntr, NULL, c->bucket_alloc);
-            APR_BRIGADE_INSERT_TAIL(bb, e);
-        }
         /* PR 41646: get HEAD right with ProxyErrorOverride */
         if (ap_is_HTTP_ERROR(r->status) && dconf->error_override) {
             /* clear r->status for override error, otherwise ErrorDocument
@@ -1669,6 +1674,43 @@ apr_status_t ap_proxy_http_process_response(apr_po
             return proxy_status;
         }
 
+        r->sent_bodyct = 1;
+        /*
+         * Is it an HTTP/0.9 response or did we maybe preread the 1st line of
+         * the response? If so, load the extra data. These are 2 mutually
+         * exclusive possibilities, that just happen to require very
+         * similar behavior.
+         */
+        if (backasswards || pread_len) {
+            apr_ssize_t cntr = (apr_ssize_t)pread_len;
+            if (backasswards) {
+                /*@@@FIXME:
+                 * At this point in response processing of a 0.9 response,
+                 * we don't know yet whether data is binary or not.
+                 * mod_charset_lite will get control later on, so it cannot
+                 * decide on the conversion of this buffer full of data.
+                 * However, chances are that we are not really talking to an
+                 * HTTP/0.9 server, but to some different protocol, therefore
+                 * the best guess IMHO is to always treat the buffer as "text/x":
+                 */
+                ap_xlate_proto_to_ascii(buffer, len);
+                cntr = (apr_ssize_t)len;
+            }
+            e = apr_bucket_heap_create(buffer, cntr, NULL, c->bucket_alloc);
+            APR_BRIGADE_INSERT_TAIL(pass_bb, e);
+        }
+
+        if (upgrade) {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                          "proxy: tunneling protocol %s", upgrade);
+            if (!APR_BRIGADE_EMPTY(pass_bb)) {
+                ap_fflush(c->output_filters, pass_bb);
+                apr_brigade_cleanup(pass_bb);
+            }
+            ap_proxy_tunnel_request(backend->r->pool, r, backend);
+            return OK;
+        }
+
         /* send body - but only if a body is expected */
         if ((!r->header_only) &&                   /* not HEAD request */
             !interim_response &&                   /* not any 1xx response */
Index: modules/proxy/mod_proxy_wstunnel.c
===================================================================
--- modules/proxy/mod_proxy_wstunnel.c	(revision 1734109)
+++ modules/proxy/mod_proxy_wstunnel.c	(working copy)
@@ -90,60 +90,6 @@ static int proxy_wstunnel_canon(request_rec *r, ch
 }
 
 
-static apr_status_t proxy_wstunnel_transfer(request_rec *r, conn_rec *c_i, conn_rec *c_o,
-                                     apr_bucket_brigade *bb, char *name, int *sent)
-{
-    apr_status_t 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, APLOGNO(02440)
-                          "read %" APR_OFF_T_FMT
-                          " bytes from %s", len, name);
-#endif
-            if (sent) {
-                *sent = 1;
-            }
-            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, APLOGNO(02441)
-                              "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, APLOGNO(02442)
-                          "error on %s - ap_get_brigade",
-                          name);
-        }
-    } while (rv == APR_SUCCESS);
-
-    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r, "wstunnel_transfer complete");
-
-    if (APR_STATUS_IS_EAGAIN(rv)) {
-        rv = APR_SUCCESS;
-    }
-
-    return rv;
-}
-
 /*
  * process the request and write the response.
  */
@@ -155,13 +101,7 @@ static int proxy_wstunnel_request(apr_pool_t *p, r
                                 char *url, char *server_portstr)
 {
     apr_status_t rv;
-    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;
     char *buf;
     apr_bucket_brigade *header_brigade;
@@ -168,9 +108,6 @@ static int proxy_wstunnel_request(apr_pool_t *p, r
     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_conn_socket(c);
-    int done = 0, replied = 0;
 
     header_brigade = apr_brigade_create(p, backconn->bucket_alloc);
 
@@ -183,7 +120,9 @@ static int proxy_wstunnel_request(apr_pool_t *p, r
         return rv;
     }
 
-    buf = apr_pstrdup(p, "Upgrade: WebSocket" CRLF "Connection: Upgrade" CRLF CRLF);
+    buf = apr_pstrdup(p, "Upgrade: WebSocket" CRLF
+                         "Connection: Upgrade" CRLF
+                         CRLF);
     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);
@@ -192,117 +131,7 @@ static int proxy_wstunnel_request(apr_pool_t *p, r
                                     header_brigade, 1)) != OK)
         return rv;
 
-    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 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, APLOGNO(02443)
-                      "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 | APR_POLLHUP;
-    pollfd.desc.s = sock;
-    pollfd.client_data = NULL;
-    apr_pollset_add(pollset, &pollfd);
-
-    pollfd.desc.s = client_socket;
-    apr_pollset_add(pollset, &pollfd);
-
-    ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
-
-    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;
-
-    /* This handler should take care of the entire connection; make it so that
-     * nothing else is attempted on the connection after returning. */
-    c->keepalive = AP_CONN_CLOSE;
-
-    do { /* Loop until done (one side closes the connection, or an error) */
-        rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled);
-        if (rv != APR_SUCCESS) {
-            if (APR_STATUS_IS_EINTR(rv)) {
-                continue;
-            }
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02444) "error apr_poll()");
-            return HTTP_INTERNAL_SERVER_ERROR;
-        }
-        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02445)
-                      "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 | APR_POLLHUP)) {
-                    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02446)
-                                  "sock was readable");
-                    done |= proxy_wstunnel_transfer(r, backconn, c, bb,
-                                                    "sock", NULL) != APR_SUCCESS;
-                }
-                else if (pollevent & APR_POLLERR) {
-                    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02447)
-                            "error on backconn");
-                    backconn->aborted = 1;
-                    done = 1;
-                }
-                else { 
-                    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02605)
-                            "unknown event on backconn %d", pollevent);
-                    done = 1;
-                }
-            }
-            else if (cur->desc.s == client_socket) {
-                pollevent = cur->rtnevents;
-                if (pollevent & (APR_POLLIN | APR_POLLHUP)) {
-                    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02448)
-                                  "client was readable");
-                    done |= proxy_wstunnel_transfer(r, c, backconn, bb,
-                                                    "client", &replied) != APR_SUCCESS;
-                }
-                else if (pollevent & APR_POLLERR) {
-                    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02607)
-                            "error on client conn");
-                    c->aborted = 1;
-                    done = 1;
-                }
-                else { 
-                    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02606)
-                            "unknown event on client conn %d", pollevent);
-                    done = 1;
-                }
-            }
-            else {
-                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02449)
-                              "unknown socket in pollset");
-                done = 1;
-            }
-
-        }
-    } while (!done);
-
-    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
-                  "finished with poll() - cleaning up");
-
-    if (!replied) {
-        return HTTP_BAD_GATEWAY;
-    }
-    else {
-        return OK;
-    }
-
-    return OK;
+    return ap_proxy_tunnel_request(r->pool, r, conn);
 }
 
 /*
@@ -315,9 +144,7 @@ static int proxy_wstunnel_handler(request_rec *r,
     int status;
     char server_portstr[32];
     proxy_conn_rec *backend = NULL;
-    const char *upgrade;
-    char *scheme;
-    int retry;
+    const char *scheme, *upgrade;
     conn_rec *c = r->connection;
     apr_pool_t *p = r->pool;
     apr_uri_t *uri;
@@ -339,7 +166,7 @@ static int proxy_wstunnel_handler(request_rec *r,
     if (!upgrade || strcasecmp(upgrade, "WebSocket") != 0) {
         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02900)
                       "declining URL %s  (not WebSocket)", url);
-        return DECLINED;
+        return HTTP_UPGRADE_REQUIRED;
     }
 
     uri = apr_palloc(p, sizeof(*uri));
@@ -357,11 +184,12 @@ static int proxy_wstunnel_handler(request_rec *r,
     }
 
     backend->is_ssl = is_ssl;
-    backend->close = 0;
 
-    retry = 0;
-    while (retry < 2) {
+    ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
+
+    do {
         char *locurl = url;
+
         /* Step One: Determine Who To Connect To */
         status = ap_proxy_determine_connection(p, r, conf, worker, backend,
                                                uri, &locurl, proxyname, proxyport,
@@ -379,6 +207,7 @@ static int proxy_wstunnel_handler(request_rec *r,
             status = HTTP_SERVICE_UNAVAILABLE;
             break;
         }
+
         /* Step Three: Create conn_rec */
         if (!backend->connection) {
             if ((status = ap_proxy_connection_create(scheme, backend,
@@ -386,14 +215,10 @@ static int proxy_wstunnel_handler(request_rec *r,
                 break;
         }
 
-        backend->close = 1; /* must be after ap_proxy_determine_connection */
-
-
         /* Step Three: Process the Request */
         status = proxy_wstunnel_request(p, r, backend, worker, conf, uri, locurl,
                                       server_portstr);
-        break;
-    }
+    } while (0);
 
     /* Do not close the socket */
     ap_proxy_release_connection(scheme, backend, r->server);
Index: modules/proxy/proxy_util.c
===================================================================
--- modules/proxy/proxy_util.c	(revision 1734109)
+++ modules/proxy/proxy_util.c	(working copy)
@@ -3421,6 +3421,7 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_poo
     if (do_100_continue) {
         const char *val;
 
+#if 0
         if (!r->expecting_100) {
             /* Don't forward any "100 Continue" response if the client is
              * not expecting it.
@@ -3428,6 +3429,7 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_poo
             apr_table_setn(r->subprocess_env, "proxy-interim-response",
                                               "Suppress");
         }
+#endif
 
         /* Add the Expect header if not already there. */
         if (((val = apr_table_get(r->headers_in, "Expect")) == NULL)
@@ -3598,6 +3600,210 @@ PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucke
     return OK;
 }
 
+static int proxy_transfer(request_rec *r, conn_rec *c_i, conn_rec *c_o,
+                          apr_bucket_brigade *bb, const char *name, int *sent)
+{
+    apr_status_t rv;
+    int passed = 0;
+
+    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) {
+                rv = APR_EPIPE;
+                break;
+            }
+            if (APR_BRIGADE_EMPTY(bb)) {
+                break;
+            }
+            rv = ap_pass_brigade(c_o->output_filters, bb);
+            if (rv != APR_SUCCESS) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02441)
+                              "proxy: transfer for %s failed on write", name);
+            }
+            passed = 1;
+        }
+        else if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02442)
+                          "proxy: transfer for %s failed on read",
+                          name);
+        }
+    } while (rv == APR_SUCCESS);
+
+    if (passed && !c_o->aborted) {
+        apr_status_t tmp;
+        apr_brigade_cleanup(bb);
+        tmp = ap_fflush(c_o->output_filters, bb);
+        if (rv == APR_SUCCESS) {
+            rv = tmp;
+        }
+    }
+    if (sent) {
+        *sent = passed;
+    }
+
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r,
+                  "proxy: transfer for %s complete", name);
+
+    if (APR_STATUS_IS_EAGAIN(rv)) {
+        rv = APR_SUCCESS;
+    }
+    return rv;
+}
+
+/*
+ * Forward anything from<=>to the client<=>backend until one end closes.
+ */
+PROXY_DECLARE(int) ap_proxy_tunnel_request(apr_pool_t *p, request_rec *r,
+                                           proxy_conn_rec *backend)
+{
+    apr_status_t rv;
+    apr_pollfd_t *pollfds;
+    apr_pollset_t *pollset;
+    const apr_pollfd_t *signalled;
+    apr_int32_t pollcnt, pi;
+    apr_int16_t pollevent;
+    conn_rec *c = r->connection;
+    conn_rec *backend_c = backend->connection;
+    apr_socket_t *backend_socket = backend->sock;
+    apr_socket_t *client_socket = ap_get_conn_socket(c);
+    apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
+    apr_interval_time_t timeout = -1, backend_timeout = -1;
+    int done = 0, replied = 0;
+
+    rv = apr_pollset_create(&pollset, 2, p, APR_POLLSET_NOCOPY);
+    if (rv != APR_SUCCESS) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02443)
+                      "proxy: tunnel: can't create pollset");
+        return HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+#if 0
+    apr_socket_opt_set(backend_socket, APR_SO_NONBLOCK, 1);
+    apr_socket_opt_set(backend_socket, 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
+
+    pollfds = apr_pcalloc(p, sizeof(apr_pollfd_t) * 2);
+    pollfds[0].p = pollfds[1].p = p;
+    pollfds[0].desc_type = pollfds[1].desc_type = APR_POLL_SOCKET;
+    pollfds[0].reqevents = pollfds[1].reqevents = APR_POLLIN | APR_POLLHUP;
+    pollfds[0].desc.s = client_socket;
+    pollfds[1].desc.s = backend_socket;
+    apr_pollset_add(pollset, &pollfds[0]);
+    apr_pollset_add(pollset, &pollfds[1]);
+
+    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;
+
+    /* This loop will take of the entire connections; make it so that
+     * nothing else is attempted on them after returning.
+     */
+    c->keepalive = AP_CONN_CLOSE;
+    backend->close = 1;
+
+    apr_socket_timeout_get(client_socket, &timeout);
+    apr_socket_timeout_get(backend_socket, &backend_timeout);
+    if (timeout < 0) {
+        timeout = backend_timeout;
+    }
+    else if (timeout > backend_timeout) {
+        timeout = backend_timeout;
+    }
+
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+                  "proxy: tunnel: starting (timeout %" APR_TIME_T_FMT ")",
+                  apr_time_sec(timeout));
+
+    do { /* Loop until done (one side closes the connection, or an error) */
+        rv = apr_pollset_poll(pollset, timeout, &pollcnt, &signalled);
+        if (rv != APR_SUCCESS) {
+            if (APR_STATUS_IS_EINTR(rv)) {
+                continue;
+            }
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02444)
+                          "proxy: tunnel: polling failed");
+            break;
+        }
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, APLOGNO(02445)
+                      "proxy: tunnel: woken up, i=%d", pollcnt);
+
+        for (pi = 0; pi < pollcnt; pi++) {
+            const apr_pollfd_t *cur = &signalled[pi];
+
+            if (cur->desc.s == backend_socket) {
+                pollevent = cur->rtnevents;
+                if (pollevent & (APR_POLLIN | APR_POLLHUP)) {
+                    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, APLOGNO(02446)
+                                  "proxy: tunnel: backend was readable");
+                    done |= (proxy_transfer(r, backend_c, c, bb, "backend",
+                                            NULL) != APR_SUCCESS);
+                }
+                else if (pollevent & APR_POLLERR) {
+                    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02447)
+                            "proxy: tunnel: error on backend connection");
+                    backend_c->aborted = 1;
+                    done = 1;
+                }
+                else { 
+                    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02605)
+                            "proxy: tunnel: unknown event %d on backend connection",
+                            (int)pollevent);
+                    done = 1;
+                }
+            }
+            else if (cur->desc.s == client_socket) {
+                pollevent = cur->rtnevents;
+                if (pollevent & (APR_POLLIN | APR_POLLHUP)) {
+                    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, APLOGNO(02448)
+                                  "proxy: tunnel: client was readable");
+                    done |= (proxy_transfer(r, c, backend_c, bb, "client",
+                                            &replied) != APR_SUCCESS);
+                }
+                else if (pollevent & APR_POLLERR) {
+                    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, APLOGNO(02607)
+                                  "proxy: tunnel: error on client connection");
+                    c->aborted = 1;
+                    done = 1;
+                }
+                else { 
+                    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02606)
+                            "proxy: tunnel: unknown event %d on client connection",
+                            (int)pollevent);
+                    done = 1;
+                }
+            }
+            else {
+                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02449)
+                              "proxy: tunnel: unknown socket in pollset");
+                done = 1;
+            }
+        }
+    } while (!done);
+
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+                  "proxy: tunnel: finished");
+
+    apr_pollset_remove(pollset, &pollfds[1]);
+    apr_pollset_remove(pollset, &pollfds[0]);
+    apr_pollset_destroy(pollset);
+    apr_brigade_destroy(bb);
+
+    if (rv) {
+        return HTTP_INTERNAL_SERVER_ERROR;
+    }
+    if (!replied) {
+        return HTTP_BAD_GATEWAY;
+    }
+
+    return OK;
+}
+
 /* Fill in unknown schemes from apr_uri_port_of_scheme() */
 
 typedef struct proxy_schemes_t {
Index: server/protocol.c
===================================================================
--- server/protocol.c	(revision 1734109)
+++ server/protocol.c	(working copy)
@@ -1765,23 +1765,25 @@ AP_DECLARE(void) ap_send_interim_response(request_
                       "Status is %d - not sending interim response", r->status);
         return;
     }
-    if ((r->status == HTTP_CONTINUE) && !r->expecting_100) {
-        /*
-         * Don't send 100-Continue when there was no Expect: 100-continue
-         * in the request headers. For origin servers this is a SHOULD NOT
-         * for proxies it is a MUST NOT according to RFC 2616 8.2.3
+    if (r->status == HTTP_CONTINUE) {
+        if (!r->expecting_100) {
+            /*
+             * Don't send 100-Continue when there was no Expect: 100-continue
+             * in the request headers. For origin servers this is a SHOULD NOT
+             * for proxies it is a MUST NOT according to RFC 2616 8.2.3
+             */
+            return;
+        }
+
+        /* if we send an interim response, we're no longer in a state of
+         * expecting one.  Also, this could feasibly be in a subrequest,
+         * so we need to propagate the fact that we responded.
          */
-        return;
+        for (rr = r; rr != NULL; rr = rr->main) {
+            rr->expecting_100 = 0;
+        }
     }
 
-    /* if we send an interim response, we're no longer in a state of
-     * expecting one.  Also, this could feasibly be in a subrequest,
-     * so we need to propagate the fact that we responded.
-     */
-    for (rr = r; rr != NULL; rr = rr->main) {
-        rr->expecting_100 = 0;
-    }
-
     status_line = apr_pstrcat(r->pool, AP_SERVER_PROTOCOL, " ", r->status_line, CRLF, NULL);
     ap_xlate_proto_to_ascii(status_line, strlen(status_line));
 
@@ -1791,7 +1793,9 @@ AP_DECLARE(void) ap_send_interim_response(request_
     ap_fputs(x.f, x.bb, status_line);
     if (send_headers) {
         apr_table_do(send_header, &x, r->headers_out, NULL);
-        apr_table_clear(r->headers_out);
+        if (send_headers != AP_SEND_INTERIM_KEEP_HEADERS) {
+            apr_table_clear(r->headers_out);
+        }
     }
     ap_fputs(x.f, x.bb, CRLF_ASCII);
     ap_fflush(x.f, x.bb);

Reply via email to