]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
several related mod_proxy_wstunnel changes that are tough to pull apart:
authorEric Covener <covener@apache.org = covener = Eric Covener covener@apache.org@apache.org>
Sun, 13 Apr 2014 18:41:05 +0000 (18:41 +0000)
committerEric Covener <covener@apache.org = covener = Eric Covener covener@apache.org@apache.org>
Sun, 13 Apr 2014 18:41:05 +0000 (18:41 +0000)
* make async websockets tunnel opt-in
* add config for how long we block a thread in asynch mode
* add config for a cap on the synchronous path
* avoid sending error responses down the upgraded tunnel

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1587075 13f79535-47bb-0310-9956-ffa450edef68

CHANGES
docs/manual/mod/mod_proxy_wstunnel.xml
modules/proxy/mod_proxy_wstunnel.c

diff --git a/CHANGES b/CHANGES
index 75c14ce94e4fdf06f69f7ed9f08e7414370adb7c..f82dc405419c7e00b7610cd24cc79cd0fdc46779 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,17 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache 2.5.0
 
+  *) mod_proxy_wstunnel: Avoid sending error responses down an upgraded
+     websockets connection as it is being close down. [Eric Covener]
+  
+  *) mod_proxy_wstunnel: Allow the administrator to cap the amount
+     of time a synchronous websockets connection stays idle with 
+     ProxyWebsocketIdleTimeout. [Eric Covener]
+
+  *) mod_proxy_wstunnel: Change to opt-in for asynchronous support, adding 
+     directives ProxyWebsocketAsynch and ProxyWebsocketAsynchDelay. 
+     [Eric Covener]
+
   *) mod_proxy_wstunnel: Stop leaking websockets backend connections under
      event MPMi (trunk-only). [Eric Covener]
 
index 14ace6cd75cc880e3abdea8984d2ae51445392b7..fcc7c00e26d07f81d42438559e97c4a53dbc58dd 100644 (file)
@@ -52,4 +52,50 @@ ProxyPass /wss2/ wss://echo.websocket.org/
 </summary>
 
 <seealso><module>mod_proxy</module></seealso>
+
+<directivesynopsis>
+<name>ProxyWebsocketAsynch</name>
+<description>Instructs this module to try to create an asynchronous tunnel</description>
+<syntax>ProxyWebsocketAsynch ON|OFF</syntax>
+<contextlist><context>server config</context>
+<context>virtual host</context>
+</contextlist>
+
+<usage>
+    <p>This directive instructs the server to try to create an asynchronous tunnel. 
+    If the current MPM does not support the necessary features, a synchronous 
+    tunnel is used.</p>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>ProxyWebsocketIdleTimeout</name>
+<description>Sets the maximum amount of time to wait for data on the websockets tunnel</description>
+<syntax>ProxyWebsocketIdleTimeout <var>num</var>[ms]</syntax>
+<default>ProxyWebsocketIdleTimeout 0</default>
+<contextlist><context>server config</context>
+<context>virtual host</context>
+</contextlist>
+
+<usage>
+    <p>This directive imposes a maximum amount of time for the tunnel to be 
+    left open while idle.  This directive is ignored if <directive>ProxyWebsocketAsynch</directive> 
+    is enabled and the running MPM supports the necessary features</p>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>ProxyWebsocketAsynchDelay</name>
+<description>Sets the amount of time the tunnel waits synchronously for data</description>
+<syntax>ProxyWebsocketAsynchDelay <var>num</var>[ms]</syntax>
+<default>ProxyWebsocketAsynchDelay 0</default>
+<contextlist><context>server config</context>
+<context>virtual host</context>
+</contextlist>
+
+<usage>
+    <p>If <directive>ProxyWebsocketAsynch</directive> is enabled, this directive 
+    controls how long the server synchronously waits for more data.</p>
+</usage>
+</directivesynopsis>
 </modulesynopsis>
index d5e42b97fdcb09c978bc36aa94dda2676eecd78b..984e9dc0df143d0398a487687e2d1df7565b2f9a 100644 (file)
 
 module AP_MODULE_DECLARE_DATA proxy_wstunnel_module;
 
