Hi,

since Upgrade is an HTTP/1 feature, I don't find it too twisted...

The primary goal would be to let the backend decide whether an Upgrade
is to be done, or otherwise continue with HTTP (still parsing the
response, filtering, caching, ...).

Currently we handle WebSocket tunneling only (in mod_proxy_wstunnel),
but it's dedicated to some configured URL(s), and if the client
chooses to not propose "Upgrade: WebSocket" on that URL(s) the request
is doomed (it falls back to the default_handler since the dedicated
scheme "ws(s)" won't be handled by anything else).

Actually mod_proxy_http has already all the state machine to handle
interim (e.g. 100-continue) and upgrade responses properly, it just
lacks the switch/tunneling to another protocol.

This what the two attached patches aim to.

The first one (mod_proxy-tunnel.patch) is to factorize all the
tunneling code(s) we currently have in proxy_connect and
proxy_wstunnel into proxy_util functions (namely
ap_proxy_tunnel_create(&tunnel, r, backconn) and
ap_proxy_tunnel_pump(tunnel, timeout)), and use them there.

The second one (mod_proxy_http-tunnel.patch) also uses them in
mod_proxy_http, where/when needed (a protocol switch is initiated by
the backend).
This is currently controlled/enabled only with "SetEnv
proxy-forward-upgrade" (we may not want to forward/tunnel Upgrades
unconditionally), it could be a ProxyUpgrade or so directive.

This is not limited to WebSocket forwarding, but it possibly would
obsolete mod_proxy_wstunnel...

WDYT?

Regards,
Yann.
Index: modules/proxy/mod_proxy.h
===================================================================
--- modules/proxy/mod_proxy.h	(revision 1754276)
+++ modules/proxy/mod_proxy.h	(working copy)
@@ -1160,7 +1160,43 @@ PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucke
                                          conn_rec *origin, apr_bucket_brigade *bb,
                                          int flush);
 
+
+typedef struct {
+    request_rec *r;
+    conn_rec *backconn;
+    apr_bucket_brigade *bb_i;
+    apr_bucket_brigade *bb_o;
+    apr_array_header_t *pfds;
+    apr_pollset_t *pollset;
+    int replied;
+} proxy_tunnel_rec;
+
 /**
+ * Create a tunnel context usable in ap_proxy_tunnel_pump().
+ * @param tunnel   tunnel created
+ * @param r        client request
+ * @param backconn backend connection
+ * @return         APR_SUCCESS or error status
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_tunnel_create(proxy_tunnel_rec **tunnel,
+                                                   request_rec *r,
+                                                   conn_rec *backconn);
+
+/**
+ * Pump anything from either side of the tunnel to the other,
+ * until one end aborts or a polling timeout/error occurs.
+ * @param tunnel  tunnel created
+ * @param timeout polling timeout
+ * @return        OK: closed/aborted on one side,
+ *                HTTP_GATEWAY_TIME_OUT: polling timeout,
+ *                HTTP_INTERNAL_SERVER_ERROR: polling error,
+ *                HTTP_BAD_GATEWAY: no response from backend, ever,
+ *                                  so client may expect one still.
+ */
+PROXY_DECLARE(int) ap_proxy_tunnel_pump(proxy_tunnel_rec *tunnel,
+                                        apr_interval_time_t timeout);
+
+/**
  * Clear the headers referenced by the Connection header from the given
  * table, and remove the Connection header.
  * @param r request
Index: modules/proxy/mod_proxy_connect.c
===================================================================
--- modules/proxy/mod_proxy_connect.c	(revision 1754276)
+++ modules/proxy/mod_proxy_connect.c	(working copy)
@@ -156,26 +156,22 @@ static int proxy_connect_handler(request_rec *r, p
     apr_socket_t *sock;
     conn_rec *c = r->connection;
     conn_rec *backconn;
-    int done = 0;
 
-    apr_bucket_brigade *bb_front = apr_brigade_create(p, c->bucket_alloc);
-    apr_bucket_brigade *bb_back;
+    apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
     apr_status_t rv;
     apr_size_t nbytes;
     char buffer[HUGE_STRING_LEN];
     apr_socket_t *client_socket = ap_get_conn_socket(c);
     int failed, rc;
-    apr_pollset_t *pollset;
-    apr_pollfd_t pollfd;
-    const apr_pollfd_t *signalled;
-    apr_int32_t pollcnt, pi;
-    apr_int16_t pollevent;
-    apr_sockaddr_t *nexthop;
 
     apr_uri_t uri;
     const char *connectname;
     apr_port_t connectport = 0;
+    apr_sockaddr_t *nexthop;
 
+    proxy_tunnel_rec *tunnel;
+    apr_interval_time_t timeout = -1, t = -1;
+
     /* is this for us? */
     if (r->method_number != M_CONNECT) {
         ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "declining URL %s", url);
