You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2023/06/20 12:01:09 UTC

svn commit: r1910507 [1/2] - in /httpd/httpd/trunk: ./ .github/workflows/ changes-entries/ docs/manual/mod/ include/ modules/http2/ modules/proxy/ server/ test/ test/clients/ test/modules/http2/ test/pyhttpd/

Author: icing
Date: Tue Jun 20 12:01:09 2023
New Revision: 1910507

URL: http://svn.apache.org/viewvc?rev=1910507&view=rev
Log:
  *) mod_http2: added support for bootstrapping WebSockets via HTTP/2, as
     described in RFC 8441. A new directive 'H2WebSockets on|off' has been
     added. The feature is by default not enabled.
     As also discussed in the manual, this feature should work for setups
     using "ProxyPass backend-url upgrade=websocket" without further changes.
     Special server modules for WebSockets will have to be adapted,
     most likely, as the handling if IO events is different with HTTP/2.
     HTTP/2 WebSockets are supported on platforms with native pipes. This
     excludes Windows.


Added:
    httpd/httpd/trunk/changes-entries/h2_websockets.txt
    httpd/httpd/trunk/modules/http2/h2_ws.c
    httpd/httpd/trunk/modules/http2/h2_ws.h
    httpd/httpd/trunk/test/clients/   (with props)
    httpd/httpd/trunk/test/clients/.gitignore
    httpd/httpd/trunk/test/clients/Makefile.in
    httpd/httpd/trunk/test/clients/h2ws.c
    httpd/httpd/trunk/test/modules/http2/test_800_websockets.py
    httpd/httpd/trunk/test/modules/http2/ws_server.py
    httpd/httpd/trunk/test/pyhttpd/ws_util.py
Modified:
    httpd/httpd/trunk/.github/workflows/linux.yml
    httpd/httpd/trunk/CMakeLists.txt
    httpd/httpd/trunk/configure.in
    httpd/httpd/trunk/docs/manual/mod/mod_http2.xml
    httpd/httpd/trunk/include/ap_mmn.h
    httpd/httpd/trunk/include/http_core.h
    httpd/httpd/trunk/modules/http2/config2.m4
    httpd/httpd/trunk/modules/http2/h2.h
    httpd/httpd/trunk/modules/http2/h2_bucket_beam.c
    httpd/httpd/trunk/modules/http2/h2_bucket_beam.h
    httpd/httpd/trunk/modules/http2/h2_c1_io.c
    httpd/httpd/trunk/modules/http2/h2_c2.c
    httpd/httpd/trunk/modules/http2/h2_c2_filter.c
    httpd/httpd/trunk/modules/http2/h2_config.c
    httpd/httpd/trunk/modules/http2/h2_config.h
    httpd/httpd/trunk/modules/http2/h2_conn_ctx.h
    httpd/httpd/trunk/modules/http2/h2_mplx.c
    httpd/httpd/trunk/modules/http2/h2_proxy_util.c
    httpd/httpd/trunk/modules/http2/h2_push.c
    httpd/httpd/trunk/modules/http2/h2_request.c
    httpd/httpd/trunk/modules/http2/h2_session.c
    httpd/httpd/trunk/modules/http2/h2_stream.c
    httpd/httpd/trunk/modules/http2/h2_util.c
    httpd/httpd/trunk/modules/http2/h2_util.h
    httpd/httpd/trunk/modules/http2/h2_version.h
    httpd/httpd/trunk/modules/http2/mod_http2.c
    httpd/httpd/trunk/modules/http2/mod_http2.dsp
    httpd/httpd/trunk/modules/proxy/proxy_util.c
    httpd/httpd/trunk/server/core.c
    httpd/httpd/trunk/test/pyhttpd/config.ini.in
    httpd/httpd/trunk/test/pyhttpd/env.py
    httpd/httpd/trunk/test/travis_run_linux.sh

Modified: httpd/httpd/trunk/.github/workflows/linux.yml
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/.github/workflows/linux.yml?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/.github/workflows/linux.yml (original)
+++ httpd/httpd/trunk/.github/workflows/linux.yml Tue Jun 20 12:01:09 2023
@@ -197,7 +197,7 @@ jobs:
           # -------------------------------------------------------------------------
           - name: HTTP/2 test suite
             config: --enable-mods-shared=reallyall --with-mpm=event --enable-mpms-shared=all
-            pkgs: curl python3-pytest nghttp2-client python3-cryptography python3-requests python3-multipart
+            pkgs: curl python3-pytest nghttp2-client python3-cryptography python3-requests python3-multipart python3-filelock python3-websockets
             env: |
               APR_VERSION=1.7.4
               APU_VERSION=1.6.3
@@ -228,7 +228,7 @@ jobs:
           ### TODO: fix caching here.
           - name: MOD_TLS test suite
             config: --enable-mods-shared=reallyall --with-mpm=event --enable-mpms-shared=event
-            pkgs: curl python3-pytest nghttp2-client python3-cryptography python3-requests python3-multipart cargo cbindgen
+            pkgs: curl python3-pytest nghttp2-client python3-cryptography python3-requests python3-multipart python3-filelock python3-websockets cargo cbindgen
             env: |
               APR_VERSION=1.7.4
               APU_VERSION=1.6.3

Modified: httpd/httpd/trunk/CMakeLists.txt
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/CMakeLists.txt?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/CMakeLists.txt (original)
+++ httpd/httpd/trunk/CMakeLists.txt Tue Jun 20 12:01:09 2023
@@ -497,6 +497,7 @@ SET(mod_http2_extra_sources
   modules/http2/h2_request.c         modules/http2/h2_session.c
   modules/http2/h2_stream.c          modules/http2/h2_switch.c
   modules/http2/h2_util.c            modules/http2/h2_workers.c
+  modules/http2/h2_ws.c
 )
 SET(mod_ldap_extra_defines           LDAP_DECLARE_EXPORT)
 SET(mod_ldap_extra_libs              wldap32)

Added: httpd/httpd/trunk/changes-entries/h2_websockets.txt
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/changes-entries/h2_websockets.txt?rev=1910507&view=auto
==============================================================================
--- httpd/httpd/trunk/changes-entries/h2_websockets.txt (added)
+++ httpd/httpd/trunk/changes-entries/h2_websockets.txt Tue Jun 20 12:01:09 2023
@@ -0,0 +1,10 @@
+  *) mod_http2: added support for bootstrapping WebSockets via HTTP/2, as
+     described in RFC 8441. A new directive 'H2WebSockets on|off' has been
+     added. The feature is by default not enabled.
+     As also discussed in the manual, this feature should work for setups
+     using "ProxyPass backend-url upgrade=websocket" without further changes.
+     Special server modules for WebSockets will have to be adapted,
+     most likely, as the handling if IO events is different with HTTP/2.
+     HTTP/2 WebSockets are supported on platforms with native pipes. This
+     excludes Windows.
+     [Stefan Eissing]
\ No newline at end of file

Modified: httpd/httpd/trunk/configure.in
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/configure.in?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/configure.in (original)
+++ httpd/httpd/trunk/configure.in Tue Jun 20 12:01:09 2023
@@ -999,6 +999,7 @@ APACHE_FAST_OUTPUT(support/Makefile)
 if test -d ./test; then
     APACHE_FAST_OUTPUT(test/Makefile)
     AC_CONFIG_FILES([test/pyhttpd/config.ini])
+    APACHE_FAST_OUTPUT(test/clients/Makefile)
 fi
 
 dnl ## Finalize the variables

Modified: httpd/httpd/trunk/docs/manual/mod/mod_http2.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/manual/mod/mod_http2.xml?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/docs/manual/mod/mod_http2.xml (original)
+++ httpd/httpd/trunk/docs/manual/mod/mod_http2.xml Tue Jun 20 12:01:09 2023
@@ -1082,4 +1082,42 @@ H2EarlyHint Link "</my.css>;rel=pr
         </usage>
     </directivesynopsis>
 
+    <directivesynopsis>
+        <name>H2WebSockets</name>
+        <description>En-/Disable WebSockets via HTTP/2</description>
+        <syntax>H2WebSockets  on|off</syntax>
+        <default>H2WebSockets off</default>
+        <contextlist>
+            <context>server config</context>
+            <context>virtual host</context>
+        </contextlist>
+        <compatibility>Available in version 2.5.1 and later.</compatibility>
+
+        <usage>
+            <p>
+                Use <directive>H2WebSockets</directive> to enable or disable
+                bootstrapping of WebSockets via the HTTP/2 protocol. This
+                protocol extension is defined in RFC 8441.
+            </p><p>
+                Such requests come as a CONNECT with an extra ':protocol'
+                header. Such requests are transformed inside the module to
+                their HTTP/1.1 equivalents before passing it to internal
+                processing.
+            </p><p>
+                This means that HTTP/2 WebSockets can be used for a
+                <directive module="mod_proxy">ProxyPass</directive> with
+                'upgrade=websocket' parameter without further changes.
+            </p><p>
+                For (3rd party) modules that handle WebSockets directly in the
+                server, the protocol bootstrapping itself will also work. However
+                the transfer of data does require extra support in case of HTTP/2.
+                The negotiated WebSocket will not be able to use the client connection
+                socket for polling IO related events.
+            </p><p>
+                Because enabling this feature might break backward compatibility
+                for such 3rd party modules, it is not enabled by default.
+            </p>
+        </usage>
+    </directivesynopsis>
+
 </modulesynopsis>

Modified: httpd/httpd/trunk/include/ap_mmn.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/include/ap_mmn.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/include/ap_mmn.h (original)
+++ httpd/httpd/trunk/include/ap_mmn.h Tue Jun 20 12:01:09 2023
@@ -718,6 +718,7 @@
  * 20211221.13 (2.5.1-dev) Add hook token_checker to check for authorization other
  *                         than username / password. Add autht_provider structure.
  * 20211221.14 (2.5.1-dev) Add request_rec->final_resp_passed bit