+typedef struct {
+    signed char is_async;
+    apr_time_t idle_timeout;
+    apr_time_t async_delay;
+} proxyws_dir_conf;
+
 typedef struct ws_baton_t {
     request_rec *r;
     proxy_conn_rec *proxy_connrec;
@@ -34,7 +40,7 @@ typedef struct ws_baton_t {
 static int proxy_wstunnel_transfer(request_rec *r, conn_rec *c_i, conn_rec *c_o,
                                      apr_bucket_brigade *bb, char *name);
 
-static int proxy_wstunnel_pump(ws_baton_t *baton, apr_time_t timeout) {
+static int proxy_wstunnel_pump(ws_baton_t *baton, apr_time_t timeout, int try_async) {
     request_rec *r = baton->r;
     conn_rec *c = r->connection;
     proxy_conn_rec *conn = baton->proxy_connrec;
@@ -49,14 +55,20 @@ static int proxy_wstunnel_pump(ws_baton_t *baton, apr_time_t timeout) {
     apr_bucket_brigade *bb = baton->bb;
 
     while(1) { 
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "poll timeout is %"APR_TIME_T_FMT"ms %s", apr_time_as_msec(timeout), try_async ? "async" : "sync");
         if ((rv = apr_pollset_poll(pollset, timeout, &pollcnt, &signalled))
                 != APR_SUCCESS) {
             if (APR_STATUS_IS_EINTR(rv)) {
                 continue;
             }
             else if (APR_STATUS_IS_TIMEUP(rv)) { 
-                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02542) "Attempting to go asynch");
-                return SUSPENDED;
+                if (try_async) { 
+                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02542) "Attempting to go asynch");
+                    return SUSPENDED;
+                }
+                else { 
+                    return HTTP_REQUEST_TIME_OUT;
+                }
             }
             else { 
                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02444) "error apr_poll()");
@@ -128,10 +140,12 @@ static int proxy_wstunnel_pump(ws_baton_t *baton, apr_time_t timeout) {
 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_socket_t *sockets[3] = {NULL, NULL, NULL};
     apr_thread_mutex_lock(baton->r->invoke_mtx);
     apr_pool_clear(baton->subpool);
-    status = proxy_wstunnel_pump(baton, apr_time_from_sec(5));
+    status = proxy_wstunnel_pump(baton, dconf->async_delay, dconf->is_async);
     sockets[0] = baton->client_soc;
     sockets[1] = baton->server_soc;
     if (status == SUSPENDED) {
@@ -317,6 +331,7 @@ static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r,
     ws_baton_t *baton = apr_pcalloc(r->pool, sizeof(ws_baton_t));
     apr_socket_t *sockets[3] = {NULL, NULL, NULL};
     int status;
+    proxyws_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &proxy_wstunnel_module);
 
     header_brigade = apr_brigade_create(p, backconn->bucket_alloc);
 
@@ -374,7 +389,6 @@ static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r,
      * nothing else is attempted on the connection after returning. */
     c->keepalive = AP_CONN_CLOSE;
 
-
     baton->r = r;
     baton->pollset = pollset;
     baton->client_soc = client_socket;
@@ -384,25 +398,37 @@ static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r,
     baton->scheme = scheme;
     apr_pool_create(&baton->subpool, r->pool);
 
-    status = proxy_wstunnel_pump(baton, apr_time_from_sec(5)); 
-    if (status == SUSPENDED) {
-        sockets[0] = baton->client_soc;
-        sockets[1] = baton->server_soc;
-        status = ap_mpm_register_socket_callback(sockets, baton->subpool, 1, proxy_wstunnel_callback, baton);
-        if (status == APR_SUCCESS) { 
-            return SUSPENDED;
-        }
-        else if (status == APR_ENOTIMPL) { 
-            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02544) "No asynch support");
-            status = proxy_wstunnel_pump(baton, -1);
-        }
-        else { 
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
-                          APLOGNO(02543) "error creating websockets tunnel");
-            return HTTP_INTERNAL_SERVER_ERROR;
+    if (!dconf->is_async) { 
+        status = proxy_wstunnel_pump(baton, dconf->idle_timeout, dconf->is_async);
+    }  
+    else { 
+        status = proxy_wstunnel_pump(baton, dconf->async_delay, dconf->is_async); 
+        if (status == SUSPENDED) {
+            sockets[0] = baton->client_soc;
+            sockets[1] = baton->server_soc;
+            status = ap_mpm_register_socket_callback(sockets, baton->subpool, 1, proxy_wstunnel_callback, baton);
+            if (status == APR_SUCCESS) { 
+                return SUSPENDED;
+            }
+            else if (status == APR_ENOTIMPL) { 
+                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02544) "No asynch support");
+                status = proxy_wstunnel_pump(baton, dconf->idle_timeout, 0); /* force no async */
+            }
+            else { 
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
+                              APLOGNO(02543) "error creating websockets tunnel");
+                return HTTP_INTERNAL_SERVER_ERROR;
+            }
         }
     }
 