@@ -261,28 +257,6 @@ static int proxy_connect_handler(request_rec *r, p
         }
     }
 
-    /* setup polling for connection */
-    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "setting up poll()");
-
-    if ((rv = apr_pollset_create(&pollset, 2, r->pool, 0)) != APR_SUCCESS) {
-        apr_socket_close(sock);
-        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01020)
-                      "error apr_pollset_create()");
-        return HTTP_INTERNAL_SERVER_ERROR;
-    }
-
-    /* Add client side to the poll */
-    pollfd.p = r->pool;
-    pollfd.desc_type = APR_POLL_SOCKET;
-    pollfd.reqevents = APR_POLLIN | APR_POLLHUP;
-    pollfd.desc.s = client_socket;
-    pollfd.client_data = NULL;
-    apr_pollset_add(pollset, &pollfd);
-
-    /* Add the server side to the poll */
-    pollfd.desc.s = sock;
-    apr_pollset_add(pollset, &pollfd);
-
     /*
      * Step Three: Send the Request
      *
@@ -315,8 +289,6 @@ static int proxy_connect_handler(request_rec *r, p
                    backconn->local_addr->port));
 
 
-    bb_back = apr_brigade_create(p, backconn->bucket_alloc);
-
     /* If we are connecting through a remote proxy, we need to pass
      * the CONNECT request on to it.
      */
@@ -325,11 +297,11 @@ static int proxy_connect_handler(request_rec *r, p
      */
         ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
                       "sending the CONNECT request to the remote proxy");
-        ap_fprintf(backconn->output_filters, bb_back,
+        ap_fprintf(backconn->output_filters, bb,
                    "CONNECT %s HTTP/1.0" CRLF, r->uri);
-        ap_fprintf(backconn->output_filters, bb_back,
+        ap_fprintf(backconn->output_filters, bb,
                    "Proxy-agent: %s" CRLF CRLF, ap_get_server_banner());
-        ap_fflush(backconn->output_filters, bb_back);
+        ap_fflush(backconn->output_filters, bb);
     }
     else {
         ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "Returning 200 OK");
@@ -336,13 +308,13 @@ static int proxy_connect_handler(request_rec *r, p
         nbytes = apr_snprintf(buffer, sizeof(buffer),
                               "HTTP/1.0 200 Connection Established" CRLF);
         ap_xlate_proto_to_ascii(buffer, nbytes);
-        ap_fwrite(c->output_filters, bb_front, buffer, nbytes);
+        ap_fwrite(c->output_filters, bb, buffer, nbytes);
         nbytes = apr_snprintf(buffer, sizeof(buffer),
                               "Proxy-agent: %s" CRLF CRLF,
                               ap_get_server_banner());
         ap_xlate_proto_to_ascii(buffer, nbytes);
-        ap_fwrite(c->output_filters, bb_front, buffer, nbytes);
-        ap_fflush(c->output_filters, bb_front);
+        ap_fwrite(c->output_filters, bb, buffer, nbytes);
+        ap_fflush(c->output_filters, bb);
 #if 0
         /* This is safer code, but it doesn't work yet.  I'm leaving it
          * here so that I can fix it later.
@@ -353,9 +325,8 @@ static int proxy_connect_handler(request_rec *r, p
         ap_rflush(r);
 #endif
     }
+    apr_brigade_cleanup(bb);
 
-    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "setting up poll()");
-
     /*
      * Step Four: Handle Data Transfer
      *
@@ -362,6 +333,14 @@ static int proxy_connect_handler(request_rec *r, p
      * Handle two way transfer of data over the socket (this is a tunnel).
      */
 
+    rv = ap_proxy_tunnel_create(&tunnel, r, backconn);
+    if (rv != APR_SUCCESS) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO()
+                      "can't create tunnel for %pI (%s)",
+                      nexthop, connectname);
+        return HTTP_INTERNAL_SERVER_ERROR;
+    }
+
     /* we are now acting as a tunnel - the input/output filter stacks should
      * not contain any non-connection filters.
      */
@@ -371,75 +350,22 @@ static int proxy_connect_handler(request_rec *r, p
     r->proto_input_filters = c->input_filters;
 /*    r->sent_bodyct = 1;*/
 
-    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;
-            }
-            apr_socket_close(sock);
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01023) "error apr_poll()");
-            return HTTP_INTERNAL_SERVER_ERROR;
-        }
+    apr_socket_timeout_get(sock, &t);
+    apr_socket_timeout_get(client_socket, &timeout);
+    if (timeout < 0 || (t >= 0 && timeout > t)) {
+        timeout = t;
+    }
 