+ * 20211221.15 (2.5.1-dev) Add ap_get_pollfd_from_conn()
  */
 
 #define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */
@@ -725,7 +726,7 @@
 #ifndef MODULE_MAGIC_NUMBER_MAJOR
 #define MODULE_MAGIC_NUMBER_MAJOR 20211221
 #endif
-#define MODULE_MAGIC_NUMBER_MINOR 14             /* 0...n */
+#define MODULE_MAGIC_NUMBER_MINOR 15             /* 0...n */
 
 /**
  * Determine if the server's current MODULE_MAGIC_NUMBER is at least a

Modified: httpd/httpd/trunk/include/http_core.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/include/http_core.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/include/http_core.h (original)
+++ httpd/httpd/trunk/include/http_core.h Tue Jun 20 12:01:09 2023
@@ -31,6 +31,7 @@
 #include "apr_optional.h"
 #include "util_filter.h"
 #include "ap_expr.h"
+#include "apr_poll.h"
 #include "apr_tables.h"
 
 #include "http_config.h"
@@ -1109,6 +1110,30 @@ AP_DECLARE(int) ap_state_query(int query
  */
 AP_CORE_DECLARE(conn_rec *) ap_create_slave_connection(conn_rec *c);
 
+/** Get a apr_pollfd_t populated with descriptor and descriptor type
+ * and the timeout to use for it.
+ * @return APR_ENOTIMPL if not supported for a connection.
+ */
+AP_DECLARE_HOOK(apr_status_t, get_pollfd_from_conn,
+                (conn_rec *c, struct apr_pollfd_t *pfd,
+                 apr_interval_time_t *ptimeout))
+
+/**
+ * Pass in a `struct apr_pollfd_t*` and get `desc_type` and `desc`
+ * populated with a suitable value for polling connection input.
+ * For primary connection (c->master == NULL), this will be the connection
+ * socket. For secondary connections this may differ or not be available
+ * at all.
+ * Note that APR_NO_DESC may be set to indicate that the connection
+ * input is already closed.
+ *
+ * @param pfd  the pollfd to set the descriptor in
+ * @param ptimeout  != NULL to retrieve the timeout in effect
+ * @return ARP_SUCCESS when the information was assigned.
+ */
+AP_CORE_DECLARE(apr_status_t) ap_get_pollfd_from_conn(conn_rec *c,
+                                      struct apr_pollfd_t *pfd,
+                                      apr_interval_time_t *ptimeout);
 
 /** Macro to provide a default value if the pointer is not yet initialised
  */

Modified: httpd/httpd/trunk/modules/http2/config2.m4
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/config2.m4?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/config2.m4 (original)
+++ httpd/httpd/trunk/modules/http2/config2.m4 Tue Jun 20 12:01:09 2023
@@ -37,6 +37,7 @@ h2_stream.lo dnl
 h2_switch.lo dnl
 h2_util.lo dnl
 h2_workers.lo dnl
+h2_ws.lo dnl
 "
 
 dnl

Modified: httpd/httpd/trunk/modules/http2/h2.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2.h (original)
+++ httpd/httpd/trunk/modules/http2/h2.h Tue Jun 20 12:01:09 2023
@@ -62,6 +62,8 @@ extern const char *H2_MAGIC_TOKEN;
 #define H2_HEADER_AUTH_LEN   10
 #define H2_HEADER_PATH       ":path"
 #define H2_HEADER_PATH_LEN   5
+#define H2_HEADER_PROTO      ":protocol"
+#define H2_HEADER_PROTO_LEN  9
 #define H2_CRLF             "\r\n"
 
 /* Size of the frame header itself in HTTP/2 */