+    if (status != OK) { 
+        /* Avoid sending error pages down an upgraded connection */
+        if (status != HTTP_REQUEST_TIME_OUT) {
+            r->status = status;
+        }
+        status = OK;
+    }
     return status;
 }    
     
@@ -490,6 +516,45 @@ static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker,
     return status;
 }
 
+static void *create_proxyws_dir_config(apr_pool_t *p, char *dummy)
+{
+    proxyws_dir_conf *new =
+        (proxyws_dir_conf *) apr_pcalloc(p, sizeof(proxyws_dir_conf));
+
+    new->idle_timeout = -1; /* no timeout */
+
+    return (void *) new;
+}
+
+static const char * proxyws_set_idle(cmd_parms *cmd, void *conf, const char *val)
+{
+    proxyws_dir_conf *dconf = conf;
+    if (ap_timeout_parameter_parse(val, &(dconf->idle_timeout), "s") != APR_SUCCESS)
+        return "ProxyWebsocketIdleTimeout timeout has wrong format";
+    return NULL;
+}
+static const char * proxyws_set_aysnch_delay(cmd_parms *cmd, void *conf, const char *val)
+{
+    proxyws_dir_conf *dconf = conf;
+    if (ap_timeout_parameter_parse(val, &(dconf->async_delay), "s") != APR_SUCCESS)
+        return "ProxyWebsocketAsynchDelay timeout has wrong format";
+    return NULL;
+}
+
+static const command_rec ws_proxy_cmds[] =
+{
+    AP_INIT_FLAG("ProxyWebsocketAsynch", ap_set_flag_slot_char, (void*)APR_OFFSETOF(proxyws_dir_conf, is_async), 
+                 RSRC_CONF|ACCESS_CONF,
+                 "on if idle websockets connections should be monitored asynchronously"),
+
+    AP_INIT_TAKE1("ProxyWebsocketIdleTimeout", proxyws_set_idle, NULL, RSRC_CONF|ACCESS_CONF,
+                 "timeout for activity in either direction, unlimited by default. Not currently supported with ProxyWebsocketAsynch"),
+
+    AP_INIT_TAKE1("ProxyWebsocketAsynchDelay", proxyws_set_aysnch_delay, NULL, RSRC_CONF|ACCESS_CONF,
+                 "amount of time to poll before going asynchronous"),
+    {NULL}
+};
+
 static void ap_proxy_http_register_hook(apr_pool_t *p)
 {
     proxy_hook_scheme_handler(proxy_wstunnel_handler, NULL, NULL, APR_HOOK_FIRST);
@@ -498,10 +563,10 @@ static void ap_proxy_http_register_hook(apr_pool_t *p)
 
 AP_DECLARE_MODULE(proxy_wstunnel) = {
     STANDARD20_MODULE_STUFF,
-    NULL,                       /* create per-directory config structure */
+    create_proxyws_dir_config,  /* 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 */
+    ws_proxy_cmds,              /* command apr_table_t */
     ap_proxy_http_register_hook /* register hooks */
 };