-        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, APLOGNO(01024)
-                      "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_TRACE2, 0, r, APLOGNO(01025)
-                                  "backend was readable");
-                    done |= ap_proxy_transfer_between_connections(r, backconn,
-                                                                  c, bb_back,
-                                                                  bb_front,
-                                                                  "backend", NULL,
-                                                                  CONN_BLKSZ, 1)
-                                                                 != APR_SUCCESS;
-                }
-                else if (pollevent & APR_POLLERR) {
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01026)
-                                  "err on backend connection");
-                    backconn->aborted = 1;
-                    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(01027)
-                                  "client was readable");
-                    done |= ap_proxy_transfer_between_connections(r, c,
-                                                                  backconn,
-                                                                  bb_front,
-                                                                  bb_back,
-                                                                  "client",
-                                                                  NULL,
-                                                                  CONN_BLKSZ, 1)
-                                                                 != APR_SUCCESS;
-                }
-                else if (pollevent & APR_POLLERR) {
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02827)
-                                  "err on client connection");
-                    c->aborted = 1;
-                    done = 1;
-                }
-            }
-            else {
-                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01028)
-                              "unknown socket in pollset");
-                done = 1;
-            }
-
+    rc = ap_proxy_tunnel_pump(tunnel, timeout);
+    if (ap_is_HTTP_ERROR(rc)) {
+        /* Don't send an error page if we sent data already */
+        if (!tunnel->replied) {
+            return rc;
         }
-    } while (!done);
+        /* Custom log may need this, still */
+        r->status = rc;
+    }
 
-    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
-                  "finished with poll() - cleaning up");
-
     /*
      * Step Five: Clean Up
      *
Index: modules/proxy/mod_proxy_wstunnel.c
===================================================================
--- modules/proxy/mod_proxy_wstunnel.c	(revision 1754276)
+++ modules/proxy/mod_proxy_wstunnel.c	(working copy)
@@ -26,141 +26,34 @@ typedef struct {
 } proxyws_dir_conf;
 
 typedef struct ws_baton_t {
-    request_rec *r;
-    proxy_conn_rec *proxy_connrec;
-    apr_socket_t *server_soc;
-    apr_socket_t *client_soc;
-    apr_pollset_t *pollset;
-    apr_bucket_brigade *bb_i;
-    apr_bucket_brigade *bb_o;
-    apr_pool_t *subpool;        /* cleared before each suspend, destroyed when request ends */
-    char *scheme;               /* required to release the proxy connection */
+    proxy_tunnel_rec *tunnel;
+    /* below required to release the proxy connection */
+    proxy_conn_rec *backend;
+    const char *scheme;
 } ws_baton_t;
 
 static void proxy_wstunnel_callback(void *b);
 
-static int proxy_wstunnel_pump(ws_baton_t *baton, apr_time_t timeout, int try_poll) {
-    request_rec *r = baton->r;
-    conn_rec *c = r->connection;
-    proxy_conn_rec *conn = baton->proxy_connrec;
-    apr_socket_t *sock = conn->sock;
-    conn_rec *backconn = conn->connection;
-    const apr_pollfd_t *signalled;
-    apr_int32_t pollcnt, pi;
-    apr_int16_t pollevent;
-    apr_pollset_t *pollset = baton->pollset;
-    apr_socket_t *client_socket = baton->client_soc;
-    apr_status_t rv;
-    apr_bucket_brigade *bb_i = baton->bb_i;
-    apr_bucket_brigade *bb_o = baton->bb_o;
-    int done = 0, replied = 0;
-
-    do { 
-        rv = apr_pollset_poll(pollset, timeout, &pollcnt, &signalled);
-        if (rv != APR_SUCCESS) {
-            if (APR_STATUS_IS_EINTR(rv)) {
-                continue;
-            }
-            else if (APR_STATUS_IS_TIMEUP(rv)) { 
-                if (try_poll) {
-                    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02542) "Attempting to go async");
-                    return SUSPENDED;
-                }
-                else { 
-                    return HTTP_REQUEST_TIME_OUT;
-                }
-            }
-            else { 
-                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_TRACE2, 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_TRACE2, 0, r, APLOGNO(02446)
-                            "backend was readable");
-                    done |= ap_proxy_transfer_between_connections(r, backconn,
-                                                                  c, bb_i, bb_o,
-                                                                  "backend",
-                                                                  &replied,
-                                                                  AP_IOBUFSIZE,
-                                                                  0)
-                                                                 != APR_SUCCESS;
-                }
-                else if (pollevent & APR_POLLERR) {
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02447)
-                            "error on backend connection");
-                    backconn->aborted = 1;
-                    done = 1;
-                }
-                else { 
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 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_TRACE2, 0, r, APLOGNO(02448)
-                            "client was readable");
-                    done |= ap_proxy_transfer_between_connections(r, c, backconn,
-                                                                  bb_o, bb_i,
-                                                                  "client", NULL,
-                                                                  AP_IOBUFSIZE,
-                                                                  0)
-                                                                 != APR_SUCCESS;
-                }
-                else if (pollevent & APR_POLLERR) {
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02607)
-                            "error on client connection");
-                    c->aborted = 1;
-                    done = 1;
-                }
-                else { 
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 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;
+static int proxy_wstunnel_pump(ws_baton_t *baton, apr_time_t timeout, int async)
+{
+    int status = ap_proxy_tunnel_pump(baton->tunnel, timeout);
+    if (async && status == HTTP_GATEWAY_TIME_OUT) {
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, baton->tunnel->r,
+                      APLOGNO(02542) "Attempting to go async");
+        return SUSPENDED;
     }
-    else {
-        return OK;
-    }
+    return status;
 }
 