@@ -153,6 +155,7 @@ struct h2_request {
     const char *scheme;
     const char *authority;
     const char *path;
+    const char *protocol;
     apr_table_t *headers;
 
     apr_time_t request_time;

Modified: httpd/httpd/trunk/modules/http2/h2_bucket_beam.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_bucket_beam.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_bucket_beam.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_bucket_beam.c Tue Jun 20 12:01:09 2023
@@ -268,6 +268,7 @@ static void beam_shutdown(h2_bucket_beam
     if (how == APR_SHUTDOWN_READWRITE) {
         beam->cons_io_cb = NULL;
         beam->recv_cb = NULL;
+        beam->eagain_cb = NULL;
     }
 
     /* shutdown sender (or both)? */
@@ -747,6 +748,9 @@ transfer:
 
 leave:
     H2_BEAM_LOG(beam, to, APLOG_TRACE2, rv, "end receive", bb);
+    if (rv == APR_EAGAIN && beam->eagain_cb) {
+        beam->eagain_cb(beam->eagain_ctx, beam);
+    }
     apr_thread_mutex_unlock(beam->lock);
     return rv;
 }
@@ -769,6 +773,15 @@ void h2_beam_on_received(h2_bucket_beam
     apr_thread_mutex_unlock(beam->lock);
 }
 
+void h2_beam_on_eagain(h2_bucket_beam *beam,
+                       h2_beam_ev_callback *eagain_cb, void *ctx)
+{
+    apr_thread_mutex_lock(beam->lock);
+    beam->eagain_cb = eagain_cb;
+    beam->eagain_ctx = ctx;
+    apr_thread_mutex_unlock(beam->lock);
+}
+
 void h2_beam_on_send(h2_bucket_beam *beam,
                      h2_beam_ev_callback *send_cb, void *ctx)
 {
@@ -846,3 +859,25 @@ int h2_beam_report_consumption(h2_bucket
     apr_thread_mutex_unlock(beam->lock);
     return rv;
 }
+
+int h2_beam_is_complete(h2_bucket_beam *beam)
+{
+    int rv = 0;
+
+    apr_thread_mutex_lock(beam->lock);
+    if (beam->closed)
+        rv = 1;
+    else {
+        apr_bucket *b;
+        for (b = H2_BLIST_FIRST(&beam->buckets_to_send);
+             b != H2_BLIST_SENTINEL(&beam->buckets_to_send);
+             b = APR_BUCKET_NEXT(b)) {
+            if (APR_BUCKET_IS_EOS(b)) {
+                rv = 1;
+                break;
+            }
+        }
+    }
+    apr_thread_mutex_unlock(beam->lock);
+    return rv;
+}

Modified: httpd/httpd/trunk/modules/http2/h2_bucket_beam.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_bucket_beam.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_bucket_beam.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_bucket_beam.h Tue Jun 20 12:01:09 2023
@@ -67,6 +67,8 @@ struct h2_bucket_beam {
     void *recv_ctx;
     h2_beam_ev_callback *send_cb;      /* event: buckets were added in h2_beam_send() */
     void *send_ctx;
+    h2_beam_ev_callback *eagain_cb;    /* event: a receive results in ARP_EAGAIN */
+    void *eagain_ctx;
 
     apr_off_t recv_bytes;             /* amount of bytes transferred in h2_beam_receive() */
     apr_off_t recv_bytes_reported;    /* amount of bytes reported as received via callback */
@@ -206,6 +208,16 @@ void h2_beam_on_received(h2_bucket_beam
                          h2_beam_ev_callback *recv_cb, void *ctx);
 
 /**
+ * Register a callback to be invoked on the receiver side whenever
+ * APR_EAGAIN is being returned in h2_beam_receive().
+ * @param beam the beam to set the callback on
+ * @param egain_cb the callback or NULL, called before APR_EAGAIN is returned
+ * @param ctx  the context to use in callback invocation
+ */
+void h2_beam_on_eagain(h2_bucket_beam *beam,
+                       h2_beam_ev_callback *eagain_cb, void *ctx);
+
+/**
  * Register a call back from the sender side to be invoked when send
  * has added buckets to the beam.
  * Unregister by passing a NULL on_send_cb.
@@ -246,4 +258,10 @@ apr_off_t h2_beam_get_buffered(h2_bucket
  */
 apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam);
 
+/**
+ * @return != 0 iff beam has been closed or has an EOS bucket buffered
+ *                  waiting to be received.
+ */
+int h2_beam_is_complete(h2_bucket_beam *beam);
+
 #endif /* h2_bucket_beam_h */

Modified: httpd/httpd/trunk/modules/http2/h2_c1_io.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_c1_io.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_c1_io.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_c1_io.c Tue Jun 20 12:01:09 2023
@@ -267,7 +267,7 @@ static apr_status_t pass_output(h2_c1_io
         /* recursive call, may be triggered by an H2EOS bucket
          * being destroyed and triggering sending more data? */
         AP_DEBUG_ASSERT(0);
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(10456)
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(10456)
                       "h2_c1_io(%ld): recursive call of h2_c1_io_pass. "
                       "Denied to prevent output corruption. This "
                       "points to a bug in the HTTP/2 implementation.",

Modified: httpd/httpd/trunk/modules/http2/h2_c2.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_c2.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_c2.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_c2.c Tue Jun 20 12:01:09 2023
@@ -48,6 +48,7 @@
 #include "h2_headers.h"
 #include "h2_session.h"
 #include "h2_stream.h"
+#include "h2_ws.h"
 #include "h2_c2.h"
 #include "h2_util.h"
 
@@ -173,6 +174,7 @@ void h2_c2_abort(conn_rec *c2, conn_rec
 
 typedef struct {
     apr_bucket_brigade *bb;       /* c2: data in holding area */
+    unsigned did_upgrade_eos:1;   /* for Upgrade, we added an extra EOS */
 } h2_c2_fctx_in_t;
 
 static apr_status_t h2_c2_filter_in(ap_filter_t* f,
@@ -216,7 +218,17 @@ static apr_status_t h2_c2_filter_in(ap_f
             APR_BRIGADE_INSERT_TAIL(fctx->bb, b);
         }
     }
-    
+
+    /* If this is a HTTP Upgrade, it means the request we process
+     * has not Content, although the stream is not necessarily closed.
+     * On first read, we insert an EOS to signal processing that it
+     * has the complete body. */
+    if (conn_ctx->is_upgrade && !fctx->did_upgrade_eos) {
+        b = apr_bucket_eos_create(f->c->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(fctx->bb, b);
+        fctx->did_upgrade_eos = 1;
+    }
+
     while (APR_BRIGADE_EMPTY(fctx->bb)) {
         /* Get more input data for our request. */
         if (APLOGctrace2(f->c)) {
@@ -547,6 +559,31 @@ static int c2_hook_pre_connection(conn_r
     return OK;
 }
 
+static apr_status_t c2_get_pollfd_from_conn(conn_rec *c,
+                                            struct apr_pollfd_t *pfd,
+                                            apr_interval_time_t *ptimeout)
+{
+    if (c->master) {
+        h2_conn_ctx_t *ctx = h2_conn_ctx_get(c);
+        if (ctx) {
+            if (ctx->beam_in && ctx->pipe_in[H2_PIPE_OUT]) {
+                pfd->desc_type = APR_POLL_FILE;
+                pfd->desc.f = ctx->pipe_in[H2_PIPE_OUT];
+                if (ptimeout)
+                    *ptimeout = h2_beam_timeout_get(ctx->beam_in);
+            }
+            else {
+                /* no input */
+                pfd->desc_type = APR_NO_DESC;
+                if (ptimeout)
+                    *ptimeout = -1;
+            }
+            return APR_SUCCESS;
+        }
+    }
+    return APR_ENOTIMPL;
+}
+
 void h2_c2_register_hooks(void)
 {
     /* When the connection processing actually starts, we might
@@ -558,8 +595,14 @@ void h2_c2_register_hooks(void)
     /* We need to manipulate the standard HTTP/1.1 protocol filters and
      * install our own. This needs to be done very early. */
     ap_hook_pre_read_request(c2_pre_read_request, NULL, NULL, APR_HOOK_MIDDLE);
-    ap_hook_post_read_request(c2_post_read_request, NULL, NULL, APR_HOOK_REALLY_FIRST);
+    ap_hook_post_read_request(c2_post_read_request, NULL, NULL,
+                              APR_HOOK_REALLY_FIRST);
     ap_hook_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST);
+#if AP_MODULE_MAGIC_AT_LEAST(20211221, 15)
+    ap_hook_get_pollfd_from_conn(c2_get_pollfd_from_conn, NULL, NULL,
+                                 APR_HOOK_MIDDLE);
+#endif
+
 
     c2_net_in_filter_handle =
         ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in,
@@ -668,11 +711,21 @@ static apr_status_t c2_process(h2_conn_c
 {
     const h2_request *req = conn_ctx->request;
     conn_state_t *cs = c->cs;
-    request_rec *r;
+    request_rec *r = NULL;
     const char *tenc;
     apr_time_t timeout;
+    apr_status_t rv = APR_SUCCESS;
+
+    if(req->protocol && !strcmp("websocket", req->protocol)) {
+        req = h2_ws_rewrite_request(req, c, conn_ctx->beam_in == NULL);
+        if (!req) {
+            rv = APR_EGENERAL;
+            goto cleanup;
+        }
+    }
+
+    r = h2_create_request_rec(req, c, conn_ctx->beam_in == NULL);
 
-    r = h2_create_request_rec(conn_ctx->request, c, conn_ctx->beam_in == NULL);
     if (!r) {
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                       "h2_c2(%s-%d): create request_rec failed, r=NULL",

Modified: httpd/httpd/trunk/modules/http2/h2_c2_filter.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_c2_filter.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_c2_filter.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_c2_filter.c Tue Jun 20 12:01:09 2023
@@ -39,6 +39,7 @@
 #include "h2_c2.h"
 #include "h2_mplx.h"
 #include "h2_request.h"
+#include "h2_ws.h"
 #include "h2_util.h"
 
 
@@ -108,15 +109,26 @@ apr_status_t h2_c2_filter_request_in(ap_
     /* This filter is a one-time wonder */
     ap_remove_input_filter(f);
 
-    if (f->c->master && (conn_ctx = h2_conn_ctx_get(f->c)) && conn_ctx->stream_id) {
-        if (conn_ctx->request->http_status != H2_HTTP_STATUS_UNSET) {
+    if (f->c->master && (conn_ctx = h2_conn_ctx_get(f->c)) &&
+        conn_ctx->stream_id) {
+        const h2_request *req = conn_ctx->request;
+
+        if (req->http_status == H2_HTTP_STATUS_UNSET &&
+            req->protocol && !strcmp("websocket", req->protocol)) {
+            req = h2_ws_rewrite_request(req, f->c, conn_ctx->beam_in == NULL);
+            if (!req)
+                return APR_EGENERAL;
+        }
+
+        if (req->http_status != H2_HTTP_STATUS_UNSET) {
             /* error was encountered preparing this request */
-            b = ap_bucket_error_create(conn_ctx->request->http_status, NULL, f->r->pool,
+            b = ap_bucket_error_create(req->http_status, NULL, f->r->pool,
                                        f->c->bucket_alloc);
             APR_BRIGADE_INSERT_TAIL(bb, b);
             return APR_SUCCESS;
         }
-        b = h2_request_create_bucket(conn_ctx->request, f->r);
+
+        b = h2_request_create_bucket(req, f->r);
         APR_BRIGADE_INSERT_TAIL(bb, b);
         if (!conn_ctx->beam_in) {
             b = apr_bucket_eos_create(f->c->bucket_alloc);
@@ -184,7 +196,7 @@ static int uniq_field_values(void *d, co
          */
         for (i = 0, strpp = (char **) values->elts; i < values->nelts;
              ++i, ++strpp) {
-            if (*strpp && apr_strnatcasecmp(*strpp, start) == 0) {
+            if (*strpp && ap_cstr_casecmp(*strpp, start) == 0) {
                 break;
             }
         }
@@ -292,7 +304,7 @@ static h2_headers *create_response(reque
 
         while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) {
             for (i = 0; i < r->content_languages->nelts; ++i) {
-                if (!apr_strnatcasecmp(token, languages[i]))
+                if (!ap_cstr_casecmp(token, languages[i]))
                     break;
             }
             if (i == r->content_languages->nelts) {
@@ -636,9 +648,11 @@ apr_status_t h2_c2_filter_catch_h1_out(a
                 int result = ap_map_http_request_error(conn_ctx->last_err,
                                                        HTTP_INTERNAL_SERVER_ERROR);
                 request_rec *r = h2_create_request_rec(conn_ctx->request, f->c, 1);
-                ap_die((result >= 400)? result : HTTP_INTERNAL_SERVER_ERROR, r);
-                b = ap_bucket_eor_create(f->c->bucket_alloc, r);
-                APR_BRIGADE_INSERT_TAIL(bb, b);
+                if (r) {
+                    ap_die((result >= 400)? result : HTTP_INTERNAL_SERVER_ERROR, r);
+                    b = ap_bucket_eor_create(f->c->bucket_alloc, r);
+                    APR_BRIGADE_INSERT_TAIL(bb, b);
+                }
             }
         }
         /* There are cases where we need to parse a serialized http/1.1 response.

Modified: httpd/httpd/trunk/modules/http2/h2_config.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_config.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_config.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_config.c Tue Jun 20 12:01:09 2023
@@ -77,6 +77,7 @@ typedef struct h2_config {
     int output_buffered;
     apr_interval_time_t stream_timeout;/* beam timeout */
     int max_data_frame_len;          /* max # bytes in a single h2 DATA frame */
+    int h2_websockets;               /* if mod_h2 negotiating WebSockets */
 } h2_config;
 
 typedef struct h2_dir_config {
@@ -115,6 +116,7 @@ static h2_config defconf = {
     1,                      /* stream output buffered */
     -1,                     /* beam timeout */
     0,                      /* max DATA frame len, 0 == no extra limit */
+    0,                      /* WebSockets negotiation, enabled */
 };
 
 static h2_dir_config defdconf = {
@@ -161,6 +163,7 @@ void *h2_config_create_svr(apr_pool_t *p
     conf->output_buffered      = DEF_VAL;
     conf->stream_timeout       = DEF_VAL;
     conf->max_data_frame_len   = DEF_VAL;
+    conf->h2_websockets        = DEF_VAL;
     return conf;
 }
 
@@ -210,6 +213,7 @@ static void *h2_config_merge(apr_pool_t
     n->padding_always       = H2_CONFIG_GET(add, base, padding_always);
     n->stream_timeout       = H2_CONFIG_GET(add, base, stream_timeout);
     n->max_data_frame_len   = H2_CONFIG_GET(add, base, max_data_frame_len);
+    n->h2_websockets        = H2_CONFIG_GET(add, base, h2_websockets);
     return n;
 }
 
@@ -301,6 +305,8 @@ static apr_int64_t h2_srv_config_geti64(
             return H2_CONFIG_GET(conf, &defconf, stream_timeout);
         case H2_CONF_MAX_DATA_FRAME_LEN:
             return H2_CONFIG_GET(conf, &defconf, max_data_frame_len);
+        case H2_CONF_WEBSOCKETS:
+            return H2_CONFIG_GET(conf, &defconf, h2_websockets);
         default:
             return DEF_VAL;
     }
@@ -363,6 +369,9 @@ static void h2_srv_config_seti(h2_config
         case H2_CONF_MAX_DATA_FRAME_LEN:
             H2_CONFIG_SET(conf, max_data_frame_len, val);
             break;
+        case H2_CONF_WEBSOCKETS:
+            H2_CONFIG_SET(conf, h2_websockets, val);
+            break;
         default:
             break;
     }
@@ -681,6 +690,24 @@ static const char *h2_conf_set_push(cmd_
     return "value must be On or Off";
 }
 
+static const char *h2_conf_set_websockets(cmd_parms *cmd,
+                                          void *dirconf, const char *value)
+{
+    if (!strcasecmp(value, "On")) {
+#if H2_USE_PIPES
+        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_WEBSOCKETS, 1);
+        return NULL;
+#else
+        return "HTTP/2 WebSockets are not supported on this platform";
+#endif
+    }
+    else if (!strcasecmp(value, "Off")) {
+        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_WEBSOCKETS, 0);
+        return NULL;
+    }
+    return "value must be On or Off";
+}
+
 static const char *h2_conf_add_push_priority(cmd_parms *cmd, void *_cfg,
                                              const char *ctype, const char *sdependency,
                                              const char *sweight)
@@ -1021,6 +1048,8 @@ const command_rec h2_cmds[] = {
                   RSRC_CONF, "maximum number of bytes in a single HTTP/2 DATA frame"),
     AP_INIT_TAKE2("H2EarlyHint", h2_conf_add_early_hint, NULL,
                    OR_FILEINFO|OR_AUTHCFG, "add a a 'Link:' header for a 103 Early Hints response."),
+    AP_INIT_TAKE1("H2WebSockets", h2_conf_set_websockets, NULL,
+                  RSRC_CONF, "off to disable WebSockets over HTTP/2"),
     AP_END_CMD
 };
 

Modified: httpd/httpd/trunk/modules/http2/h2_config.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_config.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_config.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_config.h Tue Jun 20 12:01:09 2023
@@ -44,6 +44,7 @@ typedef enum {
     H2_CONF_OUTPUT_BUFFER,
     H2_CONF_STREAM_TIMEOUT,
     H2_CONF_MAX_DATA_FRAME_LEN,
+    H2_CONF_WEBSOCKETS,
 } h2_config_var_t;
 
 struct apr_hash_t;

Modified: httpd/httpd/trunk/modules/http2/h2_conn_ctx.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_conn_ctx.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_conn_ctx.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_conn_ctx.h Tue Jun 20 12:01:09 2023
@@ -53,7 +53,8 @@ struct h2_conn_ctx_t {
     const struct h2_request *request; /* c2: the request to process */
     struct h2_bucket_beam *beam_out; /* c2: data out, created from req_pool */
     struct h2_bucket_beam *beam_in;  /* c2: data in or NULL, borrowed from request stream */
-    unsigned int input_chunked;      /* c2: if input needs HTTP/1.1 chunking applied */
+    unsigned input_chunked:1;        /* c2: if input needs HTTP/1.1 chunking applied */
+    unsigned is_upgrade:1;           /* c2: if requst is a HTTP Upgrade */
 
     apr_file_t *pipe_in[2];          /* c2: input produced notification pipe */
     apr_pollfd_t pfd;                /* c1: poll socket input, c2: NUL */

Modified: httpd/httpd/trunk/modules/http2/h2_mplx.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_mplx.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_mplx.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_mplx.c Tue Jun 20 12:01:09 2023
@@ -146,6 +146,7 @@ static void m_stream_cleanup(h2_mplx *m,
         if (c2_ctx->beam_in) {
             h2_beam_on_send(c2_ctx->beam_in, NULL, NULL);
             h2_beam_on_received(c2_ctx->beam_in, NULL, NULL);
+            h2_beam_on_eagain(c2_ctx->beam_in, NULL, NULL);
             h2_beam_on_consumed(c2_ctx->beam_in, NULL, NULL);
         }
     }
@@ -666,7 +667,9 @@ static apr_status_t c1_process_stream(h2
     if (APLOGctrace1(m->c1)) {
         const h2_request *r = stream->request;
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
-                      H2_STRM_MSG(stream, "process %s %s%s%s%s"),
+                      H2_STRM_MSG(stream, "process %s%s%s %s%s%s%s"),
+                      r->protocol? r->protocol : "",
+                      r->protocol? " " : "",
                       r->method, r->scheme? r->scheme : "",
                       r->scheme? "://" : "",
                       r->authority, r->path? r->path: "");
@@ -780,6 +783,19 @@ static void c2_beam_input_read_notify(vo
     }
 }
 
+static void c2_beam_input_read_eagain(void *ctx, h2_bucket_beam *beam)
+{
+    conn_rec *c = ctx;
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+    /* installed in the input bucket beams when we use pipes.
+     * Drain the pipe just before the beam returns APR_EAGAIN.
+     * A clean state for allowing polling on the pipe to rest
+     * when the beam is empty */
+    if (conn_ctx && conn_ctx->pipe_in[H2_PIPE_OUT]) {
+        h2_util_drain_pipe(conn_ctx->pipe_in[H2_PIPE_OUT]);
+    }
+}
+
 static void c2_beam_output_write_notify(void *ctx, h2_bucket_beam *beam)
 {
     conn_rec *c = ctx;
@@ -824,6 +840,7 @@ static apr_status_t c2_setup_io(h2_mplx
                                         c2->pool, c2->pool);
         if (APR_SUCCESS != rv) goto cleanup;
 #endif
+        h2_beam_on_eagain(stream->input, c2_beam_input_read_eagain, c2);
     }
 
 cleanup:
@@ -930,6 +947,15 @@ static void s_c2_done(h2_mplx *m, conn_r
                       "h2_c2(%s-%d): processing finished without final response",
                       conn_ctx->id, conn_ctx->stream_id);
         c2->aborted = 1;
+        if (conn_ctx->beam_out)
+          h2_beam_abort(conn_ctx->beam_out, c2);
+    }
+    else if (!conn_ctx->beam_out || !h2_beam_is_complete(conn_ctx->beam_out)) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, conn_ctx->last_err, c2,
+                      "h2_c2(%s-%d): processing finished with incomplete output",
+                      conn_ctx->id, conn_ctx->stream_id);
+        c2->aborted = 1;
+        h2_beam_abort(conn_ctx->beam_out, c2);
     }
     else if (!c2->aborted) {
         s_mplx_be_happy(m, c2, conn_ctx);

Modified: httpd/httpd/trunk/modules/http2/h2_proxy_util.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_proxy_util.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_proxy_util.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_proxy_util.c Tue Jun 20 12:01:09 2023
@@ -381,7 +381,7 @@ static int iq_bubble_down(h2_proxy_iqueu
  * h2_proxy_ngheader
  ******************************************************************************/
 #define H2_HD_MATCH_LIT_CS(l, name)  \
-    ((strlen(name) == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
+    ((strlen(name) == sizeof(l) - 1) && !ap_cstr_casecmp(l, name))
 
 static int h2_util_ignore_header(const char *name) 
 {
@@ -500,7 +500,7 @@ static int ignore_header(const literal *
     
     for (i = 0; i < llen; ++i) {
         lit = &lits[i];
-        if (lit->len == nlen && !apr_strnatcasecmp(lit->name, name)) {
+        if (lit->len == nlen && !ap_cstr_casecmp(lit->name, name)) {
             return 1;
         }
     }
@@ -542,7 +542,7 @@ void h2_proxy_util_camel_case_header(cha
 
 /** Match a header value against a string constance, case insensitive */
 #define H2_HD_MATCH_LIT(l, name, nlen)  \
-    ((nlen == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
+    ((nlen == sizeof(l) - 1) && !ap_cstr_casecmp(l, name))
 
 static apr_status_t h2_headers_add_h1(apr_table_t *headers, apr_pool_t *pool, 
                                       const char *name, size_t nlen,

Modified: httpd/httpd/trunk/modules/http2/h2_push.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_push.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_push.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_push.c Tue Jun 20 12:01:09 2023
@@ -426,7 +426,7 @@ static void inspect_link(link_ctx *ctx,
 
 static int head_iter(void *ctx, const char *key, const char *value) 
 {
-    if (!apr_strnatcasecmp("link", key)) {
+    if (!ap_cstr_casecmp("link", key)) {
         inspect_link(ctx, value, strlen(value));
     }
     return 1;

Modified: httpd/httpd/trunk/modules/http2/h2_request.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_request.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_request.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_request.c Tue Jun 20 12:01:09 2023
@@ -166,6 +166,10 @@ apr_status_t h2_request_add_header(h2_re
                  && !strncmp(H2_HEADER_AUTH, name, nlen)) {
             req->authority = apr_pstrndup(pool, value, vlen);
         }
+        else if (H2_HEADER_PROTO_LEN == nlen
+                 && !strncmp(H2_HEADER_PROTO, name, nlen)) {
+            req->protocol = apr_pstrndup(pool, value, vlen);
+        }
         else {
             char buffer[32];
             memset(buffer, 0, 32);
@@ -214,6 +218,7 @@ h2_request *h2_request_clone(apr_pool_t
     dst->scheme       = apr_pstrdup(p, src->scheme);
     dst->authority    = apr_pstrdup(p, src->authority);
     dst->path         = apr_pstrdup(p, src->path);
+    dst->protocol     = apr_pstrdup(p, src->protocol);
     dst->headers      = apr_table_clone(p, src->headers);
     return dst;
 }
@@ -299,13 +304,13 @@ apr_bucket *h2_request_create_bucket(con
 #endif
 
 static void assign_headers(request_rec *r, const h2_request *req,
-                           int no_body)
+                           int no_body, int is_connect)
 {
     const char *cl;
 
     r->headers_in = apr_table_clone(r->pool, req->headers);
 
-    if (req->authority) {
+    if (req->authority && !is_connect) {
         /* for internal handling, we have to simulate that :authority
          * came in as Host:, RFC 9113 ch. says that mismatches between
          * :authority and Host: SHOULD be rejected as malformed. However,
@@ -324,36 +329,40 @@ static void assign_headers(request_rec *
                       "set 'Host: %s' from :authority", req->authority);
     }
 
-    cl = apr_table_get(req->headers, "Content-Length");
-    if (no_body) {
-        if (!cl && apr_table_get(req->headers, "Content-Type")) {
-            /* If we have a content-type, but already seen eos, no more
-             * data will come. Signal a zero content length explicitly.
-             */
-            apr_table_setn(req->headers, "Content-Length", "0");
+    /* Unless we open a byte stream via CONNECT, apply content-length guards. */
+    if (!is_connect) {
+        cl = apr_table_get(req->headers, "Content-Length");
+        if (no_body) {
+            if (!cl && apr_table_get(req->headers, "Content-Type")) {
+                /* If we have a content-type, but already seen eos, no more
+                 * data will come. Signal a zero content length explicitly.
+                 */
+                apr_table_setn(req->headers, "Content-Length", "0");
+            }
         }
-    }
 #if !AP_HAS_RESPONSE_BUCKETS
-    else if (!cl) {
-        /* there may be a body and we have internal HTTP/1.1 processing.
-         * If the Content-Length is unspecified, we MUST simulate
-         * chunked Transfer-Encoding.
-         *
-         * HTTP/2 does not need a Content-Length for framing. Ideally
-         * all clients set the EOS flag on the header frame if they
-         * do not intent to send a body. However, forwarding proxies
-         * might just no know at the time and send an empty DATA
-         * frame with EOS much later.
-         */
-        apr_table_mergen(r->headers_in, "Transfer-Encoding", "chunked");
-    }
+        else if (!cl) {
+            /* there may be a body and we have internal HTTP/1.1 processing.
+             * If the Content-Length is unspecified, we MUST simulate
+             * chunked Transfer-Encoding.
+             *
+             * HTTP/2 does not need a Content-Length for framing. Ideally
+             * all clients set the EOS flag on the header frame if they
+             * do not intent to send a body. However, forwarding proxies
+             * might just no know at the time and send an empty DATA
+             * frame with EOS much later.
+             */
+            apr_table_mergen(r->headers_in, "Transfer-Encoding", "chunked");
+        }
 #endif /* else AP_HAS_RESPONSE_BUCKETS */
+  }
 }
 
 request_rec *h2_create_request_rec(const h2_request *req, conn_rec *c,
                                    int no_body)
 {
     int access_status = HTTP_OK;
+    int is_connect = !ap_cstr_casecmp("CONNECT", req->method);
 
 #if AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
     request_rec *r = ap_create_request(c);
@@ -362,24 +371,43 @@ request_rec *h2_create_request_rec(const
 #endif
 
 #if AP_MODULE_MAGIC_AT_LEAST(20120211, 107)
-    assign_headers(r, req, no_body);
+    assign_headers(r, req, no_body, is_connect);
     ap_run_pre_read_request(r, c);
 
     /* Time to populate r with the data we have. */
     r->request_time = req->request_time;
     AP_DEBUG_ASSERT(req->authority);
-    if (!apr_strnatcasecmp("CONNECT", req->method)) {
+    if (is_connect) {
       /* CONNECT MUST NOT have scheme or path */
+      if (req->scheme) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10458)
+                      "':scheme: %s' header present in CONNECT request",
+                      req->scheme);
+        access_status = HTTP_BAD_REQUEST;
+        goto die;
+      }
+      if (req->path) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10459)
+                      "':path: %s' header present in CONNECT request",
+                      req->path);
+        access_status = HTTP_BAD_REQUEST;
+        goto die;
+      }
       r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
                                     req->method, req->authority);
     }
-    else if (req->scheme && ap_cstr_casecmp(req->scheme, "http")
-             && ap_cstr_casecmp(req->scheme, "https")) {
-        /* FIXME: we also need to create absolute uris when we are
-         * in a forward proxy configuration! But there is currently
-         * no way to detect that. */
+    else if (req->protocol) {
+      ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10460)
+                    "':protocol: %s' header present in %s request",
+                    req->protocol, req->method);
+      access_status = HTTP_BAD_REQUEST;
+      goto die;
+    }
+    else if (req->scheme &&
+             ap_cstr_casecmp(req->scheme, ap_ssl_conn_is_ssl(c->master? c->master : c)?
+                             "https" : "http")) {
         /* Client sent a ':scheme' pseudo header for something else
-         * than what we handle by default. Make an absolute URI. */
+         * than what we have on this connection. Make an absolute URI. */
         r->the_request = apr_psprintf(r->pool, "%s %s://%s%s HTTP/2.0",
                                       req->method, req->scheme, req->authority,
                                       req->path ? req->path : "");
@@ -420,7 +448,7 @@ request_rec *h2_create_request_rec(const
     {
         const char *s;
 
-        assign_headers(r, req, no_body);
+        assign_headers(r, req, no_body, is_connect);
         ap_run_pre_read_request(r, c);
 
         /* Time to populate r with the data we have. */

Modified: httpd/httpd/trunk/modules/http2/h2_session.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_session.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_session.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_session.c Tue Jun 20 12:01:09 2023
@@ -621,9 +621,8 @@ static int on_invalid_header_cb(nghttp2_
     
     ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03456)
                   H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
-                  "invalid header '%s: %s'"),
-                  apr_pstrndup(session->pool, (const char *)name, namelen),
-                  apr_pstrndup(session->pool, (const char *)value, valuelen));
+                  "invalid header '%.*s: %.*s'"),
+                  (int)namelen, name, (int)valuelen, value);
     stream = get_stream(session, frame->hd.stream_id);
     if (stream) {
         h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
@@ -1003,7 +1002,7 @@ apr_status_t h2_session_create(h2_sessio
 static apr_status_t h2_session_start(h2_session *session, int *rv)
 {
     apr_status_t status = APR_SUCCESS;
-    nghttp2_settings_entry settings[3];
+    nghttp2_settings_entry settings[4];
     size_t slen;
     int win_size;
     
@@ -1070,7 +1069,12 @@ static apr_status_t h2_session_start(h2_
         settings[slen].value = win_size;
         ++slen;
     }
-    
+    if (h2_config_sgeti(session->s, H2_CONF_WEBSOCKETS)) {
+      settings[slen].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
+      settings[slen].value = 1;
+      ++slen;
+    }
+
     ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1,
                   H2_SSSN_LOG(APLOGNO(03201), session, 
                   "start, INITIAL_WINDOW_SIZE=%ld, MAX_CONCURRENT_STREAMS=%d"), 

Modified: httpd/httpd/trunk/modules/http2/h2_stream.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_stream.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_stream.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_stream.c Tue Jun 20 12:01:09 2023
@@ -767,6 +767,9 @@ apr_status_t h2_stream_add_header(h2_str
         status = h2_request_add_header(stream->rtmp, stream->pool,
                                        name, nlen, value, vlen,
                                        session->s->limit_req_fieldsize, &was_added);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c1,
+                      H2_STRM_MSG(stream, "add_header: '%.*s: %.*s"),
+                      (int)nlen, name, (int)vlen, value);
         if (was_added) ++stream->request_headers_added;
     }
     else if (H2_SS_OPEN == stream->state) {
@@ -897,7 +900,14 @@ apr_status_t h2_stream_end_headers(h2_st
      *      of CONNECT requests (see [RFC7230], Section 5.3)).
      */
     if (!ap_cstr_casecmp(req->method, "CONNECT")) {
-        if (req->scheme || req->path) {
+        if (req->protocol && !strcmp("websocket", req->protocol)) {
+            if (!req->scheme || !req->path) {
+                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+                              H2_STRM_LOG(APLOGNO(10457), stream, "Request to websocket CONNECT "
+                              "without :scheme or :path, sending 400 answer"));
+            }
+        }
+        else if (req->scheme || req->path) {
             ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
                           H2_STRM_LOG(APLOGNO(10384), stream, "Request to CONNECT "
                           "with :scheme or :path specified, sending 400 answer"));
@@ -1459,8 +1469,8 @@ static ssize_t stream_data_cb(nghttp2_se
                  * it is all fine. */
                  ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
                                H2_SSSN_STRM_MSG(session, stream_id, "rst stream"));
-                 h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
-                 return NGHTTP2_ERR_CALLBACK_FAILURE;
+                 h2_stream_rst(stream, H2_ERR_STREAM_CLOSED);
+                 return NGHTTP2_ERR_DEFERRED;
             }
             ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
                           H2_SSSN_STRM_MSG(session, stream_id,
@@ -1469,10 +1479,17 @@ static ssize_t stream_data_cb(nghttp2_se
             eos = 1;
             rv = APR_SUCCESS;
         }
+        else if (APR_ECONNRESET == rv || APR_ECONNABORTED == rv) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+                          H2_STRM_LOG(APLOGNO(), stream, "data_cb, reading data"));
+            h2_stream_rst(stream, H2_ERR_STREAM_CLOSED);
+            return NGHTTP2_ERR_DEFERRED;
+        }
         else {
             ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
                           H2_STRM_LOG(APLOGNO(02938), stream, "data_cb, reading data"));
-            return NGHTTP2_ERR_CALLBACK_FAILURE;
+            h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+            return NGHTTP2_ERR_DEFERRED;
         }
     }
 

Modified: httpd/httpd/trunk/modules/http2/h2_util.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_util.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_util.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_util.c Tue Jun 20 12:01:09 2023
@@ -1281,8 +1281,8 @@ apr_size_t h2_util_bucket_print(char *bu
     else if (bmax > off) {
         off += apr_snprintf(buffer+off, bmax-off, "%s[%ld]",
                             b->type->name,
-                            (long)(b->length == ((apr_size_t)-1)?
-                                   -1 : b->length));
+                            (b->length == ((apr_size_t)-1)?
+                                   -1 : (long)b->length));
     }
     return off;
 }
@@ -1650,7 +1650,7 @@ static int contains_name(const literal *
     for (i = 0; i < llen; ++i) {
         lit = &lits[i];
         if (lit->len == nv->namelen
-            && !apr_strnatcasecmp(lit->name, (const char *)nv->name)) {
+            && !ap_cstr_casecmp(lit->name, (const char *)nv->name)) {
             return 1;
         }
     }
@@ -1706,7 +1706,7 @@ static apr_status_t req_add_header(apr_t
         return APR_SUCCESS;
     }
     else if (nv->namelen == sizeof("cookie")-1
-             && !apr_strnatcasecmp("cookie", (const char *)nv->name)) {
+             && !ap_cstr_casecmp("cookie", (const char *)nv->name)) {
         existing = apr_table_get(headers, "cookie");
         if (existing) {
             /* Cookie header come separately in HTTP/2, but need
@@ -1725,7 +1725,7 @@ static apr_status_t req_add_header(apr_t
         }
     }
     else if (nv->namelen == sizeof("host")-1
-             && !apr_strnatcasecmp("host", (const char *)nv->name)) {
+             && !ap_cstr_casecmp("host", (const char *)nv->name)) {
         if (apr_table_get(headers, "Host")) {
             return APR_SUCCESS; /* ignore duplicate */
         }
@@ -1883,6 +1883,13 @@ void h2_util_drain_pipe(apr_file_t *pipe
 {
     char rb[512];
     apr_size_t nr = sizeof(rb);
+    apr_interval_time_t timeout;
+    apr_status_t trv;
+
+    /* Make the pipe non-blocking if we can */
+    trv = apr_file_pipe_timeout_get(pipe, &timeout);
+    if (trv == APR_SUCCESS)
+      apr_file_pipe_timeout_set(pipe, 0);
 
     while (apr_file_read(pipe, rb, &nr) == APR_SUCCESS) {
         /* Although we write just one byte to the other end of the pipe
@@ -1893,6 +1900,8 @@ void h2_util_drain_pipe(apr_file_t *pipe
         if (nr != sizeof(rb))
             break;
     }
+    if (trv == APR_SUCCESS)
+      apr_file_pipe_timeout_set(pipe, timeout);
 }
 
 apr_status_t h2_util_wait_on_pipe(apr_file_t *pipe)

Modified: httpd/httpd/trunk/modules/http2/h2_util.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_util.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_util.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_util.h Tue Jun 20 12:01:09 2023
@@ -337,7 +337,7 @@ apr_size_t h2_util_table_bytes(apr_table
 
 /** Match a header value against a string constance, case insensitive */
 #define H2_HD_MATCH_LIT(l, name, nlen)  \
-    ((nlen == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
+    ((nlen == sizeof(l) - 1) && !ap_cstr_casecmp(l, name))
 
 /*******************************************************************************
  * HTTP/2 header helpers

Modified: httpd/httpd/trunk/modules/http2/h2_version.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_version.h?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_version.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_version.h Tue Jun 20 12:01:09 2023
@@ -27,7 +27,7 @@
  * @macro
  * Version number of the http2 module as c string
  */
-#define MOD_HTTP2_VERSION "2.0.19-git"
+#define MOD_HTTP2_VERSION "2.0.20-git"
 
 /**
  * @macro
@@ -35,7 +35,7 @@
  * release. This is a 24 bit number with 8 bits for major number, 8 bits
  * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
  */
-#define MOD_HTTP2_VERSION_NUM 0x020013
+#define MOD_HTTP2_VERSION_NUM 0x020014
 
 
 #endif /* mod_h2_h2_version_h */

Added: httpd/httpd/trunk/modules/http2/h2_ws.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_ws.c?rev=1910507&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_ws.c (added)
+++ httpd/httpd/trunk/modules/http2/h2_ws.c Tue Jun 20 12:01:09 2023
@@ -0,0 +1,326 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_encode.h"
+#include "apr_sha1.h"
+#include "apr_strmatch.h"
+
+#include <ap_mmn.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+#include <http_ssl.h>
+#include <http_vhost.h>
+#include <util_filter.h>
+#include <ap_mpm.h>
+
+#include "h2_private.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_headers.h"
+#include "h2_request.h"
+#include "h2_ws.h"
+
+static ap_filter_rec_t *c2_ws_out_filter_handle;
+
+struct ws_filter_ctx {
+    const char *ws_accept_base64;
+    int has_final_response;
+    int override_body;
+};
+
+/**
+ * Generate the "Sec-WebSocket-Accept" header field for the given key
+ * (base64 encoded) as defined in RFC 6455 ch. 4.2.2 step 5.3
+ */
+static const char *gen_ws_accept(conn_rec *c, const char *key_base64)
+{
+    apr_byte_t dgst[APR_SHA1_DIGESTSIZE];
+    const char ws_guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+    apr_sha1_ctx_t sha1_ctx;
+
+    apr_sha1_init(&sha1_ctx);
+    apr_sha1_update(&sha1_ctx, key_base64, (unsigned int)strlen(key_base64));
+    apr_sha1_update(&sha1_ctx, ws_guid, (unsigned int)strlen(ws_guid));
+    apr_sha1_final(dgst, &sha1_ctx);
+
+    return apr_pencode_base64_binary(c->pool, dgst, sizeof(dgst),
+                                     APR_ENCODE_NONE, NULL);
+}
+
+const h2_request *h2_ws_rewrite_request(const h2_request *req,
+                                        conn_rec *c2, int no_body)
+{
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+    h2_request *wsreq;
+    unsigned char key_raw[16];
+    const char *key_base64, *accept_base64;
+    struct ws_filter_ctx *ws_ctx;
+    apr_status_t rv;
+
+    if (!conn_ctx || !req->protocol || strcmp("websocket", req->protocol))
+        return req;
+
+    if (ap_cstr_casecmp("CONNECT", req->method)) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+                      "h2_c2(%s-%d): websocket request with method %s",
+                      conn_ctx->id, conn_ctx->stream_id, req->method);
+        return req;
+    }
+    if (!req->scheme) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+                      "h2_c2(%s-%d): websocket CONNECT without :scheme",
+                      conn_ctx->id, conn_ctx->stream_id);
+        return req;
+    }
+    if (!req->path) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+                      "h2_c2(%s-%d): websocket CONNECT without :path",
+                      conn_ctx->id, conn_ctx->stream_id);
+        return req;
+    }
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+                  "h2_c2(%s-%d): websocket CONNECT for %s",
+                  conn_ctx->id, conn_ctx->stream_id, req->path);
+    /* Transform the HTTP/2 extended CONNECT to an internal GET using
+     * the HTTP/1.1 version of websocket connection setup. */
+    wsreq = h2_request_clone(c2->pool, req);
+    wsreq->method = "GET";
+    wsreq->protocol = NULL;
+    apr_table_set(wsreq->headers, "Upgrade", "websocket");
+    /* add Sec-WebSocket-Key header */
+    rv = apr_generate_random_bytes(key_raw, sizeof(key_raw));
+    if (rv != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(10461)
+                     "error generating secret");
+        return NULL;
+    }
+    key_base64 = apr_pencode_base64_binary(c2->pool, key_raw, sizeof(key_raw),
+                                           APR_ENCODE_NONE, NULL);
+    apr_table_set(wsreq->headers, "Sec-WebSocket-Key", key_base64);
+    /* This is now the request to process internally */
+
+    /* When this request gets processed and delivers a 101 response,
+     * we expect it to carry a "Sec-WebSocket-Accept" header with
+     * exactly the following value, as per RFC 6455. */
+    accept_base64 = gen_ws_accept(c2, key_base64);
+    /* Add an output filter that intercepts generated responses:
+     * - if a valid WebSocket negotiation happens, transform the
+     *   101 response to a 200
+     * - if a 2xx response happens, that does not pass the Accept test,
+     *   return a 502 indicating that the URI seems not support the websocket
+     *   protocol (RFC 8441 does not define this, but it seems the best
+     *   choice)
+     * - if a 3xx, 4xx or 5xx response happens, forward this unchanged.
+     */
+    ws_ctx = apr_pcalloc(c2->pool, sizeof(*ws_ctx));
+    ws_ctx->ws_accept_base64 = accept_base64;
+    /* insert our filter just before the C2 core filter */
+    ap_remove_output_filter_byhandle(c2->output_filters, "H2_C2_NET_OUT");
+    ap_add_output_filter("H2_C2_WS_OUT", ws_ctx, NULL, c2);
+    ap_add_output_filter("H2_C2_NET_OUT", NULL, NULL, c2);
+    /* Mark the connection as being an Upgrade, with some special handling
+     * since the request needs an EOS, without the stream being closed  */
+    conn_ctx->is_upgrade = 1;
+
+    return wsreq;
+}
+
+static apr_bucket *make_valid_resp(conn_rec *c2, int status,
+                                   apr_table_t *headers, apr_table_t *notes)
+{
+    apr_table_t *nheaders, *nnotes;
+
+    ap_assert(headers);
+    nheaders = apr_table_clone(c2->pool, headers);
+    apr_table_unset(nheaders, "Connection");
+    apr_table_unset(nheaders, "Upgrade");
+    apr_table_unset(nheaders, "Sec-WebSocket-Accept");
+    nnotes = notes? apr_table_clone(c2->pool, notes) :
+                    apr_table_make(c2->pool, 10);
+#if AP_HAS_RESPONSE_BUCKETS
+    return ap_bucket_response_create(status, NULL, nheaders, nnotes,
+                                     c2->pool, c2->bucket_alloc);
+#else
+    return h2_bucket_headers_create(c2->bucket_alloc,
+                                    h2_headers_create(status, nheaders,
+                                                      nnotes, 0, c2->pool));
+#endif
+}
+
+static apr_bucket *make_invalid_resp(conn_rec *c2, int status,
+                                     apr_table_t *notes)
+{
+    apr_table_t *nheaders, *nnotes;
+
+    nheaders = apr_table_make(c2->pool, 10);
+    apr_table_setn(nheaders, "Content-Length", "0");
+    nnotes = notes? apr_table_clone(c2->pool, notes) :
+                    apr_table_make(c2->pool, 10);
+#if AP_HAS_RESPONSE_BUCKETS
+    return ap_bucket_response_create(status, NULL, nheaders, nnotes,
+                                     c2->pool, c2->bucket_alloc);
+#else
+    return h2_bucket_headers_create(c2->bucket_alloc,
+                                    h2_headers_create(status, nheaders,
+                                                      nnotes, 0, c2->pool));
+#endif
+}
+
+static void ws_handle_resp(conn_rec *c2, h2_conn_ctx_t *conn_ctx,
+                           struct ws_filter_ctx *ws_ctx, apr_bucket *b)
+{
+#if AP_HAS_RESPONSE_BUCKETS
+    ap_bucket_response *resp = b->data;
+#else /* AP_HAS_RESPONSE_BUCKETS */
+    h2_headers *resp = h2_bucket_headers_get(b);
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
+    apr_bucket *b_override = NULL;
+    int is_final = 0;
+    int override_body = 0;
+
+    if (ws_ctx->has_final_response) {
+        /* already did, nop */
+        return;
+    }
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+                  "h2_c2(%s-%d): H2_C2_WS_OUT inspecting response %d",
+                  conn_ctx->id, conn_ctx->stream_id, resp->status);
+    if (resp->status == HTTP_SWITCHING_PROTOCOLS) {
+        /* The resource agreed to switch protocol. But this is only valid
+         * if it send back the correct Sec-WebSocket-Accept header value */
+        const char *hd = apr_table_get(resp->headers, "Sec-WebSocket-Accept");
+        if (hd && !strcmp(ws_ctx->ws_accept_base64, hd)) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+                          "h2_c2(%s-%d): websocket CONNECT, valid 101 Upgrade"
+                          ", converting to 200 response",
+                          conn_ctx->id, conn_ctx->stream_id);
+            b_override = make_valid_resp(c2, HTTP_OK, resp->headers, resp->notes);
+            is_final = 1;
+        }
+        else {
+            if (!hd) {
+                /* This points to someone being confused */
+                ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c2, APLOGNO(10462)
+                              "h2_c2(%s-%d): websocket CONNECT, got 101 response "
+                              "without Sec-WebSocket-Accept header",
+                              conn_ctx->id, conn_ctx->stream_id);
+            }
+            else {
+                /* This points to a bug, either in our WebSockets negotiation
+                 * or in the request processings implementation of WebSockets */
+                ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c2, APLOGNO(10463)
+                              "h2_c2(%s-%d): websocket CONNECT, 101 response "
+                              "without 'Sec-WebSocket-Accept: %s' but expected %s",
+                              conn_ctx->id, conn_ctx->stream_id, hd,
+                              ws_ctx->ws_accept_base64);
+            }
+            b_override = make_invalid_resp(c2, HTTP_BAD_GATEWAY, resp->notes);
+            override_body = is_final = 1;
+        }
+    }
+    else if (resp->status < 200) {
+        /* other intermediate response, pass through */
+    }
+    else if (resp->status < 300) {
+        /* Failure, we might be talking to a plain http resource */
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+                      "h2_c2(%s-%d): websocket CONNECT, invalid response %d",
+                      conn_ctx->id, conn_ctx->stream_id, resp->status);
+        b_override = make_invalid_resp(c2, HTTP_BAD_GATEWAY, resp->notes);
+        override_body = is_final = 1;
+    }
+    else {
+        /* error response, pass through. */
+        ws_ctx->has_final_response = 1;
+    }
+
+    if (b_override) {
+        APR_BUCKET_INSERT_BEFORE(b, b_override);
+        apr_bucket_delete(b);
+        b = b_override;
+    }
+    if (override_body) {
+        APR_BUCKET_INSERT_AFTER(b, apr_bucket_eos_create(c2->bucket_alloc));
+        ws_ctx->override_body = 1;
+    }
+    if (is_final) {
+        ws_ctx->has_final_response = 1;
+        conn_ctx->has_final_response = 1;
+    }
+}
+
+static apr_status_t h2_c2_ws_filter_out(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+    struct ws_filter_ctx *ws_ctx = f->ctx;
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+    apr_bucket *b, *bnext;
+
+    ap_assert(conn_ctx);
+    if (ws_ctx->override_body) {
+        /* We have overridden the original response and also its body.
+         * If this filter is called again, we signal a hard abort to
+         * allow processing to terminate at the earliest. */
+        f->c->aborted = 1;
+        return APR_ECONNABORTED;
+    }
+
+    /* Inspect the brigade, looking for RESPONSE/HEADER buckets.
+     * Remember, this filter is only active for client websocket CONNECT
+     * requests that we translated to an internal GET with websocket
+     * headers.
+     * We inspect the repsone to see if the internal resource actually
+     * agrees to talk websocket or is "just" a normal HTTP resource that
+     * ignored the websocket request headers. */
+    for (b = APR_BRIGADE_FIRST(bb);
+         b != APR_BRIGADE_SENTINEL(bb);
+         b = bnext)
+    {
+        bnext = APR_BUCKET_NEXT(b);
+        if (APR_BUCKET_IS_METADATA(b)) {
+#if AP_HAS_RESPONSE_BUCKETS
+            if (AP_BUCKET_IS_RESPONSE(b)) {
+#else
+            if (H2_BUCKET_IS_HEADERS(b)) {
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
+                ws_handle_resp(f->c, conn_ctx, ws_ctx, b);
+                continue;
+            }
+        }
+        else if (ws_ctx->override_body) {
+            apr_bucket_delete(b);
+        }
+    }
+    return ap_pass_brigade(f->next, bb);
+}
+
+void h2_ws_register_hooks(void)
+{
+    c2_ws_out_filter_handle =
+        ap_register_output_filter("H2_C2_WS_OUT", h2_c2_ws_filter_out,
+                                  NULL, AP_FTYPE_NETWORK);
+}

Added: httpd/httpd/trunk/modules/http2/h2_ws.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_ws.h?rev=1910507&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_ws.h (added)
+++ httpd/httpd/trunk/modules/http2/h2_ws.h Tue Jun 20 12:01:09 2023
@@ -0,0 +1,35 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_ws__
+#define __mod_h2__h2_ws__
+
+#include "h2.h"
+
+/**
+ * Rewrite a websocket request.
+ *
+ * @param req the h2 request to rewrite
+ * @param conn the connection to process the request on
+ * @param no_body != 0 iff the request is known to have no body
+ * @return the websocket request for internal submit
+ */
+const h2_request *h2_ws_rewrite_request(const h2_request *req,
+                                        conn_rec *c2, int no_body);
+
+void h2_ws_register_hooks(void);
+
+#endif /* defined(__mod_h2__h2_ws__) */

Modified: httpd/httpd/trunk/modules/http2/mod_http2.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_http2.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_http2.c (original)
+++ httpd/httpd/trunk/modules/http2/mod_http2.c Tue Jun 20 12:01:09 2023
@@ -42,6 +42,7 @@
 #include "h2_switch.h"
 #include "h2_version.h"
 #include "h2_bucket_beam.h"
+#include "h2_ws.h"
 
 
 static void h2_hooks(apr_pool_t *pool);
@@ -199,6 +200,7 @@ static void h2_hooks(apr_pool_t *pool)
     h2_c1_register_hooks();
     h2_switch_register_hooks();
     h2_c2_register_hooks();
+    h2_ws_register_hooks();
 
     /* Setup subprocess env for certain variables
      */

Modified: httpd/httpd/trunk/modules/http2/mod_http2.dsp
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_http2.dsp?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_http2.dsp (original)
+++ httpd/httpd/trunk/modules/http2/mod_http2.dsp Tue Jun 20 12:01:09 2023
@@ -173,6 +173,10 @@ SOURCE=./h2_workers.c
 # End Source File
 # Begin Source File
 
+SOURCE=./h2_ws.c
+# End Source File
+# Begin Source File
+
 SOURCE=./mod_http2.c
 # End Source File
 # Begin Source File

Modified: httpd/httpd/trunk/modules/proxy/proxy_util.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/proxy/proxy_util.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/proxy/proxy_util.c (original)
+++ httpd/httpd/trunk/modules/proxy/proxy_util.c Tue Jun 20 12:01:09 2023
@@ -21,6 +21,7 @@
 #include "apr_version.h"
 #include "apr_strings.h"
 #include "apr_hash.h"
+#include "http_core.h"
 #include "proxy_util.h"
 #include "ajp.h"
 #include "scgi.h"
@@ -4871,7 +4872,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_tun
 {
     apr_status_t rv;
     conn_rec *c_i = r->connection;
-    apr_interval_time_t timeout = -1;
+    apr_interval_time_t client_timeout = -1, origin_timeout = -1;
     proxy_tunnel_rec *tunnel;
 
     *ptunnel = NULL;
@@ -4898,9 +4899,16 @@ PROXY_DECLARE(apr_status_t) ap_proxy_tun
     tunnel->client->bb = apr_brigade_create(c_i->pool, c_i->bucket_alloc);
     tunnel->client->pfd = &APR_ARRAY_PUSH(tunnel->pfds, apr_pollfd_t);
     tunnel->client->pfd->p = r->pool;
-    tunnel->client->pfd->desc_type = APR_POLL_SOCKET;
-    tunnel->client->pfd->desc.s = ap_get_conn_socket(c_i);
+    tunnel->client->pfd->desc_type = APR_NO_DESC;
+    rv = ap_get_pollfd_from_conn(tunnel->client->c,
+                                 tunnel->client->pfd, &client_timeout);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
     tunnel->client->pfd->client_data = tunnel->client;
+    if (tunnel->client->pfd->desc_type == APR_POLL_SOCKET) {
+        apr_socket_opt_set(tunnel->client->pfd->desc.s, APR_SO_NONBLOCK, 1);
+    }
 
     tunnel->origin->c = c_o;
     tunnel->origin->name = "origin";
@@ -4910,17 +4918,12 @@ PROXY_DECLARE(apr_status_t) ap_proxy_tun
     tunnel->origin->pfd->desc_type = APR_POLL_SOCKET;
     tunnel->origin->pfd->desc.s = ap_get_conn_socket(c_o);
     tunnel->origin->pfd->client_data = tunnel->origin;
+    apr_socket_timeout_get(tunnel->origin->pfd->desc.s, &origin_timeout);
+    apr_socket_opt_set(tunnel->origin->pfd->desc.s, APR_SO_NONBLOCK, 1);
 
     /* Defaults to the biggest timeout of both connections */
-    apr_socket_timeout_get(tunnel->client->pfd->desc.s, &timeout);
-    apr_socket_timeout_get(tunnel->origin->pfd->desc.s, &tunnel->timeout);
-    if (timeout >= 0 && (tunnel->timeout < 0 || tunnel->timeout < timeout)) {
-        tunnel->timeout = timeout;
-    }
-
-    /* We should be nonblocking from now on the sockets */
-    apr_socket_opt_set(tunnel->client->pfd->desc.s, APR_SO_NONBLOCK, 1);
-    apr_socket_opt_set(tunnel->origin->pfd->desc.s, APR_SO_NONBLOCK, 1);
+    tunnel->timeout = (origin_timeout >= 0 && origin_timeout > client_timeout)?
+                      origin_timeout : client_timeout;
 
     /* Bidirectional non-HTTP stream will confuse mod_reqtimeoout */
     ap_remove_input_filter_byhandle(c_i->input_filters, "reqtimeout");
@@ -4938,14 +4941,43 @@ PROXY_DECLARE(apr_status_t) ap_proxy_tun
         tunnel->nohalfclose = 1;
     }
 
-    /* Start with POLLOUT and let ap_proxy_tunnel_run() schedule both
-     * directions when there are no output data pending (anymore).
-     */
-    tunnel->client->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
-    tunnel->origin->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
-    if ((rv = apr_pollset_add(tunnel->pollset, tunnel->client->pfd))
-            || (rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd))) {
-        return rv;
+    if (tunnel->client->pfd->desc_type == APR_POLL_SOCKET) {
+        /* Both ends are sockets, the poll strategy is:
+         * - poll both sides POLLOUT
+         * - when one side is writable, remove the POLLOUT
+         *   and add POLLIN to the other side.
+         * - tunnel arriving data, remove POLLIN from the source
+         *   again and add POLLOUT to the receiving side
+         * - on EOF on read, remove the POLLIN from that side
+         * Repeat until both sides are down */
+        tunnel->client->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
+        tunnel->origin->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
+        if ((rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd)) ||
+            (rv = apr_pollset_add(tunnel->pollset, tunnel->client->pfd))) {
+            return rv;
+        }
+    }
+    else if (tunnel->client->pfd->desc_type == APR_POLL_FILE) {
+        /* Input is a PIPE fd, the poll strategy is:
+         * - always POLLIN on origin
+         * - use socket strategy described above for client only
+         * otherwise the same
+         */
+        tunnel->client->pfd->reqevents = 0;
+        tunnel->origin->pfd->reqevents = APR_POLLIN | APR_POLLHUP |
+                                         APR_POLLOUT | APR_POLLERR;
+        if ((rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd))) {
+            return rv;
+        }
+    }
+    else {
+        /* input is already closed, unsual, but we know nothing about
+         * the tunneled protocol. */
+        tunnel->client->down_in = 1;
+        tunnel->origin->pfd->reqevents = APR_POLLIN | APR_POLLHUP;
+        if ((rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd))) {
+            return rv;
+        }
     }
 
     *ptunnel = tunnel;
@@ -5054,7 +5086,23 @@ static int proxy_tunnel_transfer(proxy_t
         }
 
         del_pollset(tunnel->pollset, in->pfd, APR_POLLIN);
-        add_pollset(tunnel->pollset, out->pfd, APR_POLLOUT);
+        if (out->pfd->desc_type == APR_POLL_SOCKET) {
+            /* if the output is a SOCKET, we can stop polling the input
+             * until the output signals POLLOUT again. */
+            add_pollset(tunnel->pollset, out->pfd, APR_POLLOUT);
+        }
+        else {
+            /* We can't use POLLOUT in this direction for the only
+             * APR_POLL_FILE case we have so far (mod_h2's "signal" pipe),
+             * we assume that the client's ouput filters chain will block/flush
+             * if necessary (i.e. no pending data), hence that the origin
+             * is EOF when reaching here. This direction is over. */
+            ap_assert(in->down_in && APR_STATUS_IS_EOF(rv));
+            ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, tunnel->r,
+                          "proxy: %s: %s write shutdown",
+                          tunnel->scheme, out->name);
+            out->down_out = 1;
+        }
     }
 
     return OK;

Modified: httpd/httpd/trunk/server/core.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/server/core.c?rev=1910507&r1=1910506&r2=1910507&view=diff
==============================================================================
--- httpd/httpd/trunk/server/core.c (original)
+++ httpd/httpd/trunk/server/core.c Tue Jun 20 12:01:09 2023
@@ -92,6 +92,7 @@
 APR_HOOK_STRUCT(
     APR_HOOK_LINK(get_mgmt_items)
     APR_HOOK_LINK(insert_network_bucket)
+    APR_HOOK_LINK(get_pollfd_from_conn)
 )
 
 AP_IMPLEMENT_HOOK_RUN_ALL(int, get_mgmt_items,
@@ -103,6 +104,11 @@ AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t
                              apr_socket_t *socket),
                             (c, bb, socket), AP_DECLINED)
 
+AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t, get_pollfd_from_conn,
+                            (conn_rec *c, struct apr_pollfd_t *pfd,
+                             apr_interval_time_t *ptimeout),
+                              (c, pfd, ptimeout), APR_ENOTIMPL)
+
 /* Server core module... This module provides support for really basic
  * server operations, including options and commands which control the
  * operation of other modules.  Consider this the bureaucracy module.
@@ -5971,6 +5977,28 @@ static int core_upgrade_storage(request_
     return DECLINED;
 }
 
+static apr_status_t core_get_pollfd_from_conn(conn_rec *c,
+                                              struct apr_pollfd_t *pfd,
+                                              apr_interval_time_t *ptimeout)
+{
+    if (c && !c->master) {
+        pfd->desc_type = APR_POLL_SOCKET;
+        pfd->desc.s = ap_get_conn_socket(c);
+        if (ptimeout) {
+            apr_socket_timeout_get(pfd->desc.s, ptimeout);
+        }
+        return APR_SUCCESS;
+    }
+    return APR_ENOTIMPL;
+}
+
+AP_CORE_DECLARE(apr_status_t) ap_get_pollfd_from_conn(conn_rec *c,
+                                      struct apr_pollfd_t *pfd,
+                                      apr_interval_time_t *ptimeout)
+{
+    return ap_run_get_pollfd_from_conn(c, pfd, ptimeout);
+}
+
 static void register_hooks(apr_pool_t *p)
 {
     errorlog_hash = apr_hash_make(p);
@@ -6016,6 +6044,8 @@ static void register_hooks(apr_pool_t *p
     ap_hook_open_htaccess(ap_open_htaccess, NULL, NULL, APR_HOOK_REALLY_LAST);
     ap_hook_optional_fn_retrieve(core_optional_fn_retrieve, NULL, NULL,
                                  APR_HOOK_MIDDLE);
+    ap_hook_get_pollfd_from_conn(core_get_pollfd_from_conn, NULL, NULL,
+                                 APR_HOOK_REALLY_LAST);
 
     ap_hook_input_pending(ap_filter_input_pending, NULL, NULL,
                           APR_HOOK_MIDDLE);

Propchange: httpd/httpd/trunk/test/clients/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Tue Jun 20 12:01:09 2023
@@ -0,0 +1,3 @@
+.deps
+h2ws
+Makefile

Added: httpd/httpd/trunk/test/clients/.gitignore
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/test/clients/.gitignore?rev=1910507&view=auto
==============================================================================
--- httpd/httpd/trunk/test/clients/.gitignore (added)
+++ httpd/httpd/trunk/test/clients/.gitignore Tue Jun 20 12:01:09 2023
@@ -0,0 +1 @@
+h2ws
\ No newline at end of file

Added: httpd/httpd/trunk/test/clients/Makefile.in
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/test/clients/Makefile.in?rev=1910507&view=auto
==============================================================================
--- httpd/httpd/trunk/test/clients/Makefile.in (added)
+++ httpd/httpd/trunk/test/clients/Makefile.in Tue Jun 20 12:01:09 2023
@@ -0,0 +1,20 @@
+DISTCLEAN_TARGETS = h2ws
+
+CLEAN_TARGETS = h2ws
+
+bin_PROGRAMS = h2ws
+TARGETS  = $(bin_PROGRAMS)
+
+PROGRAM_LDADD        = $(UTIL_LDFLAGS) $(PROGRAM_DEPENDENCIES) $(EXTRA_LIBS) $(AP_LIBS)
+PROGRAM_DEPENDENCIES =
+
+include $(top_builddir)/build/rules.mk
+
+h2ws.lo: h2ws.c
+	$(LIBTOOL) --mode=compile $(CC) $(ab_CFLAGS) $(ALL_CFLAGS) $(ALL_CPPFLAGS) \
+	    $(ALL_INCLUDES) $(PICFLAGS) $(LTCFLAGS) -c $< && touch $@
+h2ws_OBJECTS = h2ws.lo
+h2ws_LDADD = -lnghttp2
+h2ws: $(h2ws_OBJECTS)
+	$(LIBTOOL) --mode=link $(CC) $(ALL_CFLAGS) $(PILDFLAGS) \
+	    $(LT_LDFLAGS) $(ALL_LDFLAGS) -o $@ $(h2ws_LTFLAGS) $(h2ws_OBJECTS) $(h2ws_LDADD)