-static void proxy_wstunnel_finish(ws_baton_t *baton) { 
-    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, baton->r, "proxy_wstunnel_finish");
-    baton->proxy_connrec->close = 1; /* new handshake expected on each back-conn */
-    baton->r->connection->keepalive = AP_CONN_CLOSE;
-    ap_proxy_release_connection(baton->scheme, baton->proxy_connrec, baton->r->server);
-    ap_finalize_request_protocol(baton->r);
-    ap_lingering_close(baton->r->connection);
-    apr_socket_close(baton->client_soc);
-    ap_mpm_resume_suspended(baton->r->connection);
-    ap_process_request_after_handler(baton->r); /* don't touch baton or r after here */
+static void proxy_wstunnel_finish(ws_baton_t *baton)
+{ 
+    request_rec *r = baton->tunnel->r;
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "proxy_wstunnel_finish");
+    ap_proxy_release_connection(baton->scheme, baton->backend, r->server);
+    ap_finalize_request_protocol(r);
+    ap_lingering_close(r->connection);
+    ap_mpm_resume_suspended(r->connection);
+    ap_process_request_after_handler(r); /* don't touch baton or r after here */
 }
 
 /* If neither socket becomes readable in the specified timeout,
@@ -170,7 +63,8 @@ static void proxy_wstunnel_callback(void *b);
 static void proxy_wstunnel_cancel_callback(void *b)
 { 
     ws_baton_t *baton = (ws_baton_t*)b;
-    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, baton->r, "proxy_wstunnel_cancel_callback, IO timed out");
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, baton->tunnel->r,
+                  "proxy_wstunnel_cancel_callback, IO timed out");
     proxy_wstunnel_finish(baton);
     return;
 }
@@ -180,35 +74,22 @@ static void proxy_wstunnel_cancel_callback(void *b
  *  We don't need the invoke_mtx, since we never put multiple callback events
  *  in the queue.
  */
-static void proxy_wstunnel_callback(void *b) { 
+static void proxy_wstunnel_callback(void *b)
+{ 
     int status;
     ws_baton_t *baton = (ws_baton_t*)b;
-    proxyws_dir_conf *dconf = ap_get_module_config(baton->r->per_dir_config, &proxy_wstunnel_module);
-    apr_pool_clear(baton->subpool);
+    request_rec *r = baton->tunnel->r;
+    proxyws_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+                                                   &proxy_wstunnel_module);
     status = proxy_wstunnel_pump(baton, dconf->async_delay, dconf->mpm_can_poll);
     if (status == SUSPENDED) {
-        apr_pollfd_t *pfd;
-
-        apr_array_header_t *pfds = apr_array_make(baton->subpool, 2, sizeof(apr_pollfd_t));
-
-        pfd = apr_array_push(pfds);
-        pfd->desc_type = APR_POLL_SOCKET;
-        pfd->reqevents = APR_POLLIN | APR_POLLERR | APR_POLLHUP;
-        pfd->desc.s = baton->client_soc;
-        pfd->p = baton->subpool;
-
-        pfd = apr_array_push(pfds);
-        pfd->desc_type = APR_POLL_SOCKET;
-        pfd->reqevents = APR_POLLIN | APR_POLLERR | APR_POLLHUP;
-        pfd->desc.s = baton->server_soc;
-        pfd->p = baton->subpool;
-
-        ap_mpm_register_poll_callback_timeout(pfds,
+        ap_mpm_register_poll_callback_timeout(baton->tunnel->pfds,
             proxy_wstunnel_callback, 
             proxy_wstunnel_cancel_callback, 
             baton, 
             dconf->idle_timeout);
-        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, baton->r, "proxy_wstunnel_callback suspend");
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+                      "proxy_wstunnel_callback suspend");
     }
     else { 
         proxy_wstunnel_finish(baton);
@@ -298,10 +179,7 @@ static int proxy_wstunnel_request(apr_pool_t *p, r
                                 char *url, char *server_portstr, char *scheme)
 {
     apr_status_t rv;
-    apr_pollset_t *pollset;
-    apr_pollfd_t pollfd;
     conn_rec *c = r->connection;
-    apr_socket_t *sock = conn->sock;
     conn_rec *backconn = conn->connection;
     char *buf;
     apr_bucket_brigade *header_brigade;
@@ -308,8 +186,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);
     ws_baton_t *baton = apr_pcalloc(r->pool, sizeof(ws_baton_t));
     int status;
     proxyws_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &proxy_wstunnel_module);
@@ -336,31 +212,6 @@ static int proxy_wstunnel_request(apr_pool_t *p, r
 
     apr_brigade_cleanup(header_brigade);
 
-    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;
@@ -368,44 +219,27 @@ static int proxy_wstunnel_request(apr_pool_t *p, r
     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. */
+    /* This handler should take care of the entire connections; make it so that
+     * nothing else is attempted on the connections after returning. */
     c->keepalive = AP_CONN_CLOSE;
+    conn->close = 1;
 
-    baton->r = r;
-    baton->pollset = pollset;
-    baton->client_soc = client_socket;
-    baton->server_soc = sock;
-    baton->proxy_connrec = conn;
-    baton->bb_o = bb;
-    baton->bb_i = header_brigade;
+    baton->backend = conn;
     baton->scheme = scheme;
-    apr_pool_create(&baton->subpool, r->pool);
+    rv = ap_proxy_tunnel_create(&baton->tunnel, r, conn->connection);
+    if (rv != APR_SUCCESS) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02443)
+                      "error creating websockets tunnel");
+        return HTTP_INTERNAL_SERVER_ERROR;
+    }
 
     if (!dconf->mpm_can_poll) {
-        status = proxy_wstunnel_pump(baton, dconf->idle_timeout, dconf->mpm_can_poll);
+        status = proxy_wstunnel_pump(baton, dconf->idle_timeout, 0);
     }  
     else { 
-        status = proxy_wstunnel_pump(baton, dconf->async_delay, dconf->mpm_can_poll);
-        apr_pool_clear(baton->subpool);
+        status = proxy_wstunnel_pump(baton, dconf->async_delay, 1);
         if (status == SUSPENDED) {
-            apr_pollfd_t *pfd;
-
-            apr_array_header_t *pfds = apr_array_make(baton->subpool, 2, sizeof(apr_pollfd_t));
-
-            pfd = apr_array_push(pfds);
-            pfd->desc_type = APR_POLL_SOCKET;
-            pfd->reqevents = APR_POLLIN | APR_POLLERR | APR_POLLHUP;
-            pfd->desc.s = baton->client_soc;
-            pfd->p = baton->subpool;
-
-            pfd = apr_array_push(pfds);
-            pfd->desc_type = APR_POLL_SOCKET;
-            pfd->reqevents = APR_POLLIN | APR_POLLERR | APR_POLLHUP;
-            pfd->desc.s = baton->server_soc;
-            pfd->p = baton->subpool;
-
-            rv = ap_mpm_register_poll_callback_timeout(pfds,
+            rv = ap_mpm_register_poll_callback_timeout(baton->tunnel->pfds,
                          proxy_wstunnel_callback, 
                          proxy_wstunnel_cancel_callback, 
                          baton, 
@@ -418,23 +252,24 @@ static int proxy_wstunnel_request(apr_pool_t *p, r
                 status = proxy_wstunnel_pump(baton, dconf->idle_timeout, 0); /* force no async */
             }
             else { 
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
-                              APLOGNO(02543) "error creating websockets tunnel");
-                return HTTP_INTERNAL_SERVER_ERROR;
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02543)
+                              "error registering websockets tunnel");
+                status = HTTP_INTERNAL_SERVER_ERROR;
             }
         }
     }
 
-    if (status != OK) { 
-        /* Avoid sending error pages down an upgraded connection */
-        if (status != HTTP_REQUEST_TIME_OUT) {
-            r->status = status;
+    if (ap_is_HTTP_ERROR(status)) {
+        /* Don't send an error page down an upgraded connection */
+        if (!baton->tunnel->replied) {
+            return status;
         }
-        status = OK;
+        /* Custom log may need this, still */
+        r->status = status;
     }
-    return status;
+    return OK;
 }    
-    
+
 /*
  */
 static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker,
@@ -466,9 +301,12 @@ static int proxy_wstunnel_handler(request_rec *r,
 
     upgrade = apr_table_get(r->headers_in, "Upgrade");
     if (!upgrade || ap_cstr_casecmp(upgrade, "WebSocket") != 0) {
-        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02900)
-                      "declining URL %s  (not WebSocket)", url);
-        return DECLINED;
+        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02900)
+                      "URL %s is not WebSocket: sending upgrade required",
+                      url);
+        apr_table_setn(r->err_headers_out, "Connection", "Upgrade");
+        apr_table_setn(r->err_headers_out, "Upgrade", "WebSocket");
+        return HTTP_UPGRADE_REQUIRED;
     }
 
     uri = apr_palloc(p, sizeof(*uri));
Index: modules/proxy/proxy_util.c
===================================================================
--- modules/proxy/proxy_util.c	(revision 1754276)
+++ modules/proxy/proxy_util.c	(working copy)
@@ -3834,6 +3826,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_transfer_betw
                             APR_NONBLOCK_READ, bsize);
         if (rv == APR_SUCCESS) {
             if (c_o->aborted) {
+                apr_brigade_cleanup(bb_i);
                 return APR_EPIPE;
             }
             if (APR_BRIGADE_EMPTY(bb_i)) {
@@ -3874,7 +3867,9 @@ PROXY_DECLARE(apr_status_t) ap_proxy_transfer_betw
                               "error on %s - ap_pass_brigade",
                               name);
             }
-        } else if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) {
+            apr_brigade_cleanup(bb_o);
+        }
+        else if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) {
             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03308)
                           "ap_proxy_transfer_between_connections: "
                           "error on %s - ap_get_brigade",
@@ -3884,7 +3879,9 @@ PROXY_DECLARE(apr_status_t) ap_proxy_transfer_betw
 
     if (after) {
         ap_fflush(c_o->output_filters, bb_o);
+        apr_brigade_cleanup(bb_o);
     }
+    apr_brigade_cleanup(bb_i);
 
     ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r,
                   "ap_proxy_transfer_between_connections complete");
@@ -3896,6 +3893,169 @@ PROXY_DECLARE(apr_status_t) ap_proxy_transfer_betw
     return rv;
 }
 
+PROXY_DECLARE(apr_status_t) ap_proxy_tunnel_create(proxy_tunnel_rec **tunnel,
+                                                   request_rec *r,
+                                                   conn_rec *backconn)
+{
+    apr_status_t rv;
+    apr_pollfd_t *pfds;
+    conn_rec *c = r->connection;
+
+    (*tunnel) = apr_pcalloc(r->pool, sizeof **tunnel);
+
+    (*tunnel)->r = r;
+    (*tunnel)->backconn = backconn;
+
+    (*tunnel)->bb_i = apr_brigade_create(r->pool,
+                                         c->bucket_alloc);
+    (*tunnel)->bb_o = apr_brigade_create(backconn->pool,
+                                         backconn->bucket_alloc);
+
+    rv = apr_pollset_create(&(*tunnel)->pollset, 2, r->pool,
+                            APR_POLLSET_NOCOPY);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+    
+    (*tunnel)->pfds = apr_array_make(r->pool, 2, sizeof(apr_pollfd_t));
+    apr_array_push((*tunnel)->pfds); /* pfds[0] */
+    apr_array_push((*tunnel)->pfds); /* pfds[1] */
+
+    pfds = &APR_ARRAY_IDX((*tunnel)->pfds, 0, apr_pollfd_t);
+    pfds[0].desc_type = pfds[1].desc_type = APR_POLL_SOCKET;
+    pfds[0].reqevents = pfds[1].reqevents = APR_POLLIN | APR_POLLHUP;
+    pfds[0].p = pfds[1].p = r->pool;
+    pfds[0].desc.s = ap_get_conn_socket(c);
+    pfds[1].desc.s = ap_get_conn_socket(backconn);
+
+    return APR_SUCCESS;
+}
+
+PROXY_DECLARE(apr_status_t) ap_proxy_tunnel_pump(proxy_tunnel_rec *tunnel,
+                                                 apr_interval_time_t timeout)
+{
+    apr_status_t rv;
+    request_rec *r = tunnel->r;
+    conn_rec *c = r->connection;
+    conn_rec *backconn = tunnel->backconn;
+    apr_socket_t *sock_c = ap_get_conn_socket(c);
+    apr_socket_t *sock_b = ap_get_conn_socket(backconn);
+    apr_pollfd_t *pfds = &APR_ARRAY_IDX(tunnel->pfds, 0, apr_pollfd_t);
+    apr_pollset_t *pollset = tunnel->pollset;
+    const apr_pollfd_t *signalled;
+    apr_int32_t pollcnt, pi;
+    apr_int16_t pollevent;
+    int done = 0;
+
+    AP_DEBUG_ASSERT(tunnel->pfds->nelts == 2);
+    AP_DEBUG_ASSERT(pfds[0].desc.s == sock_c);
+    AP_DEBUG_ASSERT(pfds[1].desc.s == sock_b);
+    apr_pollset_add(pollset, &pfds[0]);
+    apr_pollset_add(pollset, &pfds[1]);
+
+    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(tunnel->pollset, timeout, &pollcnt, &signalled);
+        if (rv != APR_SUCCESS) {
+            if (APR_STATUS_IS_EINTR(rv)) {
+                continue;
+            }
+
+            apr_pollset_remove(pollset, &pfds[1]);
+            apr_pollset_remove(pollset, &pfds[0]);
+
+            if (APR_STATUS_IS_TIMEUP(rv)) {
+                return HTTP_GATEWAY_TIME_OUT;
+            }
+
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02444)
+                          "proxy: tunnel: polling failed");
+            return HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, APLOGNO(02445)
+                      "proxy: tunnel: woken up, i=%d", (int)pollcnt);
+
+        for (pi = 0; pi < pollcnt; pi++) {
+            const apr_pollfd_t *cur = &signalled[pi];
+
+            if (cur->desc.s == sock_b) {
+                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");
+                    rv = ap_proxy_transfer_between_connections(r, backconn, c,
+                                                               tunnel->bb_o,
+                                                               tunnel->bb_i,
+                                                               "backend",
+                                                               &tunnel->replied,
+                                                               AP_IOBUFSIZE,
+                                                               0);
+                    done |= (rv != APR_SUCCESS);
+                }
+                else if (pollevent & APR_POLLERR) {
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02447)
+                            "proxy: tunnel: error on backend connection");
+                    backconn->aborted = 1;
+                    done = 1;
+                }
+                else { 
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02605)
+                            "proxy: tunnel: unknown event %d on backend connection",
+                            (int)pollevent);
+                    done = 1;
+                }
+            }
+            else if (cur->desc.s == sock_c) {
+                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");
+                    rv = ap_proxy_transfer_between_connections(r, c, backconn,
+                                                               tunnel->bb_i,
+                                                               tunnel->bb_o,
+                                                               "client", NULL,
+                                                               AP_IOBUFSIZE,
+                                                               0);
+                    done |= (rv != APR_SUCCESS);
+                }
+                else if (pollevent & APR_POLLERR) {
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02607)
+                                  "proxy: tunnel: error on client connection");
+                    c->aborted = 1;
+                    done = 1;
+                }
+                else { 
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02606)
+                            "proxy: tunnel: unknown event %d on client connection",
+                            (int)pollevent);
+                    done = 1;
+                }
+            }
+            else {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 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, &pfds[1]);
+    apr_pollset_remove(pollset, &pfds[0]);
+
+    if (!tunnel->replied) {
+        return HTTP_BAD_GATEWAY;
+    }
+
+    return OK;
+}
+
 PROXY_DECLARE (const char *) ap_proxy_show_hcmethod(hcmethod_t method)
 {
     proxy_hcmethods_t *m = proxy_hcmethods;
Index: include/http_protocol.h
===================================================================
--- include/http_protocol.h	(revision 1754276)
+++ include/http_protocol.h	(working copy)
@@ -951,10 +951,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_http.c
===================================================================
--- modules/proxy/mod_proxy_http.c	(revision 1754276)
+++ modules/proxy/mod_proxy_http.c	(working copy)
@@ -694,6 +694,7 @@ static int ap_proxy_http_prefetch(apr_pool_t *p, r
     apr_bucket *e;
     char *buf;
     apr_status_t status;
+    const char *upgrade = NULL;
     apr_off_t bytes_read = 0;
     apr_off_t bytes;
     int force10, rv;
@@ -705,6 +706,9 @@ static int ap_proxy_http_prefetch(apr_pool_t *p, r
         }
         force10 = 1;
     } else {
+        if (apr_table_get(r->subprocess_env, "proxy-forward-upgrade")) {
+            upgrade = apr_table_get(r->headers_in, "Upgrade");
+        }
         force10 = 0;
     }
 
@@ -926,6 +930,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);
         }
@@ -1264,7 +1272,6 @@ int ap_proxy_http_process_response(apr_pool_t * p,
     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;
@@ -1307,6 +1314,7 @@ int ap_proxy_http_process_response(apr_pool_t * p,
     apr_table_setn(r->notes, "proxy-source-port", apr_psprintf(r->pool, "%hu",
                    origin->local_addr->port));
     do {
+        const char *te = NULL, *upgrade = NULL;
         apr_status_t rc;
 
         apr_brigade_cleanup(bb);
@@ -1491,6 +1499,11 @@ int ap_proxy_http_process_response(apr_pool_t * p,
              */
             te = apr_table_get(r->headers_out, "Transfer-Encoding");
 
+            /* Likewise for Upgrade header, if relevant */
+            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) {
@@ -1589,10 +1602,27 @@ int ap_proxy_http_process_response(apr_pool_t * p,
                                                "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)))) {
-                ap_send_interim_response(r, 1);
+            if (!policy || !strcasecmp(policy, "RFC")) {
+                int clear_headers = 1;
+                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.
+                     */
+                    clear_headers = AP_SEND_INTERIM_KEEP_HEADERS;
+                }
+                ap_send_interim_response(r, clear_headers);
             }
+            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
              */
@@ -1617,31 +1647,6 @@ int ap_proxy_http_process_response(apr_pool_t * p,
             }
         }
 
-        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
@@ -1676,6 +1681,82 @@ int ap_proxy_http_process_response(apr_pool_t * p,
             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) {
+            int status;
+            apr_status_t rv;
+            proxy_tunnel_rec *tunnel;
+            apr_interval_time_t timeout = -1, t = -1;
+
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO()
+                          "proxy: tunneling protocol %s", upgrade);
+
+            if (!APR_BRIGADE_EMPTY(pass_bb)) {
+                ap_fflush(c->output_filters, pass_bb);
+                apr_brigade_cleanup(pass_bb);
+            }
+
+            rv = ap_proxy_tunnel_create(&tunnel, r, backend->connection);
+            if (rv != APR_SUCCESS) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO()
+                              "proxy: can't create tunnel for %s", upgrade);
+                return HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            /* A tunnel takes care of the entire connections, at the connection
+             * level, plus make it so that nothing else is attempted on them
+             * after.
+             */
+            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;
+            c->keepalive = AP_CONN_CLOSE;
+            backend->close = 1;
+
+            apr_socket_timeout_get(backend->sock, &t);
+            apr_socket_timeout_get(ap_get_conn_socket(c), &timeout);
+            if (timeout < 0 || (t >= 0 && timeout > t)) {
+                timeout = t;
+            }
+
+            status = ap_proxy_tunnel_pump(tunnel, timeout);
+            if (ap_is_HTTP_ERROR(status)) {
+                /* Don't send an error page down an upgraded connection */
+                if (!tunnel->replied) {
+                    return status;
+                }
+                /* Custom log may need this, still */
+                r->status = status;
+            }
+            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/proxy_util.c
===================================================================
--- modules/proxy/proxy_util.c	(revision 1754276)
+++ modules/proxy/proxy_util.c	(working copy)
@@ -3560,14 +3560,6 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_poo
     if (do_100_continue) {
         const char *val;
 
-        if (!r->expecting_100) {
-            /* Don't forward any "100 Continue" response if the client is
-             * not expecting it.
-             */
-            apr_table_setn(r->subprocess_env, "proxy-interim-response",
-                                              "Suppress");
-        }
-
         /* Add the Expect header if not already there. */
         if (((val = apr_table_get(r->headers_in, "Expect")) == NULL)
                 || (ap_cstr_casecmp(val, "100-Continue") != 0 /* fast path */
Index: server/protocol.c
===================================================================
--- server/protocol.c	(revision 1754276)
+++ server/protocol.c	(working copy)
@@ -1923,7 +1923,6 @@ AP_DECLARE(void) ap_send_interim_response(request_
 {
     hdr_ptr x;
     char *status_line = NULL;
-    request_rec *rr;
 
     if (r->proto_num < HTTP_VERSION(1,1)) {
         /* don't send interim response to HTTP/1.0 Client */
@@ -1934,23 +1933,27 @@ 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) {
+        request_rec *rr;
+
+        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));
 
@@ -1960,7 +1963,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