diff -r -N -u a/modules/http2/config2.m4 b/modules/http2/config2.m4
--- a/modules/http2/config2.m4	2019-03-13 16:00:57.000000000 +0100
+++ b/modules/http2/config2.m4	2024-10-30 21:40:01.059958617 +0100
@@ -19,26 +19,25 @@
 dnl #  list of module object files
 http2_objs="dnl
 mod_http2.lo dnl
-h2_alt_svc.lo dnl
 h2_bucket_beam.lo dnl
 h2_bucket_eos.lo dnl
+h2_c1.lo dnl
+h2_c1_io.lo dnl
+h2_c2.lo dnl
+h2_c2_filter.lo dnl
 h2_config.lo dnl
-h2_conn.lo dnl
-h2_conn_io.lo dnl
-h2_ctx.lo dnl
-h2_filter.lo dnl
-h2_from_h1.lo dnl
-h2_h2.lo dnl
+h2_conn_ctx.lo dnl
 h2_headers.lo dnl
 h2_mplx.lo dnl
+h2_protocol.lo dnl
 h2_push.lo dnl
 h2_request.lo dnl
 h2_session.lo dnl
 h2_stream.lo dnl
 h2_switch.lo dnl
-h2_task.lo dnl
 h2_util.lo dnl
 h2_workers.lo dnl
+h2_ws.lo dnl
 "
 
 dnl
@@ -163,6 +162,12 @@
 dnl # nghttp2 >= 1.15.0: get/set stream window sizes
       AC_CHECK_FUNCS([nghttp2_session_get_stream_local_window_size], 
         [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_LOCAL_WIN_SIZE"])], [])
+dnl # nghttp2 >= 1.15.0: don't keep info on closed streams
+      AC_CHECK_FUNCS([nghttp2_option_set_no_closed_streams],
+        [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_NO_CLOSED_STREAMS"])], [])
+dnl # nghttp2 >= 1.50.0: rfc9113 leading/trailing whitespec strictness
+      AC_CHECK_FUNCS([nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation],
+        [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_RFC9113_STRICTNESS"])], [])
     else
       AC_MSG_WARN([nghttp2 version is too old])
     fi
diff -r -N -u a/modules/http2/h2_alt_svc.c b/modules/http2/h2_alt_svc.c
--- a/modules/http2/h2_alt_svc.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_alt_svc.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,132 +0,0 @@
-/* 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 <apr_strings.h>
-#include <httpd.h>
-#include <http_core.h>
-#include <http_connection.h>
-#include <http_protocol.h>
-#include <http_ssl.h>
-#include <http_log.h>
-
-#include "h2_private.h"
-#include "h2_alt_svc.h"
-#include "h2_ctx.h"
-#include "h2_config.h"
-#include "h2_h2.h"
-#include "h2_util.h"
-
-static int h2_alt_svc_handler(request_rec *r);
-
-void h2_alt_svc_register_hooks(void)
-{
-    ap_hook_post_read_request(h2_alt_svc_handler, NULL, NULL, APR_HOOK_MIDDLE);
-}
-
-/**
- * Parse an Alt-Svc specifier as described in "HTTP Alternative Services"
- * (https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-04)
- * with the following changes:
- * - do not percent encode token values
- * - do not use quotation marks
- */
-h2_alt_svc *h2_alt_svc_parse(const char *s, apr_pool_t *pool)
-{
-    const char *sep = ap_strchr_c(s, '=');
-    if (sep) {
-        const char *alpn = apr_pstrmemdup(pool, s, (apr_size_t)(sep - s));
-        const char *host = NULL;
-        int port = 0;
-        s = sep + 1;
-        sep = ap_strchr_c(s, ':');  /* mandatory : */
-        if (sep) {
-            if (sep != s) {    /* optional host */
-                host = apr_pstrmemdup(pool, s, (apr_size_t)(sep - s));
-            }
-            s = sep + 1;
-            if (*s) {          /* must be a port number */
-                port = (int)apr_atoi64(s);
-                if (port > 0 && port < (0x1 << 16)) {
-                    h2_alt_svc *as = apr_pcalloc(pool, sizeof(*as));
-                    as->alpn = alpn;
-                    as->host = host;
-                    as->port = port;
-                    return as;
-                }
-            }
-        }
-    }
-    return NULL;
-}
-
-#define h2_alt_svc_IDX(list, i) ((h2_alt_svc**)(list)->elts)[i]
-
-static int h2_alt_svc_handler(request_rec *r)
-{
-    apr_array_header_t *alt_svcs;
-    int i;
-    
-    if (r->connection->keepalives > 0) {
-        /* Only announce Alt-Svc on the first response */
-        return DECLINED;
-    }
-    
-    if (h2_ctx_rget(r)) {
-        return DECLINED;
-    }
-    
-    alt_svcs = h2_config_alt_svcs(r);
-    if (r->hostname && alt_svcs && alt_svcs->nelts > 0) {
-        const char *alt_svc_used = apr_table_get(r->headers_in, "Alt-Svc-Used");
-        if (!alt_svc_used) {
-            /* We have alt-svcs defined and client is not already using
-             * one, announce the services that were configured and match. 
-             * The security of this connection determines if we allow
-             * other host names or ports only.
-             */
-            const char *alt_svc = "";
-            const char *svc_ma = "";
-            int secure = ap_ssl_conn_is_ssl(r->connection);
-            int ma = h2_config_rgeti(r, H2_CONF_ALT_SVC_MAX_AGE);
-            if (ma >= 0) {
-                svc_ma = apr_psprintf(r->pool, "; ma=%d", ma);
-            }
-            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03043)
-                          "h2_alt_svc: announce %s for %s:%d", 
-                          (secure? "secure" : "insecure"), 
-                          r->hostname, (int)r->server->port);
-            for (i = 0; i < alt_svcs->nelts; ++i) {
-                h2_alt_svc *as = h2_alt_svc_IDX(alt_svcs, i);
-                const char *ahost = as->host;
-                if (ahost && !apr_strnatcasecmp(ahost, r->hostname)) {
-                    ahost = NULL;
-                }
-                if (secure || !ahost) {
-                    alt_svc = apr_psprintf(r->pool, "%s%s%s=\"%s:%d\"%s", 
-                                           alt_svc,
-                                           (*alt_svc? ", " : ""), as->alpn,
-                                           ahost? ahost : "", as->port,
-                                           svc_ma);
-                }
-            }
-            if (*alt_svc) {
-                apr_table_setn(r->headers_out, "Alt-Svc", alt_svc);
-            }
-        }
-    }
-    
-    return DECLINED;
-}
diff -r -N -u a/modules/http2/h2_alt_svc.h b/modules/http2/h2_alt_svc.h
--- a/modules/http2/h2_alt_svc.h	2018-02-10 16:46:12.000000000 +0100
+++ b/modules/http2/h2_alt_svc.h	1970-01-01 01:00:00.000000000 +0100
@@ -1,40 +0,0 @@
-/* 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_alt_svc__
-#define __mod_h2__h2_alt_svc__
-
-typedef struct h2_alt_svc h2_alt_svc;
-
-struct h2_alt_svc {
-    const char *alpn;
-    const char *host;
-    int port;
-};
-
-void h2_alt_svc_register_hooks(void);
-
-/**
- * Parse an Alt-Svc specifier as described in "HTTP Alternative Services"
- * (https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-04)
- * with the following changes:
- * - do not percent encode token values
- * - do not use quotation marks
- */
-h2_alt_svc *h2_alt_svc_parse(const char *s, apr_pool_t *pool);
-
-
-#endif /* defined(__mod_h2__h2_alt_svc__) */
diff -r -N -u a/modules/http2/h2_bucket_beam.c b/modules/http2/h2_bucket_beam.c
--- a/modules/http2/h2_bucket_beam.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_bucket_beam.c	2024-10-30 21:40:01.059958617 +0100
@@ -24,207 +24,84 @@
 
 #include <httpd.h>
 #include <http_protocol.h>
+#include <http_request.h>
 #include <http_log.h>
 
 #include "h2_private.h"
+#include "h2_conn_ctx.h"
+#include "h2_headers.h"
 #include "h2_util.h"
 #include "h2_bucket_beam.h"
 
-static void h2_beam_emitted(h2_bucket_beam *beam, h2_beam_proxy *proxy);
 
-#define H2_BPROXY_NEXT(e)             APR_RING_NEXT((e), link)
-#define H2_BPROXY_PREV(e)             APR_RING_PREV((e), link)
-#define H2_BPROXY_REMOVE(e)           APR_RING_REMOVE((e), link)
-
-#define H2_BPROXY_LIST_INIT(b)        APR_RING_INIT(&(b)->list, h2_beam_proxy, link);
-#define H2_BPROXY_LIST_SENTINEL(b)    APR_RING_SENTINEL(&(b)->list, h2_beam_proxy, link)
-#define H2_BPROXY_LIST_EMPTY(b)       APR_RING_EMPTY(&(b)->list, h2_beam_proxy, link)
-#define H2_BPROXY_LIST_FIRST(b)       APR_RING_FIRST(&(b)->list)
-#define H2_BPROXY_LIST_LAST(b)	      APR_RING_LAST(&(b)->list)
-#define H2_PROXY_BLIST_INSERT_HEAD(b, e) do {				\
-	h2_beam_proxy *ap__b = (e);                                        \
-	APR_RING_INSERT_HEAD(&(b)->list, ap__b, h2_beam_proxy, link);	\
+#define H2_BLIST_INIT(b)        APR_RING_INIT(&(b)->list, apr_bucket, link);
+#define H2_BLIST_SENTINEL(b)    APR_RING_SENTINEL(&(b)->list, apr_bucket, link)
+#define H2_BLIST_EMPTY(b)       APR_RING_EMPTY(&(b)->list, apr_bucket, link)
+#define H2_BLIST_FIRST(b)       APR_RING_FIRST(&(b)->list)
+#define H2_BLIST_LAST(b)	APR_RING_LAST(&(b)->list)
+#define H2_BLIST_INSERT_HEAD(b, e) do {				\
+	apr_bucket *ap__b = (e);                                        \
+	APR_RING_INSERT_HEAD(&(b)->list, ap__b, apr_bucket, link);	\
     } while (0)
-#define H2_BPROXY_LIST_INSERT_TAIL(b, e) do {				\
-	h2_beam_proxy *ap__b = (e);					\
-	APR_RING_INSERT_TAIL(&(b)->list, ap__b, h2_beam_proxy, link);	\
+#define H2_BLIST_INSERT_TAIL(b, e) do {				\
+	apr_bucket *ap__b = (e);					\
+	APR_RING_INSERT_TAIL(&(b)->list, ap__b, apr_bucket, link);	\
     } while (0)
-#define H2_BPROXY_LIST_CONCAT(a, b) do {					\
-        APR_RING_CONCAT(&(a)->list, &(b)->list, h2_beam_proxy, link);	\
+#define H2_BLIST_CONCAT(a, b) do {					\
+        APR_RING_CONCAT(&(a)->list, &(b)->list, apr_bucket, link);	\
     } while (0)
-#define H2_BPROXY_LIST_PREPEND(a, b) do {					\
-        APR_RING_PREPEND(&(a)->list, &(b)->list, h2_beam_proxy, link);	\
+#define H2_BLIST_PREPEND(a, b) do {					\
+        APR_RING_PREPEND(&(a)->list, &(b)->list, apr_bucket, link);	\
     } while (0)
 
 
-/*******************************************************************************
- * beam bucket with reference to beam and bucket it represents
- ******************************************************************************/
+static int buffer_is_empty(h2_bucket_beam *beam);
+static apr_off_t get_buffered_data_len(h2_bucket_beam *beam);
 
-const apr_bucket_type_t h2_bucket_type_beam;
-
-#define H2_BUCKET_IS_BEAM(e)     (e->type == &h2_bucket_type_beam)
-
-struct h2_beam_proxy {
-    apr_bucket_refcount refcount;
-    APR_RING_ENTRY(h2_beam_proxy) link;
-    h2_bucket_beam *beam;
-    apr_bucket *bsender;
-    apr_size_t n;
-};
-
-static const char Dummy = '\0';
-
-static apr_status_t beam_bucket_read(apr_bucket *b, const char **str, 
-                                     apr_size_t *len, apr_read_type_e block)
-{
-    h2_beam_proxy *d = b->data;
-    if (d->bsender) {
-        const char *data;
-        apr_status_t status = apr_bucket_read(d->bsender, &data, len, block);
-        if (status == APR_SUCCESS) {
-            *str = data + b->start;
-            *len = b->length;
-        }
-        return status;
-    }
-    *str = &Dummy;
-    *len = 0;
-    return APR_ECONNRESET;
-}
-
-static void beam_bucket_destroy(void *data)
+static int h2_blist_count(h2_blist *blist)
 {
-    h2_beam_proxy *d = data;
-
-    if (apr_bucket_shared_destroy(d)) {
-        /* When the beam gets destroyed before this bucket, it will
-         * NULLify its reference here. This is not protected by a mutex,
-         * so it will not help with race conditions.
-         * But it lets us shut down memory pool with circulare beam
-         * references. */
-        if (d->beam) {
-            h2_beam_emitted(d->beam, d);
-        }
-        apr_bucket_free(d);
-    }
-}
-
-static apr_bucket * h2_beam_bucket_make(apr_bucket *b, 
-                                        h2_bucket_beam *beam,
-                                        apr_bucket *bsender, apr_size_t n)
-{
-    h2_beam_proxy *d;
-
-    d = apr_bucket_alloc(sizeof(*d), b->list);
-    H2_BPROXY_LIST_INSERT_TAIL(&beam->proxies, d);
-    d->beam = beam;
-    d->bsender = bsender;
-    d->n = n;
-    
-    b = apr_bucket_shared_make(b, d, 0, bsender? bsender->length : 0);
-    b->type = &h2_bucket_type_beam;
-
-    return b;
-}
-
-static apr_bucket *h2_beam_bucket_create(h2_bucket_beam *beam,
-                                         apr_bucket *bsender,
-                                         apr_bucket_alloc_t *list,
-                                         apr_size_t n)
-{
-    apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
-
-    APR_BUCKET_INIT(b);
-    b->free = apr_bucket_free;
-    b->list = list;
-    return h2_beam_bucket_make(b, beam, bsender, n);
-}
-
-const apr_bucket_type_t h2_bucket_type_beam = {
-    "BEAM", 5, APR_BUCKET_DATA,
-    beam_bucket_destroy,
-    beam_bucket_read,
-    apr_bucket_setaside_noop,
-    apr_bucket_shared_split,
-    apr_bucket_shared_copy
-};
-
-/*******************************************************************************
- * h2_blist, a brigade without allocations
- ******************************************************************************/
-
-static apr_array_header_t *beamers;
-
-static apr_status_t cleanup_beamers(void *dummy)
-{
-    (void)dummy;
-    beamers = NULL;
-    return APR_SUCCESS;
-}
+    apr_bucket *b;
+    int count = 0;
 
-void h2_register_bucket_beamer(h2_bucket_beamer *beamer)
-{
-    if (!beamers) {
-        apr_pool_cleanup_register(apr_hook_global_pool, NULL,
-                                  cleanup_beamers, apr_pool_cleanup_null);
-        beamers = apr_array_make(apr_hook_global_pool, 10, 
-                                 sizeof(h2_bucket_beamer*));
-    }
-    APR_ARRAY_PUSH(beamers, h2_bucket_beamer*) = beamer;
-}
-
-static apr_bucket *h2_beam_bucket(h2_bucket_beam *beam, 
-                                  apr_bucket_brigade *dest,
-                                  const apr_bucket *src)
-{
-    apr_bucket *b = NULL;
-    int i;
-    if (beamers) {
-        for (i = 0; i < beamers->nelts && b == NULL; ++i) {
-            h2_bucket_beamer *beamer;
-            
-            beamer = APR_ARRAY_IDX(beamers, i, h2_bucket_beamer*);
-            b = beamer(beam, dest, src);
-        }
+    for (b = H2_BLIST_FIRST(blist); b != H2_BLIST_SENTINEL(blist);
+         b = APR_BUCKET_NEXT(b)) {
+        ++count;
     }
-    return b;
+    return count;
 }
 
+#define H2_BEAM_LOG(beam, c, level, rv, msg, bb) \
+    do { \
+        if (APLOG_C_IS_LEVEL((c),(level))) { \
+            char buffer[4 * 1024]; \
+            apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
+            len = bb? h2_util_bb_print(buffer, bmax, "", "", bb) : 0; \
+            ap_log_cerror(APLOG_MARK, (level), rv, (c), \
+                          "BEAM[%s,%s%sdata=%ld,buckets(send/consumed)=%d/%d]: %s %s", \
+                          (beam)->name, \
+                          (beam)->aborted? "aborted," : "", \
+                          buffer_is_empty(beam)? "empty," : "", \
+                          (long)get_buffered_data_len(beam), \
+                          h2_blist_count(&(beam)->buckets_to_send), \
+                          h2_blist_count(&(beam)->buckets_consumed), \
+                          (msg), len? buffer : ""); \
+        } \
+    } while (0)
 
-/*******************************************************************************
- * bucket beam that can transport buckets across threads
- ******************************************************************************/
-
-static void mutex_leave(apr_thread_mutex_t *lock)
-{
-    apr_thread_mutex_unlock(lock);
-}
-
-static apr_status_t mutex_enter(void *ctx, h2_beam_lock *pbl)
-{
-    h2_bucket_beam *beam = ctx;
-    pbl->mutex = beam->lock;
-    pbl->leave = mutex_leave;
-    return apr_thread_mutex_lock(pbl->mutex);
-}
 
-static apr_status_t enter_yellow(h2_bucket_beam *beam, h2_beam_lock *pbl)
+static int bucket_is_mmap(apr_bucket *b)
 {
-    return mutex_enter(beam, pbl);
-}
-
-static void leave_yellow(h2_bucket_beam *beam, h2_beam_lock *pbl)
-{
-    (void)beam;
-    if (pbl->leave) {
-        pbl->leave(pbl->mutex);
-    }
+#if APR_HAS_MMAP
+    return APR_BUCKET_IS_MMAP(b);
+#else
+    /* if it is not defined as enabled, it should always be no */
+    return 0;
+#endif
 }
 
 static apr_off_t bucket_mem_used(apr_bucket *b)
 {
-    if (APR_BUCKET_IS_FILE(b)) {
+    if (APR_BUCKET_IS_FILE(b) || bucket_is_mmap(b)) {
         return 0;
     }
     else {
@@ -233,53 +110,37 @@
     }
 }
 
-static int report_consumption(h2_bucket_beam *beam, h2_beam_lock *pbl)
+static int report_consumption(h2_bucket_beam *beam, int locked)
 {
     int rv = 0;
-    apr_off_t len = beam->received_bytes - beam->cons_bytes_reported;
+    apr_off_t len = beam->recv_bytes - beam->recv_bytes_reported;
     h2_beam_io_callback *cb = beam->cons_io_cb;
      
     if (len > 0) {
         if (cb) {
             void *ctx = beam->cons_ctx;
             
-            if (pbl) leave_yellow(beam, pbl);
+            if (locked) apr_thread_mutex_unlock(beam->lock);
             cb(ctx, beam, len);
-            if (pbl) enter_yellow(beam, pbl);
+            if (locked) apr_thread_mutex_lock(beam->lock);
             rv = 1;
         }
-        beam->cons_bytes_reported += len;
+        beam->recv_bytes_reported += len;
     }
     return rv;
 }
 
-static void report_prod_io(h2_bucket_beam *beam, int force, h2_beam_lock *pbl)
-{
-    apr_off_t len = beam->sent_bytes - beam->prod_bytes_reported;
-    if (force || len > 0) {
-        h2_beam_io_callback *cb = beam->prod_io_cb; 
-        if (cb) {
-            void *ctx = beam->prod_ctx;
-            
-            leave_yellow(beam, pbl);
-            cb(ctx, beam, len);
-            enter_yellow(beam, pbl);
-        }
-        beam->prod_bytes_reported += len;
-    }
-}
-
 static apr_size_t calc_buffered(h2_bucket_beam *beam)
 {
     apr_size_t len = 0;
     apr_bucket *b;
-    for (b = H2_BLIST_FIRST(&beam->send_list); 
-         b != H2_BLIST_SENTINEL(&beam->send_list);
+    for (b = H2_BLIST_FIRST(&beam->buckets_to_send);
+         b != H2_BLIST_SENTINEL(&beam->buckets_to_send);
          b = APR_BUCKET_NEXT(b)) {
         if (b->length == ((apr_size_t)-1)) {
             /* do not count */
         }
-        else if (APR_BUCKET_IS_FILE(b)) {
+        else if (APR_BUCKET_IS_FILE(b) || bucket_is_mmap(b)) {
             /* if unread, has no real mem footprint. */
         }
         else {
@@ -289,13 +150,30 @@
     return len;
 }
 
-static void r_purge_sent(h2_bucket_beam *beam)
+static void purge_consumed_buckets(h2_bucket_beam *beam)
+{
+    apr_bucket *b;
+    /* delete all sender buckets in purge brigade, needs to be called
+     * from sender thread only */
+    while (!H2_BLIST_EMPTY(&beam->buckets_consumed)) {
+        b = H2_BLIST_FIRST(&beam->buckets_consumed);
+        if(AP_BUCKET_IS_EOR(b)) {
+          APR_BUCKET_REMOVE(b);
+          H2_BLIST_INSERT_TAIL(&beam->buckets_eor, b);
+        }
+        else {
+          apr_bucket_delete(b);
+        }
+    }
+}
+
+static void purge_eor_buckets(h2_bucket_beam *beam)
 {
     apr_bucket *b;
     /* delete all sender buckets in purge brigade, needs to be called
      * from sender thread only */
-    while (!H2_BLIST_EMPTY(&beam->purge_list)) {
-        b = H2_BLIST_FIRST(&beam->purge_list);
+    while (!H2_BLIST_EMPTY(&beam->buckets_eor)) {
+        b = H2_BLIST_FIRST(&beam->buckets_eor);
         apr_bucket_delete(b);
     }
 }
@@ -311,31 +189,10 @@
 
 static int buffer_is_empty(h2_bucket_beam *beam)
 {
-    return ((!beam->recv_buffer || APR_BRIGADE_EMPTY(beam->recv_buffer))
-            && H2_BLIST_EMPTY(&beam->send_list));
-}
-
-static apr_status_t wait_empty(h2_bucket_beam *beam, apr_read_type_e block,  
-                               apr_thread_mutex_t *lock)
-{
-    apr_status_t rv = APR_SUCCESS;
-    
-    while (!buffer_is_empty(beam) && APR_SUCCESS == rv) {
-        if (APR_BLOCK_READ != block || !lock) {
-            rv = APR_EAGAIN;
-        }
-        else if (beam->timeout > 0) {
-            rv = apr_thread_cond_timedwait(beam->change, lock, beam->timeout);
-        }
-        else {
-            rv = apr_thread_cond_wait(beam->change, lock);
-        }
-    }
-    return rv;
+    return H2_BLIST_EMPTY(&beam->buckets_to_send);
 }
 
-static apr_status_t wait_not_empty(h2_bucket_beam *beam, apr_read_type_e block,  
-                                   apr_thread_mutex_t *lock)
+static apr_status_t wait_not_empty(h2_bucket_beam *beam, conn_rec *c, apr_read_type_e block)
 {
     apr_status_t rv = APR_SUCCESS;
     
@@ -346,21 +203,24 @@
         else if (beam->closed) {
             rv = APR_EOF;
         }
-        else if (APR_BLOCK_READ != block || !lock) {
+        else if (APR_BLOCK_READ != block) {
             rv = APR_EAGAIN;
         }
         else if (beam->timeout > 0) {
-            rv = apr_thread_cond_timedwait(beam->change, lock, beam->timeout);
+            H2_BEAM_LOG(beam, c, APLOG_TRACE2, rv, "wait_not_empty, timeout", NULL);
+            rv = apr_thread_cond_timedwait(beam->change, beam->lock, beam->timeout);
         }
         else {
-            rv = apr_thread_cond_wait(beam->change, lock);
+            H2_BEAM_LOG(beam, c, APLOG_TRACE2, rv, "wait_not_empty, forever", NULL);
+            rv = apr_thread_cond_wait(beam->change, beam->lock);
         }
     }
     return rv;
 }
 
-static apr_status_t wait_not_full(h2_bucket_beam *beam, apr_read_type_e block, 
-                                  apr_size_t *pspace_left, h2_beam_lock *bl)
+static apr_status_t wait_not_full(h2_bucket_beam *beam, conn_rec *c,
+                                  apr_read_type_e block,
+                                  apr_size_t *pspace_left)
 {
     apr_status_t rv = APR_SUCCESS;
     apr_size_t left;
@@ -369,15 +229,17 @@
         if (beam->aborted) {
             rv = APR_ECONNABORTED;
         }
-        else if (block != APR_BLOCK_READ || !bl->mutex) {
+        else if (block != APR_BLOCK_READ) {
             rv = APR_EAGAIN;
         }
         else {
             if (beam->timeout > 0) {
-                rv = apr_thread_cond_timedwait(beam->change, bl->mutex, beam->timeout);
+                H2_BEAM_LOG(beam, c, APLOG_TRACE2, rv, "wait_not_full, timeout", NULL);
+                rv = apr_thread_cond_timedwait(beam->change, beam->lock, beam->timeout);
             }
             else {
-                rv = apr_thread_cond_wait(beam->change, bl->mutex);
+                H2_BEAM_LOG(beam, c, APLOG_TRACE2, rv, "wait_not_full, forever", NULL);
+                rv = apr_thread_cond_wait(beam->change, beam->lock);
             }
         }
     }
@@ -385,73 +247,6 @@
     return rv;
 }
 
-static void h2_beam_emitted(h2_bucket_beam *beam, h2_beam_proxy *proxy)
-{
-    h2_beam_lock bl;
-    apr_bucket *b, *next;
-
-    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        /* even when beam buckets are split, only the one where
-         * refcount drops to 0 will call us */
-        H2_BPROXY_REMOVE(proxy);
-        /* invoked from receiver thread, the last beam bucket for the send
-         * bucket is about to be destroyed.
-         * remove it from the hold, where it should be now */
-        if (proxy->bsender) {
-            for (b = H2_BLIST_FIRST(&beam->hold_list); 
-                 b != H2_BLIST_SENTINEL(&beam->hold_list);
-                 b = APR_BUCKET_NEXT(b)) {
-                 if (b == proxy->bsender) {
-                    break;
-                 }
-            }
-            if (b != H2_BLIST_SENTINEL(&beam->hold_list)) {
-                /* bucket is in hold as it should be, mark this one
-                 * and all before it for purging. We might have placed meta
-                 * buckets without a receiver proxy into the hold before it 
-                 * and schedule them for purging now */
-                for (b = H2_BLIST_FIRST(&beam->hold_list); 
-                     b != H2_BLIST_SENTINEL(&beam->hold_list);
-                     b = next) {
-                    next = APR_BUCKET_NEXT(b);
-                    if (b == proxy->bsender) {
-                        APR_BUCKET_REMOVE(b);
-                        H2_BLIST_INSERT_TAIL(&beam->purge_list, b);
-                        break;
-                    }
-                    else if (APR_BUCKET_IS_METADATA(b)) {
-                        APR_BUCKET_REMOVE(b);
-                        H2_BLIST_INSERT_TAIL(&beam->purge_list, b);
-                    }
-                    else {
-                        /* another data bucket before this one in hold. this
-                         * is normal since DATA buckets need not be destroyed
-                         * in order */
-                    }
-                }
-                
-                proxy->bsender = NULL;
-            }
-            else {
-                /* it should be there unless we screwed up */
-                ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, beam->send_pool, 
-                              APLOGNO(03384) "h2_beam(%d-%s): emitted bucket not "
-                              "in hold, n=%d", beam->id, beam->tag, 
-                              (int)proxy->n);
-                ap_assert(!proxy->bsender);
-            }
-        }
-        /* notify anyone waiting on space to become available */
-        if (!bl.mutex) {
-            r_purge_sent(beam);
-        }
-        else {
-            apr_thread_cond_broadcast(beam->change);
-        }
-        leave_yellow(beam, &bl);
-    }
-}
-
 static void h2_blist_cleanup(h2_blist *bl)
 {
     apr_bucket *e;
@@ -462,337 +257,203 @@
     }
 }
 
-static apr_status_t beam_close(h2_bucket_beam *beam)
+static void beam_shutdown(h2_bucket_beam *beam, apr_shutdown_how_e how)
 {
-    if (!beam->closed) {
-        beam->closed = 1;
-        apr_thread_cond_broadcast(beam->change);
+    if (!beam->pool) {
+        /* pool being cleared already */
+        return;
     }
-    return APR_SUCCESS;
-}
-
-int h2_beam_is_closed(h2_bucket_beam *beam)
-{
-    return beam->closed;
-}
 
-static int pool_register(h2_bucket_beam *beam, apr_pool_t *pool, 
-                         apr_status_t (*cleanup)(void *))
-{
-    if (pool && pool != beam->pool) {
-        apr_pool_pre_cleanup_register(pool, beam, cleanup);
-        return 1;
+    /* shutdown both receiver and sender? */
+    if (how == APR_SHUTDOWN_READWRITE) {
+        beam->cons_io_cb = NULL;
+        beam->recv_cb = NULL;
+        beam->eagain_cb = NULL;
     }
-    return 0;
-}
 
-static int pool_kill(h2_bucket_beam *beam, apr_pool_t *pool,
-                     apr_status_t (*cleanup)(void *)) {
-    if (pool && pool != beam->pool) {
-        apr_pool_cleanup_kill(pool, beam, cleanup);
-        return 1;
+    /* shutdown sender (or both)? */
+    if (how != APR_SHUTDOWN_READ) {
+        purge_consumed_buckets(beam);
+        h2_blist_cleanup(&beam->buckets_to_send);
     }
-    return 0;
-}
-
-static apr_status_t beam_recv_cleanup(void *data)
-{
-    h2_bucket_beam *beam = data;
-    /* receiver pool has gone away, clear references */
-    beam->recv_buffer = NULL;
-    beam->recv_pool = NULL;
-    return APR_SUCCESS;
 }
 
-static apr_status_t beam_send_cleanup(void *data)
+static apr_status_t beam_cleanup(void *data)
 {
     h2_bucket_beam *beam = data;
-    /* sender is going away, clear up all references to its memory */
-    r_purge_sent(beam);
-    h2_blist_cleanup(&beam->send_list);
-    report_consumption(beam, NULL);
-    while (!H2_BPROXY_LIST_EMPTY(&beam->proxies)) {
-        h2_beam_proxy *proxy = H2_BPROXY_LIST_FIRST(&beam->proxies);
-        H2_BPROXY_REMOVE(proxy);
-        proxy->beam = NULL;
-        proxy->bsender = NULL;
-    }
-    h2_blist_cleanup(&beam->purge_list);
-    h2_blist_cleanup(&beam->hold_list);
-    beam->send_pool = NULL;
+    beam_shutdown(beam, APR_SHUTDOWN_READWRITE);
+    purge_eor_buckets(beam);
+    beam->pool = NULL; /* the pool is clearing now */
     return APR_SUCCESS;
 }
 
-static void beam_set_send_pool(h2_bucket_beam *beam, apr_pool_t *pool) 
-{
-    if (beam->send_pool != pool) {
-        if (beam->send_pool && beam->send_pool != beam->pool) {
-            pool_kill(beam, beam->send_pool, beam_send_cleanup);
-            beam_send_cleanup(beam);
-        }
-        beam->send_pool = pool;
-        pool_register(beam, beam->send_pool, beam_send_cleanup);
-    }
-}
-
-static void recv_buffer_cleanup(h2_bucket_beam *beam, h2_beam_lock *bl)
-{
-    if (beam->recv_buffer && !APR_BRIGADE_EMPTY(beam->recv_buffer)) {
-        apr_bucket_brigade *bb = beam->recv_buffer;
-        apr_off_t bblen = 0;
-        
-        beam->recv_buffer = NULL;
-        apr_brigade_length(bb, 0, &bblen);
-        beam->received_bytes += bblen;
-        
-        /* need to do this unlocked since bucket destroy might 
-         * call this beam again. */
-        if (bl) leave_yellow(beam, bl);
-        apr_brigade_destroy(bb);
-        if (bl) enter_yellow(beam, bl);
-        
-        apr_thread_cond_broadcast(beam->change);
-        if (beam->cons_ev_cb) { 
-            beam->cons_ev_cb(beam->cons_ctx, beam);
-        }
-    }
-}
-
-static apr_status_t beam_cleanup(h2_bucket_beam *beam, int from_pool)
+apr_status_t h2_beam_destroy(h2_bucket_beam *beam, conn_rec *c)
 {
-    apr_status_t status = APR_SUCCESS;
-    int safe_send = (beam->owner == H2_BEAM_OWNER_SEND);
-    int safe_recv = (beam->owner == H2_BEAM_OWNER_RECV);
-    
-    /* 
-     * Owner of the beam is going away, depending on which side it owns,
-     * cleanup strategies will differ.
-     *
-     * In general, receiver holds references to memory from sender. 
-     * Clean up receiver first, if safe, then cleanup sender, if safe.
-     */
-     
-     /* When called from pool destroy, io callbacks are disabled */
-     if (from_pool) {
-         beam->cons_io_cb = NULL;
-     }
-     
-    /* When modify send is not safe, this means we still have multi-thread
-     * protection and the owner is receiving the buckets. If the sending
-     * side has not gone away, this means we could have dangling buckets
-     * in our lists that never get destroyed. This should not happen. */
-    ap_assert(safe_send || !beam->send_pool);
-    if (!H2_BLIST_EMPTY(&beam->send_list)) {
-        ap_assert(beam->send_pool);
-    }
-    
-    if (safe_recv) {
-        if (beam->recv_pool) {
-            pool_kill(beam, beam->recv_pool, beam_recv_cleanup);
-            beam->recv_pool = NULL;
-        }
-        recv_buffer_cleanup(beam, NULL);
-    }
-    else {
-        beam->recv_buffer = NULL;
-        beam->recv_pool = NULL;
-    }
-    
-    if (safe_send && beam->send_pool) {
-        pool_kill(beam, beam->send_pool, beam_send_cleanup);
-        status = beam_send_cleanup(beam);
-    }
-    
-    if (safe_recv) {
-        ap_assert(H2_BPROXY_LIST_EMPTY(&beam->proxies));
-        ap_assert(H2_BLIST_EMPTY(&beam->send_list));
-        ap_assert(H2_BLIST_EMPTY(&beam->hold_list));
-        ap_assert(H2_BLIST_EMPTY(&beam->purge_list));
+    if (beam->pool) {
+        H2_BEAM_LOG(beam, c, APLOG_TRACE2, 0, "destroy", NULL);
+        apr_pool_cleanup_run(beam->pool, beam, beam_cleanup);
     }
-    return status;
-}
-
-static apr_status_t beam_pool_cleanup(void *data)
-{
-    return beam_cleanup(data, 1);
-}
-
-apr_status_t h2_beam_destroy(h2_bucket_beam *beam)
-{
-    apr_pool_cleanup_kill(beam->pool, beam, beam_pool_cleanup);
-    return beam_cleanup(beam, 0);
+    H2_BEAM_LOG(beam, c, APLOG_TRACE2, 0, "destroyed", NULL);
+    return APR_SUCCESS;
 }
 
-apr_status_t h2_beam_create(h2_bucket_beam **pbeam, apr_pool_t *pool, 
-                            int id, const char *tag, 
-                            h2_beam_owner_t owner,
+apr_status_t h2_beam_create(h2_bucket_beam **pbeam, conn_rec *from,
+                            apr_pool_t *pool, int id, const char *tag,
                             apr_size_t max_buf_size,
                             apr_interval_time_t timeout)
 {
     h2_bucket_beam *beam;
-    apr_status_t rv = APR_SUCCESS;
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(from);
+    apr_status_t rv;
     
     beam = apr_pcalloc(pool, sizeof(*beam));
-    if (!beam) {
-        return APR_ENOMEM;
-    }
-
-    beam->id = id;
-    beam->tag = tag;
     beam->pool = pool;
-    beam->owner = owner;
-    H2_BLIST_INIT(&beam->send_list);
-    H2_BLIST_INIT(&beam->hold_list);
-    H2_BLIST_INIT(&beam->purge_list);
-    H2_BPROXY_LIST_INIT(&beam->proxies);
+    beam->from = from;
+    beam->id = id;
+    beam->name = apr_psprintf(pool, "%s-%d-%s",
+                              conn_ctx->id, id, tag);
+
+    H2_BLIST_INIT(&beam->buckets_to_send);
+    H2_BLIST_INIT(&beam->buckets_consumed);
+    H2_BLIST_INIT(&beam->buckets_eor);
     beam->tx_mem_limits = 1;
     beam->max_buf_size = max_buf_size;
     beam->timeout = timeout;
 
     rv = apr_thread_mutex_create(&beam->lock, APR_THREAD_MUTEX_DEFAULT, pool);
-    if (APR_SUCCESS == rv) {
-        rv = apr_thread_cond_create(&beam->change, pool);
-        if (APR_SUCCESS == rv) {
-            apr_pool_pre_cleanup_register(pool, beam, beam_pool_cleanup);
-            *pbeam = beam;
-        }
-    }
+    if (APR_SUCCESS != rv) goto cleanup;
+    rv = apr_thread_cond_create(&beam->change, pool);
+    if (APR_SUCCESS != rv) goto cleanup;
+    apr_pool_pre_cleanup_register(pool, beam, beam_cleanup);
+
+cleanup:
+    H2_BEAM_LOG(beam, from, APLOG_TRACE2, rv, "created", NULL);
+    *pbeam = (APR_SUCCESS == rv)? beam : NULL;
     return rv;
 }
 
 void h2_beam_buffer_size_set(h2_bucket_beam *beam, apr_size_t buffer_size)
 {
-    h2_beam_lock bl;
-    
-    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        beam->max_buf_size = buffer_size;
-        leave_yellow(beam, &bl);
-    }
+    apr_thread_mutex_lock(beam->lock);
+    beam->max_buf_size = buffer_size;
+    apr_thread_mutex_unlock(beam->lock);
 }
 
-apr_size_t h2_beam_buffer_size_get(h2_bucket_beam *beam)
+void h2_beam_set_copy_files(h2_bucket_beam * beam, int enabled)
 {
-    h2_beam_lock bl;
-    apr_size_t buffer_size = 0;
-    
-    if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
-        buffer_size = beam->max_buf_size;
-        leave_yellow(beam, &bl);
-    }
-    return buffer_size;
+    apr_thread_mutex_lock(beam->lock);
+    beam->copy_files = enabled;
+    apr_thread_mutex_unlock(beam->lock);
 }
 
-void h2_beam_timeout_set(h2_bucket_beam *beam, apr_interval_time_t timeout)
+apr_size_t h2_beam_buffer_size_get(h2_bucket_beam *beam)
 {
-    h2_beam_lock bl;
+    apr_size_t buffer_size = 0;
     
-    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        beam->timeout = timeout;
-        leave_yellow(beam, &bl);
-    }
+    apr_thread_mutex_lock(beam->lock);
+    buffer_size = beam->max_buf_size;
+    apr_thread_mutex_unlock(beam->lock);
+    return buffer_size;
 }
 
 apr_interval_time_t h2_beam_timeout_get(h2_bucket_beam *beam)
 {
-    h2_beam_lock bl;
-    apr_interval_time_t timeout = 0;
-    
-    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        timeout = beam->timeout;
-        leave_yellow(beam, &bl);
-    }
+    apr_interval_time_t timeout;
+
+    apr_thread_mutex_lock(beam->lock);
+    timeout = beam->timeout;
+    apr_thread_mutex_unlock(beam->lock);
     return timeout;
 }
 
-void h2_beam_abort(h2_bucket_beam *beam)
+void h2_beam_timeout_set(h2_bucket_beam *beam, apr_interval_time_t timeout)
 {
-    h2_beam_lock bl;
-    
-    if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
-        beam->aborted = 1;
-        r_purge_sent(beam);
-        h2_blist_cleanup(&beam->send_list);
-        report_consumption(beam, &bl);
-        apr_thread_cond_broadcast(beam->change);
-        leave_yellow(beam, &bl);
-    }
+    apr_thread_mutex_lock(beam->lock);
+    beam->timeout = timeout;
+    apr_thread_mutex_unlock(beam->lock);
 }
 
-apr_status_t h2_beam_close(h2_bucket_beam *beam)
+void h2_beam_abort(h2_bucket_beam *beam, conn_rec *c)
 {
-    h2_beam_lock bl;
-    
-    if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
-        r_purge_sent(beam);
-        beam_close(beam);
-        report_consumption(beam, &bl);
-        leave_yellow(beam, &bl);
-    }
-    return beam->aborted? APR_ECONNABORTED : APR_SUCCESS;
-}
+    apr_thread_mutex_lock(beam->lock);
+    beam->aborted = 1;
+    if (c == beam->from) {
+        /* sender aborts */
+        if (beam->send_cb) {
+            beam->send_cb(beam->send_ctx, beam);
+        }
+        if (beam->was_empty_cb && buffer_is_empty(beam)) {
+            beam->was_empty_cb(beam->was_empty_ctx, beam);
+        }
+        /* no more consumption reporting to sender */
+        report_consumption(beam, 1);
+        beam->cons_ctx = NULL;
 
-apr_status_t h2_beam_leave(h2_bucket_beam *beam)
-{
-    h2_beam_lock bl;
-    
-    if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
-        recv_buffer_cleanup(beam, &bl);
-        beam->aborted = 1;
-        beam_close(beam);
-        leave_yellow(beam, &bl);
+        beam_shutdown(beam, APR_SHUTDOWN_WRITE);
     }
-    return APR_SUCCESS;
-}
-
-apr_status_t h2_beam_wait_empty(h2_bucket_beam *beam, apr_read_type_e block)
-{
-    apr_status_t status;
-    h2_beam_lock bl;
-    
-    if ((status = enter_yellow(beam, &bl)) == APR_SUCCESS) {
-        status = wait_empty(beam, block, bl.mutex);
-        leave_yellow(beam, &bl);
+    else {
+        /* receiver aborts */
+        beam_shutdown(beam, APR_SHUTDOWN_READ);
     }
-    return status;
+    apr_thread_cond_broadcast(beam->change);
+    apr_thread_mutex_unlock(beam->lock);
 }
 
-static void move_to_hold(h2_bucket_beam *beam, 
-                         apr_bucket_brigade *sender_bb)
+void h2_beam_close(h2_bucket_beam *beam, conn_rec *c)
 {
-    apr_bucket *b;
-    while (sender_bb && !APR_BRIGADE_EMPTY(sender_bb)) {
-        b = APR_BRIGADE_FIRST(sender_bb);
-        APR_BUCKET_REMOVE(b);
-        H2_BLIST_INSERT_TAIL(&beam->send_list, b);
+    apr_thread_mutex_lock(beam->lock);
+    if (!beam->closed) {
+        /* should only be called from sender */
+        ap_assert(c == beam->from);
+        beam->closed = 1;
+        if (beam->send_cb) {
+            beam->send_cb(beam->send_ctx, beam);
+        }
+        if (beam->was_empty_cb && buffer_is_empty(beam)) {
+            beam->was_empty_cb(beam->was_empty_ctx, beam);
+        }
+        apr_thread_cond_broadcast(beam->change);
     }
+    apr_thread_mutex_unlock(beam->lock);
 }
 
-static apr_status_t append_bucket(h2_bucket_beam *beam, 
-                                  apr_bucket *b,
+static apr_status_t append_bucket(h2_bucket_beam *beam,
+                                  apr_bucket_brigade *bb,
                                   apr_read_type_e block,
                                   apr_size_t *pspace_left,
-                                  h2_beam_lock *pbl)
+                                  apr_off_t *pwritten)
 {
+    apr_bucket *b;
     const char *data;
     apr_size_t len;
-    apr_status_t status;
-    int can_beam = 0, check_len;
+    apr_status_t rv = APR_SUCCESS;
+    int can_beam = 0;
     
     (void)block;
-    (void)pbl;
     if (beam->aborted) {
-        return APR_ECONNABORTED;
+        rv = APR_ECONNABORTED;
+        goto cleanup;
     }
-    
+
+    ap_assert(beam->pool);
+
+    b = APR_BRIGADE_FIRST(bb);
     if (APR_BUCKET_IS_METADATA(b)) {
-        if (APR_BUCKET_IS_EOS(b)) {
-            beam->closed = 1;
-        }
         APR_BUCKET_REMOVE(b);
-        H2_BLIST_INSERT_TAIL(&beam->send_list, b);
-        return APR_SUCCESS;
+        apr_bucket_setaside(b, beam->pool);
+        H2_BLIST_INSERT_TAIL(&beam->buckets_to_send, b);
+        goto cleanup;
+    }
+    /* non meta bucket */
+
+    /* in case of indeterminate length, we need to read the bucket,
+     * so that it transforms itself into something stable. */
+    if (b->length == ((apr_size_t)-1)) {
+        rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+        if (rv != APR_SUCCESS) goto cleanup;
     }
-    else if (APR_BUCKET_IS_FILE(b)) {
+
+    if (APR_BUCKET_IS_FILE(b)) {
         /* For file buckets the problem is their internal readpool that
          * is used on the first read to allocate buffer/mmap.
          * Since setting aside a file bucket will de-register the
@@ -809,482 +470,414 @@
          * of open file handles and rather use a less efficient beam
          * transport. */
         apr_bucket_file *bf = b->data;
-        apr_file_t *fd = bf->fd;
-        can_beam = (bf->refcount.refcount == 1);
-        if (can_beam && beam->can_beam_fn) {
-            can_beam = beam->can_beam_fn(beam->can_beam_ctx, beam, fd);
-        }
-        check_len = !can_beam;
+        can_beam = !beam->copy_files && (bf->refcount.refcount == 1);
     }
-    else {
-        if (b->length == ((apr_size_t)-1)) {
-            const char *data2;
-            status = apr_bucket_read(b, &data2, &len, APR_BLOCK_READ);
-            if (status != APR_SUCCESS) {
-                return status;
-            }
-        }
-        check_len = 1;
+    else if (bucket_is_mmap(b)) {
+        can_beam = !beam->copy_files;
     }
-    
-    if (check_len) {
-        if (b->length > *pspace_left) {
-            apr_bucket_split(b, *pspace_left);
-        }
-        *pspace_left -= b->length;
+
+    if (b->length == 0) {
+        apr_bucket_delete(b);
+        rv = APR_SUCCESS;
+        goto cleanup;
     }
 
-    /* The fundamental problem is that reading a sender bucket from
-     * a receiver thread is a total NO GO, because the bucket might use
-     * its pool/bucket_alloc from a foreign thread and that will
-     * corrupt. */
-    status = APR_ENOTIMPL;
-    if (APR_BUCKET_IS_TRANSIENT(b)) {
-        /* this takes care of transient buckets and converts them
-         * into heap ones. Other bucket types might or might not be
-         * affected by this. */
-        status = apr_bucket_setaside(b, beam->send_pool);
+    if (!*pspace_left) {
+        rv = APR_EAGAIN;
+        goto cleanup;
     }
-    else if (APR_BUCKET_IS_HEAP(b)) {
-        /* For heap buckets read from a receiver thread is fine. The
+
+    /* bucket is accepted and added to beam->buckets_to_send */
+    if (APR_BUCKET_IS_HEAP(b)) {
+        /* For heap buckets, a read from a receiver thread is fine. The
          * data will be there and live until the bucket itself is
          * destroyed. */
-        status = APR_SUCCESS;
+        rv = apr_bucket_setaside(b, beam->pool);
+        if (rv != APR_SUCCESS) goto cleanup;
     }
-    else if (APR_BUCKET_IS_POOL(b)) {
-        /* pool buckets are bastards that register at pool cleanup
-         * to morph themselves into heap buckets. That may happen anytime,
-         * even after the bucket data pointer has been read. So at
-         * any time inside the receiver thread, the pool bucket memory
-         * may disappear. yikes. */
-        status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
-        if (status == APR_SUCCESS) {
-            apr_bucket_heap_make(b, data, len, NULL);
-        }
+    else if (can_beam && (APR_BUCKET_IS_FILE(b) || bucket_is_mmap(b))) {
+        rv = apr_bucket_setaside(b, beam->pool);
+        if (rv != APR_SUCCESS) goto cleanup;
     }
-    else if (APR_BUCKET_IS_FILE(b) && can_beam) {
-        status = apr_bucket_setaside(b, beam->send_pool);
+    else {
+        /* we know of no special shortcut to transfer the bucket to
+         * another pool without copying. So we make it a heap bucket. */
+        apr_bucket *b2;
+
+        rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+        if (rv != APR_SUCCESS) goto cleanup;
+        /* this allocates and copies data */
+        b2 = apr_bucket_heap_create(data, len, NULL, bb->bucket_alloc);
+        apr_bucket_delete(b);
+        b = b2;
+        APR_BRIGADE_INSERT_HEAD(bb, b);
     }
     
-    if (status == APR_ENOTIMPL) {
-        /* we have no knowledge about the internals of this bucket,
-         * but hope that after read, its data stays immutable for the
-         * lifetime of the bucket. (see pool bucket handling above for
-         * a counter example).
-         * We do the read while in the sender thread, so that the bucket may
-         * use pools/allocators safely. */
-        status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
-        if (status == APR_SUCCESS) {
-            status = apr_bucket_setaside(b, beam->send_pool);
-        }
+    APR_BUCKET_REMOVE(b);
+    H2_BLIST_INSERT_TAIL(&beam->buckets_to_send, b);
+    *pwritten += (apr_off_t)b->length;
+    if (b->length > *pspace_left) {
+        *pspace_left = 0;
     }
-    
-    if (status != APR_SUCCESS && status != APR_ENOTIMPL) {
-        return status;
+    else {
+        *pspace_left -= b->length;
     }
-    
-    APR_BUCKET_REMOVE(b);
-    H2_BLIST_INSERT_TAIL(&beam->send_list, b);
-    beam->sent_bytes += b->length;
-
-    return APR_SUCCESS;
-}
 
-void h2_beam_send_from(h2_bucket_beam *beam, apr_pool_t *p)
-{
-    h2_beam_lock bl;
-    /* Called from the sender thread to add buckets to the beam */
-    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        r_purge_sent(beam);
-        beam_set_send_pool(beam, p);
-        leave_yellow(beam, &bl);
-    }
+cleanup:
+    return rv;
 }
 
-apr_status_t h2_beam_send(h2_bucket_beam *beam, 
+apr_status_t h2_beam_send(h2_bucket_beam *beam, conn_rec *from,
                           apr_bucket_brigade *sender_bb, 
-                          apr_read_type_e block)
+                          apr_read_type_e block,
+                          apr_off_t *pwritten)
 {
-    apr_bucket *b;
     apr_status_t rv = APR_SUCCESS;
     apr_size_t space_left = 0;
-    h2_beam_lock bl;
+    int was_empty;
+
+    ap_assert(beam->pool);
 
     /* Called from the sender thread to add buckets to the beam */
-    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        ap_assert(beam->send_pool);
-        r_purge_sent(beam);
-        
+    apr_thread_mutex_lock(beam->lock);
+    ap_assert(beam->from == from);
+    ap_assert(sender_bb);
+    H2_BEAM_LOG(beam, from, APLOG_TRACE2, rv, "start send", sender_bb);
+    purge_consumed_buckets(beam);
+    *pwritten = 0;
+    was_empty = buffer_is_empty(beam);
+
+    space_left = calc_space_left(beam);
+    while (!APR_BRIGADE_EMPTY(sender_bb) && APR_SUCCESS == rv) {
+        rv = append_bucket(beam, sender_bb, block, &space_left, pwritten);
         if (beam->aborted) {
-            move_to_hold(beam, sender_bb);
-            rv = APR_ECONNABORTED;
+            goto cleanup;
         }
-        else if (sender_bb) {
-            int force_report = !APR_BRIGADE_EMPTY(sender_bb);
-            
-            space_left = calc_space_left(beam);
-            while (!APR_BRIGADE_EMPTY(sender_bb) && APR_SUCCESS == rv) {
-                if (space_left <= 0) {
-                    report_prod_io(beam, force_report, &bl);
-                    r_purge_sent(beam);
-                    rv = wait_not_full(beam, block, &space_left, &bl);
-                    if (APR_SUCCESS != rv) {
-                        break;
-                    }
-                }
-                b = APR_BRIGADE_FIRST(sender_bb);
-                rv = append_bucket(beam, b, block, &space_left, &bl);
+        else if (APR_EAGAIN == rv) {
+            /* bucket was not added, as beam buffer has no space left.
+             * Trigger event callbacks, so receiver can know there is something
+             * to receive before we do a conditional wait. */
+            purge_consumed_buckets(beam);
+            if (beam->send_cb) {
+                beam->send_cb(beam->send_ctx, beam);
             }
-            
-            report_prod_io(beam, force_report, &bl);
-            apr_thread_cond_broadcast(beam->change);
+            if (was_empty && beam->was_empty_cb) {
+                beam->was_empty_cb(beam->was_empty_ctx, beam);
+            }
+            rv = wait_not_full(beam, from, block, &space_left);
+            if (APR_SUCCESS != rv) {
+                break;
+            }
+            was_empty = buffer_is_empty(beam);
         }
-        report_consumption(beam, &bl);
-        leave_yellow(beam, &bl);
     }
+
+cleanup:
+    if (beam->send_cb && !buffer_is_empty(beam)) {
+        beam->send_cb(beam->send_ctx, beam);
+    }
+    if (was_empty && beam->was_empty_cb && !buffer_is_empty(beam)) {
+        beam->was_empty_cb(beam->was_empty_ctx, beam);
+    }
+    apr_thread_cond_broadcast(beam->change);
+
+    report_consumption(beam, 1);
+    if (beam->aborted) {
+        rv = APR_ECONNABORTED;
+    }
+    H2_BEAM_LOG(beam, from, APLOG_TRACE2, rv, "end send", sender_bb);
+    if(rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv) && sender_bb != NULL) {
+        apr_brigade_cleanup(sender_bb);
+    }
+    apr_thread_mutex_unlock(beam->lock);
     return rv;
 }
 
-apr_status_t h2_beam_receive(h2_bucket_beam *beam, 
+apr_status_t h2_beam_receive(h2_bucket_beam *beam,
+                             conn_rec *to,
                              apr_bucket_brigade *bb, 
                              apr_read_type_e block,
-                             apr_off_t readbytes,
-                             int *pclosed)
+                             apr_off_t readbytes)
 {
-    h2_beam_lock bl;
     apr_bucket *bsender, *brecv, *ng;
     int transferred = 0;
-    apr_status_t status = APR_SUCCESS;
+    apr_status_t rv = APR_SUCCESS;
     apr_off_t remain;
-    int transferred_buckets = 0;
+    int consumed_buckets = 0;
+
+    apr_thread_mutex_lock(beam->lock);
+    H2_BEAM_LOG(beam, to, APLOG_TRACE2, 0, "start receive", bb);
+    if (readbytes <= 0) {
+        readbytes = (apr_off_t)APR_SIZE_MAX;
+    }
+    remain = readbytes;
 
-    /* Called from the receiver thread to take buckets from the beam */
-    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        if (readbytes <= 0) {
-            readbytes = (apr_off_t)APR_SIZE_MAX;
-        }
-        remain = readbytes;
-        
 transfer:
-        if (beam->aborted) {
-            recv_buffer_cleanup(beam, &bl);
-            status = APR_ECONNABORTED;
-            goto leave;
-        }
+    if (beam->aborted) {
+        beam_shutdown(beam, APR_SHUTDOWN_READ);
+        rv = APR_ECONNABORTED;
+        goto leave;
+    }
 
-        /* transfer enough buckets from our receiver brigade, if we have one */
-        while (remain >= 0 
-               && beam->recv_buffer 
-               && !APR_BRIGADE_EMPTY(beam->recv_buffer)) {
-               
-            brecv = APR_BRIGADE_FIRST(beam->recv_buffer);
-            if (brecv->length > 0 && remain <= 0) {
-                break;
-            }            
-            APR_BUCKET_REMOVE(brecv);
-            APR_BRIGADE_INSERT_TAIL(bb, brecv);
-            remain -= brecv->length;
-            ++transferred;
+    ap_assert(beam->pool);
+
+    /* transfer from our sender brigade, transforming sender buckets to
+     * receiver ones until we have enough */
+    while (remain >= 0 && !H2_BLIST_EMPTY(&beam->buckets_to_send)) {
+
+        brecv = NULL;
+        bsender = H2_BLIST_FIRST(&beam->buckets_to_send);
+        if (bsender->length > 0 && remain <= 0) {
+            break;
         }
 
-        /* transfer from our sender brigade, transforming sender buckets to
-         * receiver ones until we have enough */
-        while (remain >= 0 && !H2_BLIST_EMPTY(&beam->send_list)) {
-               
-            brecv = NULL;
-            bsender = H2_BLIST_FIRST(&beam->send_list);            
-            if (bsender->length > 0 && remain <= 0) {
-                break;
+        if (APR_BUCKET_IS_METADATA(bsender)) {
+            /* we need a real copy into the receivers bucket_alloc */
+            if (APR_BUCKET_IS_EOS(bsender)) {
+                /* this closes the beam */
+                beam->closed = 1;
+                brecv = apr_bucket_eos_create(bb->bucket_alloc);
             }
-                        
-            if (APR_BUCKET_IS_METADATA(bsender)) {
-                if (APR_BUCKET_IS_EOS(bsender)) {
-                    brecv = apr_bucket_eos_create(bb->bucket_alloc);
-                    beam->close_sent = 1;
-                }
-                else if (APR_BUCKET_IS_FLUSH(bsender)) {
-                    brecv = apr_bucket_flush_create(bb->bucket_alloc);
-                }
-                else if (AP_BUCKET_IS_ERROR(bsender)) {
-                    ap_bucket_error *eb = (ap_bucket_error *)bsender;
-                    brecv = ap_bucket_error_create(eb->status, eb->data,
-                                                    bb->p, bb->bucket_alloc);
-                }
+            else if (APR_BUCKET_IS_FLUSH(bsender)) {
+                brecv = apr_bucket_flush_create(bb->bucket_alloc);
             }
-            else if (bsender->length == 0) {
-                APR_BUCKET_REMOVE(bsender);
-                H2_BLIST_INSERT_TAIL(&beam->hold_list, bsender);
-                continue;
+#if AP_HAS_RESPONSE_BUCKETS
+            else if (AP_BUCKET_IS_RESPONSE(bsender)) {
+                brecv = ap_bucket_response_clone(bsender, bb->p, bb->bucket_alloc);
             }
-            else if (APR_BUCKET_IS_FILE(bsender)) {
-                /* This is set aside into the target brigade pool so that 
-                 * any read operation messes with that pool and not 
-                 * the sender one. */
-                apr_bucket_file *f = (apr_bucket_file *)bsender->data;
-                apr_file_t *fd = f->fd;
-                int setaside = (f->readpool != bb->p);
-                
-                if (setaside) {
-                    status = apr_file_setaside(&fd, fd, bb->p);
-                    if (status != APR_SUCCESS) {
-                        goto leave;
-                    }
-                    ++beam->files_beamed;
-                }
-                ng = apr_brigade_insert_file(bb, fd, bsender->start, (apr_off_t)bsender->length, 
-                                             bb->p);
-#if APR_HAS_MMAP
-                /* disable mmap handling as this leads to segfaults when
-                 * the underlying file is changed while memory pointer has
-                 * been handed out. See also PR 59348 */
-                apr_bucket_file_enable_mmap(ng, 0);
-#endif
-                APR_BUCKET_REMOVE(bsender);
-                H2_BLIST_INSERT_TAIL(&beam->hold_list, bsender);
-
-                remain -= bsender->length;
-                beam->received_bytes += bsender->length;
-                ++transferred;
-                ++transferred_buckets;
-                continue;
+            else if (AP_BUCKET_IS_REQUEST(bsender)) {
+                brecv = ap_bucket_request_clone(bsender, bb->p, bb->bucket_alloc);
             }
-            else {
-                /* create a "receiver" standin bucket. we took care about the
-                 * underlying sender bucket and its data when we placed it into
-                 * the sender brigade.
-                 * the beam bucket will notify us on destruction that bsender is
-                 * no longer needed. */
-                brecv = h2_beam_bucket_create(beam, bsender, bb->bucket_alloc,
-                                               beam->buckets_sent++);
+            else if (AP_BUCKET_IS_HEADERS(bsender)) {
+                brecv = ap_bucket_headers_clone(bsender, bb->p, bb->bucket_alloc);
             }
-            
-            /* Place the sender bucket into our hold, to be destroyed when no
-             * receiver bucket references it any more. */
-            APR_BUCKET_REMOVE(bsender);
-            H2_BLIST_INSERT_TAIL(&beam->hold_list, bsender);
-            
-            beam->received_bytes += bsender->length;
-            ++transferred_buckets;
-            
-            if (brecv) {
-                APR_BRIGADE_INSERT_TAIL(bb, brecv);
-                remain -= brecv->length;
-                ++transferred;
+#else
+            else if (H2_BUCKET_IS_HEADERS(bsender)) {
+                brecv = h2_bucket_headers_clone(bsender, bb->p, bb->bucket_alloc);
             }
-            else {
-                /* let outside hook determine how bucket is beamed */
-                leave_yellow(beam, &bl);
-                brecv = h2_beam_bucket(beam, bb, bsender);
-                enter_yellow(beam, &bl);
-                
-                while (brecv && brecv != APR_BRIGADE_SENTINEL(bb)) {
-                    ++transferred;
-                    remain -= brecv->length;
-                    brecv = APR_BUCKET_NEXT(brecv);
-                }
+#endif /* AP_HAS_RESPONSE_BUCKETS */
+            else if (AP_BUCKET_IS_ERROR(bsender)) {
+                ap_bucket_error *eb = bsender->data;
+                brecv = ap_bucket_error_create(eb->status, eb->data,
+                                               bb->p, bb->bucket_alloc);
             }
         }
-
-        if (remain < 0) {
-            /* too much, put some back into out recv_buffer */
-            remain = readbytes;
-            for (brecv = APR_BRIGADE_FIRST(bb);
-                 brecv != APR_BRIGADE_SENTINEL(bb);
-                 brecv = APR_BUCKET_NEXT(brecv)) {
-                remain -= (beam->tx_mem_limits? bucket_mem_used(brecv) 
-                           : (apr_off_t)brecv->length);
-                if (remain < 0) {
-                    apr_bucket_split(brecv, (apr_size_t)((apr_off_t)brecv->length+remain));
-                    beam->recv_buffer = apr_brigade_split_ex(bb, 
-                                                             APR_BUCKET_NEXT(brecv), 
-                                                             beam->recv_buffer);
-                    break;
-                }
-            }
+        else if (bsender->length == 0) {
+            /* nop */
         }
-
-        if (beam->closed && buffer_is_empty(beam)) {
-            /* beam is closed and we have nothing more to receive */ 
-            if (!beam->close_sent) {
-                apr_bucket *b = apr_bucket_eos_create(bb->bucket_alloc);
-                APR_BRIGADE_INSERT_TAIL(bb, b);
-                beam->close_sent = 1;
-                ++transferred;
-                status = APR_SUCCESS;
-            }
+#if APR_HAS_MMAP
+        else if (APR_BUCKET_IS_MMAP(bsender)) {
+            apr_bucket_mmap *bmmap = bsender->data;
+            apr_mmap_t *mmap;
+            rv = apr_mmap_dup(&mmap, bmmap->mmap, bb->p);
+            if (rv != APR_SUCCESS) goto leave;
+            brecv = apr_bucket_mmap_create(mmap, bsender->start, bsender->length, bb->bucket_alloc);
         }
-        
-        if (transferred_buckets > 0) {
-           if (beam->cons_ev_cb) { 
-               beam->cons_ev_cb(beam->cons_ctx, beam);
+#endif
+        else if (APR_BUCKET_IS_FILE(bsender)) {
+            /* This is setaside into the target brigade pool so that
+             * any read operation messes with that pool and not
+             * the sender one. */
+            apr_bucket_file *f = (apr_bucket_file *)bsender->data;
+            apr_file_t *fd = f->fd;
+            int setaside = (f->readpool != bb->p);
+
+            if (setaside) {
+                rv = apr_file_setaside(&fd, fd, bb->p);
+                if (rv != APR_SUCCESS) goto leave;
             }
-        }
-        
-        if (transferred) {
-            apr_thread_cond_broadcast(beam->change);
-            status = APR_SUCCESS;
+            ng = apr_brigade_insert_file(bb, fd, bsender->start, (apr_off_t)bsender->length,
+                                         bb->p);
+#if APR_HAS_MMAP
+            /* disable mmap handling as this leads to segfaults when
+             * the underlying file is changed while memory pointer has
+             * been handed out. See also PR 59348 */
+            apr_bucket_file_enable_mmap(ng, 0);
+#endif
+            remain -= bsender->length;
+            ++transferred;
         }
         else {
-            status = wait_not_empty(beam, block, bl.mutex);
-            if (status != APR_SUCCESS) {
-                goto leave;
-            }
-            goto transfer;
+            const char *data;
+            apr_size_t dlen;
+            /* we did that when the bucket was added, so this should
+             * give us the same data as before without changing the bucket
+             * or anything (pool) connected to it. */
+            rv = apr_bucket_read(bsender, &data, &dlen, APR_BLOCK_READ);
+            if (rv != APR_SUCCESS) goto leave;
+            rv = apr_brigade_write(bb, NULL, NULL, data, dlen);
+            if (rv != APR_SUCCESS) goto leave;
+
+            remain -= dlen;
+            ++transferred;
+        }
+
+        if (brecv) {
+            /* we have a proxy that we can give the receiver */
+            APR_BRIGADE_INSERT_TAIL(bb, brecv);
+            remain -= brecv->length;
+            ++transferred;
+        }
+        APR_BUCKET_REMOVE(bsender);
+        H2_BLIST_INSERT_TAIL(&beam->buckets_consumed, bsender);
+        beam->recv_bytes += bsender->length;
+        ++consumed_buckets;
+    }
+
+    if (beam->recv_cb && consumed_buckets > 0) {
+        beam->recv_cb(beam->recv_ctx, beam);
+    }
+
+    if (transferred) {
+        apr_thread_cond_broadcast(beam->change);
+        rv = APR_SUCCESS;
+    }
+    else if (beam->aborted) {
+        rv = APR_ECONNABORTED;
+    }
+    else if (beam->closed) {
+        rv = APR_EOF;
+    }
+    else {
+        rv = wait_not_empty(beam, to, block);
+        if (rv != APR_SUCCESS) {
+            goto leave;
         }
+        goto transfer;
+    }
+
 leave:
-        if (pclosed) *pclosed = beam->closed? 1 : 0;
-        leave_yellow(beam, &bl);
+    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);
     }
-    return status;
+    apr_thread_mutex_unlock(beam->lock);
+    return rv;
 }
 
 void h2_beam_on_consumed(h2_bucket_beam *beam, 
-                         h2_beam_ev_callback *ev_cb,
                          h2_beam_io_callback *io_cb, void *ctx)
 {
-    h2_beam_lock bl;
-    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        beam->cons_ev_cb = ev_cb;
-        beam->cons_io_cb = io_cb;
-        beam->cons_ctx = ctx;
-        leave_yellow(beam, &bl);
-    }
+    apr_thread_mutex_lock(beam->lock);
+    beam->cons_io_cb = io_cb;
+    beam->cons_ctx = ctx;
+    apr_thread_mutex_unlock(beam->lock);
 }
 
-void h2_beam_on_produced(h2_bucket_beam *beam, 
-                         h2_beam_io_callback *io_cb, void *ctx)
+void h2_beam_on_received(h2_bucket_beam *beam,
+                         h2_beam_ev_callback *recv_cb, void *ctx)
 {
-    h2_beam_lock bl;
-    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        beam->prod_io_cb = io_cb;
-        beam->prod_ctx = ctx;
-        leave_yellow(beam, &bl);
-    }
+    apr_thread_mutex_lock(beam->lock);
+    beam->recv_cb = recv_cb;
+    beam->recv_ctx = ctx;
+    apr_thread_mutex_unlock(beam->lock);
 }
 
-void h2_beam_on_file_beam(h2_bucket_beam *beam, 
-                          h2_beam_can_beam_callback *cb, void *ctx)
+void h2_beam_on_eagain(h2_bucket_beam *beam,
+                       h2_beam_ev_callback *eagain_cb, void *ctx)
 {
-    h2_beam_lock bl;
-    
-    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        beam->can_beam_fn = cb;
-        beam->can_beam_ctx = ctx;
-        leave_yellow(beam, &bl);
-    }
+    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)
+{
+    apr_thread_mutex_lock(beam->lock);
+    beam->send_cb = send_cb;
+    beam->send_ctx = ctx;
+    apr_thread_mutex_unlock(beam->lock);
+}
 
-apr_off_t h2_beam_get_buffered(h2_bucket_beam *beam)
+void h2_beam_on_was_empty(h2_bucket_beam *beam,
+                          h2_beam_ev_callback *was_empty_cb, void *ctx)
 {
-    apr_bucket *b;
-    apr_off_t l = 0;
-    h2_beam_lock bl;
-    
-    if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
-        for (b = H2_BLIST_FIRST(&beam->send_list); 
-            b != H2_BLIST_SENTINEL(&beam->send_list);
-            b = APR_BUCKET_NEXT(b)) {
-            /* should all have determinate length */
-            l += b->length;
-        }
-        leave_yellow(beam, &bl);
-    }
-    return l;
+    apr_thread_mutex_lock(beam->lock);
+    beam->was_empty_cb = was_empty_cb;
+    beam->was_empty_ctx = ctx;
+    apr_thread_mutex_unlock(beam->lock);
 }
 
-apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam)
+
+static apr_off_t get_buffered_data_len(h2_bucket_beam *beam)
 {
     apr_bucket *b;
     apr_off_t l = 0;
-    h2_beam_lock bl;
-    
-    if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
-        for (b = H2_BLIST_FIRST(&beam->send_list); 
-            b != H2_BLIST_SENTINEL(&beam->send_list);
-            b = APR_BUCKET_NEXT(b)) {
-            l += bucket_mem_used(b);
-        }
-        leave_yellow(beam, &bl);
+
+    for (b = H2_BLIST_FIRST(&beam->buckets_to_send);
+        b != H2_BLIST_SENTINEL(&beam->buckets_to_send);
+        b = APR_BUCKET_NEXT(b)) {
+        /* should all have determinate length */
+        l += b->length;
     }
     return l;
 }
 
-int h2_beam_empty(h2_bucket_beam *beam)
+apr_off_t h2_beam_get_buffered(h2_bucket_beam *beam)
 {
-    int empty = 1;
-    h2_beam_lock bl;
-    
-    if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
-        empty = (H2_BLIST_EMPTY(&beam->send_list) 
-                 && (!beam->recv_buffer || APR_BRIGADE_EMPTY(beam->recv_buffer)));
-        leave_yellow(beam, &bl);
-    }
-    return empty;
-}
+    apr_off_t l = 0;
 
-int h2_beam_holds_proxies(h2_bucket_beam *beam)
-{
-    int has_proxies = 1;
-    h2_beam_lock bl;
-    
-    if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
-        has_proxies = !H2_BPROXY_LIST_EMPTY(&beam->proxies);
-        leave_yellow(beam, &bl);
-    }
-    return has_proxies;
+    apr_thread_mutex_lock(beam->lock);
+    l = get_buffered_data_len(beam);
+    apr_thread_mutex_unlock(beam->lock);
+    return l;
 }
 
-int h2_beam_was_received(h2_bucket_beam *beam)
+apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam)
 {
-    int happend = 0;
-    h2_beam_lock bl;
-    
-    if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
-        happend = (beam->received_bytes > 0);
-        leave_yellow(beam, &bl);
-    }
-    return happend;
-}
+    apr_bucket *b;
+    apr_off_t l = 0;
 
-apr_size_t h2_beam_get_files_beamed(h2_bucket_beam *beam)
-{
-    apr_size_t n = 0;
-    h2_beam_lock bl;
-    
-    if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
-        n = beam->files_beamed;
-        leave_yellow(beam, &bl);
+    apr_thread_mutex_lock(beam->lock);
+    for (b = H2_BLIST_FIRST(&beam->buckets_to_send);
+        b != H2_BLIST_SENTINEL(&beam->buckets_to_send);
+        b = APR_BUCKET_NEXT(b)) {
+        l += bucket_mem_used(b);
     }
-    return n;
+    apr_thread_mutex_unlock(beam->lock);
+    return l;
 }
 
-int h2_beam_no_files(void *ctx, h2_bucket_beam *beam, apr_file_t *file)
+int h2_beam_empty(h2_bucket_beam *beam)
 {
-    (void)ctx; (void)beam; (void)file;
-    return 0;
+    int empty = 1;
+
+    apr_thread_mutex_lock(beam->lock);
+    empty = buffer_is_empty(beam);
+    apr_thread_mutex_unlock(beam->lock);
+    return empty;
 }
 
 int h2_beam_report_consumption(h2_bucket_beam *beam)
 {
-    h2_beam_lock bl;
     int rv = 0;
-    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        rv = report_consumption(beam, &bl);
-        leave_yellow(beam, &bl);
-    }
+
+    apr_thread_mutex_lock(beam->lock);
+    rv = report_consumption(beam, 1);
+    apr_thread_mutex_unlock(beam->lock);
     return rv;
 }
 
-void h2_beam_log(h2_bucket_beam *beam, conn_rec *c, int level, const char *msg)
+int h2_beam_is_complete(h2_bucket_beam *beam)
 {
-    if (beam && APLOG_C_IS_LEVEL(c,level)) {
-        ap_log_cerror(APLOG_MARK, level, 0, c, 
-                      "beam(%ld-%d,%s,closed=%d,aborted=%d,empty=%d,buf=%ld): %s", 
-                      (c->master? c->master->id : c->id), beam->id, beam->tag, 
-                      beam->closed, beam->aborted, h2_beam_empty(beam), 
-                      (long)h2_beam_get_buffered(beam), msg);
+    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;
 }
-
-
diff -r -N -u a/modules/http2/h2_bucket_beam.h b/modules/http2/h2_bucket_beam.h
--- a/modules/http2/h2_bucket_beam.h	2021-04-18 20:55:43.000000000 +0200
+++ b/modules/http2/h2_bucket_beam.h	2024-10-30 21:40:01.059958617 +0100
@@ -17,190 +17,63 @@
 #ifndef h2_bucket_beam_h
 #define h2_bucket_beam_h
 
+#include "h2_conn_ctx.h"
+
 struct apr_thread_mutex_t;
 struct apr_thread_cond_t;
 
-/*******************************************************************************
- * apr_bucket list without bells and whistles
- ******************************************************************************/
- 
-/**
- * h2_blist can hold a list of buckets just like apr_bucket_brigade, but
- * does not to any allocations or related features.
- */
-typedef struct {
-    APR_RING_HEAD(h2_bucket_list, apr_bucket) list;
-} h2_blist;
-
-#define H2_BLIST_INIT(b)        APR_RING_INIT(&(b)->list, apr_bucket, link);
-#define H2_BLIST_SENTINEL(b)    APR_RING_SENTINEL(&(b)->list, apr_bucket, link)
-#define H2_BLIST_EMPTY(b)       APR_RING_EMPTY(&(b)->list, apr_bucket, link)
-#define H2_BLIST_FIRST(b)       APR_RING_FIRST(&(b)->list)
-#define H2_BLIST_LAST(b)	APR_RING_LAST(&(b)->list)
-#define H2_BLIST_INSERT_HEAD(b, e) do {				\
-	apr_bucket *ap__b = (e);                                        \
-	APR_RING_INSERT_HEAD(&(b)->list, ap__b, apr_bucket, link);	\
-    } while (0)
-#define H2_BLIST_INSERT_TAIL(b, e) do {				\
-	apr_bucket *ap__b = (e);					\
-	APR_RING_INSERT_TAIL(&(b)->list, ap__b, apr_bucket, link);	\
-    } while (0)
-#define H2_BLIST_CONCAT(a, b) do {					\
-        APR_RING_CONCAT(&(a)->list, &(b)->list, apr_bucket, link);	\
-    } while (0)
-#define H2_BLIST_PREPEND(a, b) do {					\
-        APR_RING_PREPEND(&(a)->list, &(b)->list, apr_bucket, link);	\
-    } while (0)
-
-/*******************************************************************************
- * h2_bucket_beam
- ******************************************************************************/
-
 /**
  * A h2_bucket_beam solves the task of transferring buckets, esp. their data,
- * across threads with zero buffer copies.
- *
- * When a thread, let's call it the sender thread, wants to send buckets to
- * another, the green thread, it creates a h2_bucket_beam and adds buckets
- * via the h2_beam_send(). It gives the beam to the green thread which then
- * can receive buckets into its own brigade via h2_beam_receive().
- *
- * Sending and receiving can happen concurrently.
- *
- * The beam can limit the amount of data it accepts via the buffer_size. This
- * can also be adjusted during its lifetime. Sends and receives can be done blocking. 
- * A timeout can be set for such blocks.
- *
- * Care needs to be taken when terminating the beam. The beam registers at
- * the pool it was created with and will cleanup after itself. However, if
- * received buckets do still exist, already freed memory might be accessed.
- * The beam does a assertion on this condition.
- * 
- * The proper way of shutting down a beam is to first make sure there are no
- * more green buckets out there, then cleanup the beam to purge eventually
- * still existing sender buckets and then, possibly, terminate the beam itself
- * (or the pool it was created with).
- *
- * The following restrictions apply to bucket transport:
- * - only EOS and FLUSH meta buckets are copied through. All other meta buckets
- *   are kept in the beams hold.
- * - all kind of data buckets are transported through:
- *   - transient buckets are converted to heap ones on send
- *   - heap and pool buckets require no extra handling
- *   - buckets with indeterminate length are read on send
- *   - file buckets will transfer the file itself into a new bucket, if allowed
- *   - all other buckets are read on send to make sure data is present
- *
- * This assures that when the sender thread sends its sender buckets, the data
- * is made accessible while still on the sender side. The sender bucket then enters
- * the beams hold storage.
- * When the green thread calls receive, sender buckets in the hold are wrapped
- * into special beam buckets. Beam buckets on read present the data directly
- * from the internal sender one, but otherwise live on the green side. When a
- * beam bucket gets destroyed, it notifies its beam that the corresponding
- * sender bucket from the hold may be destroyed.
- * Since the destruction of green buckets happens in the green thread, any
- * corresponding sender bucket can not immediately be destroyed, as that would
- * result in race conditions.
- * Instead, the beam transfers such sender buckets from the hold to the purge
- * storage. Next time there is a call from the sender side, the buckets in
- * purge will be deleted.
- *
- * There are callbacks that can be registesender with a beam:
- * - a "consumed" callback that gets called on the sender side with the
- *   amount of data that has been received by the green side. The amount
- *   is a delta from the last callback invocation. The sender side can trigger
- *   these callbacks by calling h2_beam_send() with a NULL brigade.
- * - a "can_beam_file" callback that can prohibit the transfer of file handles
- *   through the beam. This will cause file buckets to be read on send and
- *   its data buffer will then be transports just like a heap bucket would.
- *   When no callback is registered, no restrictions apply and all files are
- *   passed through.
- *   File handles transfersender to the green side will stay there until the
- *   receiving brigade's pool is destroyed/cleared. If the pool lives very
- *   long or if many different files are beamed, the process might run out
- *   of available file handles.
- *
- * The name "beam" of course is inspired by good old transporter
- * technology where humans are kept inside the transporter's memory
- * buffers until the transmission is complete. Star gates use a similar trick.
+ * across threads with as little copying as possible.
  */
 
-typedef void h2_beam_mutex_leave(struct apr_thread_mutex_t *lock);
-
-typedef struct {
-    apr_thread_mutex_t *mutex;
-    h2_beam_mutex_leave *leave;
-} h2_beam_lock;
-
 typedef struct h2_bucket_beam h2_bucket_beam;
 
-typedef apr_status_t h2_beam_mutex_enter(void *ctx, h2_beam_lock *pbl);
-
 typedef void h2_beam_io_callback(void *ctx, h2_bucket_beam *beam,
                                  apr_off_t bytes);
 typedef void h2_beam_ev_callback(void *ctx, h2_bucket_beam *beam);
 
-typedef struct h2_beam_proxy h2_beam_proxy;
-typedef struct {
-    APR_RING_HEAD(h2_beam_proxy_list, h2_beam_proxy) list;
-} h2_bproxy_list;
-
-typedef int h2_beam_can_beam_callback(void *ctx, h2_bucket_beam *beam,
-                                      apr_file_t *file);
-
-typedef enum {
-    H2_BEAM_OWNER_SEND,
-    H2_BEAM_OWNER_RECV
-} h2_beam_owner_t;
-
 /**
- * Will deny all transfer of apr_file_t across the beam and force
- * a data copy instead.
+ * h2_blist can hold a list of buckets just like apr_bucket_brigade, but
+ * does not to any allocations or related features.
  */
-int h2_beam_no_files(void *ctx, h2_bucket_beam *beam, apr_file_t *file);
+typedef struct {
+    APR_RING_HEAD(h2_bucket_list, apr_bucket) list;
+} h2_blist;
 
 struct h2_bucket_beam {
     int id;
-    const char *tag;
+    const char *name;
+    conn_rec *from;
     apr_pool_t *pool;
-    h2_beam_owner_t owner;
-    h2_blist send_list;
-    h2_blist hold_list;
-    h2_blist purge_list;
-    apr_bucket_brigade *recv_buffer;
-    h2_bproxy_list proxies;
-    apr_pool_t *send_pool;
-    apr_pool_t *recv_pool;
-    
+    h2_blist buckets_to_send;
+    h2_blist buckets_consumed;
+    h2_blist buckets_eor;
+
     apr_size_t max_buf_size;
     apr_interval_time_t timeout;
 
-    apr_off_t sent_bytes;     /* amount of bytes send */
-    apr_off_t received_bytes; /* amount of bytes received */
-
-    apr_size_t buckets_sent;  /* # of beam buckets sent */
-    apr_size_t files_beamed;  /* how many file handles have been set aside */
-    
-    unsigned int aborted : 1;
-    unsigned int closed : 1;
-    unsigned int close_sent : 1;
-    unsigned int tx_mem_limits : 1; /* only memory size counts on transfers */
+    int aborted;
+    int closed;
+    int tx_mem_limits; /* only memory size counts on transfers */
+    int copy_files;
 
     struct apr_thread_mutex_t *lock;
     struct apr_thread_cond_t *change;
     
-    apr_off_t cons_bytes_reported;    /* amount of bytes reported as consumed */
-    h2_beam_ev_callback *cons_ev_cb;
-    h2_beam_io_callback *cons_io_cb;
+    h2_beam_ev_callback *was_empty_cb; /* event: beam changed to non-empty in h2_beam_send() */
+    void *was_empty_ctx;
+    h2_beam_ev_callback *recv_cb;      /* event: buckets were transfered in h2_beam_receive() */
+    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 */
+    h2_beam_io_callback *cons_io_cb;  /* report: recv_bytes deltas for sender */
     void *cons_ctx;
-
-    apr_off_t prod_bytes_reported;    /* amount of bytes reported as produced */
-    h2_beam_io_callback *prod_io_cb;
-    void *prod_ctx;
-
-    h2_beam_can_beam_callback *can_beam_fn;
-    void *can_beam_ctx;
 };
 
 /**
@@ -211,62 +84,68 @@
  * that is only used inside that same mutex.
  *
  * @param pbeam         will hold the created beam on return
+ * @param c_from        connection from which buchets are sent
  * @param pool          pool owning the beam, beam will cleanup when pool released
  * @param id            identifier of the beam
  * @param tag           tag identifying beam for logging
- * @param owner         if the beam is owned by the sender or receiver, e.g. if
- *                      the pool owner is using this beam for sending or receiving
  * @param buffer_size   maximum memory footprint of buckets buffered in beam, or
  *                      0 for no limitation
  * @param timeout       timeout for blocking operations
  */
 apr_status_t h2_beam_create(h2_bucket_beam **pbeam,
+                            conn_rec *from,
                             apr_pool_t *pool, 
                             int id, const char *tag,
-                            h2_beam_owner_t owner,  
                             apr_size_t buffer_size,
                             apr_interval_time_t timeout);
 
 /**
  * Destroys the beam immediately without cleanup.
  */ 
-apr_status_t h2_beam_destroy(h2_bucket_beam *beam);
+apr_status_t h2_beam_destroy(h2_bucket_beam *beam, conn_rec *c);
 
 /**
- * Send buckets from the given brigade through the beam. Will hold buckets 
- * internally as long as they have not been processed by the receiving side.
- * All accepted buckets are removed from the given brigade. Will return with
- * APR_EAGAIN on non-blocking sends when not all buckets could be accepted.
- * 
- * Call from the sender side only.
+ * Switch copying of file buckets on/off.
  */
-apr_status_t h2_beam_send(h2_bucket_beam *beam,  
-                          apr_bucket_brigade *bb, 
-                          apr_read_type_e block);
+void h2_beam_set_copy_files(h2_bucket_beam * beam, int enabled);
 
 /**
- * Register the pool from which future buckets are send. This defines
- * the lifetime of the buckets, e.g. the pool should not be cleared/destroyed
- * until the data is no longer needed (or has been received).
+ * Send buckets from the given brigade through the beam.
+ * This can block of the amount of bucket data is above the buffer limit.
+ * @param beam the beam to add buckets to
+ * @param from the connection the sender operates on, must be the same as
+ *             used to create the beam
+ * @param bb the brigade to take buckets from
+ * @param block if the sending should block when the buffer is full
+ * @param pwritten on return, contains the number of data bytes sent
+ * @return APR_SUCCESS when buckets were added to the beam. This can be
+ *                     a partial transfer and other buckets may still remain in bb
+ *         APR_EAGAIN on non-blocking send when the buffer is full
+ *         APR_TIMEUP on blocking semd that time out
+ *         APR_ECONNABORTED when beam has been aborted
  */
-void h2_beam_send_from(h2_bucket_beam *beam, apr_pool_t *p);
+apr_status_t h2_beam_send(h2_bucket_beam *beam, conn_rec *from,
+                          apr_bucket_brigade *bb, 
+                          apr_read_type_e block,
+                          apr_off_t *pwritten);
 
 /**
- * Receive buckets from the beam into the given brigade. Will return APR_EOF
- * when reading past an EOS bucket. Reads can be blocking until data is 
- * available or the beam has been closed. Non-blocking calls return APR_EAGAIN
- * if no data is available.
- *
- * Call from the receiver side only.
- * @param pclosed  on return != 0 iff the beam has been closed by the sender. It
- *                 may still hold untransfered data. Maybe NULL if the caller is
- *                 not interested in this.
+ * Receive buckets from the beam into the given brigade. The caller is
+ * operating on connection `to`.
+ * @param beam the beam to receive buckets from
+ * @param to the connection the receiver is working with
+ * @param bb the bucket brigade to append to
+ * @param block if the read should block when buckets are unavailable
+ * @param readbytes the amount of data the receiver wants
+ * @return APR_SUCCESS when buckets were appended
+ *         APR_EAGAIN on non-blocking read when no buckets are available
+ *         APR_TIMEUP on blocking reads that time out
+ *         APR_ECONNABORTED when beam has been aborted
  */
-apr_status_t h2_beam_receive(h2_bucket_beam *beam, 
-                             apr_bucket_brigade *green_buckets, 
+apr_status_t h2_beam_receive(h2_bucket_beam *beam, conn_rec *to,
+                             apr_bucket_brigade *bb,
                              apr_read_type_e block,
-                             apr_off_t readbytes,
-                             int *pclosed);
+                             apr_off_t readbytes);
 
 /**
  * Determine if beam is empty. 
@@ -274,53 +153,27 @@
 int h2_beam_empty(h2_bucket_beam *beam);
 
 /**
- * Determine if beam has handed out proxy buckets that are not destroyed. 
- */
-int h2_beam_holds_proxies(h2_bucket_beam *beam);
-
-/**
- * Abort the beam. Will cleanup any buffered buckets and answer all send
- * and receives with APR_ECONNABORTED.
- * 
- * Call from the sender side only.
- */
-void h2_beam_abort(h2_bucket_beam *beam);
-
-/**
- * Close the beam. Sending an EOS bucket serves the same purpose. 
- * 
- * Call from the sender side only.
- */
-apr_status_t h2_beam_close(h2_bucket_beam *beam);
-
-/**
- * Receives leaves the beam, e.g. will no longer read. This will
- * interrupt any sender blocked writing and fail future send. 
- * 
- * Call from the receiver side only.
+ * Abort the beam, either from receiving or sending side.
+ *
+ * @param beam the beam to abort
+ * @param c the connection the caller is working with
  */
-apr_status_t h2_beam_leave(h2_bucket_beam *beam);
-
-int h2_beam_is_closed(h2_bucket_beam *beam);
+void h2_beam_abort(h2_bucket_beam *beam, conn_rec *c);
 
 /**
- * Return APR_SUCCESS when all buckets in transit have been handled. 
- * When called with APR_BLOCK_READ and a mutex set, will wait until the green
- * side has consumed all data. Otherwise APR_EAGAIN is returned.
- * With clear_buffers set, any queued data is discarded.
- * If a timeout is set on the beam, waiting might also time out and
- * return APR_ETIMEUP.
+ * Close the beam. Make certain an EOS is sent.
  *
- * Call from the sender side only.
+ * @param beam the beam to abort
+ * @param c the connection the caller is working with
  */
-apr_status_t h2_beam_wait_empty(h2_bucket_beam *beam, apr_read_type_e block);
+void h2_beam_close(h2_bucket_beam *beam, conn_rec *c);
 
-/** 
- * Set/get the timeout for blocking read/write operations. Only works
- * if a mutex has been set for the beam.
+/**
+ * Set/get the timeout for blocking sebd/receive operations.
  */
 void h2_beam_timeout_set(h2_bucket_beam *beam, 
                          apr_interval_time_t timeout);
+
 apr_interval_time_t h2_beam_timeout_get(h2_bucket_beam *beam);
 
 /**
@@ -335,7 +188,6 @@
  * amount of bytes that have been consumed by the receiver, since the
  * last callback invocation or reset.
  * @param beam the beam to set the callback on
- * @param ev_cb the callback or NULL, called when bytes are consumed
  * @param io_cb the callback or NULL, called on sender with bytes consumed
  * @param ctx  the context to use in callback invocation
  * 
@@ -343,43 +195,58 @@
  * from any side.
  */
 void h2_beam_on_consumed(h2_bucket_beam *beam, 
-                         h2_beam_ev_callback *ev_cb,
                          h2_beam_io_callback *io_cb, void *ctx);
 
 /**
- * Call any registered consumed handler, if any changes have happened
- * since the last invocation. 
- * @return !=0 iff a handler has been called
- *
- * Needs to be invoked from the sending side.
+ * Register a callback to be invoked on the receiver side whenever
+ * buckets have been transfered in a h2_beam_receive() call.
+ * @param beam the beam to set the callback on
+ * @param recv_cb the callback or NULL, called when buckets are received
+ * @param ctx  the context to use in callback invocation
  */
-int h2_beam_report_consumption(h2_bucket_beam *beam);
+void h2_beam_on_received(h2_bucket_beam *beam,
+                         h2_beam_ev_callback *recv_cb, void *ctx);
 
 /**
- * Register a callback to be invoked on the receiver side with the
- * amount of bytes that have been produces by the sender, since the
- * last callback invocation or reset.
+ * 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 io_cb the callback or NULL, called on receiver with bytes produced
+ * @param egain_cb the callback or NULL, called before APR_EAGAIN is returned
  * @param ctx  the context to use in callback invocation
- * 
- * Call from the receiver side, callbacks invoked on either side.
  */
-void h2_beam_on_produced(h2_bucket_beam *beam, 
-                         h2_beam_io_callback *io_cb, void *ctx);
+void h2_beam_on_eagain(h2_bucket_beam *beam,
+                       h2_beam_ev_callback *eagain_cb, void *ctx);
 
 /**
- * Register a callback that may prevent a file from being beam as
- * file handle, forcing the file content to be copied. Then no callback
- * is set (NULL), file handles are transferred directly.
+ * 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.
  * @param beam the beam to set the callback on
- * @param io_cb the callback or NULL, called on receiver with bytes produced
+ * @param on_send_cb the callback to invoke after buckets were added
  * @param ctx  the context to use in callback invocation
- * 
- * Call from the receiver side, callbacks invoked on either side.
  */
-void h2_beam_on_file_beam(h2_bucket_beam *beam, 
-                          h2_beam_can_beam_callback *cb, void *ctx);
+void h2_beam_on_send(h2_bucket_beam *beam,
+                     h2_beam_ev_callback *on_send_cb, void *ctx);
+
+/**
+ * Register a call back from the sender side to be invoked when send
+ * has added to a previously empty beam.
+ * Unregister by passing a NULL was_empty_cb.
+ * @param beam the beam to set the callback on
+ * @param was_empty_cb the callback to invoke on blocked send
+ * @param ctx  the context to use in callback invocation
+ */
+void h2_beam_on_was_empty(h2_bucket_beam *beam,
+                          h2_beam_ev_callback *was_empty_cb, void *ctx);
+
+/**
+ * Call any registered consumed handler, if any changes have happened
+ * since the last invocation. 
+ * @return !=0 iff a handler has been called
+ *
+ * Needs to be invoked from the sending side.
+ */
+int h2_beam_report_consumption(h2_bucket_beam *beam);
 
 /**
  * Get the amount of bytes currently buffered in the beam (unread).
@@ -392,18 +259,9 @@
 apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam);
 
 /**
- * Return != 0 iff (some) data from the beam has been received.
+ * @return != 0 iff beam has been closed or has an EOS bucket buffered
+ *                  waiting to be received.
  */
-int h2_beam_was_received(h2_bucket_beam *beam);
-
-apr_size_t h2_beam_get_files_beamed(h2_bucket_beam *beam);
-
-typedef apr_bucket *h2_bucket_beamer(h2_bucket_beam *beam, 
-                                     apr_bucket_brigade *dest,
-                                     const apr_bucket *src);
-
-void h2_register_bucket_beamer(h2_bucket_beamer *beamer);
-
-void h2_beam_log(h2_bucket_beam *beam, conn_rec *c, int level, const char *msg);
+int h2_beam_is_complete(h2_bucket_beam *beam);
 
 #endif /* h2_bucket_beam_h */
diff -r -N -u a/modules/http2/h2_bucket_eos.c b/modules/http2/h2_bucket_eos.c
--- a/modules/http2/h2_bucket_eos.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_bucket_eos.c	2024-10-30 21:40:01.059958617 +0100
@@ -21,6 +21,7 @@
 #include <http_core.h>
 #include <http_connection.h>
 #include <http_log.h>
+#include <http_protocol.h>
 
 #include "h2_private.h"
 #include "h2.h"
diff -r -N -u a/modules/http2/h2_c1.c b/modules/http2/h2_c1.c
--- a/modules/http2/h2_c1.c	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_c1.c	2024-10-30 21:40:01.059958617 +0100
@@ -0,0 +1,323 @@
+/* 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_strings.h>
+
+#include <ap_mpm.h>
+#include <ap_mmn.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_log.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_ssl.h>
+
+#include <mpm_common.h>
+
+#include "h2_private.h"
+#include "h2.h"
+#include "h2_bucket_beam.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_mplx.h"
+#include "h2_session.h"
+#include "h2_stream.h"
+#include "h2_protocol.h"
+#include "h2_workers.h"
+#include "h2_c1.h"
+#include "h2_version.h"
+#include "h2_util.h"
+
+static struct h2_workers *workers;
+
+static int async_mpm;
+
+APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_c_logio_add_bytes_in;
+APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_c_logio_add_bytes_out;
+
+apr_status_t h2_c1_child_init(apr_pool_t *pool, server_rec *s)
+{
+    apr_status_t status = APR_SUCCESS;
+    int minw, maxw;
+    apr_time_t idle_limit;
+
+    status = ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm);
+    if (status != APR_SUCCESS) {
+        /* some MPMs do not implemnent this */
+        async_mpm = 0;
+        status = APR_SUCCESS;
+    }
+
+    h2_config_init(pool);
+
+    h2_get_workers_config(s, &minw, &maxw, &idle_limit);
+    workers = h2_workers_create(s, pool, maxw, minw, idle_limit);
+ 
+    h2_c_logio_add_bytes_in = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_in);
+    h2_c_logio_add_bytes_out = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_out);
+
+    return h2_mplx_c1_child_init(pool, s);
+}
+
+void h2_c1_child_stopping(apr_pool_t *pool, int graceful)
+{
+    if (workers) {
+        h2_workers_shutdown(workers, graceful);
+    }
+}
+
+
+apr_status_t h2_c1_setup(conn_rec *c, request_rec *r, server_rec *s)
+{
+    h2_session *session;
+    h2_conn_ctx_t *ctx;
+    apr_status_t rv;
+    
+    if (!workers) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911) 
+                      "workers not initialized");
+        rv = APR_EGENERAL;
+        goto cleanup;
+    }
+
+    rv = h2_session_create(&session, c, r, s, workers);
+    if (APR_SUCCESS != rv) goto cleanup;
+
+    ctx = h2_conn_ctx_get(c);
+    ap_assert(ctx);
+    h2_conn_ctx_assign_session(ctx, session);
+    /* remove the input filter of mod_reqtimeout, now that the connection
+     * is established and we have switched to h2. reqtimeout has supervised
+     * possibly configured handshake timeouts and needs to get out of the way
+     * now since the rest of its state handling assumes http/1.x to take place. */
+    ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
+
+cleanup:
+    return rv;
+}
+
+apr_status_t h2_c1_run(conn_rec *c)
+{
+    apr_status_t status;
+    int mpm_state = 0;
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+    
+    ap_assert(conn_ctx);
+    ap_assert(conn_ctx->session);
+    do {
+        if (c->cs) {
+            c->cs->sense = CONN_SENSE_DEFAULT;
+            c->cs->state = CONN_STATE_HANDLER;
+        }
+    
+        status = h2_session_process(conn_ctx->session, async_mpm);
+        
+        if (APR_STATUS_IS_EOF(status)) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, 
+                          H2_SSSN_LOG(APLOGNO(03045), conn_ctx->session,
+                          "process, closing conn"));
+            c->keepalive = AP_CONN_CLOSE;
+        }
+        else {
+            c->keepalive = AP_CONN_KEEPALIVE;
+        }
+        
+        if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
+            break;
+        }
+    } while (!async_mpm
+             && c->keepalive == AP_CONN_KEEPALIVE 
+             && mpm_state != AP_MPMQ_STOPPING);
+
+    if (c->cs) {
+        switch (conn_ctx->session->state) {
+            case H2_SESSION_ST_INIT:
+            case H2_SESSION_ST_IDLE:
+            case H2_SESSION_ST_BUSY:
+            case H2_SESSION_ST_WAIT:
+                c->cs->state = CONN_STATE_WRITE_COMPLETION;
+                if (c->cs && !conn_ctx->session->remote.emitted_count) {
+                    /* let the MPM know that we are not done and want
+                     * the Timeout behaviour instead of a KeepAliveTimeout
+                     * See PR 63534. 
+                     */
+                    c->cs->sense = CONN_SENSE_WANT_READ;
+                }
+                break;
+            case H2_SESSION_ST_CLEANUP:
+            case H2_SESSION_ST_DONE:
+            default:
+                c->cs->state = CONN_STATE_LINGER;
+            break;
+        }
+    }
+
+    return APR_SUCCESS;
+}
+
+apr_status_t h2_c1_pre_close(struct h2_conn_ctx_t *ctx, conn_rec *c)
+{
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+
+    if (conn_ctx && conn_ctx->session) {
+        apr_status_t status = h2_session_pre_close(conn_ctx->session, async_mpm);
+        return (status == APR_SUCCESS)? DONE : status;
+    }
+    return DONE;
+}
+
+int h2_c1_allows_direct(conn_rec *c)
+{
+    if (!c->master) {
+        int is_tls = ap_ssl_conn_is_ssl(c);
+        const char *needed_protocol = is_tls? "h2" : "h2c";
+        int h2_direct = h2_config_cgeti(c, H2_CONF_DIRECT);
+
+        if (h2_direct < 0) {
+            h2_direct = is_tls? 0 : 1;
+        }
+        return (h2_direct && ap_is_allowed_protocol(c, NULL, NULL, needed_protocol));
+    }
+    return 0;
+}
+
+int h2_c1_can_upgrade(request_rec *r)
+{
+    if (!r->connection->master) {
+        int h2_upgrade = h2_config_rgeti(r, H2_CONF_UPGRADE);
+        return h2_upgrade > 0 || (h2_upgrade < 0 && !ap_ssl_conn_is_ssl(r->connection));
+    }
+    return 0;
+}
+
+static int h2_c1_hook_process_connection(conn_rec* c)
+{
+    apr_status_t status;
+    h2_conn_ctx_t *ctx;
+
+    if (c->master) goto declined;
+    ctx = h2_conn_ctx_get(c);
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn");
+    if (!ctx && c->keepalives == 0) {
+        const char *proto = ap_get_protocol(c);
+
+        if (APLOGctrace1(c)) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn, "
+                          "new connection using protocol '%s', direct=%d, "
+                          "tls acceptable=%d", proto, h2_c1_allows_direct(c),
+                          h2_protocol_is_acceptable_c1(c, NULL, 1));
+        }
+
+        if (!strcmp(AP_PROTOCOL_HTTP1, proto)
+            && h2_c1_allows_direct(c)
+            && h2_protocol_is_acceptable_c1(c, NULL, 1)) {
+            /* Fresh connection still is on http/1.1 and H2Direct is enabled.
+             * Otherwise connection is in a fully acceptable state.
+             * -> peek at the first 24 incoming bytes
+             */
+            apr_bucket_brigade *temp;
+            char *peek = NULL;
+            apr_size_t peeklen;
+
+            temp = apr_brigade_create(c->pool, c->bucket_alloc);
+            status = ap_get_brigade(c->input_filters, temp,
+                                    AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24);
+
+            if (status != APR_SUCCESS) {
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03054)
+                              "h2_h2, error reading 24 bytes speculative");
+                apr_brigade_destroy(temp);
+                return DECLINED;
+            }
+
+            apr_brigade_pflatten(temp, &peek, &peeklen, c->pool);
+            if ((peeklen >= 24) && !memcmp(H2_MAGIC_TOKEN, peek, 24)) {
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                              "h2_h2, direct mode detected");
+                ctx = h2_conn_ctx_create_for_c1(c, c->base_server,
+                                                ap_ssl_conn_is_ssl(c)? "h2" : "h2c");
+            }
+            else if (APLOGctrace2(c)) {
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+                              "h2_h2, not detected in %d bytes(base64): %s",
+                              (int)peeklen, h2_util_base64url_encode(peek, peeklen, c->pool));
+            }
+            apr_brigade_destroy(temp);
+        }
+    }
+
+    if (!ctx) goto declined;
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "process_conn");
+    if (!ctx->session) {
+        status = h2_c1_setup(c, NULL, ctx->server? ctx->server : c->base_server);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c, "conn_setup");
+        if (status != APR_SUCCESS) {
+            h2_conn_ctx_detach(c);
+            return !OK;
+        }
+    }
+    h2_c1_run(c);
+    return OK;
+
+declined:
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined");
+    return DECLINED;
+}
+
+static int h2_c1_hook_pre_close(conn_rec *c)
+{
+    h2_conn_ctx_t *ctx;
+
+    /* secondary connection? */
+    if (c->master) {
+        return DECLINED;
+    }
+
+    ctx = h2_conn_ctx_get(c);
+    if (ctx) {
+        /* If the session has been closed correctly already, we will not
+         * find a h2_conn_ctx_there. The presence indicates that the session
+         * is still ongoing. */
+        return h2_c1_pre_close(ctx, c);
+    }
+    return DECLINED;
+}
+
+static const char* const mod_ssl[]        = { "mod_ssl.c", NULL};
+static const char* const mod_reqtimeout[] = { "mod_ssl.c", "mod_reqtimeout.c", NULL};
+
+void h2_c1_register_hooks(void)
+{
+    /* Our main processing needs to run quite late. Definitely after mod_ssl,
+     * as we need its connection filters, but also before reqtimeout as its
+     * method of timeouts is specific to HTTP/1.1 (as of now).
+     * The core HTTP/1 processing run as REALLY_LAST, so we will have
+     * a chance to take over before it.
+     */
+    ap_hook_process_connection(h2_c1_hook_process_connection,
+                               mod_reqtimeout, NULL, APR_HOOK_LAST);
+
+    /* One last chance to properly say goodbye if we have not done so
+     * already. */
+    ap_hook_pre_close_connection(h2_c1_hook_pre_close, NULL, mod_ssl, APR_HOOK_LAST);
+}
+
diff -r -N -u a/modules/http2/h2_c1.h b/modules/http2/h2_c1.h
--- a/modules/http2/h2_c1.h	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_c1.h	2024-10-30 21:40:01.059958617 +0100
@@ -0,0 +1,83 @@
+/* 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_c1__
+#define __mod_h2__h2_c1__
+
+#include <http_core.h>
+
+struct h2_conn_ctx_t;
+
+extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_c_logio_add_bytes_in;
+extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_c_logio_add_bytes_out;
+
+/* Initialize this child process for h2 primary connection work,
+ * to be called once during child init before multi processing
+ * starts.
+ */
+apr_status_t h2_c1_child_init(apr_pool_t *pool, server_rec *s);
+
+/**
+ * Setup the primary connection and our context for HTTP/2 processing
+ *
+ * @param c the connection HTTP/2 is starting on
+ * @param r the upgrade request that still awaits an answer, optional
+ * @param s the server selected for this connection (can be != c->base_server)
+ */
+apr_status_t h2_c1_setup(conn_rec *c, request_rec *r, server_rec *s);
+
+/**
+ * Run the HTTP/2 primary connection in synchronous fashion.
+ * Return when the HTTP/2 session is done
+ * and the connection will close or a fatal error occurred.
+ *
+ * @param c the http2 connection to run
+ * @return APR_SUCCESS when session is done.
+ */
+apr_status_t h2_c1_run(conn_rec *c);
+
+/**
+ * The primary connection is about to close. If we have not send a GOAWAY
+ * yet, this is the last chance.
+ */
+apr_status_t h2_c1_pre_close(struct h2_conn_ctx_t *ctx, conn_rec *c);
+
+/**
+ * Check if the connection allows a direct detection of HTTPP/2,
+ * as configurable by the H2Direct directive.
+ * @param c the connection to check on
+ * @return != 0 if direct detection is enabled
+ */
+int h2_c1_allows_direct(conn_rec *c);
+
+/**
+ * Check if the "Upgrade" HTTP/1.1 mode of protocol switching is enabled
+ * for the given request.
+ * @param r the request to check
+ * @return != 0 iff Upgrade switching is enabled
+ */
+int h2_c1_can_upgrade(request_rec *r);
+
+/* Register hooks for h2 handling on primary connections.
+ */
+void h2_c1_register_hooks(void);
+
+/**
+ * Child is about to be stopped, release unused resources
+ */
+void h2_c1_child_stopping(apr_pool_t *pool, int graceful);
+
+#endif /* defined(__mod_h2__h2_c1__) */
diff -r -N -u a/modules/http2/h2_c1_io.c b/modules/http2/h2_c1_io.c
--- a/modules/http2/h2_c1_io.c	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_c1_io.c	2024-10-30 21:40:01.059958617 +0100
@@ -0,0 +1,559 @@
+/* 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_strings.h>
+#include <ap_mpm.h>
+#include <mpm_common.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_ssl.h>
+
+#include "h2_private.h"
+#include "h2_bucket_eos.h"
+#include "h2_config.h"
+#include "h2_c1.h"
+#include "h2_c1_io.h"
+#include "h2_protocol.h"
+#include "h2_session.h"
+#include "h2_util.h"
+
+#define TLS_DATA_MAX          (16*1024) 
+
+/* Calculated like this: assuming MTU 1500 bytes
+ * 1500 - 40 (IP) - 20 (TCP) - 40 (TCP options) 
+ *      - TLS overhead (60-100) 
+ * ~= 1300 bytes */
+#define WRITE_SIZE_INITIAL    1300
+
+/* The maximum we'd like to write in one chunk is
+ * the max size of a TLS record. When pushing
+ * many frames down the h2 connection, this might
+ * align differently because of headers and other
+ * frames or simply as not sufficient data is
+ * in a response body.
+ * However keeping frames at or below this limit
+ * should make optimizations at the layer that writes
+ * to TLS easier.
+ */
+#define WRITE_SIZE_MAX        (TLS_DATA_MAX) 
+
+#define BUF_REMAIN            ((apr_size_t)(bmax-off))
+
+static void h2_c1_io_bb_log(conn_rec *c, int stream_id, int level,
+                            const char *tag, apr_bucket_brigade *bb)
+{
+    char buffer[16 * 1024];
+    const char *line = "(null)";
+    int bmax = sizeof(buffer)/sizeof(buffer[0]);
+    int off = 0;
+    apr_bucket *b;
+    
+    (void)stream_id;
+    if (bb) {
+        memset(buffer, 0, bmax--);
+        for (b = APR_BRIGADE_FIRST(bb); 
+             bmax && (b != APR_BRIGADE_SENTINEL(bb));
+             b = APR_BUCKET_NEXT(b)) {
+            
+            if (APR_BUCKET_IS_METADATA(b)) {
+                if (APR_BUCKET_IS_EOS(b)) {
+                    off += apr_snprintf(buffer+off, BUF_REMAIN, "eos ");
+                }
+                else if (APR_BUCKET_IS_FLUSH(b)) {
+                    off += apr_snprintf(buffer+off, BUF_REMAIN, "flush ");
+                }
+                else if (AP_BUCKET_IS_EOR(b)) {
+                    off += apr_snprintf(buffer+off, BUF_REMAIN, "eor ");
+                }
+                else if (H2_BUCKET_IS_H2EOS(b)) {
+                    off += apr_snprintf(buffer+off, BUF_REMAIN, "h2eos ");
+                }
+                else {
+                    off += apr_snprintf(buffer+off, BUF_REMAIN, "meta(unknown) ");
+                }
+            }
+            else {
+                const char *btype = "data";
+                if (APR_BUCKET_IS_FILE(b)) {
+                    btype = "file";
+                }
+                else if (APR_BUCKET_IS_PIPE(b)) {
+                    btype = "pipe";
+                }
+                else if (APR_BUCKET_IS_SOCKET(b)) {
+                    btype = "socket";
+                }
+                else if (APR_BUCKET_IS_HEAP(b)) {
+                    btype = "heap";
+                }
+                else if (APR_BUCKET_IS_TRANSIENT(b)) {
+                    btype = "transient";
+                }
+                else if (APR_BUCKET_IS_IMMORTAL(b)) {
+                    btype = "immortal";
+                }
+#if APR_HAS_MMAP
+                else if (APR_BUCKET_IS_MMAP(b)) {
+                    btype = "mmap";
+                }
+#endif
+                else if (APR_BUCKET_IS_POOL(b)) {
+                    btype = "pool";
+                }
+                
+                off += apr_snprintf(buffer+off, BUF_REMAIN, "%s[%ld] ", 
+                                    btype, 
+                                    (long)(b->length == ((apr_size_t)-1)? -1UL : b->length));
+            }
+        }
+        line = *buffer? buffer : "(empty)";
+    }
+    /* Intentional no APLOGNO */
+    ap_log_cerror(APLOG_MARK, level, 0, c, "h2_session(%ld)-%s: %s", 
+                  c->id, tag, line);
+
+}
+#define C1_IO_BB_LOG(c, stream_id, level, tag, bb) \
+    if (APLOG_C_IS_LEVEL(c, level)) { \
+        h2_c1_io_bb_log((c), (stream_id), (level), (tag), (bb)); \
+    }
+
+
+apr_status_t h2_c1_io_init(h2_c1_io *io, h2_session *session)
+{
+    conn_rec *c = session->c1;
+
+    io->session = session;
+    io->output = apr_brigade_create(c->pool, c->bucket_alloc);
+    io->is_tls = ap_ssl_conn_is_ssl(session->c1);
+    io->buffer_output  = io->is_tls;
+    io->flush_threshold = 4 * (apr_size_t)h2_config_sgeti64(session->s, H2_CONF_STREAM_MAX_MEM);
+
+    if (io->buffer_output) {
+        /* This is what we start with, 
+         * see https://issues.apache.org/jira/browse/TS-2503 
+         */
+        io->warmup_size = h2_config_sgeti64(session->s, H2_CONF_TLS_WARMUP_SIZE);
+        io->cooldown_usecs = (h2_config_sgeti(session->s, H2_CONF_TLS_COOLDOWN_SECS)
+                              * APR_USEC_PER_SEC);
+        io->cooldown_usecs = 0;
+        io->write_size = (io->cooldown_usecs > 0?
+                          WRITE_SIZE_INITIAL : WRITE_SIZE_MAX);
+    }
+    else {
+        io->warmup_size = 0;
+        io->cooldown_usecs = 0;
+        io->write_size = 0;
+    }
+
+    if (APLOGctrace1(c)) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c,
+                      "h2_c1_io(%ld): init, buffering=%d, warmup_size=%ld, "
+                      "cd_secs=%f", c->id, io->buffer_output,
+                      (long)io->warmup_size,
+                      ((double)io->cooldown_usecs/APR_USEC_PER_SEC));
+    }
+
+    return APR_SUCCESS;
+}
+
+static void append_scratch(h2_c1_io *io)
+{
+    if (io->scratch && io->slen > 0) {
+        apr_bucket *b = apr_bucket_heap_create(io->scratch, io->slen,
+                                               apr_bucket_free,
+                                               io->session->c1->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(io->output, b);
+        io->buffered_len += io->slen;
+        io->scratch = NULL;
+        io->slen = io->ssize = 0;
+    }
+}
+
+static apr_size_t assure_scratch_space(h2_c1_io *io) {
+    apr_size_t remain = io->ssize - io->slen; 
+    if (io->scratch && remain == 0) {
+        append_scratch(io);
+    }
+    if (!io->scratch) {
+        /* we control the size and it is larger than what buckets usually
+         * allocate. */
+        io->scratch = apr_bucket_alloc(io->write_size, io->session->c1->bucket_alloc);
+        io->ssize = io->write_size;
+        io->slen = 0;
+        remain = io->ssize;
+    }
+    return remain;
+}
+    
+static apr_status_t read_to_scratch(h2_c1_io *io, apr_bucket *b)
+{
+    apr_status_t status;
+    const char *data;
+    apr_size_t len;
+    
+    if (!b->length) {
+        return APR_SUCCESS;
+    }
+    
+    ap_assert(b->length <= (io->ssize - io->slen));
+    if (APR_BUCKET_IS_FILE(b)) {
+        apr_bucket_file *f = (apr_bucket_file *)b->data;
+        apr_file_t *fd = f->fd;
+        apr_off_t offset = b->start;
+        
+        len = b->length;
+        /* file buckets will read 8000 byte chunks and split
+         * themselves. However, we do know *exactly* how many
+         * bytes we need where. So we read the file directly to
+         * where we need it.
+         */
+        status = apr_file_seek(fd, APR_SET, &offset);
+        if (status != APR_SUCCESS) {
+            return status;
+        }
+        status = apr_file_read(fd, io->scratch + io->slen, &len);
+        if (status != APR_SUCCESS && status != APR_EOF) {
+            return status;
+        }
+        io->slen += len;
+    }
+    else if (APR_BUCKET_IS_MMAP(b)) {
+        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, io->session->c1,
+                      "h2_c1_io(%ld): seeing mmap bucket of size %ld, scratch remain=%ld",
+                      io->session->c1->id, (long)b->length, (long)(io->ssize - io->slen));
+        status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+        if (status == APR_SUCCESS) {
+            memcpy(io->scratch+io->slen, data, len);
+            io->slen += len;
+        }
+    }
+    else {
+        status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+        if (status == APR_SUCCESS) {
+            memcpy(io->scratch+io->slen, data, len);
+            io->slen += len;
+        }
+    }
+    return status;
+}
+
+static apr_status_t pass_output(h2_c1_io *io, int flush)
+{
+    conn_rec *c = io->session->c1;
+    apr_off_t bblen = 0;
+    apr_status_t rv;
+
+    if (io->is_passing) {
+        /* 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, 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.",
+                      c->id);
+        return APR_EGENERAL;
+    }
+    io->is_passing = 1;
+
+    append_scratch(io);
+    if (flush) {
+        if (!APR_BUCKET_IS_FLUSH(APR_BRIGADE_LAST(io->output))) {
+            apr_bucket *b = apr_bucket_flush_create(c->bucket_alloc);
+            APR_BRIGADE_INSERT_TAIL(io->output, b);
+        }
+    }
+    if (APR_BRIGADE_EMPTY(io->output)) {
+        rv = APR_SUCCESS;
+        goto cleanup;
+    }
+
+    io->unflushed = !APR_BUCKET_IS_FLUSH(APR_BRIGADE_LAST(io->output));
+    apr_brigade_length(io->output, 0, &bblen);
+    C1_IO_BB_LOG(c, 0, APLOG_TRACE2, "out", io->output);
+
+    rv = ap_pass_brigade(c->output_filters, io->output);
+    if (APR_SUCCESS != rv) goto cleanup;
+    io->bytes_written += (apr_size_t)bblen;
+
+    if (io->write_size < WRITE_SIZE_MAX
+         && io->bytes_written >= io->warmup_size) {
+        /* connection is hot, use max size */
+        io->write_size = WRITE_SIZE_MAX;
+    }
+    else if (io->cooldown_usecs > 0
+             && io->write_size > WRITE_SIZE_INITIAL) {
+        apr_time_t now = apr_time_now();
+        if ((now - io->last_write) >= io->cooldown_usecs) {
+            /* long time not written, reset write size */
+            io->write_size = WRITE_SIZE_INITIAL;
+            io->bytes_written = 0;
+        }
+        else {
+            io->last_write = now;
+        }
+    }
+
+cleanup:
+    if (APR_SUCCESS != rv) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(03044)
+                      "h2_c1_io(%ld): pass_out brigade %ld bytes",
+                      c->id, (long)bblen);
+    }
+    apr_brigade_cleanup(io->output);
+    io->buffered_len = 0;
+    io->is_passing = 0;
+    return rv;
+}
+
+int h2_c1_io_needs_flush(h2_c1_io *io)
+{
+    return io->buffered_len >= io->flush_threshold;
+}
+
+int h2_c1_io_pending(h2_c1_io *io)
+{
+    return !APR_BRIGADE_EMPTY(io->output) || (io->scratch && io->slen > 0);
+}
+
+apr_status_t h2_c1_io_pass(h2_c1_io *io)
+{
+    apr_status_t rv = APR_SUCCESS;
+
+    if (h2_c1_io_pending(io)) {
+        rv = pass_output(io, 0);
+    }
+    return rv;
+}
+
+apr_status_t h2_c1_io_assure_flushed(h2_c1_io *io)
+{
+    apr_status_t rv = APR_SUCCESS;
+
+    if (h2_c1_io_pending(io) || io->unflushed) {
+        rv = pass_output(io, 1);
+        if (APR_SUCCESS != rv) goto cleanup;
+    }
+cleanup:
+    return rv;
+}
+
+apr_status_t h2_c1_io_add_data(h2_c1_io *io, const char *data, size_t length)
+{
+    apr_status_t status = APR_SUCCESS;
+    apr_size_t remain;
+    
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->session->c1,
+                  "h2_c1_io(%ld): adding %ld data bytes",
+                  io->session->c1->id, (long)length);
+    if (io->buffer_output) {
+        while (length > 0) {
+            remain = assure_scratch_space(io);
+            if (remain >= length) {
+                memcpy(io->scratch + io->slen, data, length);
+                io->slen += length;
+                length = 0;
+            }
+            else {
+                memcpy(io->scratch + io->slen, data, remain);
+                io->slen += remain;
+                data += remain;
+                length -= remain;
+            }
+        }
+    }
+    else {
+        status = apr_brigade_write(io->output, NULL, NULL, data, length);
+        io->buffered_len += length;
+    }
+    return status;
+}
+
+apr_status_t h2_c1_io_append(h2_c1_io *io, apr_bucket_brigade *bb)
+{
+    apr_bucket *b;
+    apr_status_t rv = APR_SUCCESS;
+
+    while (!APR_BRIGADE_EMPTY(bb)) {
+        b = APR_BRIGADE_FIRST(bb);
+        if (APR_BUCKET_IS_METADATA(b) || APR_BUCKET_IS_MMAP(b)) {
+            /* need to finish any open scratch bucket, as meta data
+             * needs to be forward "in order". */
+            append_scratch(io);
+            APR_BUCKET_REMOVE(b);
+            APR_BRIGADE_INSERT_TAIL(io->output, b);
+        }
+        else if (io->buffer_output) {
+            apr_size_t remain = assure_scratch_space(io);
+            if (b->length > remain) {
+                apr_bucket_split(b, remain);
+                if (io->slen == 0) {
+                    /* complete write_size bucket, append unchanged */
+                    APR_BUCKET_REMOVE(b);
+                    APR_BRIGADE_INSERT_TAIL(io->output, b);
+                    io->buffered_len += b->length;
+                    continue;
+                }
+            }
+            else {
+                /* bucket fits in remain, copy to scratch */
+                rv = read_to_scratch(io, b);
+                apr_bucket_delete(b);
+                if (APR_SUCCESS != rv) goto cleanup;
+                continue;
+            }
+        }
+        else {
+            /* no buffering, forward buckets setaside on flush */
+            apr_bucket_setaside(b, io->session->c1->pool);
+            APR_BUCKET_REMOVE(b);
+            APR_BRIGADE_INSERT_TAIL(io->output, b);
+            io->buffered_len += b->length;
+        }
+    }
+cleanup:
+    return rv;
+}
+
+static apr_status_t c1_in_feed_bucket(h2_session *session,
+                                      apr_bucket *b, apr_ssize_t *inout_len)
+{
+    apr_status_t rv = APR_SUCCESS;
+    apr_size_t len;
+    const char *data;
+    ssize_t n;
+
+    rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+    while (APR_SUCCESS == rv && len > 0) {
+        n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len);
+
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, session->c1,
+                      H2_SSSN_MSG(session, "fed %ld bytes to nghttp2, %ld read"),
+                      (long)len, (long)n);
+        if (n < 0) {
+            if (nghttp2_is_fatal((int)n)) {
+                h2_session_event(session, H2_SESSION_EV_PROTO_ERROR,
+                                 (int)n, nghttp2_strerror((int)n));
+                rv = APR_EGENERAL;
+            }
+        }
+        else {
+            *inout_len += n;
+            if ((apr_ssize_t)len <= n) {
+                break;
+            }
+            len -= (apr_size_t)n;
+            data += n;
+        }
+    }
+
+    return rv;
+}
+
+static apr_status_t c1_in_feed_brigade(h2_session *session,
+                                       apr_bucket_brigade *bb,
+                                       apr_ssize_t *inout_len)
+{
+    apr_status_t rv = APR_SUCCESS;
+    apr_bucket* b;
+
+    *inout_len = 0;
+    while (!APR_BRIGADE_EMPTY(bb)) {
+        b = APR_BRIGADE_FIRST(bb);
+        if (!APR_BUCKET_IS_METADATA(b)) {
+            rv = c1_in_feed_bucket(session, b, inout_len);
+            if (APR_SUCCESS != rv) goto cleanup;
+        }
+        apr_bucket_delete(b);
+    }
+cleanup:
+    apr_brigade_cleanup(bb);
+    return rv;
+}
+
+static apr_status_t read_and_feed(h2_session *session)
+{
+    apr_ssize_t bytes_fed, bytes_requested;
+    apr_status_t rv;
+
+    bytes_requested = H2MAX(APR_BUCKET_BUFF_SIZE, session->max_stream_mem * 4);
+    rv = ap_get_brigade(session->c1->input_filters,
+                        session->bbtmp, AP_MODE_READBYTES,
+                        APR_NONBLOCK_READ, bytes_requested);
+
+    if (APR_SUCCESS == rv) {
+        if (!APR_BRIGADE_EMPTY(session->bbtmp)) {
+            h2_util_bb_log(session->c1, session->id, APLOG_TRACE2, "c1 in",
+                           session->bbtmp);
+            rv = c1_in_feed_brigade(session, session->bbtmp, &bytes_fed);
+            session->io.bytes_read += bytes_fed;
+        }
+        else {
+            rv = APR_EAGAIN;
+        }
+    }
+    return rv;
+}
+
+apr_status_t h2_c1_read(h2_session *session)
+{
+    apr_status_t rv;
+
+    /* H2_IN filter handles all incoming data against the session.
+     * We just pull at the filter chain to make it happen */
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+                  H2_SSSN_MSG(session, "session_read start"));
+    rv = read_and_feed(session);
+
+    if (APR_SUCCESS == rv) {
+        h2_session_dispatch_event(session, H2_SESSION_EV_INPUT_PENDING, 0, NULL);
+    }
+    else if (APR_STATUS_IS_EAGAIN(rv)) {
+        /* Signal that we have exhausted the input momentarily.
+         * This might switch to polling the socket */
+        h2_session_dispatch_event(session, H2_SESSION_EV_INPUT_EXHAUSTED, 0, NULL);
+    }
+    else if (APR_SUCCESS != rv) {
+        if (APR_STATUS_IS_ETIMEDOUT(rv)
+            || APR_STATUS_IS_ECONNABORTED(rv)
+            || APR_STATUS_IS_ECONNRESET(rv)
+            || APR_STATUS_IS_EOF(rv)
+            || APR_STATUS_IS_EBADF(rv)) {
+            /* common status for a client that has left */
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, session->c1,
+                          H2_SSSN_MSG(session, "input gone"));
+        }
+        else {
+            /* uncommon status, log on INFO so that we see this */
+            ap_log_cerror( APLOG_MARK, APLOG_DEBUG, rv, session->c1,
+                          H2_SSSN_LOG(APLOGNO(02950), session,
+                          "error reading, terminating"));
+        }
+        h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+    }
+
+    apr_brigade_cleanup(session->bbtmp);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, session->c1,
+                  H2_SSSN_MSG(session, "session_read done"));
+    return rv;
+}
diff -r -N -u a/modules/http2/h2_c1_io.h b/modules/http2/h2_c1_io.h
--- a/modules/http2/h2_c1_io.h	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_c1_io.h	2024-10-30 21:40:01.059958617 +0100
@@ -0,0 +1,101 @@
+/* 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_c1_io__
+#define __mod_h2__h2_c1_io__
+
+struct h2_config;
+struct h2_session;
+
+/* h2_io is the basic handler of a httpd connection. It keeps two brigades,
+ * one for input, one for output and works with the installed connection
+ * filters.
+ * The read is done via a callback function, so that input can be processed
+ * directly without copying.
+ */
+typedef struct {
+    struct h2_session *session;
+    apr_bucket_brigade *output;
+
+    int is_tls;
+    int unflushed;
+    apr_time_t cooldown_usecs;
+    apr_int64_t warmup_size;
+    
+    apr_size_t write_size;
+    apr_time_t last_write;
+    apr_int64_t bytes_read;
+    apr_int64_t bytes_written;
+    
+    int buffer_output;
+    apr_off_t buffered_len;
+    apr_off_t flush_threshold;
+    unsigned int is_flushed : 1;
+    unsigned int is_passing : 1;
+
+    char *scratch;
+    apr_size_t ssize;
+    apr_size_t slen;
+} h2_c1_io;
+
+apr_status_t h2_c1_io_init(h2_c1_io *io, struct h2_session *session);
+
+/**
+ * Append data to the buffered output.
+ * @param buf the data to append
+ * @param length the length of the data to append
+ */
+apr_status_t h2_c1_io_add_data(h2_c1_io *io,
+                         const char *buf,
+                         size_t length);
+
+apr_status_t h2_c1_io_add(h2_c1_io *io, apr_bucket *b);
+
+apr_status_t h2_c1_io_append(h2_c1_io *io, apr_bucket_brigade *bb);
+
+/**
+ * Pass any buffered data on to the connection output filters.
+ * @param io the connection io
+ */
+apr_status_t h2_c1_io_pass(h2_c1_io *io);
+
+/**
+ * if there is any data pendiong or was any data send
+ * since the last FLUSH, send out a FLUSH now.
+ */
+apr_status_t h2_c1_io_assure_flushed(h2_c1_io *io);
+
+/**
+ * Check if the buffered amount of data needs flushing.
+ */
+int h2_c1_io_needs_flush(h2_c1_io *io);
+
+/**
+ * Check if we have output pending.
+ */
+int h2_c1_io_pending(h2_c1_io *io);
+
+struct h2_session;
+
+/**
+ * Read c1 input and pass it on to nghttp2.
+ * @param session the session
+ * @param when_pending != 0 if only pending input (sitting in filters)
+ *                     needs to be read
+ */
+apr_status_t h2_c1_read(struct h2_session *session);
+
+#endif /* defined(__mod_h2__h2_c1_io__) */
diff -r -N -u a/modules/http2/h2_c2.c b/modules/http2/h2_c2.c
--- a/modules/http2/h2_c2.c	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_c2.c	2024-10-30 21:40:01.059958617 +0100
@@ -0,0 +1,942 @@
+/* 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 <stddef.h>
+
+#include <apr_atomic.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+#include <http_vhost.h>
+#include <util_filter.h>
+#include <ap_mmn.h>
+#include <ap_mpm.h>
+#include <mpm_common.h>
+#include <mod_core.h>
+#include <scoreboard.h>
+
+#include "h2_private.h"
+#include "h2.h"
+#include "h2_bucket_beam.h"
+#include "h2_c1.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_c2_filter.h"
+#include "h2_protocol.h"
+#include "h2_mplx.h"
+#include "h2_request.h"
+#include "h2_headers.h"
+#include "h2_session.h"
+#include "h2_stream.h"
+#include "h2_ws.h"
+#include "h2_c2.h"
+#include "h2_util.h"
+#include "mod_http2.h"
+
+
+static module *mpm_module;
+static int mpm_supported = 1;
+static apr_socket_t *dummy_socket;
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+static ap_filter_rec_t *c2_net_in_filter_handle;
+static ap_filter_rec_t *c2_net_out_filter_handle;
+static ap_filter_rec_t *c2_request_in_filter_handle;
+static ap_filter_rec_t *c2_notes_out_filter_handle;
+
+#endif /* AP_HAS_RESPONSE_BUCKETS */
+
+static void check_modules(int force)
+{
+    static int checked = 0;
+    int i;
+
+    if (force || !checked) {
+        for (i = 0; ap_loaded_modules[i]; ++i) {
+            module *m = ap_loaded_modules[i];
+
+            if (!strcmp("event.c", m->name)) {
+                mpm_module = m;
+                break;
+            }
+            else if (!strcmp("motorz.c", m->name)) {
+                mpm_module = m;
+                break;
+            }
+            else if (!strcmp("mpm_netware.c", m->name)) {
+                mpm_module = m;
+                break;
+            }
+            else if (!strcmp("prefork.c", m->name)) {
+                mpm_module = m;
+                /* While http2 can work really well on prefork, it collides
+                 * today's use case for prefork: running single-thread app engines
+                 * like php. If we restrict h2_workers to 1 per process, php will
+                 * work fine, but browser will be limited to 1 active request at a
+                 * time. */
+                mpm_supported = 0;
+                break;
+            }
+            else if (!strcmp("simple_api.c", m->name)) {
+                mpm_module = m;
+                mpm_supported = 0;
+                break;
+            }
+            else if (!strcmp("mpm_winnt.c", m->name)) {
+                mpm_module = m;
+                break;
+            }
+            else if (!strcmp("worker.c", m->name)) {
+                mpm_module = m;
+                break;
+            }
+        }
+        checked = 1;
+    }
+}
+
+const char *h2_conn_mpm_name(void)
+{
+    check_modules(0);
+    return mpm_module? mpm_module->name : "unknown";
+}
+
+int h2_mpm_supported(void)
+{
+    check_modules(0);
+    return mpm_supported;
+}
+
+apr_status_t h2_c2_child_init(apr_pool_t *pool, server_rec *s)
+{
+    check_modules(1);
+    return apr_socket_create(&dummy_socket, APR_INET, SOCK_STREAM,
+                             APR_PROTO_TCP, pool);
+}
+
+static void h2_c2_log_io(conn_rec *c2, apr_off_t bytes_sent)
+{
+    if (bytes_sent && h2_c_logio_add_bytes_out) {
+        h2_c_logio_add_bytes_out(c2, bytes_sent);
+    }
+}
+
+void h2_c2_destroy(conn_rec *c2)
+{
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c2,
+                  "h2_c2(%s): destroy", c2->log_id);
+    if(!c2->aborted && conn_ctx && conn_ctx->bytes_sent) {
+      h2_c2_log_io(c2, conn_ctx->bytes_sent);
+    }
+    apr_pool_destroy(c2->pool);
+}
+
+void h2_c2_abort(conn_rec *c2, conn_rec *from)
+{
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+
+    AP_DEBUG_ASSERT(conn_ctx);
+    AP_DEBUG_ASSERT(conn_ctx->stream_id);
+    if(!c2->aborted && conn_ctx->bytes_sent) {
+      h2_c2_log_io(c2, conn_ctx->bytes_sent);
+    }
+
+    if (conn_ctx->beam_in) {
+        h2_beam_abort(conn_ctx->beam_in, from);
+    }
+    if (conn_ctx->beam_out) {
+        h2_beam_abort(conn_ctx->beam_out, from);
+    }
+    c2->aborted = 1;
+}
+
+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,
+                                           apr_bucket_brigade* bb,
+                                           ap_input_mode_t mode,
+                                           apr_read_type_e block,
+                                           apr_off_t readbytes)
+{
+    h2_conn_ctx_t *conn_ctx;
+    h2_c2_fctx_in_t *fctx = f->ctx;
+    apr_status_t status = APR_SUCCESS;
+    apr_bucket *b;
+    apr_off_t bblen;
+    apr_size_t rmax = (readbytes < APR_INT32_MAX)?
+                       (apr_size_t)readbytes : APR_INT32_MAX;
+    
+    conn_ctx = h2_conn_ctx_get(f->c);
+    AP_DEBUG_ASSERT(conn_ctx);
+
+    if (mode == AP_MODE_INIT) {
+        return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
+    }
+    
+    if (f->c->aborted) {
+        return APR_ECONNABORTED;
+    }
+    
+    if (APLOGctrace3(f->c)) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, f->c,
+                      "h2_c2_in(%s-%d): read, mode=%d, block=%d, readbytes=%ld",
+                      conn_ctx->id, conn_ctx->stream_id, mode, block,
+                      (long)readbytes);
+    }
+
+    if (!fctx) {
+        fctx = apr_pcalloc(f->c->pool, sizeof(*fctx));
+        f->ctx = fctx;
+        fctx->bb = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
+        if (!conn_ctx->beam_in) {
+            b = apr_bucket_eos_create(f->c->bucket_alloc);
+            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)) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c,
+                          "h2_c2_in(%s-%d): get more data from mplx, block=%d, "
+                          "readbytes=%ld",
+                          conn_ctx->id, conn_ctx->stream_id, block, (long)readbytes);
+        }
+        if (conn_ctx->beam_in) {
+            if (conn_ctx->pipe_in[H2_PIPE_OUT]) {
+receive:
+                status = h2_beam_receive(conn_ctx->beam_in, f->c, fctx->bb, APR_NONBLOCK_READ,
+                                         conn_ctx->mplx->stream_max_mem);
+                if (APR_STATUS_IS_EAGAIN(status) && APR_BLOCK_READ == block) {
+                    status = h2_util_wait_on_pipe(conn_ctx->pipe_in[H2_PIPE_OUT]);
+                    if (APR_SUCCESS == status) {
+                        goto receive;
+                    }
+                }
+            }
+            else {
+                status = h2_beam_receive(conn_ctx->beam_in, f->c, fctx->bb, block,
+                                         conn_ctx->mplx->stream_max_mem);
+            }
+        }
+        else {
+            status = APR_EOF;
+        }
+        
+        if (APLOGctrace3(f->c)) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, f->c,
+                          "h2_c2_in(%s-%d): read returned",
+                          conn_ctx->id, conn_ctx->stream_id);
+        }
+        if (APR_STATUS_IS_EAGAIN(status) 
+            && (mode == AP_MODE_GETLINE || block == APR_BLOCK_READ)) {
+            /* chunked input handling does not seem to like it if we
+             * return with APR_EAGAIN from a GETLINE read... 
+             * upload 100k test on test-ser.example.org hangs */
+            status = APR_SUCCESS;
+        }
+        else if (APR_STATUS_IS_EOF(status)) {
+            break;
+        }
+        else if (status != APR_SUCCESS) {
+            conn_ctx->last_err = status;
+            return status;
+        }
+
+        if (APLOGctrace3(f->c)) {
+            h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE3,
+                        "c2 input recv raw", fctx->bb);
+        }
+        if (h2_c_logio_add_bytes_in) {
+            apr_brigade_length(bb, 0, &bblen);
+            h2_c_logio_add_bytes_in(f->c, bblen);
+        }
+    }
+    
+    /* Nothing there, no more data to get. Return. */
+    if (status == APR_EOF && APR_BRIGADE_EMPTY(fctx->bb)) {
+        return status;
+    }
+
+    if (APLOGctrace3(f->c)) {
+        h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE3,
+                    "c2 input.bb", fctx->bb);
+    }
+           
+    if (APR_BRIGADE_EMPTY(fctx->bb)) {
+        if (APLOGctrace3(f->c)) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, f->c,
+                          "h2_c2_in(%s-%d): no data",
+                          conn_ctx->id, conn_ctx->stream_id);
+        }
+        return (block == APR_NONBLOCK_READ)? APR_EAGAIN : APR_EOF;
+    }
+    
+    if (mode == AP_MODE_EXHAUSTIVE) {
+        /* return all we have */
+        APR_BRIGADE_CONCAT(bb, fctx->bb);
+    }
+    else if (mode == AP_MODE_READBYTES) {
+        status = h2_brigade_concat_length(bb, fctx->bb, rmax);
+    }
+    else if (mode == AP_MODE_SPECULATIVE) {
+        status = h2_brigade_copy_length(bb, fctx->bb, rmax);
+    }
+    else if (mode == AP_MODE_GETLINE) {
+        /* we are reading a single LF line, e.g. the HTTP headers. 
+         * this has the nasty side effect to split the bucket, even
+         * though it ends with CRLF and creates a 0 length bucket */
+        status = apr_brigade_split_line(bb, fctx->bb, block,
+                                        HUGE_STRING_LEN);
+        if (APLOGctrace3(f->c)) {
+            char buffer[1024];
+            apr_size_t len = sizeof(buffer)-1;
+            apr_brigade_flatten(bb, buffer, &len);
+            buffer[len] = 0;
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, f->c,
+                          "h2_c2_in(%s-%d): getline: %s",
+                          conn_ctx->id, conn_ctx->stream_id, buffer);
+        }
+    }
+    else {
+        /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not
+         * to support it. Seems to work. */
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
+                      APLOGNO(03472) 
+                      "h2_c2_in(%s-%d), unsupported READ mode %d",
+                      conn_ctx->id, conn_ctx->stream_id, mode);
+        status = APR_ENOTIMPL;
+    }
+    
+    if (APLOGctrace3(f->c)) {
+        apr_brigade_length(bb, 0, &bblen);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, f->c,
+                      "h2_c2_in(%s-%d): %ld data bytes",
+                      conn_ctx->id, conn_ctx->stream_id, (long)bblen);
+    }
+    return status;
+}
+
+static apr_status_t beam_out(conn_rec *c2, h2_conn_ctx_t *conn_ctx, apr_bucket_brigade* bb)
+{
+    apr_off_t written = 0;
+    apr_status_t rv;
+
+    rv = h2_beam_send(conn_ctx->beam_out, c2, bb, APR_BLOCK_READ, &written);
+    if (APR_STATUS_IS_EAGAIN(rv)) {
+        rv = APR_SUCCESS;
+    }
+    return rv;
+}
+
+static apr_status_t h2_c2_filter_out(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+    apr_status_t rv;
+
+    ap_assert(conn_ctx);
+#if AP_HAS_RESPONSE_BUCKETS
+    if (!conn_ctx->has_final_response) {
+        apr_bucket *e;
+
+        for (e = APR_BRIGADE_FIRST(bb);
+             e != APR_BRIGADE_SENTINEL(bb);
+             e = APR_BUCKET_NEXT(e))
+        {
+            if (AP_BUCKET_IS_RESPONSE(e)) {
+                ap_bucket_response *resp = e->data;
+                if (resp->status >= 200) {
+                    conn_ctx->has_final_response = 1;
+                    break;
+                }
+            }
+            if (APR_BUCKET_IS_EOS(e)) {
+                break;
+            }
+        }
+    }
+#endif /* AP_HAS_RESPONSE_BUCKETS */
+    rv = beam_out(f->c, conn_ctx, bb);
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, f->c,
+                  "h2_c2(%s-%d): output leave",
+                  conn_ctx->id, conn_ctx->stream_id);
+    if (APR_SUCCESS != rv) {
+        h2_c2_abort(f->c, f->c);
+    }
+    return rv;
+}
+
+static int addn_headers(void *udata, const char *name, const char *value)
+{
+    apr_table_t *dest = udata;
+    apr_table_addn(dest, name, value);
+    return 1;
+}
+
+static void check_early_hints(request_rec *r, const char *tag)
+{
+    apr_array_header_t *push_list = h2_config_push_list(r);
+    apr_table_t *early_headers = h2_config_early_headers(r);
+
+    if (!r->expecting_100 &&
+        ((push_list && push_list->nelts > 0) ||
+         (early_headers && !apr_is_empty_table(early_headers)))) {
+        int have_hints = 0, i;
+
+        if (push_list && push_list->nelts > 0) {
+            ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+                          "%s, early announcing %d resources for push",
+                          tag, push_list->nelts);
+            for (i = 0; i < push_list->nelts; ++i) {
+                h2_push_res *push = &APR_ARRAY_IDX(push_list, i, h2_push_res);
+                apr_table_add(r->headers_out, "Link",
+                               apr_psprintf(r->pool, "<%s>; rel=preload%s",
+                                            push->uri_ref, push->critical? "; critical" : ""));
+            }
+            have_hints = 1;
+        }
+        if (early_headers && !apr_is_empty_table(early_headers)) {
+            apr_table_do(addn_headers, r->headers_out, early_headers, NULL);
+            have_hints = 1;
+        }
+
+        if (have_hints) {
+          int old_status;
+          const char *old_line;
+
+          if (h2_config_rgeti(r, H2_CONF_PUSH) == 0 &&
+              h2_config_sgeti(r->server, H2_CONF_PUSH) != 0) {
+              apr_table_setn(r->connection->notes, H2_PUSH_MODE_NOTE, "0");
+          }
+          old_status = r->status;
+          old_line = r->status_line;
+          r->status = 103;
+          r->status_line = "103 Early Hints";
+          ap_send_interim_response(r, 1);
+          r->status = old_status;
+          r->status_line = old_line;
+        }
+    }
+}
+
+static int c2_hook_fixups(request_rec *r)
+{
+    conn_rec *c2 = r->connection;
+    h2_conn_ctx_t *conn_ctx;
+
+    if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) {
+        return DECLINED;
+    }
+
+    check_early_hints(r, "late_fixup");
+
+    return DECLINED;
+}
+
+static apr_status_t http2_get_pollfd_from_conn(conn_rec *c,
+                                               struct apr_pollfd_t *pfd,
+                                               apr_interval_time_t *ptimeout)
+{
+#if H2_USE_PIPES
+    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;
+        }
+    }
+#else
+    (void)c;
+    (void)pfd;
+    (void)ptimeout;
+#endif /* H2_USE_PIPES */
+    return APR_ENOTIMPL;
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+static void c2_pre_read_request(request_rec *r, conn_rec *c2)
+{
+    h2_conn_ctx_t *conn_ctx;
+
+    if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) {
+        return;
+    }
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+                  "h2_c2(%s-%d): adding request filters",
+                  conn_ctx->id, conn_ctx->stream_id);
+    ap_add_input_filter_handle(c2_request_in_filter_handle, NULL, r, r->connection);
+    ap_add_output_filter_handle(c2_notes_out_filter_handle, NULL, r, r->connection);
+}
+
+static int c2_post_read_request(request_rec *r)
+{
+    h2_conn_ctx_t *conn_ctx;
+    conn_rec *c2 = r->connection;
+    apr_time_t timeout;
+
+    if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) {
+        return DECLINED;
+    }
+    /* Now that the request_rec is fully initialized, set relevant params */
+    conn_ctx->server = r->server;
+    timeout = h2_config_geti64(r, r->server, H2_CONF_STREAM_TIMEOUT);
+    if (timeout <= 0) {
+        timeout = r->server->timeout;
+    }
+    h2_conn_ctx_set_timeout(conn_ctx, timeout);
+    /* We only handle this one request on the connection and tell everyone
+     * that there is no need to keep it "clean" if something fails. Also,
+     * this prevents mod_reqtimeout from doing funny business with monitoring
+     * keepalive timeouts.
+     */
+    r->connection->keepalive = AP_CONN_CLOSE;
+
+    if (conn_ctx->beam_in && !apr_table_get(r->headers_in, "Content-Length")) {
+        r->body_indeterminate = 1;
+    }
+
+    if (h2_config_sgeti(conn_ctx->server, H2_CONF_COPY_FILES)) {
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+                      "h2_mplx(%s-%d): copy_files in output",
+                      conn_ctx->id, conn_ctx->stream_id);
+        h2_beam_set_copy_files(conn_ctx->beam_out, 1);
+    }
+
+    /* Add the raw bytes of the request (e.g. header frame lengths to
+     * the logio for this request. */
+    if (conn_ctx->request->raw_bytes && h2_c_logio_add_bytes_in) {
+        h2_c_logio_add_bytes_in(c2, conn_ctx->request->raw_bytes);
+    }
+    return OK;
+}
+
+static int c2_hook_pre_connection(conn_rec *c2, void *csd)
+{
+    h2_conn_ctx_t *conn_ctx;
+
+    if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) {
+        return DECLINED;
+    }
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+                  "h2_c2(%s-%d), adding filters",
+                  conn_ctx->id, conn_ctx->stream_id);
+    ap_add_input_filter_handle(c2_net_in_filter_handle, NULL, NULL, c2);
+    ap_add_output_filter_handle(c2_net_out_filter_handle, NULL, NULL, c2);
+    if (c2->keepalives == 0) {
+        /* Simulate that we had already a request on this connection. Some
+         * hooks trigger special behaviour when keepalives is 0.
+         * (Not necessarily in pre_connection, but later. Set it here, so it
+         * is in place.) */
+        c2->keepalives = 1;
+        /* We signal that this connection will be closed after the request.
+         * Which is true in that sense that we throw away all traffic data
+         * on this c2 connection after each requests. Although we might
+         * reuse internal structures like memory pools.
+         * The wanted effect of this is that httpd does not try to clean up
+         * any dangling data on this connection when a request is done. Which
+         * is unnecessary on a h2 stream.
+         */
+        c2->keepalive = AP_CONN_CLOSE;
+    }
+    return OK;
+}
+
+void h2_c2_register_hooks(void)
+{
+    /* When the connection processing actually starts, we might
+     * take over, if the connection is for a h2 stream.
+     */
+    ap_hook_pre_connection(c2_hook_pre_connection,
+                           NULL, NULL, APR_HOOK_MIDDLE);
+
+    /* 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_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST);
+#if H2_USE_POLLFD_FROM_CONN
+    ap_hook_get_pollfd_from_conn(http2_get_pollfd_from_conn, NULL, NULL,
+                                 APR_HOOK_MIDDLE);
+#endif
+    APR_REGISTER_OPTIONAL_FN(http2_get_pollfd_from_conn);
+
+    c2_net_in_filter_handle =
+        ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in,
+                                 NULL, AP_FTYPE_NETWORK);
+    c2_net_out_filter_handle =
+        ap_register_output_filter("H2_C2_NET_OUT", h2_c2_filter_out,
+                                  NULL, AP_FTYPE_NETWORK);
+    c2_request_in_filter_handle =
+        ap_register_input_filter("H2_C2_REQUEST_IN", h2_c2_filter_request_in,
+                                 NULL, AP_FTYPE_PROTOCOL);
+    c2_notes_out_filter_handle =
+        ap_register_output_filter("H2_C2_NOTES_OUT", h2_c2_filter_notes_out,
+                                  NULL, AP_FTYPE_PROTOCOL);
+}
+
+#else /* AP_HAS_RESPONSE_BUCKETS */
+
+static apr_status_t c2_run_pre_connection(conn_rec *c2, apr_socket_t *csd)
+{
+    if (c2->keepalives == 0) {
+        /* Simulate that we had already a request on this connection. Some
+         * hooks trigger special behaviour when keepalives is 0.
+         * (Not necessarily in pre_connection, but later. Set it here, so it
+         * is in place.) */
+        c2->keepalives = 1;
+        /* We signal that this connection will be closed after the request.
+         * Which is true in that sense that we throw away all traffic data
+         * on this c2 connection after each requests. Although we might
+         * reuse internal structures like memory pools.
+         * The wanted effect of this is that httpd does not try to clean up
+         * any dangling data on this connection when a request is done. Which
+         * is unnecessary on a h2 stream.
+         */
+        c2->keepalive = AP_CONN_CLOSE;
+        return ap_run_pre_connection(c2, csd);
+    }
+    ap_assert(c2->output_filters);
+    return APR_SUCCESS;
+}
+
+apr_status_t h2_c2_process(conn_rec *c2, apr_thread_t *thread, int worker_id)
+{
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+
+    ap_assert(conn_ctx);
+    ap_assert(conn_ctx->mplx);
+
+    /* See the discussion at <https://github.com/icing/mod_h2/issues/195>
+     *
+     * Each conn_rec->id is supposed to be unique at a point in time. Since
+     * some modules (and maybe external code) uses this id as an identifier
+     * for the request_rec they handle, it needs to be unique for secondary
+     * connections also.
+     *
+     * The MPM module assigns the connection ids and mod_unique_id is using
+     * that one to generate identifier for requests. While the implementation
+     * works for HTTP/1.x, the parallel execution of several requests per
+     * connection will generate duplicate identifiers on load.
+     *
+     * The original implementation for secondary connection identifiers used
+     * to shift the master connection id up and assign the stream id to the
+     * lower bits. This was cramped on 32 bit systems, but on 64bit there was
+     * enough space.
+     *
+     * As issue 195 showed, mod_unique_id only uses the lower 32 bit of the
+     * connection id, even on 64bit systems. Therefore collisions in request ids.
+     *
+     * The way master connection ids are generated, there is some space "at the
+     * top" of the lower 32 bits on allmost all systems. If you have a setup
+     * with 64k threads per child and 255 child processes, you live on the edge.
+     *
+     * The new implementation shifts 8 bits and XORs in the worker
+     * id. This will experience collisions with > 256 h2 workers and heavy
+     * load still. There seems to be no way to solve this in all possible
+     * configurations by mod_h2 alone.
+     */
+    c2->id = (c2->master->id << 8)^worker_id;
+
+    if (!conn_ctx->pre_conn_done) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+                      "h2_c2(%s-%d), adding filters",
+                      conn_ctx->id, conn_ctx->stream_id);
+        ap_add_input_filter("H2_C2_NET_IN", NULL, NULL, c2);
+        ap_add_output_filter("H2_C2_NET_CATCH_H1", NULL, NULL, c2);
+        ap_add_output_filter("H2_C2_NET_OUT", NULL, NULL, c2);
+
+        c2_run_pre_connection(c2, ap_get_conn_socket(c2));
+        conn_ctx->pre_conn_done = 1;
+    }
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+                  "h2_c2(%s-%d): process connection",
+                  conn_ctx->id, conn_ctx->stream_id);
+
+    c2->current_thread = thread;
+    ap_run_process_connection(c2);
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+                  "h2_c2(%s-%d): processing done",
+                  conn_ctx->id, conn_ctx->stream_id);
+
+    return APR_SUCCESS;
+}
+
+static apr_status_t c2_process(h2_conn_ctx_t *conn_ctx, conn_rec *c)
+{
+    const h2_request *req = conn_ctx->request;
+    conn_state_t *cs = c->cs;
+    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);
+
+    if (!r) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                      "h2_c2(%s-%d): create request_rec failed, r=NULL",
+                      conn_ctx->id, conn_ctx->stream_id);
+        goto cleanup;
+    }
+    if (r->status != HTTP_OK) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                      "h2_c2(%s-%d): create request_rec failed, r->status=%d",
+                      conn_ctx->id, conn_ctx->stream_id, r->status);
+        goto cleanup;
+    }
+
+    tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+    conn_ctx->input_chunked = tenc && ap_is_chunked(r->pool, tenc);
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                  "h2_c2(%s-%d): created request_rec for %s",
+                  conn_ctx->id, conn_ctx->stream_id, r->the_request);
+    conn_ctx->server = r->server;
+    timeout = h2_config_geti64(r, r->server, H2_CONF_STREAM_TIMEOUT);
+    if (timeout <= 0) {
+        timeout = r->server->timeout;
+    }
+    h2_conn_ctx_set_timeout(conn_ctx, timeout);
+
+    if (h2_config_sgeti(conn_ctx->server, H2_CONF_COPY_FILES)) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                      "h2_mplx(%s-%d): copy_files in output",
+                      conn_ctx->id, conn_ctx->stream_id);
+        h2_beam_set_copy_files(conn_ctx->beam_out, 1);
+    }
+
+    ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, r);
+    if (cs) {
+        cs->state = CONN_STATE_HANDLER;
+    }
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                  "h2_c2(%s-%d): start process_request",
+                  conn_ctx->id, conn_ctx->stream_id);
+
+    /* Add the raw bytes of the request (e.g. header frame lengths to
+     * the logio for this request. */
+    if (req->raw_bytes && h2_c_logio_add_bytes_in) {
+        h2_c_logio_add_bytes_in(c, req->raw_bytes);
+    }
+
+    ap_process_request(r);
+    /* After the call to ap_process_request, the
+     * request pool may have been deleted. */
+    r = NULL;
+    if (conn_ctx->beam_out) {
+        h2_beam_close(conn_ctx->beam_out, c);
+    }
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                  "h2_c2(%s-%d): process_request done",
+                  conn_ctx->id, conn_ctx->stream_id);
+    if (cs)
+        cs->state = CONN_STATE_WRITE_COMPLETION;
+
+cleanup:
+    return rv;
+}
+
+conn_rec *h2_c2_create(conn_rec *c1, apr_pool_t *parent,
+                       apr_bucket_alloc_t *buckt_alloc)
+{
+    apr_pool_t *pool;
+    conn_rec *c2;
+    void *cfg;
+
+    ap_assert(c1);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c1,
+                  "h2_c2: create for c1(%ld)", c1->id);
+
+    /* We create a pool with its own allocator to be used for
+     * processing a request. This is the only way to have the processing
+     * independent of its parent pool in the sense that it can work in
+     * another thread.
+     */
+    apr_pool_create(&pool, parent);
+    apr_pool_tag(pool, "h2_c2_conn");
+
+    c2 = (conn_rec *) apr_palloc(pool, sizeof(conn_rec));
+    memcpy(c2, c1, sizeof(conn_rec));
+
+    c2->master                 = c1;
+    c2->pool                   = pool;
+    c2->conn_config            = ap_create_conn_config(pool);
+    c2->notes                  = apr_table_make(pool, 5);
+    c2->input_filters          = NULL;
+    c2->output_filters         = NULL;
+    c2->keepalives             = 0;
+#if AP_MODULE_MAGIC_AT_LEAST(20180903, 1)
+    c2->filter_conn_ctx        = NULL;
+#endif
+    c2->bucket_alloc           = apr_bucket_alloc_create(pool);
+#if !AP_MODULE_MAGIC_AT_LEAST(20180720, 1)
+    c2->data_in_input_filters  = 0;
+    c2->data_in_output_filters = 0;
+#endif
+    /* prevent mpm_event from making wrong assumptions about this connection,
+     * like e.g. using its socket for an async read check. */
+    c2->clogging_input_filters = 1;
+    c2->log                    = NULL;
+    c2->aborted                = 0;
+    /* We cannot install the master connection socket on the secondary, as
+     * modules mess with timeouts/blocking of the socket, with
+     * unwanted side effects to the master connection processing.
+     * Fortunately, since we never use the secondary socket, we can just install
+     * a single, process-wide dummy and everyone is happy.
+     */
+    ap_set_module_config(c2->conn_config, &core_module, dummy_socket);
+    /* TODO: these should be unique to this thread */
+    c2->sbh = NULL; /*c1->sbh;*/
+    /* TODO: not all mpm modules have learned about secondary connections yet.
+     * copy their config from master to secondary.
+     */
+    if (mpm_module) {
+        cfg = ap_get_module_config(c1->conn_config, mpm_module);
+        ap_set_module_config(c2->conn_config, mpm_module, cfg);
+    }
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c2,
+                  "h2_c2(%s): created", c2->log_id);
+    return c2;
+}
+
+static int h2_c2_hook_post_read_request(request_rec *r)
+{
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(r->connection);
+
+    if (conn_ctx && conn_ctx->stream_id && ap_is_initial_req(r)) {
+
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+                      "h2_c2(%s-%d): adding request filters",
+                      conn_ctx->id, conn_ctx->stream_id);
+
+        /* setup the correct filters to process the request for h2 */
+        ap_add_input_filter("H2_C2_REQUEST_IN", NULL, r, r->connection);
+
+        /* replace the core http filter that formats response headers
+         * in HTTP/1 with our own that collects status and headers */
+        ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
+
+        ap_add_output_filter("H2_C2_RESPONSE_OUT", NULL, r, r->connection);
+        ap_add_output_filter("H2_C2_TRAILERS_OUT", NULL, r, r->connection);
+    }
+    return DECLINED;
+}
+
+static int h2_c2_hook_process(conn_rec* c)
+{
+    h2_conn_ctx_t *ctx;
+
+    if (!c->master) {
+        return DECLINED;
+    }
+
+    ctx = h2_conn_ctx_get(c);
+    if (ctx->stream_id) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                      "h2_h2, processing request directly");
+        c2_process(ctx, c);
+        return DONE;
+    }
+    else {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                      "secondary_conn(%ld): no h2 stream assing?", c->id);
+    }
+    return DECLINED;
+}
+
+void h2_c2_register_hooks(void)
+{
+    /* When the connection processing actually starts, we might
+     * take over, if the connection is for a h2 stream.
+     */
+    ap_hook_process_connection(h2_c2_hook_process,
+                               NULL, NULL, APR_HOOK_FIRST);
+    /* We need to manipulate the standard HTTP/1.1 protocol filters and
+     * install our own. This needs to be done very early. */
+    ap_hook_post_read_request(h2_c2_hook_post_read_request, NULL, NULL, APR_HOOK_REALLY_FIRST);
+    ap_hook_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST);
+#if H2_USE_POLLFD_FROM_CONN
+    ap_hook_get_pollfd_from_conn(http2_get_pollfd_from_conn, NULL, NULL,
+                                 APR_HOOK_MIDDLE);
+#endif
+    APR_REGISTER_OPTIONAL_FN(http2_get_pollfd_from_conn);
+
+    ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in,
+                             NULL, AP_FTYPE_NETWORK);
+    ap_register_output_filter("H2_C2_NET_OUT", h2_c2_filter_out,
+                              NULL, AP_FTYPE_NETWORK);
+    ap_register_output_filter("H2_C2_NET_CATCH_H1", h2_c2_filter_catch_h1_out,
+                              NULL, AP_FTYPE_NETWORK);
+
+    ap_register_input_filter("H2_C2_REQUEST_IN", h2_c2_filter_request_in,
+                             NULL, AP_FTYPE_PROTOCOL);
+    ap_register_output_filter("H2_C2_RESPONSE_OUT", h2_c2_filter_response_out,
+                              NULL, AP_FTYPE_PROTOCOL);
+    ap_register_output_filter("H2_C2_TRAILERS_OUT", h2_c2_filter_trailers_out,
+                              NULL, AP_FTYPE_PROTOCOL);
+}
+
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
diff -r -N -u a/modules/http2/h2_c2_filter.c b/modules/http2/h2_c2_filter.c
--- a/modules/http2/h2_c2_filter.c	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_c2_filter.c	2024-10-30 21:40:01.059958617 +0100
@@ -0,0 +1,1056 @@
+/* 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 <stdio.h>
+
+#include <apr_date.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <util_time.h>
+
+#include "h2_private.h"
+#include "h2.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_headers.h"
+#include "h2_c1.h"
+#include "h2_c2_filter.h"
+#include "h2_c2.h"
+#include "h2_mplx.h"
+#include "h2_request.h"
+#include "h2_ws.h"
+#include "h2_util.h"
+
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+apr_status_t h2_c2_filter_notes_out(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+    apr_bucket *b;
+    request_rec *r_prev;
+    ap_bucket_response *resp;
+    const char *err;
+
+    if (!f->r) {
+        goto pass;
+    }
+
+    for (b = APR_BRIGADE_FIRST(bb);
+         b != APR_BRIGADE_SENTINEL(bb);
+         b = APR_BUCKET_NEXT(b))
+    {
+        if (AP_BUCKET_IS_RESPONSE(b)) {
+            resp = b->data;
+            if (resp->status >= 400 && f->r->prev) {
+                /* Error responses are commonly handled via internal
+                 * redirects to error documents. That creates a new
+                 * request_rec with 'prev' set to the original.
+                 * Each of these has its onw 'notes'.
+                 * We'd like to copy interesting ones into the current 'r->notes'
+                 * as we reset HTTP/2 stream with H2 specific error codes then.
+                 */
+                for (r_prev = f->r; r_prev != NULL; r_prev = r_prev->prev) {
+                    if ((err = apr_table_get(r_prev->notes, "ssl-renegotiate-forbidden"))) {
+                        if (r_prev != f->r) {
+                            apr_table_setn(resp->notes, "ssl-renegotiate-forbidden", err);
+                        }
+                        break;
+                    }
+                }
+            }
+            else if (h2_config_rgeti(f->r, H2_CONF_PUSH) == 0
+                     && h2_config_sgeti(f->r->server, H2_CONF_PUSH) != 0) {
+                /* location configuration turns off H2 PUSH handling */
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+                              "h2_c2_filter_notes_out, turning PUSH off");
+                apr_table_setn(resp->notes, H2_PUSH_MODE_NOTE, "0");
+            }
+        }
+    }
+pass:
+    return ap_pass_brigade(f->next, bb);
+}
+
+apr_status_t h2_c2_filter_request_in(ap_filter_t *f,
+                                     apr_bucket_brigade *bb,
+                                     ap_input_mode_t mode,
+                                     apr_read_type_e block,
+                                     apr_off_t readbytes)
+{
+    h2_conn_ctx_t *conn_ctx;
+    apr_bucket *b;
+
+    /* just get out of the way for things we don't want to handle. */
+    if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) {
+        return ap_get_brigade(f->next, bb, mode, block, readbytes);
+    }
+
+    /* 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) {
+        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;
+        }
+
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+                      "h2_c2_filter_request_in(%s): adding request bucket",
+                      conn_ctx->id);
+        b = h2_request_create_bucket(req, f->r);
+        APR_BRIGADE_INSERT_TAIL(bb, b);
+
+        if (req->http_status != H2_HTTP_STATUS_UNSET) {
+            /* error was encountered preparing this request */
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+                          "h2_c2_filter_request_in(%s): adding error bucket %d",
+                          conn_ctx->id, req->http_status);
+            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;
+        }
+
+        if (!conn_ctx->beam_in) {
+            b = apr_bucket_eos_create(f->c->bucket_alloc);
+            APR_BRIGADE_INSERT_TAIL(bb, b);
+        }
+
+        return APR_SUCCESS;
+    }
+
+    return ap_get_brigade(f->next, bb, mode, block, readbytes);
+}
+
+#else /* AP_HAS_RESPONSE_BUCKETS */
+
+#define H2_FILTER_LOG(name, c, level, rv, msg, bb) \
+    do { \
+        if (APLOG_C_IS_LEVEL((c),(level))) { \
+            char buffer[4 * 1024]; \
+            apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
+            len = h2_util_bb_print(buffer, bmax, "", "", (bb)); \
+            ap_log_cerror(APLOG_MARK, (level), rv, (c), \
+                          "FILTER[%s]: %s %s", \
+                          (name), (msg), len? buffer : ""); \
+        } \
+    } while (0)
+
+
+/* This routine is called by apr_table_do and merges all instances of
+ * the passed field values into a single array that will be further
+ * processed by some later routine.  Originally intended to help split
+ * and recombine multiple Vary fields, though it is generic to any field
+ * consisting of comma/space-separated tokens.
+ */
+static int uniq_field_values(void *d, const char *key, const char *val)
+{
+    apr_array_header_t *values;
+    char *start;
+    char *e;
+    char **strpp;
+    int  i;
+
+    (void)key;
+    values = (apr_array_header_t *)d;
+
+    e = apr_pstrdup(values->pool, val);
+
+    do {
+        /* Find a non-empty fieldname */
+
+        while (*e == ',' || apr_isspace(*e)) {
+            ++e;
+        }
+        if (*e == '\0') {
+            break;
+        }
+        start = e;
+        while (*e != '\0' && *e != ',' && !apr_isspace(*e)) {
+            ++e;
+        }
+        if (*e != '\0') {
+            *e++ = '\0';
+        }
+
+        /* Now add it to values if it isn't already represented.
+         * Could be replaced by a ap_array_strcasecmp() if we had one.
+         */
+        for (i = 0, strpp = (char **) values->elts; i < values->nelts;
+             ++i, ++strpp) {
+            if (*strpp && apr_strnatcasecmp(*strpp, start) == 0) {
+                break;
+            }
+        }
+        if (i == values->nelts) {  /* if not found */
+            *(char **)apr_array_push(values) = start;
+        }
+    } while (*e != '\0');
+
+    return 1;
+}
+
+/*
+ * Since some clients choke violently on multiple Vary fields, or
+ * Vary fields with duplicate tokens, combine any multiples and remove
+ * any duplicates.
+ */
+static void fix_vary(request_rec *r)
+{
+    apr_array_header_t *varies;
+
+    varies = apr_array_make(r->pool, 5, sizeof(char *));
+
+    /* Extract all Vary fields from the headers_out, separate each into
+     * its comma-separated fieldname values, and then add them to varies
+     * if not already present in the array.
+     */
+    apr_table_do(uniq_field_values, varies, r->headers_out, "Vary", NULL);
+
+    /* If we found any, replace old Vary fields with unique-ified value */
+
+    if (varies->nelts > 0) {
+        apr_table_setn(r->headers_out, "Vary",
+                       apr_array_pstrcat(r->pool, varies, ','));
+    }
+}
+
+static h2_headers *create_response(request_rec *r)
+{
+    const char *clheader;
+    const char *ctype;
+
+    /*
+     * Now that we are ready to send a response, we need to combine the two
+     * header field tables into a single table.  If we don't do this, our
+     * later attempts to set or unset a given fieldname might be bypassed.
+     */
+    if (!apr_is_empty_table(r->err_headers_out)) {
+        r->headers_out = apr_table_overlay(r->pool, r->err_headers_out,
+                                           r->headers_out);
+        apr_table_clear(r->err_headers_out);
+    }
+
+    /*
+     * Remove the 'Vary' header field if the client can't handle it.
+     * Since this will have nasty effects on HTTP/1.1 caches, force
+     * the response into HTTP/1.0 mode.
+     */
+    if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) {
+        apr_table_unset(r->headers_out, "Vary");
+        r->proto_num = HTTP_VERSION(1,0);
+        apr_table_setn(r->subprocess_env, "force-response-1.0", "1");
+    }
+    else {
+        fix_vary(r);
+    }
+
+    /*
+     * Now remove any ETag response header field if earlier processing
+     * says so (such as a 'FileETag None' directive).
+     */
+    if (apr_table_get(r->notes, "no-etag") != NULL) {
+        apr_table_unset(r->headers_out, "ETag");
+    }
+
+    /* determine the protocol and whether we should use keepalives. */
+    ap_set_keepalive(r);
+
+    if (AP_STATUS_IS_HEADER_ONLY(r->status)) {
+        apr_table_unset(r->headers_out, "Transfer-Encoding");
+        apr_table_unset(r->headers_out, "Content-Length");
+        r->content_type = r->content_encoding = NULL;
+        r->content_languages = NULL;
+        r->clength = r->chunked = 0;
+    }
+    else if (r->chunked) {
+        apr_table_mergen(r->headers_out, "Transfer-Encoding", "chunked");
+        apr_table_unset(r->headers_out, "Content-Length");
+    }
+
+    ctype = ap_make_content_type(r, r->content_type);
+    if (ctype) {
+        apr_table_setn(r->headers_out, "Content-Type", ctype);
+    }
+
+    if (r->content_encoding) {
+        apr_table_setn(r->headers_out, "Content-Encoding",
+                       r->content_encoding);
+    }
+
+    if (!apr_is_empty_array(r->content_languages)) {
+        int i;
+        char *token;
+        char **languages = (char **)(r->content_languages->elts);
+        const char *field = apr_table_get(r->headers_out, "Content-Language");
+
+        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]))
+                    break;
+            }
+            if (i == r->content_languages->nelts) {
+                *((char **) apr_array_push(r->content_languages)) = token;
+            }
+        }
+
+        field = apr_array_pstrcat(r->pool, r->content_languages, ',');
+        apr_table_setn(r->headers_out, "Content-Language", field);
+    }
+
+    /*
+     * Control cachability for non-cachable responses if not already set by
+     * some other part of the server configuration.
+     */
+    if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) {
+        char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
+        ap_recent_rfc822_date(date, r->request_time);
+        apr_table_add(r->headers_out, "Expires", date);
+    }
+
+    /* This is a hack, but I can't find anyway around it.  The idea is that
+     * we don't want to send out 0 Content-Lengths if it is a head request.
+     * This happens when modules try to outsmart the server, and return
+     * if they see a HEAD request.  Apache 1.3 handlers were supposed to
+     * just return in that situation, and the core handled the HEAD.  In
+     * 2.0, if a handler returns, then the core sends an EOS bucket down
+     * the filter stack, and the content-length filter computes a C-L of
+     * zero and that gets put in the headers, and we end up sending a
+     * zero C-L to the client.  We can't just remove the C-L filter,
+     * because well behaved 2.0 handlers will send their data down the stack,
+     * and we will compute a real C-L for the head request. RBB
+     */
+    if (r->header_only
+        && (clheader = apr_table_get(r->headers_out, "Content-Length"))
+        && !strcmp(clheader, "0")) {
+        apr_table_unset(r->headers_out, "Content-Length");
+    }
+
+    /*
+     * keep the set-by-proxy server and date headers, otherwise
+     * generate a new server header / date header
+     */
+    if (r->proxyreq == PROXYREQ_NONE
+        || !apr_table_get(r->headers_out, "Date")) {
+        char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
+        ap_recent_rfc822_date(date, r->request_time);
+        apr_table_setn(r->headers_out, "Date", date );
+    }
+    if (r->proxyreq == PROXYREQ_NONE
+        || !apr_table_get(r->headers_out, "Server")) {
+        const char *us = ap_get_server_banner();
+        if (us && *us) {
+            apr_table_setn(r->headers_out, "Server", us);
+        }
+    }
+
+    return h2_headers_rcreate(r, r->status, r->headers_out, r->pool);
+}
+
+typedef enum {
+    H2_RP_STATUS_LINE,
+    H2_RP_HEADER_LINE,
+    H2_RP_DONE
+} h2_rp_state_t;
+
+typedef struct h2_response_parser h2_response_parser;
+struct h2_response_parser {
+    const char *id;
+    h2_rp_state_t state;
+    conn_rec *c;
+    apr_pool_t *pool;
+    int http_status;
+    apr_array_header_t *hlines;
+    apr_bucket_brigade *tmp;
+    apr_bucket_brigade *saveto;
+};
+
+static apr_status_t parse_header(h2_response_parser *parser, char *line) {
+    const char *hline;
+    if (line[0] == ' ' || line[0] == '\t') {
+        char **plast;
+        /* continuation line from the header before this */
+        while (line[0] == ' ' || line[0] == '\t') {
+            ++line;
+        }
+
+        plast = apr_array_pop(parser->hlines);
+        if (plast == NULL) {
+            /* not well formed */
+            return APR_EINVAL;
+        }
+        hline = apr_psprintf(parser->pool, "%s %s", *plast, line);
+    }
+    else {
+        /* new header line */
+        hline = apr_pstrdup(parser->pool, line);
+    }
+    APR_ARRAY_PUSH(parser->hlines, const char*) = hline;
+    return APR_SUCCESS;
+}
+
+static apr_status_t get_line(h2_response_parser *parser, apr_bucket_brigade *bb,
+                             char *line, apr_size_t len)
+{
+    apr_status_t status;
+
+    if (!parser->tmp) {
+        parser->tmp = apr_brigade_create(parser->pool, parser->c->bucket_alloc);
+    }
+    status = apr_brigade_split_line(parser->tmp, bb, APR_BLOCK_READ,
+                                    len);
+    if (status == APR_SUCCESS) {
+        --len;
+        status = apr_brigade_flatten(parser->tmp, line, &len);
+        if (status == APR_SUCCESS) {
+            /* we assume a non-0 containing line and remove trailing crlf. */
+            line[len] = '\0';
+            /*
+             * XXX: What to do if there is an LF but no CRLF?
+             *      Should we error out?
+             */
+            if (len >= 2 && !strcmp(H2_CRLF, line + len - 2)) {
+                len -= 2;
+                line[len] = '\0';
+                apr_brigade_cleanup(parser->tmp);
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
+                              "h2_c2(%s): read response line: %s",
+                              parser->id, line);
+            }
+            else {
+                apr_off_t brigade_length;
+
+                /*
+                 * If the brigade parser->tmp becomes longer than our buffer
+                 * for flattening we never have a chance to get a complete
+                 * line. This can happen if we are called multiple times after
+                 * previous calls did not find a H2_CRLF and we returned
+                 * APR_EAGAIN. In this case parser->tmp (correctly) grows
+                 * with each call to apr_brigade_split_line.
+                 *
+                 * XXX: Currently a stack based buffer of HUGE_STRING_LEN is
+                 * used. This means we cannot cope with lines larger than
+                 * HUGE_STRING_LEN which might be an issue.
+                 */
+                status = apr_brigade_length(parser->tmp, 0, &brigade_length);
+                if ((status != APR_SUCCESS) || (brigade_length > (apr_off_t)len)) {
+                    ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, parser->c, APLOGNO(10257)
+                                  "h2_c2(%s): read response, line too long",
+                                  parser->id);
+                    return APR_ENOSPC;
+                }
+                /* this does not look like a complete line yet */
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
+                              "h2_c2(%s): read response, incomplete line: %s",
+                              parser->id, line);
+                if (!parser->saveto) {
+                    parser->saveto = apr_brigade_create(parser->pool,
+                                                        parser->c->bucket_alloc);
+                }
+                /*
+                 * Be on the save side and save the parser->tmp brigade
+                 * as it could contain transient buckets which could be
+                 * invalid next time we are here.
+                 *
+                 * NULL for the filter parameter is ok since we
+                 * provide our own brigade as second parameter
+                 * and ap_save_brigade does not need to create one.
+                 */
+                ap_save_brigade(NULL, &(parser->saveto), &(parser->tmp),
+                                parser->tmp->p);
+                APR_BRIGADE_CONCAT(parser->tmp, parser->saveto);
+                return APR_EAGAIN;
+            }
+        }
+    }
+    apr_brigade_cleanup(parser->tmp);
+    return status;
+}
+
+static apr_table_t *make_table(h2_response_parser *parser)
+{
+    apr_array_header_t *hlines = parser->hlines;
+    if (hlines) {
+        apr_table_t *headers = apr_table_make(parser->pool, hlines->nelts);
+        int i;
+
+        for (i = 0; i < hlines->nelts; ++i) {
+            char *hline = ((char **)hlines->elts)[i];
+            char *sep = ap_strchr(hline, ':');
+            if (!sep) {
+                ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, parser->c,
+                              APLOGNO(02955) "h2_c2(%s): invalid header[%d] '%s'",
+                              parser->id, i, (char*)hline);
+                /* not valid format, abort */
+                return NULL;
+            }
+            (*sep++) = '\0';
+            while (*sep == ' ' || *sep == '\t') {
+                ++sep;
+            }
+
+            if (!h2_util_ignore_resp_header(hline)) {
+                apr_table_merge(headers, hline, sep);
+            }
+        }
+        return headers;
+    }
+    else {
+        return apr_table_make(parser->pool, 0);
+    }
+}
+
+static apr_status_t pass_response(h2_conn_ctx_t *conn_ctx, ap_filter_t *f,
+                                  h2_response_parser *parser)
+{
+    apr_bucket *b;
+    apr_status_t status;
+    h2_headers *response = h2_headers_create(parser->http_status,
+                                             make_table(parser),
+                                             parser->c->notes,
+                                             0, parser->pool);
+    apr_brigade_cleanup(parser->tmp);
+    b = h2_bucket_headers_create(parser->c->bucket_alloc, response);
+    APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
+    b = apr_bucket_flush_create(parser->c->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
+    status = ap_pass_brigade(f->next, parser->tmp);
+    apr_brigade_cleanup(parser->tmp);
+
+    /* reset parser for possible next response */
+    parser->state = H2_RP_STATUS_LINE;
+    apr_array_clear(parser->hlines);
+
+    if (response->status >= 200) {
+        conn_ctx->has_final_response = 1;
+    }
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
+                  APLOGNO(03197) "h2_c2(%s): passed response %d",
+                  parser->id, response->status);
+    return status;
+}
+
+static apr_status_t parse_status(h2_response_parser *parser, char *line)
+{
+    int sindex = (apr_date_checkmask(line, "HTTP/#.# ###*")? 9 :
+                  (apr_date_checkmask(line, "HTTP/# ###*")? 7 : 0));
+    if (sindex > 0) {
+        int k = sindex + 3;
+        char keepchar = line[k];
+        line[k] = '\0';
+        parser->http_status = atoi(&line[sindex]);
+        line[k] = keepchar;
+        parser->state = H2_RP_HEADER_LINE;
+
+        return APR_SUCCESS;
+    }
+    /* Seems like there is garbage on the connection. May be a leftover
+     * from a previous proxy request.
+     * This should only happen if the H2_RESPONSE filter is not yet in
+     * place (post_read_request has not been reached and the handler wants
+     * to write something. Probably just the interim response we are
+     * waiting for. But if there is other data hanging around before
+     * that, this needs to fail. */
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c, APLOGNO(03467)
+                  "h2_c2(%s): unable to parse status line: %s",
+                  parser->id, line);
+    return APR_EINVAL;
+}
+
+static apr_status_t parse_response(h2_response_parser *parser,
+                                   h2_conn_ctx_t *conn_ctx,
+                                   ap_filter_t* f, apr_bucket_brigade *bb)
+{
+    char line[HUGE_STRING_LEN];
+    apr_status_t status = APR_SUCCESS;
+
+    while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
+        switch (parser->state) {
+            case H2_RP_STATUS_LINE:
+            case H2_RP_HEADER_LINE:
+                status = get_line(parser, bb, line, sizeof(line));
+                if (status == APR_EAGAIN) {
+                    /* need more data */
+                    return APR_SUCCESS;
+                }
+                else if (status != APR_SUCCESS) {
+                    return status;
+                }
+                if (parser->state == H2_RP_STATUS_LINE) {
+                    /* instead of parsing, just take it directly */
+                    status = parse_status(parser, line);
+                }
+                else if (line[0] == '\0') {
+                    /* end of headers, pass response onward */
+                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, parser->c,
+                                  "h2_c2(%s): end of response", parser->id);
+                    return pass_response(conn_ctx, f, parser);
+                }
+                else {
+                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, parser->c,
+                                  "h2_c2(%s): response header %s", parser->id, line);
+                    status = parse_header(parser, line);
+                }
+                break;
+
+            default:
+                return status;
+        }
+    }
+    return status;
+}
+
+apr_status_t h2_c2_filter_catch_h1_out(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+    h2_response_parser *parser = f->ctx;
+    apr_status_t rv;
+
+    ap_assert(conn_ctx);
+    H2_FILTER_LOG("c2_catch_h1_out", f->c, APLOG_TRACE2, 0, "check", bb);
+
+    if (!f->c->aborted && !conn_ctx->has_final_response) {
+        if (!parser) {
+            parser = apr_pcalloc(f->c->pool, sizeof(*parser));
+            parser->id = apr_psprintf(f->c->pool, "%s-%d", conn_ctx->id, conn_ctx->stream_id);
+            parser->pool = f->c->pool;
+            parser->c = f->c;
+            parser->state = H2_RP_STATUS_LINE;
+            parser->hlines = apr_array_make(parser->pool, 10, sizeof(char *));
+            f->ctx = parser;
+        }
+
+        if (!APR_BRIGADE_EMPTY(bb)) {
+            apr_bucket *b = APR_BRIGADE_FIRST(bb);
+            if (AP_BUCKET_IS_EOR(b)) {
+                /* TODO: Yikes, this happens when errors are encountered on input
+                 * before anything from the repsonse has been processed. The
+                 * ap_die_r() call will do nothing in certain conditions.
+                 */
+                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);
+                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.
+         * One example is a 100-continue answer via a mod_proxy setup. */
+        while (bb && !f->c->aborted && !conn_ctx->has_final_response) {
+            rv = parse_response(parser, conn_ctx, f, bb);
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, f->c,
+                          "h2_c2(%s): parsed response", parser->id);
+            if (APR_BRIGADE_EMPTY(bb) || APR_SUCCESS != rv) {
+                return rv;
+            }
+        }
+    }
+
+    return ap_pass_brigade(f->next, bb);
+}
+
+apr_status_t h2_c2_filter_response_out(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+    request_rec *r = f->r;
+    apr_bucket *b, *bresp, *body_bucket = NULL, *next;
+    ap_bucket_error *eb = NULL;
+    h2_headers *response = NULL;
+    int headers_passing = 0;
+
+    H2_FILTER_LOG("c2_response_out", f->c, APLOG_TRACE1, 0, "called with", bb);
+
+    if (f->c->aborted || !conn_ctx || conn_ctx->has_final_response) {
+        return ap_pass_brigade(f->next, bb);
+    }
+
+    if (!conn_ctx->has_final_response) {
+        /* check, if we need to send the response now. Until we actually
+         * see a DATA bucket or some EOS/EOR, we do not do so. */
+        for (b = APR_BRIGADE_FIRST(bb);
+             b != APR_BRIGADE_SENTINEL(bb);
+             b = APR_BUCKET_NEXT(b))
+        {
+            if (AP_BUCKET_IS_ERROR(b) && !eb) {
+                eb = b->data;
+            }
+            else if (AP_BUCKET_IS_EOC(b)) {
+                /* If we see an EOC bucket it is a signal that we should get out
+                 * of the way doing nothing.
+                 */
+                ap_remove_output_filter(f);
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+                              "h2_c2(%s): eoc bucket passed", conn_ctx->id);
+                return ap_pass_brigade(f->next, bb);
+            }
+            else if (H2_BUCKET_IS_HEADERS(b)) {
+                headers_passing = 1;
+            }
+            else if (!APR_BUCKET_IS_FLUSH(b)) {
+                body_bucket = b;
+                break;
+            }
+        }
+
+        if (eb) {
+            int st = eb->status;
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03047)
+                          "h2_c2(%s): err bucket status=%d",
+                          conn_ctx->id, st);
+            /* throw everything away and replace it with the error response
+             * generated by ap_die() */
+            apr_brigade_cleanup(bb);
+            ap_die(st, r);
+            return AP_FILTER_ERROR;
+        }
+
+        if (body_bucket || !headers_passing) {
+            /* time to insert the response bucket before the body or if
+             * no h2_headers is passed, e.g. the response is empty */
+            response = create_response(r);
+            if (response == NULL) {
+                ap_log_cerror(APLOG_MARK, APLOG_NOTICE, 0, f->c, APLOGNO(03048)
+                              "h2_c2(%s): unable to create response", conn_ctx->id);
+                return APR_ENOMEM;
+            }
+
+            bresp = h2_bucket_headers_create(f->c->bucket_alloc, response);
+            if (body_bucket) {
+                APR_BUCKET_INSERT_BEFORE(body_bucket, bresp);
+            }
+            else {
+                APR_BRIGADE_INSERT_HEAD(bb, bresp);
+            }
+            conn_ctx->has_final_response = 1;
+            r->sent_bodyct = 1;
+            ap_remove_output_filter_byhandle(f->r->output_filters, "H2_C2_NET_CATCH_H1");
+        }
+    }
+
+    if (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status)) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+                      "h2_c2(%s): headers only, cleanup output brigade", conn_ctx->id);
+        b = body_bucket? body_bucket : APR_BRIGADE_FIRST(bb);
+        while (b != APR_BRIGADE_SENTINEL(bb)) {
+            next = APR_BUCKET_NEXT(b);
+            if (APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b)) {
+                break;
+            }
+            if (!H2_BUCKET_IS_HEADERS(b)) {
+                APR_BUCKET_REMOVE(b);
+                apr_bucket_destroy(b);
+            }
+            b = next;
+        }
+    }
+    if (conn_ctx->has_final_response) {
+        /* lets get out of the way, our task is done */
+        ap_remove_output_filter(f);
+    }
+    return ap_pass_brigade(f->next, bb);
+}
+
+
+struct h2_chunk_filter_t {
+    const char *id;
+    int eos_chunk_added;
+    apr_bucket_brigade *bbchunk;
+    apr_off_t chunked_total;
+};
+typedef struct h2_chunk_filter_t h2_chunk_filter_t;
+
+
+static void make_chunk(conn_rec *c, h2_chunk_filter_t *fctx, apr_bucket_brigade *bb,
+                       apr_bucket *first, apr_off_t chunk_len,
+                       apr_bucket *tail)
+{
+    /* Surround the buckets [first, tail[ with new buckets carrying the
+     * HTTP/1.1 chunked encoding format. If tail is NULL, the chunk extends
+     * to the end of the brigade. */
+    char buffer[128];
+    apr_bucket *b;
+    apr_size_t len;
+
+    len = (apr_size_t)apr_snprintf(buffer, H2_ALEN(buffer),
+                                   "%"APR_UINT64_T_HEX_FMT"\r\n", (apr_uint64_t)chunk_len);
+    b = apr_bucket_heap_create(buffer, len, NULL, bb->bucket_alloc);
+    APR_BUCKET_INSERT_BEFORE(first, b);
+    b = apr_bucket_immortal_create("\r\n", 2, bb->bucket_alloc);
+    if (tail) {
+        APR_BUCKET_INSERT_BEFORE(tail, b);
+    }
+    else {
+        APR_BRIGADE_INSERT_TAIL(bb, b);
+    }
+    fctx->chunked_total += chunk_len;
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+                  "h2_c2(%s): added chunk %ld, total %ld",
+                  fctx->id, (long)chunk_len, (long)fctx->chunked_total);
+}
+
+static int ser_header(void *ctx, const char *name, const char *value)
+{
+    apr_bucket_brigade *bb = ctx;
+    apr_brigade_printf(bb, NULL, NULL, "%s: %s\r\n", name, value);
+    return 1;
+}
+
+static apr_status_t read_and_chunk(ap_filter_t *f, h2_conn_ctx_t *conn_ctx,
+                                   apr_read_type_e block) {
+    h2_chunk_filter_t *fctx = f->ctx;
+    request_rec *r = f->r;
+    apr_status_t status = APR_SUCCESS;
+
+    if (!fctx->bbchunk) {
+        fctx->bbchunk = apr_brigade_create(r->pool, f->c->bucket_alloc);
+    }
+
+    if (APR_BRIGADE_EMPTY(fctx->bbchunk)) {
+        apr_bucket *b, *next, *first_data = NULL;
+        apr_bucket_brigade *tmp;
+        apr_off_t bblen = 0;
+
+        /* get more data from the lower layer filters. Always do this
+         * in larger pieces, since we handle the read modes ourself. */
+        status = ap_get_brigade(f->next, fctx->bbchunk,
+                                AP_MODE_READBYTES, block, conn_ctx->mplx->stream_max_mem);
+        if (status != APR_SUCCESS) {
+            return status;
+        }
+
+        for (b = APR_BRIGADE_FIRST(fctx->bbchunk);
+             b != APR_BRIGADE_SENTINEL(fctx->bbchunk);
+             b = next) {
+            next = APR_BUCKET_NEXT(b);
+            if (APR_BUCKET_IS_METADATA(b)) {
+                if (first_data) {
+                    make_chunk(f->c, fctx, fctx->bbchunk, first_data, bblen, b);
+                    first_data = NULL;
+                }
+
+                if (H2_BUCKET_IS_HEADERS(b)) {
+                    h2_headers *headers = h2_bucket_headers_get(b);
+
+                    ap_assert(headers);
+                    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+                                  "h2_c2(%s-%d): receiving trailers",
+                                  conn_ctx->id, conn_ctx->stream_id);
+                    tmp = apr_brigade_split_ex(fctx->bbchunk, b, NULL);
+                    if (!apr_is_empty_table(headers->headers)) {
+                        status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n");
+                        apr_table_do(ser_header, fctx->bbchunk, headers->headers, NULL);
+                        status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "\r\n");
+                    }
+                    else {
+                        status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n\r\n");
+                    }
+                    r->trailers_in = apr_table_clone(r->pool, headers->headers);
+                    APR_BUCKET_REMOVE(b);
+                    apr_bucket_destroy(b);
+                    APR_BRIGADE_CONCAT(fctx->bbchunk, tmp);
+                    apr_brigade_destroy(tmp);
+                    fctx->eos_chunk_added = 1;
+                }
+                else if (APR_BUCKET_IS_EOS(b)) {
+                    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+                                  "h2_c2(%s-%d): receiving eos",
+                                  conn_ctx->id, conn_ctx->stream_id);
+                    if (!fctx->eos_chunk_added) {
+                        tmp = apr_brigade_split_ex(fctx->bbchunk, b, NULL);
+                        status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n\r\n");
+                        APR_BRIGADE_CONCAT(fctx->bbchunk, tmp);
+                        apr_brigade_destroy(tmp);
+                    }
+                    fctx->eos_chunk_added = 0;
+                }
+            }
+            else if (b->length == 0) {
+                APR_BUCKET_REMOVE(b);
+                apr_bucket_destroy(b);
+            }
+            else {
+                if (!first_data) {
+                    first_data = b;
+                    bblen = 0;
+                }
+                bblen += b->length;
+            }
+        }
+
+        if (first_data) {
+            make_chunk(f->c, fctx, fctx->bbchunk, first_data, bblen, NULL);
+        }
+    }
+    return status;
+}
+
+apr_status_t h2_c2_filter_request_in(ap_filter_t* f,
+                                     apr_bucket_brigade* bb,
+                                     ap_input_mode_t mode,
+                                     apr_read_type_e block,
+                                     apr_off_t readbytes)
+{
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+    h2_chunk_filter_t *fctx = f->ctx;
+    request_rec *r = f->r;
+    apr_status_t status = APR_SUCCESS;
+    apr_bucket *b, *next;
+    core_server_config *conf =
+        (core_server_config *) ap_get_module_config(r->server->module_config,
+                                                    &core_module);
+    ap_assert(conn_ctx);
+
+    if (!fctx) {
+        fctx = apr_pcalloc(r->pool, sizeof(*fctx));
+        fctx->id = apr_psprintf(r->pool, "%s-%d", conn_ctx->id, conn_ctx->stream_id);
+        f->ctx = fctx;
+    }
+
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, f->r,
+                  "h2_c2(%s-%d): request input, mode=%d, block=%d, "
+                  "readbytes=%ld, exp=%d",
+                  conn_ctx->id, conn_ctx->stream_id, mode, block,
+                  (long)readbytes, r->expecting_100);
+    if (!conn_ctx->input_chunked) {
+        status = ap_get_brigade(f->next, bb, mode, block, readbytes);
+        /* pipe data through, just take care of trailers */
+        for (b = APR_BRIGADE_FIRST(bb);
+             b != APR_BRIGADE_SENTINEL(bb); b = next) {
+            next = APR_BUCKET_NEXT(b);
+            if (H2_BUCKET_IS_HEADERS(b)) {
+                h2_headers *headers = h2_bucket_headers_get(b);
+                ap_assert(headers);
+                ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+                              "h2_c2(%s-%d): receiving trailers",
+                              conn_ctx->id, conn_ctx->stream_id);
+                r->trailers_in = headers->headers;
+                if (conf && conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE) {
+                    r->headers_in = apr_table_overlay(r->pool, r->headers_in,
+                                                      r->trailers_in);
+                }
+                APR_BUCKET_REMOVE(b);
+                apr_bucket_destroy(b);
+                ap_remove_input_filter(f);
+
+                if (headers->raw_bytes && h2_c_logio_add_bytes_in) {
+                    h2_c_logio_add_bytes_in(f->c, headers->raw_bytes);
+                }
+                break;
+            }
+        }
+        return status;
+    }
+
+    /* Things are more complicated. The standard HTTP input filter, which
+     * does a lot what we do not want to duplicate, also cares about chunked
+     * transfer encoding and trailers.
+     * We need to simulate chunked encoding for it to be happy.
+     */
+    if ((status = read_and_chunk(f, conn_ctx, block)) != APR_SUCCESS) {
+        return status;
+    }
+
+    if (mode == AP_MODE_EXHAUSTIVE) {
+        /* return all we have */
+        APR_BRIGADE_CONCAT(bb, fctx->bbchunk);
+    }
+    else if (mode == AP_MODE_READBYTES) {
+        status = h2_brigade_concat_length(bb, fctx->bbchunk, readbytes);
+    }
+    else if (mode == AP_MODE_SPECULATIVE) {
+        status = h2_brigade_copy_length(bb, fctx->bbchunk, readbytes);
+    }
+    else if (mode == AP_MODE_GETLINE) {
+        /* we are reading a single LF line, e.g. the HTTP headers.
+         * this has the nasty side effect to split the bucket, even
+         * though it ends with CRLF and creates a 0 length bucket */
+        status = apr_brigade_split_line(bb, fctx->bbchunk, block, HUGE_STRING_LEN);
+        if (APLOGctrace1(f->c)) {
+            char buffer[1024];
+            apr_size_t len = sizeof(buffer)-1;
+            apr_brigade_flatten(bb, buffer, &len);
+            buffer[len] = 0;
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+                          "h2_c2(%s-%d): getline: %s",
+                          conn_ctx->id, conn_ctx->stream_id, buffer);
+        }
+    }
+    else {
+        /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not
+         * to support it. Seems to work. */
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
+                      APLOGNO(02942)
+                      "h2_c2, unsupported READ mode %d", mode);
+        status = APR_ENOTIMPL;
+    }
+
+    h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE2, "returning input", bb);
+    return status;
+}
+
+apr_status_t h2_c2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+    request_rec *r = f->r;
+    apr_bucket *b, *e;
+
+    if (conn_ctx && r) {
+        /* Detect the EOS/EOR bucket and forward any trailers that may have
+         * been set to our h2_headers.
+         */
+        for (b = APR_BRIGADE_FIRST(bb);
+             b != APR_BRIGADE_SENTINEL(bb);
+             b = APR_BUCKET_NEXT(b))
+        {
+            if ((APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b))
+                && r->trailers_out && !apr_is_empty_table(r->trailers_out)) {
+                h2_headers *headers;
+                apr_table_t *trailers;
+
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03049)
+                              "h2_c2(%s-%d): sending trailers",
+                              conn_ctx->id, conn_ctx->stream_id);
+                trailers = apr_table_clone(r->pool, r->trailers_out);
+                headers = h2_headers_rcreate(r, HTTP_OK, trailers, r->pool);
+                e = h2_bucket_headers_create(bb->bucket_alloc, headers);
+                APR_BUCKET_INSERT_BEFORE(b, e);
+                apr_table_clear(r->trailers_out);
+                ap_remove_output_filter(f);
+                break;
+            }
+        }
+    }
+
+    return ap_pass_brigade(f->next, bb);
+}
+
+#endif /* else #if AP_HAS_RESPONSE_BUCKETS */
diff -r -N -u a/modules/http2/h2_c2_filter.h b/modules/http2/h2_c2_filter.h
--- a/modules/http2/h2_c2_filter.h	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_c2_filter.h	2024-10-30 21:40:01.059958617 +0100
@@ -0,0 +1,68 @@
+/* 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_c2_filter__
+#define __mod_h2__h2_c2_filter__
+
+#include "h2.h"
+
+/**
+ * Input filter on secondary connections that insert the REQUEST bucket
+ * with the request to perform and then removes itself.
+ */
+apr_status_t h2_c2_filter_request_in(ap_filter_t *f,
+                                     apr_bucket_brigade *bb,
+                                     ap_input_mode_t mode,
+                                     apr_read_type_e block,
+                                     apr_off_t readbytes);
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+/**
+ * Output filter that inspects the request_rec->notes of the request
+ * itself and possible internal redirects to detect conditions that
+ * merit specific HTTP/2 response codes, such as 421.
+ */
+apr_status_t h2_c2_filter_notes_out(ap_filter_t *f, apr_bucket_brigade *bb);
+
+#else /* AP_HAS_RESPONSE_BUCKETS */
+
+/**
+ * h2_from_h1 parses a HTTP/1.1 response into
+ * - response status
+ * - a list of header values
+ * - a series of bytes that represent the response body alone, without
+ *   any meta data, such as inserted by chunked transfer encoding.
+ *
+ * All data is allocated from the stream memory pool.
+ *
+ * Again, see comments in h2_request: ideally we would take the headers
+ * and status from the httpd structures instead of parsing them here, but
+ * we need to have all handlers and filters involved in request/response
+ * processing, so this seems to be the way for now.
+ */
+struct h2_headers;
+struct h2_response_parser;
+
+apr_status_t h2_c2_filter_catch_h1_out(ap_filter_t* f, apr_bucket_brigade* bb);
+
+apr_status_t h2_c2_filter_response_out(ap_filter_t *f, apr_bucket_brigade *bb);
+
+apr_status_t h2_c2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb);
+
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+
+#endif /* defined(__mod_h2__h2_c2_filter__) */
diff -r -N -u a/modules/http2/h2_c2.h b/modules/http2/h2_c2.h
--- a/modules/http2/h2_c2.h	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_c2.h	2024-10-30 21:40:01.059958617 +0100
@@ -0,0 +1,57 @@
+/* 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_c2__
+#define __mod_h2__h2_c2__
+
+#include <http_core.h>
+
+#include "h2.h"
+
+const char *h2_conn_mpm_name(void);
+int h2_mpm_supported(void);
+
+/* Initialize this child process for h2 secondary connection work,
+ * to be called once during child init before multi processing
+ * starts.
+ */
+apr_status_t h2_c2_child_init(apr_pool_t *pool, server_rec *s);
+
+#if !AP_HAS_RESPONSE_BUCKETS
+
+conn_rec *h2_c2_create(conn_rec *c1, apr_pool_t *parent,
+                       apr_bucket_alloc_t *buckt_alloc);
+
+/**
+ * Process a secondary connection for a HTTP/2 stream request.
+ */
+apr_status_t h2_c2_process(conn_rec *c, apr_thread_t *thread, int worker_id);
+
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
+
+void h2_c2_destroy(conn_rec *c2);
+
+/**
+ * Abort the I/O processing of a secondary connection. And
+ * in-/output beams will return errors and c2->aborted is set.
+ * @param c2 the secondary connection to abort
+ * @param from the connection this is invoked from
+ */
+void h2_c2_abort(conn_rec *c2, conn_rec *from);
+
+void h2_c2_register_hooks(void);
+
+#endif /* defined(__mod_h2__h2_c2__) */
diff -r -N -u a/modules/http2/h2_config.c b/modules/http2/h2_config.c
--- a/modules/http2/h2_config.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_config.c	2024-10-30 21:40:01.059958617 +0100
@@ -30,11 +30,10 @@
 #include <apr_strings.h>
 
 #include "h2.h"
-#include "h2_alt_svc.h"
-#include "h2_ctx.h"
-#include "h2_conn.h"
+#include "h2_conn_ctx.h"
+#include "h2_c1.h"
 #include "h2_config.h"
-#include "h2_h2.h"
+#include "h2_protocol.h"
 #include "h2_private.h"
 
 #define DEF_VAL     (-1)
@@ -54,41 +53,42 @@
 /* Apache httpd module configuration for h2. */
 typedef struct h2_config {
     const char *name;
-    int h2_max_streams;           /* max concurrent # streams (http2) */
-    int h2_window_size;           /* stream window size (http2) */
-    int min_workers;              /* min # of worker threads/child */
-    int max_workers;              /* max # of worker threads/child */
-    int max_worker_idle_secs;     /* max # of idle seconds for worker */
-    int stream_max_mem_size;      /* max # bytes held in memory/stream */
-    apr_array_header_t *alt_svcs; /* h2_alt_svc specs for this server */
-    int alt_svc_max_age;          /* seconds clients can rely on alt-svc info*/
-    int serialize_headers;        /* Use serialized HTTP/1.1 headers for 
-                                     processing, better compatibility */
-    int h2_direct;                /* if mod_h2 is active directly */
-    int modern_tls_only;          /* Accept only modern TLS in HTTP/2 connections */  
-    int h2_upgrade;               /* Allow HTTP/1 upgrade to h2/h2c */
-    apr_int64_t tls_warmup_size;  /* Amount of TLS data to send before going full write size */
-    int tls_cooldown_secs;        /* Seconds of idle time before going back to small TLS records */
-    int h2_push;                  /* if HTTP/2 server push is enabled */
-    struct apr_hash_t *priorities;/* map of content-type to h2_priority records */
-    
-    int push_diary_size;          /* # of entries in push diary */
-    int copy_files;               /* if files shall be copied vs setaside on output */
-    apr_array_header_t *push_list;/* list of h2_push_res configurations */
-    int early_hints;              /* support status code 103 */
+    int h2_max_streams;              /* max concurrent # streams (http2) */
+    int h2_window_size;              /* stream window size (http2) */
+    int min_workers;                 /* min # of worker threads/child */
+    int max_workers;                 /* max # of worker threads/child */
+    apr_interval_time_t idle_limit;  /* max duration for idle workers */
+    int stream_max_mem_size;         /* max # bytes held in memory/stream */
+    int h2_direct;                   /* if mod_h2 is active directly */
+    int modern_tls_only;             /* Accept only modern TLS in HTTP/2 connections */  
+    int h2_upgrade;                  /* Allow HTTP/1 upgrade to h2/h2c */
+    apr_int64_t tls_warmup_size;     /* Amount of TLS data to send before going full write size */
+    int tls_cooldown_secs;           /* Seconds of idle time before going back to small TLS records */
+    int h2_push;                     /* if HTTP/2 server push is enabled */
+    struct apr_hash_t *priorities;   /* map of content-type to h2_priority records */
+    
+    int push_diary_size;             /* # of entries in push diary */
+    int copy_files;                  /* if files shall be copied vs setaside on output */
+    apr_array_header_t *push_list;   /* list of h2_push_res configurations */
+    apr_table_t *early_headers;      /* HTTP headers for a 103 response */
+    int early_hints;                 /* support status code 103 */
     int padding_bits;
     int padding_always;
     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 proxy_requests;              /* act as forward proxy */
+    int h2_websockets;               /* if mod_h2 negotiating WebSockets */
 } h2_config;
 
 typedef struct h2_dir_config {
     const char *name;
-    apr_array_header_t *alt_svcs; /* h2_alt_svc specs for this server */
-    int alt_svc_max_age;          /* seconds clients can rely on alt-svc info*/
-    int h2_upgrade;               /* Allow HTTP/1 upgrade to h2/h2c */
-    int h2_push;                  /* if HTTP/2 server push is enabled */
-    apr_array_header_t *push_list;/* list of h2_push_res configurations */
-    int early_hints;              /* support status code 103 */
+    int h2_upgrade;                  /* Allow HTTP/1 upgrade to h2/h2c */
+    int h2_push;                     /* if HTTP/2 server push is enabled */
+    apr_array_header_t *push_list;   /* list of h2_push_res configurations */
+    apr_table_t *early_headers;      /* HTTP headers for a 103 response */
+    int early_hints;                 /* support status code 103 */
+    apr_interval_time_t stream_timeout;/* beam timeout */
 } h2_dir_config;
 
 
@@ -98,11 +98,8 @@
     H2_INITIAL_WINDOW_SIZE, /* window_size */
     -1,                     /* min workers */
     -1,                     /* max workers */
-    10 * 60,                /* max workers idle secs */
+    apr_time_from_sec(10 * 60), /* workers idle limit */
     32 * 1024,              /* stream max mem size */
-    NULL,                   /* no alt-svcs */
-    -1,                     /* alt-svc max age */
-    0,                      /* serialize headers */
     -1,                     /* h2 direct mode */
     1,                      /* modern TLS only */
     -1,                     /* HTTP/1 Upgrade support */
@@ -113,20 +110,25 @@
     256,                    /* push diary size */
     0,                      /* copy files across threads */
     NULL,                   /* push list */
+    NULL,                   /* early headers */
     0,                      /* early hints, http status 103 */
     0,                      /* padding bits */
     1,                      /* padding always */
-    1,                      /* strean output buffered */
+    1,                      /* stream output buffered */
+    -1,                     /* beam timeout */
+    0,                      /* max DATA frame len, 0 == no extra limit */
+    0,                      /* forward proxy */
+    0,                      /* WebSockets negotiation, enabled */
 };
 
 static h2_dir_config defdconf = {
     "default",
-    NULL,                   /* no alt-svcs */
-    -1,                     /* alt-svc max age */
     -1,                     /* HTTP/1 Upgrade support */
     -1,                     /* HTTP/2 server push enabled */
     NULL,                   /* push list */
+    NULL,                   /* early headers */
     -1,                     /* early hints, http status 103 */
+    -1,                     /* beam timeout */
 };
 
 void h2_config_init(apr_pool_t *pool)
@@ -144,10 +146,8 @@
     conf->h2_window_size       = DEF_VAL;
     conf->min_workers          = DEF_VAL;
     conf->max_workers          = DEF_VAL;
-    conf->max_worker_idle_secs = DEF_VAL;
+    conf->idle_limit           = DEF_VAL;
     conf->stream_max_mem_size  = DEF_VAL;
-    conf->alt_svc_max_age      = DEF_VAL;
-    conf->serialize_headers    = DEF_VAL;
     conf->h2_direct            = DEF_VAL;
     conf->modern_tls_only      = DEF_VAL;
     conf->h2_upgrade           = DEF_VAL;
@@ -158,10 +158,15 @@
     conf->push_diary_size      = DEF_VAL;
     conf->copy_files           = DEF_VAL;
     conf->push_list            = NULL;
+    conf->early_headers        = NULL;
     conf->early_hints          = DEF_VAL;
     conf->padding_bits         = DEF_VAL;
     conf->padding_always       = DEF_VAL;
     conf->output_buffered      = DEF_VAL;
+    conf->stream_timeout       = DEF_VAL;
+    conf->max_data_frame_len   = DEF_VAL;
+    conf->proxy_requests       = DEF_VAL;
+    conf->h2_websockets        = DEF_VAL;
     return conf;
 }
 
@@ -177,11 +182,8 @@
     n->h2_window_size       = H2_CONFIG_GET(add, base, h2_window_size);
     n->min_workers          = H2_CONFIG_GET(add, base, min_workers);
     n->max_workers          = H2_CONFIG_GET(add, base, max_workers);
-    n->max_worker_idle_secs = H2_CONFIG_GET(add, base, max_worker_idle_secs);
+    n->idle_limit           = H2_CONFIG_GET(add, base, idle_limit);
     n->stream_max_mem_size  = H2_CONFIG_GET(add, base, stream_max_mem_size);
-    n->alt_svcs             = add->alt_svcs? add->alt_svcs : base->alt_svcs;
-    n->alt_svc_max_age      = H2_CONFIG_GET(add, base, alt_svc_max_age);
-    n->serialize_headers    = H2_CONFIG_GET(add, base, serialize_headers);
     n->h2_direct            = H2_CONFIG_GET(add, base, h2_direct);
     n->modern_tls_only      = H2_CONFIG_GET(add, base, modern_tls_only);
     n->h2_upgrade           = H2_CONFIG_GET(add, base, h2_upgrade);
@@ -203,9 +205,19 @@
     else {
         n->push_list        = add->push_list? add->push_list : base->push_list;
     }
+    if (add->early_headers && base->early_headers) {
+        n->early_headers    = apr_table_overlay(pool, add->early_headers, base->early_headers);
+    }
+    else {
+        n->early_headers    = add->early_headers? add->early_headers : base->early_headers;
+    }
     n->early_hints          = H2_CONFIG_GET(add, base, early_hints);
     n->padding_bits         = H2_CONFIG_GET(add, base, padding_bits);
     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->proxy_requests       = H2_CONFIG_GET(add, base, proxy_requests);
+    n->h2_websockets        = H2_CONFIG_GET(add, base, h2_websockets);
     return n;
 }
 
@@ -221,10 +233,10 @@
     char *name = apr_pstrcat(pool, "dir[", s, "]", NULL);
     
     conf->name                 = name;
-    conf->alt_svc_max_age      = DEF_VAL;
     conf->h2_upgrade           = DEF_VAL;
     conf->h2_push              = DEF_VAL;
     conf->early_hints          = DEF_VAL;
+    conf->stream_timeout         = DEF_VAL;
     return conf;
 }
 
@@ -235,8 +247,6 @@
     h2_dir_config *n = (h2_dir_config *)apr_pcalloc(pool, sizeof(h2_dir_config));
 
     n->name = apr_pstrcat(pool, "merged[", add->name, ", ", base->name, "]", NULL);
-    n->alt_svcs             = add->alt_svcs? add->alt_svcs : base->alt_svcs;
-    n->alt_svc_max_age      = H2_CONFIG_GET(add, base, alt_svc_max_age);
     n->h2_upgrade           = H2_CONFIG_GET(add, base, h2_upgrade);
     n->h2_push              = H2_CONFIG_GET(add, base, h2_push);
     if (add->push_list && base->push_list) {
@@ -245,7 +255,14 @@
     else {
         n->push_list        = add->push_list? add->push_list : base->push_list;
     }
+    if (add->early_headers && base->early_headers) {
+        n->early_headers    = apr_table_overlay(pool, add->early_headers, base->early_headers);
+    }
+    else {
+        n->early_headers    = add->early_headers? add->early_headers : base->early_headers;
+    }
     n->early_hints          = H2_CONFIG_GET(add, base, early_hints);
+    n->stream_timeout         = H2_CONFIG_GET(add, base, stream_timeout);
     return n;
 }
 
@@ -260,14 +277,10 @@
             return H2_CONFIG_GET(conf, &defconf, min_workers);
         case H2_CONF_MAX_WORKERS:
             return H2_CONFIG_GET(conf, &defconf, max_workers);
-        case H2_CONF_MAX_WORKER_IDLE_SECS:
-            return H2_CONFIG_GET(conf, &defconf, max_worker_idle_secs);
+        case H2_CONF_MAX_WORKER_IDLE_LIMIT:
+            return H2_CONFIG_GET(conf, &defconf, idle_limit);
         case H2_CONF_STREAM_MAX_MEM:
             return H2_CONFIG_GET(conf, &defconf, stream_max_mem_size);
-        case H2_CONF_ALT_SVC_MAX_AGE:
-            return H2_CONFIG_GET(conf, &defconf, alt_svc_max_age);
-        case H2_CONF_SER_HEADERS:
-            return H2_CONFIG_GET(conf, &defconf, serialize_headers);
         case H2_CONF_MODERN_TLS_ONLY:
             return H2_CONFIG_GET(conf, &defconf, modern_tls_only);
         case H2_CONF_UPGRADE:
@@ -292,6 +305,14 @@
             return H2_CONFIG_GET(conf, &defconf, padding_always);
         case H2_CONF_OUTPUT_BUFFER:
             return H2_CONFIG_GET(conf, &defconf, output_buffered);
+        case H2_CONF_STREAM_TIMEOUT:
+            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_PROXY_REQUESTS:
+            return H2_CONFIG_GET(conf, &defconf, proxy_requests);
+        case H2_CONF_WEBSOCKETS:
+            return H2_CONFIG_GET(conf, &defconf, h2_websockets);
         default:
             return DEF_VAL;
     }
@@ -312,18 +333,9 @@
         case H2_CONF_MAX_WORKERS:
             H2_CONFIG_SET(conf, max_workers, val);
             break;
-        case H2_CONF_MAX_WORKER_IDLE_SECS:
-            H2_CONFIG_SET(conf, max_worker_idle_secs, val);
-            break;
         case H2_CONF_STREAM_MAX_MEM:
             H2_CONFIG_SET(conf, stream_max_mem_size, val);
             break;
-        case H2_CONF_ALT_SVC_MAX_AGE:
-            H2_CONFIG_SET(conf, alt_svc_max_age, val);
-            break;
-        case H2_CONF_SER_HEADERS:
-            H2_CONFIG_SET(conf, serialize_headers, val);
-            break;
         case H2_CONF_MODERN_TLS_ONLY:
             H2_CONFIG_SET(conf, modern_tls_only, val);
             break;
@@ -360,6 +372,15 @@
         case H2_CONF_OUTPUT_BUFFER:
             H2_CONFIG_SET(conf, output_buffered, val);
             break;
+        case H2_CONF_MAX_DATA_FRAME_LEN:
+            H2_CONFIG_SET(conf, max_data_frame_len, val);
+            break;
+        case H2_CONF_PROXY_REQUESTS:
+            H2_CONFIG_SET(conf, proxy_requests, val);
+            break;
+        case H2_CONF_WEBSOCKETS:
+            H2_CONFIG_SET(conf, h2_websockets, val);
+            break;
         default:
             break;
     }
@@ -371,6 +392,12 @@
         case H2_CONF_TLS_WARMUP_SIZE:
             H2_CONFIG_SET(conf, tls_warmup_size, val);
             break;
+        case H2_CONF_STREAM_TIMEOUT:
+            H2_CONFIG_SET(conf, stream_timeout, val);
+            break;
+        case H2_CONF_MAX_WORKER_IDLE_LIMIT:
+            H2_CONFIG_SET(conf, idle_limit, val);
+            break;
         default:
             h2_srv_config_seti(conf, var, (int)val);
             break;
@@ -396,14 +423,14 @@
 static apr_int64_t h2_dir_config_geti64(const h2_dir_config *conf, h2_config_var_t var)
 {
     switch(var) {
-        case H2_CONF_ALT_SVC_MAX_AGE:
-            return H2_CONFIG_GET(conf, &defdconf, alt_svc_max_age);
         case H2_CONF_UPGRADE:
             return H2_CONFIG_GET(conf, &defdconf, h2_upgrade);
         case H2_CONF_PUSH:
             return H2_CONFIG_GET(conf, &defdconf, h2_push);
         case H2_CONF_EARLY_HINTS:
             return H2_CONFIG_GET(conf, &defdconf, early_hints);
+        case H2_CONF_STREAM_TIMEOUT:
+            return H2_CONFIG_GET(conf, &defdconf, stream_timeout);
 
         default:
             return DEF_VAL;
@@ -415,9 +442,6 @@
     int set_srv = !dconf;
     if (dconf) {
         switch(var) {
-            case H2_CONF_ALT_SVC_MAX_AGE:
-                H2_CONFIG_SET(dconf, alt_svc_max_age, val);
-                break;
             case H2_CONF_UPGRADE:
                 H2_CONFIG_SET(dconf, h2_upgrade, val);
                 break;
@@ -444,6 +468,9 @@
     int set_srv = !dconf;
     if (dconf) {
         switch(var) {
+            case H2_CONF_STREAM_TIMEOUT:
+                H2_CONFIG_SET(dconf, stream_timeout, val);
+                break;
             default:
                 /* not handled in dir_conf */
                 set_srv = 1;
@@ -458,18 +485,11 @@
 
 static const h2_config *h2_config_get(conn_rec *c)
 {
-    h2_ctx *ctx = h2_ctx_get(c, 0);
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
     
-    if (ctx) {
-        if (ctx->config) {
-            return ctx->config;
-        }
-        else if (ctx->server) {
-            ctx->config = h2_config_sget(ctx->server);
-            return ctx->config;
-        }
+    if (conn_ctx && conn_ctx->server) {
+        return h2_config_sget(conn_ctx->server);
     }
-    
     return h2_config_sget(c->base_server);
 }
 
@@ -526,16 +546,16 @@
     return sconf? sconf->push_list : NULL;
 }
 
-apr_array_header_t *h2_config_alt_svcs(request_rec *r)
+apr_table_t *h2_config_early_headers(request_rec *r)
 {
     const h2_config *sconf;
     const h2_dir_config *conf = h2_config_rget(r);
-    
-    if (conf && conf->alt_svcs) {
-        return conf->alt_svcs;
+
+    if (conf && conf->early_headers) {
+        return conf->early_headers;
     }
-    sconf = h2_config_sget(r->server); 
-    return sconf? sconf->alt_svcs : NULL;
+    sconf = h2_config_sget(r->server);
+    return sconf? sconf->early_headers : NULL;
 }
 
 const struct h2_priority *h2_cconfig_get_priority(conn_rec *c, const char *content_type)
@@ -593,14 +613,18 @@
     return NULL;
 }
 
-static const char *h2_conf_set_max_worker_idle_secs(cmd_parms *cmd,
-                                                    void *dirconf, const char *value)
+static const char *h2_conf_set_max_worker_idle_limit(cmd_parms *cmd,
+                                                     void *dirconf, const char *value)
 {
-    int val = (int)apr_atoi64(value);
-    if (val < 1) {
-        return "value must be > 0";
+    apr_interval_time_t timeout;
+    apr_status_t rv = ap_timeout_parameter_parse(value, &timeout, "s");
+    if (rv != APR_SUCCESS) {
+        return "Invalid idle limit value";
+    }
+    if (timeout <= 0) {
+        timeout = DEF_VAL;
     }
-    CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MAX_WORKER_IDLE_SECS, val);
+    CONFIG_CMD_SET64(cmd, dirconf, H2_CONF_MAX_WORKER_IDLE_LIMIT, timeout);
     return NULL;
 }
 
@@ -615,38 +639,14 @@
     return NULL;
 }
 
-static const char *h2_add_alt_svc(cmd_parms *cmd,
-                                  void *dirconf, const char *value)
-{
-    if (value && *value) {
-        h2_alt_svc *as = h2_alt_svc_parse(value, cmd->pool);
-        if (!as) {
-            return "unable to parse alt-svc specifier";
-        }
-
-        if (cmd->path) {
-            h2_dir_config *dcfg = (h2_dir_config *)dirconf;
-            if (!dcfg->alt_svcs) {
-                dcfg->alt_svcs = apr_array_make(cmd->pool, 5, sizeof(h2_alt_svc*));
-            }
-            APR_ARRAY_PUSH(dcfg->alt_svcs, h2_alt_svc*) = as;
-        }
-        else {
-            h2_config *cfg = (h2_config *)h2_config_sget(cmd->server);
-            if (!cfg->alt_svcs) {
-                cfg->alt_svcs = apr_array_make(cmd->pool, 5, sizeof(h2_alt_svc*));
-            }
-            APR_ARRAY_PUSH(cfg->alt_svcs, h2_alt_svc*) = as;
-        }
-    }
-    return NULL;
-}
-
-static const char *h2_conf_set_alt_svc_max_age(cmd_parms *cmd,
-                                               void *dirconf, const char *value)
+static const char *h2_conf_set_max_data_frame_len(cmd_parms *cmd,
+                                                  void *dirconf, const char *value)
 {
     int val = (int)apr_atoi64(value);
-    CONFIG_CMD_SET(cmd, dirconf, H2_CONF_ALT_SVC_MAX_AGE, val);
+    if (val < 0) {
+        return "value must be 0 or larger";
+    }
+    CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MAX_DATA_FRAME_LEN, val);
     return NULL;
 }
 
@@ -661,18 +661,15 @@
     return NULL;
 }
 
-static const char *h2_conf_set_serialize_headers(cmd_parms *cmd,
+static const char *h2_conf_set_serialize_headers(cmd_parms *parms,
                                                  void *dirconf, const char *value)
 {
     if (!strcasecmp(value, "On")) {
-        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_SER_HEADERS, 1);
-        return NULL;
-    }
-    else if (!strcasecmp(value, "Off")) {
-        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_SER_HEADERS, 0);
-        return NULL;
+        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, parms->server, APLOGNO(10307)
+                     "%s: this feature has been disabled and the directive "
+                     "to enable it is ignored.", parms->cmd->name);
     }
-    return "value must be On or Off";
+    return NULL;
 }
 
 static const char *h2_conf_set_direct(cmd_parms *cmd,
@@ -702,6 +699,26 @@
     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_WEBSOCKETS
+        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_WEBSOCKETS, 1);
+        return NULL;
+#elif !H2_USE_PIPES
+        return "HTTP/2 WebSockets are not supported on this platform";
+#else
+        return "HTTP/2 WebSockets are not supported in this server version";
+#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)
@@ -882,6 +899,37 @@
     return NULL;
 }
 
+static const char *h2_conf_add_early_hint(cmd_parms *cmd, void *dirconf,
+                                          const char *name, const char *value)
+{
+    apr_table_t *hds, **phds;
+
+    if(!name || !*name)
+      return "Early Hint header name must not be empty";
+    if(!value)
+      return "Early Hint header value must not be empty";
+    while (apr_isspace(*value))
+        ++value;
+    if(!*value)
+      return "Early Hint header value must not be empty/only space";
+    if (*ap_scan_http_field_content(value))
+      return "Early Hint header value contains invalid characters";
+
+    if (cmd->path) {
+        phds = &((h2_dir_config*)dirconf)->early_headers;
+    }
+    else {
+        phds = &(h2_config_sget(cmd->server))->early_headers;
+    }
+    hds = *phds;
+    if (!hds) {
+      *phds = hds = apr_table_make(cmd->pool, 10);
+    }
+    apr_table_add(hds, name, value);
+
+    return NULL;
+}
+
 static const char *h2_conf_set_early_hints(cmd_parms *cmd,
                                            void *dirconf, const char *value)
 {
@@ -928,27 +976,50 @@
     return "value must be On or Off";
 }
 
-void h2_get_num_workers(server_rec *s, int *minw, int *maxw)
+static const char *h2_conf_set_stream_timeout(cmd_parms *cmd,
+                                            void *dirconf, const char *value)
+{
+    apr_status_t rv;
+    apr_interval_time_t timeout;
+
+    rv = ap_timeout_parameter_parse(value, &timeout, "s");
+    if (rv != APR_SUCCESS) {
+        return "Invalid timeout value";
+    }
+    CONFIG_CMD_SET64(cmd, dirconf, H2_CONF_STREAM_TIMEOUT, timeout);
+    return NULL;
+}
+
+static const char *h2_conf_set_proxy_requests(cmd_parms *cmd,
+                                              void *dirconf, const char *value)
+{
+    if (!strcasecmp(value, "On")) {
+        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PROXY_REQUESTS, 1);
+        return NULL;
+    }
+    else if (!strcasecmp(value, "Off")) {
+        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PROXY_REQUESTS, 0);
+        return NULL;
+    }
+    return "value must be On or Off";
+}
+
+void h2_get_workers_config(server_rec *s, int *pminw, int *pmaxw,
+                           apr_time_t *pidle_limit)
 {
     int threads_per_child = 0;
 
-    *minw = h2_config_sgeti(s, H2_CONF_MIN_WORKERS);
-    *maxw = h2_config_sgeti(s, H2_CONF_MAX_WORKERS);    
-    ap_mpm_query(AP_MPMQ_MAX_THREADS, &threads_per_child);
+    *pminw = h2_config_sgeti(s, H2_CONF_MIN_WORKERS);
+    *pmaxw = h2_config_sgeti(s, H2_CONF_MAX_WORKERS);
 
-    if (*minw <= 0) {
-        *minw = threads_per_child;
+    ap_mpm_query(AP_MPMQ_MAX_THREADS, &threads_per_child);
+    if (*pminw <= 0) {
+        *pminw = threads_per_child;
     }
-    if (*maxw <= 0) {
-        /* As a default, this seems to work quite well under mpm_event. 
-         * For people enabling http2 under mpm_prefork, start 4 threads unless 
-         * configured otherwise. People get unhappy if their http2 requests are 
-         * blocking each other. */
-        *maxw = 3 * (*minw) / 2;
-        if (*maxw < 4) {
-            *maxw = 4;
-        }
+    if (*pmaxw <= 0) {
+        *pmaxw = H2MAX(4, 3 * (*pminw) / 2);
     }
+    *pidle_limit = h2_config_sgeti64(s, H2_CONF_MAX_WORKER_IDLE_LIMIT);
 }
 
 #define AP_END_CMD     AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
@@ -962,16 +1033,12 @@
                   RSRC_CONF, "minimum number of worker threads per child"),
     AP_INIT_TAKE1("H2MaxWorkers", h2_conf_set_max_workers, NULL,
                   RSRC_CONF, "maximum number of worker threads per child"),
-    AP_INIT_TAKE1("H2MaxWorkerIdleSeconds", h2_conf_set_max_worker_idle_secs, NULL,
+    AP_INIT_TAKE1("H2MaxWorkerIdleSeconds", h2_conf_set_max_worker_idle_limit, NULL,
                   RSRC_CONF, "maximum number of idle seconds before a worker shuts down"),
     AP_INIT_TAKE1("H2StreamMaxMemSize", h2_conf_set_stream_max_mem_size, NULL,
                   RSRC_CONF, "maximum number of bytes buffered in memory for a stream"),
-    AP_INIT_TAKE1("H2AltSvc", h2_add_alt_svc, NULL,
-                  RSRC_CONF, "adds an Alt-Svc for this server"),
-    AP_INIT_TAKE1("H2AltSvcMaxAge", h2_conf_set_alt_svc_max_age, NULL,
-                  RSRC_CONF, "set the maximum age (in seconds) that client can rely on alt-svc information"),
     AP_INIT_TAKE1("H2SerializeHeaders", h2_conf_set_serialize_headers, NULL,
-                  RSRC_CONF, "on to enable header serialization for compatibility"),
+                  RSRC_CONF, "disabled, this directive has no longer an effect."),
     AP_INIT_TAKE1("H2ModernTLSOnly", h2_conf_set_modern_tls_only, NULL,
                   RSRC_CONF, "off to not impose RFC 7540 restrictions on TLS"),
     AP_INIT_TAKE1("H2Upgrade", h2_conf_set_upgrade, NULL,
@@ -1000,6 +1067,16 @@
                   RSRC_CONF, "set payload padding"),
     AP_INIT_TAKE1("H2OutputBuffering", h2_conf_set_output_buffer, NULL,
                   RSRC_CONF, "set stream output buffer on/off"),
+    AP_INIT_TAKE1("H2StreamTimeout", h2_conf_set_stream_timeout, NULL,
+                  RSRC_CONF, "set stream timeout"),
+    AP_INIT_TAKE1("H2MaxDataFrameLen", h2_conf_set_max_data_frame_len, NULL,
+                  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("H2ProxyRequests", h2_conf_set_proxy_requests, NULL,
+                  OR_FILEINFO, "Enables forward proxy requests via HTTP/2"),
+    AP_INIT_TAKE1("H2WebSockets", h2_conf_set_websockets, NULL,
+                  RSRC_CONF, "off to disable WebSockets over HTTP/2"),
     AP_END_CMD
 };
 
diff -r -N -u a/modules/http2/h2_config.h b/modules/http2/h2_config.h
--- a/modules/http2/h2_config.h	2021-03-24 15:28:49.000000000 +0100
+++ b/modules/http2/h2_config.h	2024-10-30 21:40:01.059958617 +0100
@@ -28,11 +28,8 @@
     H2_CONF_WIN_SIZE,
     H2_CONF_MIN_WORKERS,
     H2_CONF_MAX_WORKERS,
-    H2_CONF_MAX_WORKER_IDLE_SECS,
+    H2_CONF_MAX_WORKER_IDLE_LIMIT,
     H2_CONF_STREAM_MAX_MEM,
-    H2_CONF_ALT_SVCS,
-    H2_CONF_ALT_SVC_MAX_AGE,
-    H2_CONF_SER_HEADERS,
     H2_CONF_DIRECT,
     H2_CONF_MODERN_TLS_ONLY,
     H2_CONF_UPGRADE,
@@ -45,6 +42,10 @@
     H2_CONF_PADDING_BITS,
     H2_CONF_PADDING_ALWAYS,
     H2_CONF_OUTPUT_BUFFER,
+    H2_CONF_STREAM_TIMEOUT,
+    H2_CONF_MAX_DATA_FRAME_LEN,
+    H2_CONF_PROXY_REQUESTS,
+    H2_CONF_WEBSOCKETS,
 } h2_config_var_t;
 
 struct apr_hash_t;
@@ -88,10 +89,11 @@
 apr_int64_t h2_config_rgeti64(request_rec *r, h2_config_var_t var);
 
 apr_array_header_t *h2_config_push_list(request_rec *r);
-apr_array_header_t *h2_config_alt_svcs(request_rec *r);
+apr_table_t *h2_config_early_headers(request_rec *r);
 
 
-void h2_get_num_workers(server_rec *s, int *minw, int *maxw);
+void h2_get_workers_config(server_rec *s, int *pminw, int *pmaxw,
+                           apr_time_t *pidle_limit);
 void h2_config_init(apr_pool_t *pool);
 
 const struct h2_priority *h2_cconfig_get_priority(conn_rec *c, const char *content_type);
diff -r -N -u a/modules/http2/h2_conn.c b/modules/http2/h2_conn.c
--- a/modules/http2/h2_conn.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_conn.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,402 +0,0 @@
-/* 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_strings.h>
-
-#include <ap_mpm.h>
-#include <ap_mmn.h>
-
-#include <httpd.h>
-#include <http_core.h>
-#include <http_config.h>
-#include <http_log.h>
-#include <http_connection.h>
-#include <http_protocol.h>
-#include <http_request.h>
-
-#include <mpm_common.h>
-
-#include "h2_private.h"
-#include "h2.h"
-#include "h2_config.h"
-#include "h2_ctx.h"
-#include "h2_filter.h"
-#include "h2_mplx.h"
-#include "h2_session.h"
-#include "h2_stream.h"
-#include "h2_h2.h"
-#include "h2_task.h"
-#include "h2_workers.h"
-#include "h2_conn.h"
-#include "h2_version.h"
-
-static struct h2_workers *workers;
-
-static h2_mpm_type_t mpm_type = H2_MPM_UNKNOWN;
-static module *mpm_module;
-static int async_mpm;
-static int mpm_supported = 1;
-static apr_socket_t *dummy_socket;
-
-static void check_modules(int force) 
-{
-    static int checked = 0;
-    int i;
-
-    if (force || !checked) {
-        for (i = 0; ap_loaded_modules[i]; ++i) {
-            module *m = ap_loaded_modules[i];
-            
-            if (!strcmp("event.c", m->name)) {
-                mpm_type = H2_MPM_EVENT;
-                mpm_module = m;
-                break;
-            }
-            else if (!strcmp("motorz.c", m->name)) {
-                mpm_type = H2_MPM_MOTORZ;
-                mpm_module = m;
-                break;
-            }
-            else if (!strcmp("mpm_netware.c", m->name)) {
-                mpm_type = H2_MPM_NETWARE;
-                mpm_module = m;
-                break;
-            }
-            else if (!strcmp("prefork.c", m->name)) {
-                mpm_type = H2_MPM_PREFORK;
-                mpm_module = m;
-                /* While http2 can work really well on prefork, it collides
-                 * today's use case for prefork: running single-thread app engines
-                 * like php. If we restrict h2_workers to 1 per process, php will
-                 * work fine, but browser will be limited to 1 active request at a
-                 * time. */
-                mpm_supported = 0;
-                break;
-            }
-            else if (!strcmp("simple_api.c", m->name)) {
-                mpm_type = H2_MPM_SIMPLE;
-                mpm_module = m;
-                mpm_supported = 0;
-                break;
-            }
-            else if (!strcmp("mpm_winnt.c", m->name)) {
-                mpm_type = H2_MPM_WINNT;
-                mpm_module = m;
-                break;
-            }
-            else if (!strcmp("worker.c", m->name)) {
-                mpm_type = H2_MPM_WORKER;
-                mpm_module = m;
-                break;
-            }
-        }
-        checked = 1;
-    }
-}
-
-apr_status_t h2_conn_child_init(apr_pool_t *pool, server_rec *s)
-{
-    apr_status_t status = APR_SUCCESS;
-    int minw, maxw;
-    int max_threads_per_child = 0;
-    int idle_secs = 0;
-
-    check_modules(1);
-    ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads_per_child);
-    
-    status = ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm);
-    if (status != APR_SUCCESS) {
-        /* some MPMs do not implemnent this */
-        async_mpm = 0;
-        status = APR_SUCCESS;
-    }
-
-    h2_config_init(pool);
-    
-    h2_get_num_workers(s, &minw, &maxw);
-    
-    idle_secs = h2_config_sgeti(s, H2_CONF_MAX_WORKER_IDLE_SECS);
-    ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
-                 "h2_workers: min=%d max=%d, mthrpchild=%d, idle_secs=%d", 
-                 minw, maxw, max_threads_per_child, idle_secs);
-    workers = h2_workers_create(s, pool, minw, maxw, idle_secs);
- 
-    ap_register_input_filter("H2_IN", h2_filter_core_input,
-                             NULL, AP_FTYPE_CONNECTION);
-   
-    status = h2_mplx_m_child_init(pool, s);
-
-    if (status == APR_SUCCESS) {
-        status = apr_socket_create(&dummy_socket, APR_INET, SOCK_STREAM,
-                                   APR_PROTO_TCP, pool);
-    }
-
-    return status;
-}
-
-void h2_conn_child_stopping(apr_pool_t *pool, int graceful)
-{
-    if (workers && graceful) {
-        h2_workers_graceful_shutdown(workers);
-    }
-}
-
-h2_mpm_type_t h2_conn_mpm_type(void)
-{
-    check_modules(0);
-    return mpm_type;
-}
-
-const char *h2_conn_mpm_name(void)
-{
-    check_modules(0);
-    return mpm_module? mpm_module->name : "unknown";
-}
-
-int h2_mpm_supported(void)
-{
-    check_modules(0);
-    return mpm_supported;
-}
-
-static module *h2_conn_mpm_module(void)
-{
-    check_modules(0);
-    return mpm_module;
-}
-
-apr_status_t h2_conn_setup(conn_rec *c, request_rec *r, server_rec *s)
-{
-    h2_session *session;
-    h2_ctx *ctx;
-    apr_status_t status;
-    
-    if (!workers) {
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911) 
-                      "workers not initialized");
-        return APR_EGENERAL;
-    }
-    
-    if (APR_SUCCESS == (status = h2_session_create(&session, c, r, s, workers))) {
-        ctx = h2_ctx_get(c, 1);
-        h2_ctx_session_set(ctx, session);
-
-        /* remove the input filter of mod_reqtimeout, now that the connection
-         * is established and we have swtiched to h2. reqtimeout has supervised
-         * possibly configured handshake timeouts and needs to get out of the way
-         * now since the rest of its state handling assumes http/1.x to take place. */
-        ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
-    }
-    
-    return status;
-}
-
-apr_status_t h2_conn_run(conn_rec *c)
-{
-    apr_status_t status;
-    int mpm_state = 0;
-    h2_session *session = h2_ctx_get_session(c);
-    
-    ap_assert(session);
-    do {
-        if (c->cs) {
-            c->cs->sense = CONN_SENSE_DEFAULT;
-            c->cs->state = CONN_STATE_HANDLER;
-        }
-    
-        status = h2_session_process(session, async_mpm);
-        
-        if (APR_STATUS_IS_EOF(status)) {
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, 
-                          H2_SSSN_LOG(APLOGNO(03045), session, 
-                          "process, closing conn"));
-            c->keepalive = AP_CONN_CLOSE;
-        }
-        else {
-            c->keepalive = AP_CONN_KEEPALIVE;
-        }
-        
-        if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
-            break;
-        }
-    } while (!async_mpm
-             && c->keepalive == AP_CONN_KEEPALIVE 
-             && mpm_state != AP_MPMQ_STOPPING);
-
-    if (c->cs) {
-        switch (session->state) {
-            case H2_SESSION_ST_INIT:
-            case H2_SESSION_ST_IDLE:
-            case H2_SESSION_ST_BUSY:
-            case H2_SESSION_ST_WAIT:
-                c->cs->state = CONN_STATE_WRITE_COMPLETION;
-                if (c->cs && (session->open_streams || !session->remote.emitted_count)) {
-                    /* let the MPM know that we are not done and want
-                     * the Timeout behaviour instead of a KeepAliveTimeout
-                     * See PR 63534. 
-                     */
-                    c->cs->sense = CONN_SENSE_WANT_READ;
-                }
-                break;
-            case H2_SESSION_ST_CLEANUP:
-            case H2_SESSION_ST_DONE:
-            default:
-                c->cs->state = CONN_STATE_LINGER;
-            break;
-        }
-    }
-
-    return APR_SUCCESS;
-}
-
-apr_status_t h2_conn_pre_close(struct h2_ctx *ctx, conn_rec *c)
-{
-    h2_session *session = h2_ctx_get_session(c);
-    
-    (void)c;
-    if (session) {
-        apr_status_t status = h2_session_pre_close(session, async_mpm);
-        return (status == APR_SUCCESS)? DONE : status;
-    }
-    return DONE;
-}
-
-/* APR callback invoked if allocation fails. */
-static int abort_on_oom(int retcode)
-{
-    ap_abort_on_oom();
-    return retcode; /* unreachable, hopefully. */
-}
-
-conn_rec *h2_secondary_create(conn_rec *master, int sec_id, apr_pool_t *parent)
-{
-    apr_allocator_t *allocator;
-    apr_status_t status;
-    apr_pool_t *pool;
-    conn_rec *c;
-    void *cfg;
-    module *mpm;
-    
-    ap_assert(master);
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, master,
-                  "h2_stream(%ld-%d): create secondary", master->id, sec_id);
-    
-    /* We create a pool with its own allocator to be used for
-     * processing a request. This is the only way to have the processing
-     * independent of its parent pool in the sense that it can work in
-     * another thread. Also, the new allocator needs its own mutex to
-     * synchronize sub-pools.
-     */
-    apr_allocator_create(&allocator);
-    apr_allocator_max_free_set(allocator, ap_max_mem_free);
-    status = apr_pool_create_ex(&pool, parent, NULL, allocator);
-    if (status != APR_SUCCESS) {
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, status, master, 
-                      APLOGNO(10004) "h2_session(%ld-%d): create secondary pool",
-                      master->id, sec_id);
-        return NULL;
-    }
-    apr_allocator_owner_set(allocator, pool);
-    apr_pool_abort_set(abort_on_oom, pool);
-    apr_pool_tag(pool, "h2_secondary_conn");
-
-    c = (conn_rec *) apr_palloc(pool, sizeof(conn_rec));
-    if (c == NULL) {
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, master, 
-                      APLOGNO(02913) "h2_session(%ld-%d): create secondary",
-                      master->id, sec_id);
-        apr_pool_destroy(pool);
-        return NULL;
-    }
-    
-    memcpy(c, master, sizeof(conn_rec));
-        
-    c->master                 = master;
-    c->pool                   = pool;   
-    c->conn_config            = ap_create_conn_config(pool);
-    c->notes                  = apr_table_make(pool, 5);
-    c->input_filters          = NULL;
-    c->output_filters         = NULL;
-    c->keepalives             = 0;
-#if AP_MODULE_MAGIC_AT_LEAST(20180903, 1)
-    c->filter_conn_ctx        = NULL;
-#endif
-    c->bucket_alloc           = apr_bucket_alloc_create(pool);
-#if !AP_MODULE_MAGIC_AT_LEAST(20180720, 1)
-    c->data_in_input_filters  = 0;
-    c->data_in_output_filters = 0;
-#endif
-    /* prevent mpm_event from making wrong assumptions about this connection,
-     * like e.g. using its socket for an async read check. */
-    c->clogging_input_filters = 1;
-    c->log                    = NULL;
-    c->log_id                 = apr_psprintf(pool, "%ld-%d", 
-                                             master->id, sec_id);
-    c->aborted                = 0;
-    /* We cannot install the master connection socket on the secondary, as
-     * modules mess with timeouts/blocking of the socket, with
-     * unwanted side effects to the master connection processing.
-     * Fortunately, since we never use the secondary socket, we can just install
-     * a single, process-wide dummy and everyone is happy.
-     */
-    ap_set_module_config(c->conn_config, &core_module, dummy_socket);
-    /* TODO: these should be unique to this thread */
-    c->sbh                    = master->sbh;
-    /* TODO: not all mpm modules have learned about secondary connections yet.
-     * copy their config from master to secondary.
-     */
-    if ((mpm = h2_conn_mpm_module()) != NULL) {
-        cfg = ap_get_module_config(master->conn_config, mpm);
-        ap_set_module_config(c->conn_config, mpm, cfg);
-    }
-
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c, 
-                  "h2_secondary(%s): created", c->log_id);
-    return c;
-}
-
-void h2_secondary_destroy(conn_rec *secondary)
-{
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, secondary,
-                  "h2_secondary(%s): destroy", secondary->log_id);
-    secondary->sbh = NULL;
-    apr_pool_destroy(secondary->pool);
-}
-
-apr_status_t h2_secondary_run_pre_connection(conn_rec *secondary, apr_socket_t *csd)
-{
-    if (secondary->keepalives == 0) {
-        /* Simulate that we had already a request on this connection. Some
-         * hooks trigger special behaviour when keepalives is 0. 
-         * (Not necessarily in pre_connection, but later. Set it here, so it
-         * is in place.) */
-        secondary->keepalives = 1;
-        /* We signal that this connection will be closed after the request.
-         * Which is true in that sense that we throw away all traffic data
-         * on this secondary connection after each requests. Although we might
-         * reuse internal structures like memory pools.
-         * The wanted effect of this is that httpd does not try to clean up
-         * any dangling data on this connection when a request is done. Which
-         * is unnecessary on a h2 stream.
-         */
-        secondary->keepalive = AP_CONN_CLOSE;
-        return ap_run_pre_connection(secondary, csd);
-    }
-    ap_assert(secondary->output_filters);
-    return APR_SUCCESS;
-}
-
diff -r -N -u a/modules/http2/h2_conn_ctx.c b/modules/http2/h2_conn_ctx.c
--- a/modules/http2/h2_conn_ctx.c	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_conn_ctx.c	2024-10-30 21:40:01.059958617 +0100
@@ -0,0 +1,123 @@
+/* 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_strings.h>
+#include <apr_atomic.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_log.h>
+#include <http_protocol.h>
+
+#include "h2_private.h"
+#include "h2_session.h"
+#include "h2_bucket_beam.h"
+#include "h2_c2.h"
+#include "h2_mplx.h"
+#include "h2_stream.h"
+#include "h2_util.h"
+#include "h2_conn_ctx.h"
+
+
+void h2_conn_ctx_detach(conn_rec *c)
+{
+    ap_set_module_config(c->conn_config, &http2_module, NULL);
+}
+
+static h2_conn_ctx_t *ctx_create(conn_rec *c, const char *id)
+{
+    h2_conn_ctx_t *conn_ctx = apr_pcalloc(c->pool, sizeof(*conn_ctx));
+    conn_ctx->id = id;
+    conn_ctx->server = c->base_server;
+    apr_atomic_set32(&conn_ctx->started, 1);
+    conn_ctx->started_at = apr_time_now();
+
+    ap_set_module_config(c->conn_config, &http2_module, conn_ctx);
+    return conn_ctx;
+}
+
+h2_conn_ctx_t *h2_conn_ctx_create_for_c1(conn_rec *c1, server_rec *s, const char *protocol)
+{
+    h2_conn_ctx_t *ctx;
+
+    ctx = ctx_create(c1, apr_psprintf(c1->pool, "%ld", c1->id));
+    ctx->server = s;
+    ctx->protocol = apr_pstrdup(c1->pool, protocol);
+
+    ctx->pfd.desc_type = APR_POLL_SOCKET;
+    ctx->pfd.desc.s = ap_get_conn_socket(c1);
+    ctx->pfd.reqevents = APR_POLLIN | APR_POLLERR | APR_POLLHUP;
+    ctx->pfd.client_data = ctx;
+    apr_socket_opt_set(ctx->pfd.desc.s, APR_SO_NONBLOCK, 1);
+
+    return ctx;
+}
+
+void h2_conn_ctx_assign_session(h2_conn_ctx_t *ctx, struct h2_session *session)
+{
+    ctx->session = session;
+    ctx->id = apr_psprintf(session->pool, "%d-%lu", session->child_num, (unsigned long)session->id);
+}
+
+apr_status_t h2_conn_ctx_init_for_c2(h2_conn_ctx_t **pctx, conn_rec *c2,
+                                     struct h2_mplx *mplx, struct h2_stream *stream,
+                                     struct h2_c2_transit *transit)
+{
+    h2_conn_ctx_t *conn_ctx;
+    apr_status_t rv = APR_SUCCESS;
+
+    ap_assert(c2->master);
+    conn_ctx = h2_conn_ctx_get(c2);
+    if (!conn_ctx) {
+        h2_conn_ctx_t *c1_ctx;
+
+        c1_ctx = h2_conn_ctx_get(c2->master);
+        ap_assert(c1_ctx);
+        ap_assert(c1_ctx->session);
+
+        conn_ctx = ctx_create(c2, c1_ctx->id);
+        conn_ctx->server = c2->master->base_server;
+    }
+
+    conn_ctx->mplx = mplx;
+    conn_ctx->transit = transit;
+    conn_ctx->stream_id = stream->id;
+    apr_pool_create(&conn_ctx->req_pool, c2->pool);
+    apr_pool_tag(conn_ctx->req_pool, "H2_C2_REQ");
+    conn_ctx->request = stream->request;
+    apr_atomic_set32(&conn_ctx->started, 1);
+    conn_ctx->started_at = apr_time_now();
+    conn_ctx->done = 0;
+    conn_ctx->done_at = 0;
+
+    *pctx = conn_ctx;
+    return rv;
+}
+
+void h2_conn_ctx_set_timeout(h2_conn_ctx_t *conn_ctx, apr_interval_time_t timeout)
+{
+    if (conn_ctx->beam_out) {
+        h2_beam_timeout_set(conn_ctx->beam_out, timeout);
+    }
+    if (conn_ctx->beam_in) {
+        h2_beam_timeout_set(conn_ctx->beam_in, timeout);
+    }
+    if (conn_ctx->pipe_in[H2_PIPE_OUT]) {
+        apr_file_pipe_timeout_set(conn_ctx->pipe_in[H2_PIPE_OUT], timeout);
+    }
+}
diff -r -N -u a/modules/http2/h2_conn_ctx.h b/modules/http2/h2_conn_ctx.h
--- a/modules/http2/h2_conn_ctx.h	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_conn_ctx.h	2024-10-30 21:40:01.059958617 +0100
@@ -0,0 +1,100 @@
+/* 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_conn_ctx__
+#define __mod_h2__h2_conn_ctx__
+
+#include "h2.h"
+
+struct h2_session;
+struct h2_stream;
+struct h2_mplx;
+struct h2_bucket_beam;
+struct h2_response_parser;
+struct h2_c2_transit;
+
+#define H2_PIPE_OUT     0
+#define H2_PIPE_IN      1
+
+/**
+ * The h2 module context associated with a connection.
+ *
+ * It keeps track of the different types of connections:
+ * - those from clients that use HTTP/2 protocol
+ * - those from clients that do not use HTTP/2
+ * - those created by ourself to perform work on HTTP/2 streams
+ */
+struct h2_conn_ctx_t {
+    const char *id;                 /* c*: our identifier of this connection */
+    server_rec *server;             /* c*: httpd server selected. */
+    const char *protocol;           /* c1: the protocol negotiated */
+    struct h2_session *session;     /* c1: the h2 session established */
+    struct h2_mplx *mplx;           /* c2: the multiplexer */
+    struct h2_c2_transit *transit;  /* c2: transit pool and bucket_alloc */
+
+#if !AP_HAS_RESPONSE_BUCKETS
+    int pre_conn_done;               /* has pre_connection setup run? */
+#endif
+    int stream_id;                  /* c1: 0, c2: stream id processed */
+    apr_pool_t *req_pool;            /* c2: a c2 child pool for a request */
+    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 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 */
+
+    int has_final_response;          /* final HTTP response passed on out */
+    apr_status_t last_err;           /* APR_SUCCES or last error encountered in filters */
+
+    apr_off_t bytes_sent;            /* c2: bytes acutaly sent via c1 */
+    /* atomic */ apr_uint32_t started; /* c2: processing was started */
+    apr_time_t started_at;           /* c2: when processing started */
+    /* atomic */ apr_uint32_t done;  /* c2: processing has finished */
+    apr_time_t done_at;              /* c2: when processing was done */
+};
+typedef struct h2_conn_ctx_t h2_conn_ctx_t;
+
+/**
+ * Get the h2 connection context.
+ * @param c the connection to look at
+ * @return h2 context of this connection
+ */
+#define h2_conn_ctx_get(c) \
+    ((c)? (h2_conn_ctx_t*)ap_get_module_config((c)->conn_config, &http2_module) : NULL)
+
+/**
+ * Create the h2 connection context.
+ * @param c the connection to create it at
+ * @param s the server in use
+ * @param protocol the protocol selected
+ * @return created h2 context of this connection
+ */
+h2_conn_ctx_t *h2_conn_ctx_create_for_c1(conn_rec *c, server_rec *s, const char *protocol);
+
+void h2_conn_ctx_assign_session(h2_conn_ctx_t *ctx, struct h2_session *session);
+
+apr_status_t h2_conn_ctx_init_for_c2(h2_conn_ctx_t **pctx, conn_rec *c,
+                                     struct h2_mplx *mplx, struct h2_stream *stream,
+                                     struct h2_c2_transit *transit);
+
+void h2_conn_ctx_detach(conn_rec *c);
+
+void h2_conn_ctx_set_timeout(h2_conn_ctx_t *conn_ctx, apr_interval_time_t timeout);
+
+#endif /* defined(__mod_h2__h2_conn_ctx__) */
diff -r -N -u a/modules/http2/h2_conn.h b/modules/http2/h2_conn.h
--- a/modules/http2/h2_conn.h	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_conn.h	1970-01-01 01:00:00.000000000 +0100
@@ -1,82 +0,0 @@
-/* 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_conn__
-#define __mod_h2__h2_conn__
-
-struct h2_ctx;
-struct h2_task;
-
-/**
- * Setup the connection and our context for HTTP/2 processing
- *
- * @param c the connection HTTP/2 is starting on
- * @param r the upgrade request that still awaits an answer, optional
- * @param s the server selected for this connection (can be != c->base_server)
- */
-apr_status_t h2_conn_setup(conn_rec *c, request_rec *r, server_rec *s);
-
-/**
- * Run the HTTP/2 connection in synchronous fashion. 
- * Return when the HTTP/2 session is done
- * and the connection will close or a fatal error occurred.
- *
- * @param c the http2 connection to run
- * @return APR_SUCCESS when session is done.
- */
-apr_status_t h2_conn_run(conn_rec *c);
-
-/**
- * The connection is about to close. If we have not send a GOAWAY
- * yet, this is the last chance.
- */
-apr_status_t h2_conn_pre_close(struct h2_ctx *ctx, conn_rec *c);
-
-/**
- * Initialize this child process for h2 connection work,
- * to be called once during child init before multi processing
- * starts.
- */
-apr_status_t h2_conn_child_init(apr_pool_t *pool, server_rec *s);
-
-/**
- * Child is about to be stopped, release unused resources
- */
-void h2_conn_child_stopping(apr_pool_t *pool, int graceful);
-
-typedef enum {
-    H2_MPM_UNKNOWN,
-    H2_MPM_WORKER,
-    H2_MPM_EVENT,
-    H2_MPM_PREFORK,
-    H2_MPM_MOTORZ,
-    H2_MPM_SIMPLE,
-    H2_MPM_NETWARE,
-    H2_MPM_WINNT,
-} h2_mpm_type_t;
-
-/* Returns the type of MPM module detected */
-h2_mpm_type_t h2_conn_mpm_type(void);
-const char *h2_conn_mpm_name(void);
-int h2_mpm_supported(void);
-
-conn_rec *h2_secondary_create(conn_rec *master, int sec_id, apr_pool_t *parent);
-void h2_secondary_destroy(conn_rec *secondary);
-
-apr_status_t h2_secondary_run_pre_connection(conn_rec *secondary, apr_socket_t *csd);
-void h2_secondary_run_connection(conn_rec *secondary);
-
-#endif /* defined(__mod_h2__h2_conn__) */
diff -r -N -u a/modules/http2/h2_conn_io.c b/modules/http2/h2_conn_io.c
--- a/modules/http2/h2_conn_io.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_conn_io.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,396 +0,0 @@
-/* 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_strings.h>
-#include <ap_mpm.h>
-
-#include <httpd.h>
-#include <http_core.h>
-#include <http_log.h>
-#include <http_connection.h>
-#include <http_protocol.h>
-#include <http_request.h>
-#include <http_ssl.h>
-
-#include "h2_private.h"
-#include "h2_bucket_eos.h"
-#include "h2_config.h"
-#include "h2_conn_io.h"
-#include "h2_h2.h"
-#include "h2_session.h"
-#include "h2_util.h"
-
-#define TLS_DATA_MAX          (16*1024) 
-
-/* Calculated like this: assuming MTU 1500 bytes
- * 1500 - 40 (IP) - 20 (TCP) - 40 (TCP options) 
- *      - TLS overhead (60-100) 
- * ~= 1300 bytes */
-#define WRITE_SIZE_INITIAL    1300
-
-/* The maximum we'd like to write in one chunk is
- * the max size of a TLS record. When pushing
- * many frames down the h2 connection, this might
- * align differently because of headers and other
- * frames or simply as not sufficient data is
- * in a response body.
- * However keeping frames at or below this limit
- * should make optimizations at the layer that writes
- * to TLS easier.
- */
-#define WRITE_SIZE_MAX        (TLS_DATA_MAX) 
-
-#define BUF_REMAIN            ((apr_size_t)(bmax-off))
-
-static void h2_conn_io_bb_log(conn_rec *c, int stream_id, int level, 
-                              const char *tag, apr_bucket_brigade *bb)
-{
-    char buffer[16 * 1024];
-    const char *line = "(null)";
-    int bmax = sizeof(buffer)/sizeof(buffer[0]);
-    int off = 0;
-    apr_bucket *b;
-    
-    (void)stream_id;
-    if (bb) {
-        memset(buffer, 0, bmax--);
-        for (b = APR_BRIGADE_FIRST(bb); 
-             bmax && (b != APR_BRIGADE_SENTINEL(bb));
-             b = APR_BUCKET_NEXT(b)) {
-            
-            if (APR_BUCKET_IS_METADATA(b)) {
-                if (APR_BUCKET_IS_EOS(b)) {
-                    off += apr_snprintf(buffer+off, BUF_REMAIN, "eos ");
-                }
-                else if (APR_BUCKET_IS_FLUSH(b)) {
-                    off += apr_snprintf(buffer+off, BUF_REMAIN, "flush ");
-                }
-                else if (AP_BUCKET_IS_EOR(b)) {
-                    off += apr_snprintf(buffer+off, BUF_REMAIN, "eor ");
-                }
-                else if (H2_BUCKET_IS_H2EOS(b)) {
-                    off += apr_snprintf(buffer+off, BUF_REMAIN, "h2eos ");
-                }
-                else {
-                    off += apr_snprintf(buffer+off, BUF_REMAIN, "meta(unknown) ");
-                }
-            }
-            else {
-                const char *btype = "data";
-                if (APR_BUCKET_IS_FILE(b)) {
-                    btype = "file";
-                }
-                else if (APR_BUCKET_IS_PIPE(b)) {
-                    btype = "pipe";
-                }
-                else if (APR_BUCKET_IS_SOCKET(b)) {
-                    btype = "socket";
-                }
-                else if (APR_BUCKET_IS_HEAP(b)) {
-                    btype = "heap";
-                }
-                else if (APR_BUCKET_IS_TRANSIENT(b)) {
-                    btype = "transient";
-                }
-                else if (APR_BUCKET_IS_IMMORTAL(b)) {
-                    btype = "immortal";
-                }
-#if APR_HAS_MMAP
-                else if (APR_BUCKET_IS_MMAP(b)) {
-                    btype = "mmap";
-                }
-#endif
-                else if (APR_BUCKET_IS_POOL(b)) {
-                    btype = "pool";
-                }
-                
-                off += apr_snprintf(buffer+off, BUF_REMAIN, "%s[%ld] ", 
-                                    btype, 
-                                    (long)(b->length == ((apr_size_t)-1)? -1UL : b->length));
-            }
-        }
-        line = *buffer? buffer : "(empty)";
-    }
-    /* Intentional no APLOGNO */
-    ap_log_cerror(APLOG_MARK, level, 0, c, "h2_session(%ld)-%s: %s", 
-                  c->id, tag, line);
-
-}
-
-apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, server_rec *s)
-{
-    io->c              = c;
-    io->output         = apr_brigade_create(c->pool, c->bucket_alloc);
-    io->is_tls         = ap_ssl_conn_is_ssl(c);
-    io->buffer_output  = io->is_tls;
-    io->flush_threshold = (apr_size_t)h2_config_sgeti64(s, H2_CONF_STREAM_MAX_MEM);
-
-    if (io->is_tls) {
-        /* This is what we start with, 
-         * see https://issues.apache.org/jira/browse/TS-2503 
-         */
-        io->warmup_size    = h2_config_sgeti64(s, H2_CONF_TLS_WARMUP_SIZE);
-        io->cooldown_usecs = (h2_config_sgeti(s, H2_CONF_TLS_COOLDOWN_SECS) 
-                              * APR_USEC_PER_SEC);
-        io->write_size     = (io->cooldown_usecs > 0? 
-                              WRITE_SIZE_INITIAL : WRITE_SIZE_MAX); 
-    }
-    else {
-        io->warmup_size    = 0;
-        io->cooldown_usecs = 0;
-        io->write_size     = 0;
-    }
-
-    if (APLOGctrace1(c)) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->c,
-                      "h2_conn_io(%ld): init, buffering=%d, warmup_size=%ld, "
-                      "cd_secs=%f", io->c->id, io->buffer_output, 
-                      (long)io->warmup_size,
-                      ((double)io->cooldown_usecs/APR_USEC_PER_SEC));
-    }
-
-    return APR_SUCCESS;
-}
-
-static void append_scratch(h2_conn_io *io) 
-{
-    if (io->scratch && io->slen > 0) {
-        apr_bucket *b = apr_bucket_heap_create(io->scratch, io->slen,
-                                               apr_bucket_free,
-                                               io->c->bucket_alloc);
-        APR_BRIGADE_INSERT_TAIL(io->output, b);
-        io->scratch = NULL;
-        io->slen = io->ssize = 0;
-    }
-}
-
-static apr_size_t assure_scratch_space(h2_conn_io *io) {
-    apr_size_t remain = io->ssize - io->slen; 
-    if (io->scratch && remain == 0) {
-        append_scratch(io);
-    }
-    if (!io->scratch) {
-        /* we control the size and it is larger than what buckets usually
-         * allocate. */
-        io->scratch = apr_bucket_alloc(io->write_size, io->c->bucket_alloc);
-        io->ssize = io->write_size;
-        io->slen = 0;
-        remain = io->ssize;
-    }
-    return remain;
-}
-    
-static apr_status_t read_to_scratch(h2_conn_io *io, apr_bucket *b)
-{
-    apr_status_t status;
-    const char *data;
-    apr_size_t len;
-    
-    if (!b->length) {
-        return APR_SUCCESS;
-    }
-    
-    ap_assert(b->length <= (io->ssize - io->slen));
-    if (APR_BUCKET_IS_FILE(b)) {
-        apr_bucket_file *f = (apr_bucket_file *)b->data;
-        apr_file_t *fd = f->fd;
-        apr_off_t offset = b->start;
-        
-        len = b->length;
-        /* file buckets will either mmap (which we do not want) or
-         * read 8000 byte chunks and split themself. However, we do
-         * know *exactly* how many bytes we need where.
-         */
-        status = apr_file_seek(fd, APR_SET, &offset);
-        if (status != APR_SUCCESS) {
-            return status;
-        }
-        status = apr_file_read(fd, io->scratch + io->slen, &len);
-        if (status != APR_SUCCESS && status != APR_EOF) {
-            return status;
-        }
-        io->slen += len;
-    }
-    else {
-        status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
-        if (status == APR_SUCCESS) {
-            memcpy(io->scratch+io->slen, data, len);
-            io->slen += len;
-        }
-    }
-    return status;
-}
-
-static void check_write_size(h2_conn_io *io) 
-{
-    if (io->write_size > WRITE_SIZE_INITIAL 
-        && (io->cooldown_usecs > 0)
-        && (apr_time_now() - io->last_write) >= io->cooldown_usecs) {
-        /* long time not written, reset write size */
-        io->write_size = WRITE_SIZE_INITIAL;
-        io->bytes_written = 0;
-    }
-    else if (io->write_size < WRITE_SIZE_MAX 
-             && io->bytes_written >= io->warmup_size) {
-        /* connection is hot, use max size */
-        io->write_size = WRITE_SIZE_MAX;
-    }
-}
-
-static apr_status_t pass_output(h2_conn_io *io, int flush)
-{
-    conn_rec *c = io->c;
-    apr_bucket_brigade *bb = io->output;
-    apr_bucket *b;
-    apr_off_t bblen;
-    apr_status_t status;
-    
-    append_scratch(io);
-    if (flush && !io->is_flushed) {
-        b = apr_bucket_flush_create(c->bucket_alloc);
-        APR_BRIGADE_INSERT_TAIL(bb, b);
-    }
-    
-    if (APR_BRIGADE_EMPTY(bb)) {
-        return APR_SUCCESS;
-    }
-    
-    ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, NULL);
-    apr_brigade_length(bb, 0, &bblen);
-    h2_conn_io_bb_log(c, 0, APLOG_TRACE2, "out", bb);
-    
-    status = ap_pass_brigade(c->output_filters, bb);
-    if (status == APR_SUCCESS) {
-        io->bytes_written += (apr_size_t)bblen;
-        io->last_write = apr_time_now();
-        if (flush) {
-            io->is_flushed = 1;
-        }
-    }
-    apr_brigade_cleanup(bb);
-
-    if (status != APR_SUCCESS) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03044)
-                      "h2_conn_io(%ld): pass_out brigade %ld bytes",
-                      c->id, (long)bblen);
-    }
-    return status;
-}
-
-int h2_conn_io_needs_flush(h2_conn_io *io)
-{
-    if (!io->is_flushed) {
-        apr_off_t len = h2_brigade_mem_size(io->output);
-        if (len > (apr_off_t)io->flush_threshold) {
-            return 1;
-        }
-        /* if we do not exceed flush length due to memory limits,
-         * we want at least flush when we have that amount of data. */
-        apr_brigade_length(io->output, 0, &len);
-        return len > (apr_off_t)(4 * io->flush_threshold);
-    }
-    return 0;
-}
-
-apr_status_t h2_conn_io_flush(h2_conn_io *io)
-{
-    apr_status_t status;
-    status = pass_output(io, 1);
-    check_write_size(io);
-    return status;
-}
-
-apr_status_t h2_conn_io_write(h2_conn_io *io, const char *data, size_t length)
-{
-    apr_status_t status = APR_SUCCESS;
-    apr_size_t remain;
-    
-    if (length > 0) {
-        io->is_flushed = 0;
-    }
-    
-    if (io->buffer_output) {
-        while (length > 0) {
-            remain = assure_scratch_space(io);
-            if (remain >= length) {
-                memcpy(io->scratch + io->slen, data, length);
-                io->slen += length;
-                length = 0;
-            }
-            else {
-                memcpy(io->scratch + io->slen, data, remain);
-                io->slen += remain;
-                data += remain;
-                length -= remain;
-            }
-        }
-    }
-    else {
-        status = apr_brigade_write(io->output, NULL, NULL, data, length);
-    }
-    return status;
-}
-
-apr_status_t h2_conn_io_pass(h2_conn_io *io, apr_bucket_brigade *bb)
-{
-    apr_bucket *b;
-    apr_status_t status = APR_SUCCESS;
-    
-    if (!APR_BRIGADE_EMPTY(bb)) {
-        io->is_flushed = 0;
-    }
-
-    while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
-        b = APR_BRIGADE_FIRST(bb);
-        
-        if (APR_BUCKET_IS_METADATA(b)) {
-            /* need to finish any open scratch bucket, as meta data 
-             * needs to be forward "in order". */
-            append_scratch(io);
-            APR_BUCKET_REMOVE(b);
-            APR_BRIGADE_INSERT_TAIL(io->output, b);
-        }
-        else if (io->buffer_output) {
-            apr_size_t remain = assure_scratch_space(io);
-            if (b->length > remain) {
-                apr_bucket_split(b, remain);
-                if (io->slen == 0) {
-                    /* complete write_size bucket, append unchanged */
-                    APR_BUCKET_REMOVE(b);
-                    APR_BRIGADE_INSERT_TAIL(io->output, b);
-                    continue;
-                }
-            }
-            else {
-                /* bucket fits in remain, copy to scratch */
-                status = read_to_scratch(io, b);
-                apr_bucket_delete(b);
-                continue;
-            }
-        }
-        else {
-            /* no buffering, forward buckets setaside on flush */
-            if (APR_BUCKET_IS_TRANSIENT(b)) {
-                apr_bucket_setaside(b, io->c->pool);
-            }
-            APR_BUCKET_REMOVE(b);
-            APR_BRIGADE_INSERT_TAIL(io->output, b);
-        }
-    }
-    return status;
-}
-
diff -r -N -u a/modules/http2/h2_conn_io.h b/modules/http2/h2_conn_io.h
--- a/modules/http2/h2_conn_io.h	2019-03-13 16:00:57.000000000 +0100
+++ b/modules/http2/h2_conn_io.h	1970-01-01 01:00:00.000000000 +0100
@@ -1,76 +0,0 @@
-/* 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_conn_io__
-#define __mod_h2__h2_conn_io__
-
-struct h2_config;
-struct h2_session;
-
-/* h2_io is the basic handler of a httpd connection. It keeps two brigades,
- * one for input, one for output and works with the installed connection
- * filters.
- * The read is done via a callback function, so that input can be processed
- * directly without copying.
- */
-typedef struct {
-    conn_rec *c;
-    apr_bucket_brigade *output;
-
-    int is_tls;
-    apr_time_t cooldown_usecs;
-    apr_int64_t warmup_size;
-    
-    apr_size_t write_size;
-    apr_time_t last_write;
-    apr_int64_t bytes_read;
-    apr_int64_t bytes_written;
-    
-    int buffer_output;
-    apr_size_t flush_threshold;
-    unsigned int is_flushed : 1;
-    
-    char *scratch;
-    apr_size_t ssize;
-    apr_size_t slen;
-} h2_conn_io;
-
-apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, server_rec *s);
-
-/**
- * Append data to the buffered output.
- * @param buf the data to append
- * @param length the length of the data to append
- */
-apr_status_t h2_conn_io_write(h2_conn_io *io,
-                         const char *buf,
-                         size_t length);
-
-apr_status_t h2_conn_io_pass(h2_conn_io *io, apr_bucket_brigade *bb);
-
-/**
- * Pass any buffered data on to the connection output filters.
- * @param io the connection io
- * @param flush if a flush bucket should be appended to any output
- */
-apr_status_t h2_conn_io_flush(h2_conn_io *io);
-
-/**
- * Check if the buffered amount of data needs flushing.
- */
-int h2_conn_io_needs_flush(h2_conn_io *io);
-
-#endif /* defined(__mod_h2__h2_conn_io__) */
diff -r -N -u a/modules/http2/h2_ctx.c b/modules/http2/h2_ctx.c
--- a/modules/http2/h2_ctx.c	2019-03-13 16:00:57.000000000 +0100
+++ b/modules/http2/h2_ctx.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,106 +0,0 @@
-/* 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 <httpd.h>
-#include <http_core.h>
-#include <http_config.h>
-
-#include "h2_private.h"
-#include "h2_session.h"
-#include "h2_task.h"
-#include "h2_ctx.h"
-
-static h2_ctx *h2_ctx_create(const conn_rec *c)
-{
-    h2_ctx *ctx = apr_pcalloc(c->pool, sizeof(h2_ctx));
-    ap_assert(ctx);
-    h2_ctx_server_update(ctx, c->base_server);
-    ap_set_module_config(c->conn_config, &http2_module, ctx);
-    return ctx;
-}
-
-void h2_ctx_clear(const conn_rec *c)
-{
-    ap_assert(c);
-    ap_set_module_config(c->conn_config, &http2_module, NULL);
-}
-
-h2_ctx *h2_ctx_create_for(const conn_rec *c, h2_task *task)
-{
-    h2_ctx *ctx = h2_ctx_create(c);
-    if (ctx) {
-        ctx->task = task;
-    }
-    return ctx;
-}
-
-h2_ctx *h2_ctx_get(const conn_rec *c, int create)
-{
-    h2_ctx *ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &http2_module);
-    if (ctx == NULL && create) {
-        ctx = h2_ctx_create(c);
-    }
-    return ctx;
-}
-
-h2_ctx *h2_ctx_rget(const request_rec *r)
-{
-    return h2_ctx_get(r->connection, 0);
-}
-
-const char *h2_ctx_protocol_get(const conn_rec *c)
-{
-    h2_ctx *ctx;
-    if (c->master) {
-        c = c->master;
-    }
-    ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &http2_module);
-    return ctx? ctx->protocol : NULL;
-}
-
-h2_ctx *h2_ctx_protocol_set(h2_ctx *ctx, const char *proto)
-{
-    ctx->protocol = proto;
-    return ctx;
-}
-
-h2_session *h2_ctx_get_session(conn_rec *c)
-{
-    h2_ctx *ctx = h2_ctx_get(c, 0);
-    return ctx? ctx->session : NULL;
-}
-
-void h2_ctx_session_set(h2_ctx *ctx, struct h2_session *session)
-{
-    ctx->session = session;
-}
-
-h2_ctx *h2_ctx_server_update(h2_ctx *ctx, server_rec *s)
-{
-    if (ctx->server != s) {
-        ctx->server = s;
-    }
-    return ctx;
-}
-
-h2_task *h2_ctx_get_task(conn_rec *c)
-{
-    h2_ctx *ctx = h2_ctx_get(c, 0);
-    return ctx? ctx->task : NULL;
-}
-
diff -r -N -u a/modules/http2/h2_ctx.h b/modules/http2/h2_ctx.h
--- a/modules/http2/h2_ctx.h	2019-03-13 16:00:57.000000000 +0100
+++ b/modules/http2/h2_ctx.h	1970-01-01 01:00:00.000000000 +0100
@@ -1,75 +0,0 @@
-/* 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_ctx__
-#define __mod_h2__h2_ctx__
-
-struct h2_session;
-struct h2_task;
-struct h2_config;
-
-/**
- * The h2 module context associated with a connection. 
- *
- * It keeps track of the different types of connections:
- * - those from clients that use HTTP/2 protocol
- * - those from clients that do not use HTTP/2
- * - those created by ourself to perform work on HTTP/2 streams
- */
-typedef struct h2_ctx {
-    const char *protocol;           /* the protocol negotiated */
-    struct h2_session *session;     /* the session established */
-    struct h2_task *task;           /* the h2_task executing or NULL */
-    const char *hostname;           /* hostname negotiated via SNI, optional */
-    server_rec *server;             /* httpd server config selected. */
-    const struct h2_config *config; /* effective config in this context */
-} h2_ctx;
-
-/**
- * Get (or create) a h2 context record for this connection.
- * @param c the connection to look at
- * @param create != 0 iff missing context shall be created
- * @return h2 context of this connection
- */
-h2_ctx *h2_ctx_get(const conn_rec *c, int create);
-void h2_ctx_clear(const conn_rec *c);
-
-h2_ctx *h2_ctx_rget(const request_rec *r);
-h2_ctx *h2_ctx_create_for(const conn_rec *c, struct h2_task *task);
-
-
-/* Set the h2 protocol established on this connection context or
- * NULL when other protocols are in place.
- */
-h2_ctx *h2_ctx_protocol_set(h2_ctx *ctx, const char *proto);
-
-/* Update the server_rec relevant for this context. A server for
- * a connection may change during SNI handling, for example.
- */
-h2_ctx *h2_ctx_server_update(h2_ctx *ctx, server_rec *s);
-
-void h2_ctx_session_set(h2_ctx *ctx, struct h2_session *session);
-
-/**
- * Get the h2 protocol negotiated for this connection, or NULL.
- */
-const char *h2_ctx_protocol_get(const conn_rec *c);
-
-struct h2_session *h2_ctx_get_session(conn_rec *c);
-struct h2_task *h2_ctx_get_task(conn_rec *c);
-
-
-#endif /* defined(__mod_h2__h2_ctx__) */
diff -r -N -u a/modules/http2/h2_filter.c b/modules/http2/h2_filter.c
--- a/modules/http2/h2_filter.c	2020-07-08 13:53:48.000000000 +0200
+++ b/modules/http2/h2_filter.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,613 +0,0 @@
-/* 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_strings.h>
-#include <httpd.h>
-#include <http_core.h>
-#include <http_protocol.h>
-#include <http_log.h>
-#include <http_connection.h>
-#include <scoreboard.h>
-
-#include "h2_private.h"
-#include "h2.h"
-#include "h2_config.h"
-#include "h2_conn_io.h"
-#include "h2_ctx.h"
-#include "h2_mplx.h"
-#include "h2_push.h"
-#include "h2_task.h"
-#include "h2_stream.h"
-#include "h2_request.h"
-#include "h2_headers.h"
-#include "h2_stream.h"
-#include "h2_session.h"
-#include "h2_util.h"
-#include "h2_version.h"
-
-#include "h2_filter.h"
-
-#define UNSET       -1
-#define H2MIN(x,y) ((x) < (y) ? (x) : (y))
-
-static apr_status_t recv_RAW_DATA(conn_rec *c, h2_filter_cin *cin, 
-                                  apr_bucket *b, apr_read_type_e block)
-{
-    h2_session *session = cin->session;
-    apr_status_t status = APR_SUCCESS;
-    apr_size_t len;
-    const char *data;
-    ssize_t n;
-    
-    (void)c;
-    status = apr_bucket_read(b, &data, &len, block);
-    
-    while (status == APR_SUCCESS && len > 0) {
-        n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len);
-        
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                      H2_SSSN_MSG(session, "fed %ld bytes to nghttp2, %ld read"),
-                      (long)len, (long)n);
-        if (n < 0) {
-            if (nghttp2_is_fatal((int)n)) {
-                h2_session_event(session, H2_SESSION_EV_PROTO_ERROR, 
-                                 (int)n, nghttp2_strerror((int)n));
-                status = APR_EGENERAL;
-            }
-        }
-        else {
-            session->io.bytes_read += n;
-            if ((apr_ssize_t)len <= n) {
-                break;
-            }
-            len -= (apr_size_t)n;
-            data += n;
-        }
-    }
-    
-    return status;
-}
-
-static apr_status_t recv_RAW_brigade(conn_rec *c, h2_filter_cin *cin, 
-                                     apr_bucket_brigade *bb, 
-                                     apr_read_type_e block)
-{
-    apr_status_t status = APR_SUCCESS;
-    apr_bucket* b;
-    int consumed = 0;
-    
-    h2_util_bb_log(c, c->id, APLOG_TRACE2, "RAW_in", bb);
-    while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)) {
-        b = APR_BRIGADE_FIRST(bb);
-
-        if (APR_BUCKET_IS_METADATA(b)) {
-            /* nop */
-        }
-        else {
-            status = recv_RAW_DATA(c, cin, b, block);
-        }
-        consumed = 1;
-        apr_bucket_delete(b);
-    }
-    
-    if (!consumed && status == APR_SUCCESS && block == APR_NONBLOCK_READ) {
-        return APR_EAGAIN;
-    }
-    return status;
-}
-
-h2_filter_cin *h2_filter_cin_create(h2_session *session)
-{
-    h2_filter_cin *cin;
-    
-    cin = apr_pcalloc(session->pool, sizeof(*cin));
-    if (!cin) {
-        return NULL;
-    }
-    cin->session = session;
-    return cin;
-}
-
-void h2_filter_cin_timeout_set(h2_filter_cin *cin, apr_interval_time_t timeout)
-{
-    cin->timeout = timeout;
-}
-
-apr_status_t h2_filter_core_input(ap_filter_t* f,
-                                  apr_bucket_brigade* brigade,
-                                  ap_input_mode_t mode,
-                                  apr_read_type_e block,
-                                  apr_off_t readbytes) 
-{
-    h2_filter_cin *cin = f->ctx;
-    apr_status_t status = APR_SUCCESS;
-    apr_interval_time_t saved_timeout = UNSET;
-    const int trace1 = APLOGctrace1(f->c);
-    
-    if (trace1) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                      "h2_session(%ld): read, %s, mode=%d, readbytes=%ld", 
-                      (long)f->c->id, (block == APR_BLOCK_READ)? 
-                      "BLOCK_READ" : "NONBLOCK_READ", mode, (long)readbytes);
-    }
-    
-    if (mode == AP_MODE_INIT || mode == AP_MODE_SPECULATIVE) {
-        return ap_get_brigade(f->next, brigade, mode, block, readbytes);
-    }
-    
-    if (mode != AP_MODE_READBYTES) {
-        return (block == APR_BLOCK_READ)? APR_SUCCESS : APR_EAGAIN;
-    }
-    
-    if (!cin->bb) {
-        cin->bb = apr_brigade_create(cin->session->pool, f->c->bucket_alloc);
-    }
-
-    if (!cin->socket) {
-        cin->socket = ap_get_conn_socket(f->c);
-    }
-    
-    if (APR_BRIGADE_EMPTY(cin->bb)) {
-        /* We only do a blocking read when we have no streams to process. So,
-         * in httpd scoreboard lingo, we are in a KEEPALIVE connection state.
-         */
-        if (block == APR_BLOCK_READ) {
-            if (cin->timeout > 0) {
-                apr_socket_timeout_get(cin->socket, &saved_timeout);
-                apr_socket_timeout_set(cin->socket, cin->timeout);
-            }
-        }
-        status = ap_get_brigade(f->next, cin->bb, AP_MODE_READBYTES,
-                                block, readbytes);
-        if (saved_timeout != UNSET) {
-            apr_socket_timeout_set(cin->socket, saved_timeout);
-        }
-    }
-    
-    switch (status) {
-        case APR_SUCCESS:
-            status = recv_RAW_brigade(f->c, cin, cin->bb, block);
-            break;
-        case APR_EOF:
-        case APR_EAGAIN:
-        case APR_TIMEUP:
-            if (trace1) {
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
-                              "h2_session(%ld): read", f->c->id);
-            }
-            break;
-        default:
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, f->c, APLOGNO(03046)
-                          "h2_session(%ld): error reading", f->c->id);
-            break;
-    }
-    return status;
-}
-
-/*******************************************************************************
- * http2 connection status handler + stream out source
- ******************************************************************************/
-
-typedef struct {
-    apr_bucket_refcount refcount;
-    h2_bucket_event_cb *cb;
-    void *ctx;
-} h2_bucket_observer;
- 
-static apr_status_t bucket_read(apr_bucket *b, const char **str,
-                                apr_size_t *len, apr_read_type_e block)
-{
-    (void)b;
-    (void)block;
-    *str = NULL;
-    *len = 0;
-    return APR_SUCCESS;
-}
-
-static void bucket_destroy(void *data)
-{
-    h2_bucket_observer *h = data;
-    if (apr_bucket_shared_destroy(h)) {
-        if (h->cb) {
-            h->cb(h->ctx, H2_BUCKET_EV_BEFORE_DESTROY, NULL);
-        }
-        apr_bucket_free(h);
-    }
-}
-
-apr_bucket * h2_bucket_observer_make(apr_bucket *b, h2_bucket_event_cb *cb,
-                                 void *ctx)
-{
-    h2_bucket_observer *br;
-
-    br = apr_bucket_alloc(sizeof(*br), b->list);
-    br->cb = cb;
-    br->ctx = ctx;
-
-    b = apr_bucket_shared_make(b, br, 0, 0);
-    b->type = &h2_bucket_type_observer;
-    return b;
-} 
-
-apr_bucket * h2_bucket_observer_create(apr_bucket_alloc_t *list, 
-                                       h2_bucket_event_cb *cb, void *ctx)
-{
-    apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
-
-    APR_BUCKET_INIT(b);
-    b->free = apr_bucket_free;
-    b->list = list;
-    b = h2_bucket_observer_make(b, cb, ctx);
-    return b;
-}
-                                       
-apr_status_t h2_bucket_observer_fire(apr_bucket *b, h2_bucket_event event)
-{
-    if (H2_BUCKET_IS_OBSERVER(b)) {
-        h2_bucket_observer *l = (h2_bucket_observer *)b->data; 
-        return l->cb(l->ctx, event, b);
-    }
-    return APR_EINVAL;
-}
-
-const apr_bucket_type_t h2_bucket_type_observer = {
-    "H2OBS", 5, APR_BUCKET_METADATA,
-    bucket_destroy,
-    bucket_read,
-    apr_bucket_setaside_noop,
-    apr_bucket_split_notimpl,
-    apr_bucket_shared_copy
-};
-
-apr_bucket *h2_bucket_observer_beam(struct h2_bucket_beam *beam,
-                                    apr_bucket_brigade *dest,
-                                    const apr_bucket *src)
-{
-    (void)beam;
-    if (H2_BUCKET_IS_OBSERVER(src)) {
-        h2_bucket_observer *l = (h2_bucket_observer *)src->data; 
-        apr_bucket *b = h2_bucket_observer_create(dest->bucket_alloc, 
-                                                  l->cb, l->ctx);
-        APR_BRIGADE_INSERT_TAIL(dest, b);
-        l->cb = NULL;
-        l->ctx = NULL;
-        h2_bucket_observer_fire(b, H2_BUCKET_EV_BEFORE_MASTER_SEND);
-        return b;
-    }
-    return NULL;
-}
-
-static apr_status_t bbout(apr_bucket_brigade *bb, const char *fmt, ...)
-                             __attribute__((format(printf,2,3)));
-static apr_status_t bbout(apr_bucket_brigade *bb, const char *fmt, ...)
-{
-    va_list args;
-    apr_status_t rv;
-
-    va_start(args, fmt);
-    rv = apr_brigade_vprintf(bb, NULL, NULL, fmt, args);
-    va_end(args);
-
-    return rv;
-}
-
-static void add_settings(apr_bucket_brigade *bb, h2_session *s, int last) 
-{
-    h2_mplx *m = s->mplx;
-    
-    bbout(bb, "  \"settings\": {\n");
-    bbout(bb, "    \"SETTINGS_MAX_CONCURRENT_STREAMS\": %d,\n", m->max_streams); 
-    bbout(bb, "    \"SETTINGS_MAX_FRAME_SIZE\": %d,\n", 16*1024); 
-    bbout(bb, "    \"SETTINGS_INITIAL_WINDOW_SIZE\": %d,\n", h2_config_sgeti(s->s, H2_CONF_WIN_SIZE));
-    bbout(bb, "    \"SETTINGS_ENABLE_PUSH\": %d\n", h2_session_push_enabled(s)); 
-    bbout(bb, "  }%s\n", last? "" : ",");
-}
-
-static void add_peer_settings(apr_bucket_brigade *bb, h2_session *s, int last) 
-{
-    bbout(bb, "  \"peerSettings\": {\n");
-    bbout(bb, "    \"SETTINGS_MAX_CONCURRENT_STREAMS\": %d,\n", 
-        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)); 
-    bbout(bb, "    \"SETTINGS_MAX_FRAME_SIZE\": %d,\n", 
-        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_FRAME_SIZE)); 
-    bbout(bb, "    \"SETTINGS_INITIAL_WINDOW_SIZE\": %d,\n", 
-        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE)); 
-    bbout(bb, "    \"SETTINGS_ENABLE_PUSH\": %d,\n", 
-        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_ENABLE_PUSH)); 
-    bbout(bb, "    \"SETTINGS_HEADER_TABLE_SIZE\": %d,\n", 
-        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE)); 
-    bbout(bb, "    \"SETTINGS_MAX_HEADER_LIST_SIZE\": %d\n", 
-        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE)); 
-    bbout(bb, "  }%s\n", last? "" : ",");
-}
-
-typedef struct {
-    apr_bucket_brigade *bb;
-    h2_session *s;
-    int idx;
-} stream_ctx_t;
-
-static int add_stream(h2_stream *stream, void *ctx)
-{
-    stream_ctx_t *x = ctx;
-    int32_t flowIn, flowOut;
-    
-    flowIn = nghttp2_session_get_stream_effective_local_window_size(x->s->ngh2, stream->id); 
-    flowOut = nghttp2_session_get_stream_remote_window_size(x->s->ngh2, stream->id);
-    bbout(x->bb, "%s\n    \"%d\": {\n", (x->idx? "," : ""), stream->id);
-    bbout(x->bb, "    \"state\": \"%s\",\n", h2_stream_state_str(stream));
-    bbout(x->bb, "    \"created\": %f,\n", ((double)stream->created)/APR_USEC_PER_SEC);
-    bbout(x->bb, "    \"flowIn\": %d,\n", flowIn);
-    bbout(x->bb, "    \"flowOut\": %d,\n", flowOut);
-    bbout(x->bb, "    \"dataIn\": %"APR_OFF_T_FMT",\n", stream->in_data_octets);  
-    bbout(x->bb, "    \"dataOut\": %"APR_OFF_T_FMT"\n", stream->out_data_octets);  
-    bbout(x->bb, "    }");
-    
-    ++x->idx;
-    return 1;
-} 
-
-static void add_streams(apr_bucket_brigade *bb, h2_session *s, int last) 
-{
-    stream_ctx_t x;
-    
-    x.bb = bb;
-    x.s = s;
-    x.idx = 0;
-    bbout(bb, "  \"streams\": {");
-    h2_mplx_m_stream_do(s->mplx, add_stream, &x);
-    bbout(bb, "\n  }%s\n", last? "" : ",");
-}
-
-static void add_push(apr_bucket_brigade *bb, h2_session *s, 
-                     h2_stream *stream, int last) 
-{
-    h2_push_diary *diary;
-    apr_status_t status;
-    
-    bbout(bb, "    \"push\": {\n");
-    diary = s->push_diary;
-    if (diary) {
-        const char *data;
-        const char *base64_digest;
-        apr_size_t len;
-        
-        status = h2_push_diary_digest_get(diary, bb->p, 256, 
-                                          stream->request->authority, 
-                                          &data, &len);
-        if (status == APR_SUCCESS) {
-            base64_digest = h2_util_base64url_encode(data, len, bb->p);
-            bbout(bb, "      \"cacheDigest\": \"%s\",\n", base64_digest);
-        }
-    }
-    bbout(bb, "      \"promises\": %d,\n", s->pushes_promised);
-    bbout(bb, "      \"submits\": %d,\n", s->pushes_submitted);
-    bbout(bb, "      \"resets\": %d\n", s->pushes_reset);
-    bbout(bb, "    }%s\n", last? "" : ",");
-}
-
-static void add_in(apr_bucket_brigade *bb, h2_session *s, int last) 
-{
-    bbout(bb, "    \"in\": {\n");
-    bbout(bb, "      \"requests\": %d,\n", s->remote.emitted_count);
-    bbout(bb, "      \"resets\": %d, \n", s->streams_reset);
-    bbout(bb, "      \"frames\": %ld,\n", (long)s->frames_received);
-    bbout(bb, "      \"octets\": %"APR_UINT64_T_FMT"\n", s->io.bytes_read);
-    bbout(bb, "    }%s\n", last? "" : ",");
-}
-
-static void add_out(apr_bucket_brigade *bb, h2_session *s, int last) 
-{
-    bbout(bb, "    \"out\": {\n");
-    bbout(bb, "      \"responses\": %d,\n", s->responses_submitted);
-    bbout(bb, "      \"frames\": %ld,\n", (long)s->frames_sent);
-    bbout(bb, "      \"octets\": %"APR_UINT64_T_FMT"\n", s->io.bytes_written);
-    bbout(bb, "    }%s\n", last? "" : ",");
-}
-
-static void add_stats(apr_bucket_brigade *bb, h2_session *s, 
-                     h2_stream *stream, int last) 
-{
-    bbout(bb, "  \"stats\": {\n");
-    add_in(bb, s, 0);
-    add_out(bb, s, 0);
-    add_push(bb, s, stream, 1);
-    bbout(bb, "  }%s\n", last? "" : ",");
-}
-
-static apr_status_t h2_status_insert(h2_task *task, apr_bucket *b)
-{
-    h2_mplx *m = task->mplx;
-    h2_stream *stream = h2_mplx_t_stream_get(m, task);
-    h2_session *s;
-    conn_rec *c;
-    
-    apr_bucket_brigade *bb;
-    apr_bucket *e;
-    int32_t connFlowIn, connFlowOut;
-    
-    if (!stream) {
-        /* stream already done */
-        return APR_SUCCESS;
-    }
-    s = stream->session;
-    c = s->c;
-    
-    bb = apr_brigade_create(stream->pool, c->bucket_alloc);
-    
-    connFlowIn = nghttp2_session_get_effective_local_window_size(s->ngh2); 
-    connFlowOut = nghttp2_session_get_remote_window_size(s->ngh2);
-     
-    bbout(bb, "{\n");
-    bbout(bb, "  \"version\": \"draft-01\",\n");
-    add_settings(bb, s, 0);
-    add_peer_settings(bb, s, 0);
-    bbout(bb, "  \"connFlowIn\": %d,\n", connFlowIn);
-    bbout(bb, "  \"connFlowOut\": %d,\n", connFlowOut);
-    bbout(bb, "  \"sentGoAway\": %d,\n", s->local.shutdown);
-
-    add_streams(bb, s, 0);
-    
-    add_stats(bb, s, stream, 1);
-    bbout(bb, "}\n");
-    
-    while ((e = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) {
-        APR_BUCKET_REMOVE(e);
-        APR_BUCKET_INSERT_AFTER(b, e);
-        b = e;
-    }
-    apr_brigade_destroy(bb);
-    
-    return APR_SUCCESS;
-}
-
-static apr_status_t status_event(void *ctx, h2_bucket_event event, 
-                                 apr_bucket *b)
-{
-    h2_task *task = ctx;
-    
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, task->c->master, 
-                  "status_event(%s): %d", task->id, event);
-    switch (event) {
-        case H2_BUCKET_EV_BEFORE_MASTER_SEND:
-            h2_status_insert(task, b);
-            break;
-        default:
-            break;
-    }
-    return APR_SUCCESS;
-}
-
-static apr_status_t discard_body(request_rec *r, apr_off_t maxlen)
-{
-    apr_bucket_brigade *bb;
-    int seen_eos;
-    apr_status_t rv;
-
-    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
-    seen_eos = 0;
-    do {
-        apr_bucket *bucket;
-
-        rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
-                            APR_BLOCK_READ, HUGE_STRING_LEN);
-
-        if (rv != APR_SUCCESS) {
-            apr_brigade_destroy(bb);
-            return rv;
-        }
-
-        for (bucket = APR_BRIGADE_FIRST(bb);
-             bucket != APR_BRIGADE_SENTINEL(bb);
-             bucket = APR_BUCKET_NEXT(bucket))
-        {
-            const char *data;
-            apr_size_t len;
-
-            if (APR_BUCKET_IS_EOS(bucket)) {
-                seen_eos = 1;
-                break;
-            }
-            if (bucket->length == 0) {
-                continue;
-            }
-            rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
-            if (rv != APR_SUCCESS) {
-                apr_brigade_destroy(bb);
-                return rv;
-            }
-            maxlen -= bucket->length;
-        }
-        apr_brigade_cleanup(bb);
-    } while (!seen_eos && maxlen >= 0);
-
-    return APR_SUCCESS;
-}
-
-int h2_filter_h2_status_handler(request_rec *r)
-{
-    conn_rec *c = r->connection;
-    h2_task *task;
-    apr_bucket_brigade *bb;
-    apr_bucket *b;
-    apr_status_t status;
-    
-    if (strcmp(r->handler, "http2-status")) {
-        return DECLINED;
-    }
-    if (r->method_number != M_GET && r->method_number != M_POST) {
-        return DECLINED;
-    }
-
-    task = h2_ctx_get_task(r->connection);
-    if (task) {
-        /* In this handler, we do some special sauce to send footers back,
-         * IFF we received footers in the request. This is used in our test
-         * cases, since CGI has no way of handling those. */
-        if ((status = discard_body(r, 1024)) != OK) {
-            return status;
-        }
-        
-        /* We need to handle the actual output on the main thread, as
-         * we need to access h2_session information. */
-        r->status = 200;
-        r->clength = -1;
-        r->chunked = 1;
-        apr_table_unset(r->headers_out, "Content-Length");
-        /* Discourage content-encodings */
-        apr_table_unset(r->headers_out, "Content-Encoding");
-        apr_table_setn(r->subprocess_env, "no-brotli", "1");
-        apr_table_setn(r->subprocess_env, "no-gzip", "1");
-
-        ap_set_content_type(r, "application/json");
-        apr_table_setn(r->notes, H2_FILTER_DEBUG_NOTE, "on");
-
-        bb = apr_brigade_create(r->pool, c->bucket_alloc);
-        b = h2_bucket_observer_create(c->bucket_alloc, status_event, task);
-        APR_BRIGADE_INSERT_TAIL(bb, b);
-        b = apr_bucket_eos_create(c->bucket_alloc);
-        APR_BRIGADE_INSERT_TAIL(bb, b);
-
-        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
-                      "status_handler(%s): checking for incoming trailers", 
-                      task->id);
-        if (r->trailers_in && !apr_is_empty_table(r->trailers_in)) {
-            ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
-                          "status_handler(%s): seeing incoming trailers", 
-                          task->id);
-            apr_table_setn(r->trailers_out, "h2-trailers-in", 
-                           apr_itoa(r->pool, 1));
-        }
-        
-        status = ap_pass_brigade(r->output_filters, bb);
-        if (status == APR_SUCCESS
-            || r->status != HTTP_OK
-            || c->aborted) {
-            return OK;
-        }
-        else {
-            /* no way to know what type of error occurred */
-            ap_log_rerror(APLOG_MARK, APLOG_TRACE1, status, r,
-                          "status_handler(%s): ap_pass_brigade failed", 
-                          task->id);
-            return AP_FILTER_ERROR;
-        }
-    }
-    return DECLINED;
-}
-
diff -r -N -u a/modules/http2/h2_filter.h b/modules/http2/h2_filter.h
--- a/modules/http2/h2_filter.h	2018-02-10 16:46:12.000000000 +0100
+++ b/modules/http2/h2_filter.h	1970-01-01 01:00:00.000000000 +0100
@@ -1,73 +0,0 @@
-/* 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_filter__
-#define __mod_h2__h2_filter__
-
-struct h2_bucket_beam;
-struct h2_headers;
-struct h2_stream;
-struct h2_session;
-
-typedef struct h2_filter_cin {
-    apr_pool_t *pool;
-    apr_socket_t *socket;
-    apr_interval_time_t timeout;
-    apr_bucket_brigade *bb;
-    struct h2_session *session;
-    apr_bucket *cur;
-} h2_filter_cin;
-
-h2_filter_cin *h2_filter_cin_create(struct h2_session *session);
-
-void h2_filter_cin_timeout_set(h2_filter_cin *cin, apr_interval_time_t timeout);
-
-apr_status_t h2_filter_core_input(ap_filter_t* filter,
-                                  apr_bucket_brigade* brigade,
-                                  ap_input_mode_t mode,
-                                  apr_read_type_e block,
-                                  apr_off_t readbytes);
-
-/******* observer bucket ******************************************************/
-
-typedef enum {
-    H2_BUCKET_EV_BEFORE_DESTROY,
-    H2_BUCKET_EV_BEFORE_MASTER_SEND
-} h2_bucket_event;
-
-extern const apr_bucket_type_t h2_bucket_type_observer;
-
-typedef apr_status_t h2_bucket_event_cb(void *ctx, h2_bucket_event event, apr_bucket *b);
-
-#define H2_BUCKET_IS_OBSERVER(e)     (e->type == &h2_bucket_type_observer)
-
-apr_bucket * h2_bucket_observer_make(apr_bucket *b, h2_bucket_event_cb *cb, 
-                                     void *ctx); 
-
-apr_bucket * h2_bucket_observer_create(apr_bucket_alloc_t *list, 
-                                       h2_bucket_event_cb *cb, void *ctx); 
-                                       
-apr_status_t h2_bucket_observer_fire(apr_bucket *b, h2_bucket_event event);
-
-apr_bucket *h2_bucket_observer_beam(struct h2_bucket_beam *beam,
-                                    apr_bucket_brigade *dest,
-                                    const apr_bucket *src);
-
-/******* /.well-known/h2/state handler ****************************************/
-
-int h2_filter_h2_status_handler(request_rec *r);
-
-#endif /* __mod_h2__h2_filter__ */
diff -r -N -u a/modules/http2/h2_from_h1.c b/modules/http2/h2_from_h1.c
--- a/modules/http2/h2_from_h1.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_from_h1.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,861 +0,0 @@
-/* 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 <stdio.h>
-
-#include <apr_date.h>
-#include <apr_lib.h>
-#include <apr_strings.h>
-
-#include <httpd.h>
-#include <http_core.h>
-#include <http_log.h>
-#include <http_connection.h>
-#include <http_protocol.h>
-#include <http_request.h>
-#include <util_time.h>
-
-#include "h2_private.h"
-#include "h2_headers.h"
-#include "h2_from_h1.h"
-#include "h2_task.h"
-#include "h2_util.h"
-
-
-/* This routine is called by apr_table_do and merges all instances of
- * the passed field values into a single array that will be further
- * processed by some later routine.  Originally intended to help split
- * and recombine multiple Vary fields, though it is generic to any field
- * consisting of comma/space-separated tokens.
- */
-static int uniq_field_values(void *d, const char *key, const char *val)
-{
-    apr_array_header_t *values;
-    char *start;
-    char *e;
-    char **strpp;
-    int  i;
-    
-    (void)key;
-    values = (apr_array_header_t *)d;
-    
-    e = apr_pstrdup(values->pool, val);
-    
-    do {
-        /* Find a non-empty fieldname */
-        
-        while (*e == ',' || apr_isspace(*e)) {
-            ++e;
-        }
-        if (*e == '\0') {
-            break;
-        }
-        start = e;
-        while (*e != '\0' && *e != ',' && !apr_isspace(*e)) {
-            ++e;
-        }
-        if (*e != '\0') {
-            *e++ = '\0';
-        }
-        
-        /* Now add it to values if it isn't already represented.
-         * Could be replaced by a ap_array_strcasecmp() if we had one.
-         */
-        for (i = 0, strpp = (char **) values->elts; i < values->nelts;
-             ++i, ++strpp) {
-            if (*strpp && apr_strnatcasecmp(*strpp, start) == 0) {
-                break;
-            }
-        }
-        if (i == values->nelts) {  /* if not found */
-            *(char **)apr_array_push(values) = start;
-        }
-    } while (*e != '\0');
-    
-    return 1;
-}
-
-/*
- * Since some clients choke violently on multiple Vary fields, or
- * Vary fields with duplicate tokens, combine any multiples and remove
- * any duplicates.
- */
-static void fix_vary(request_rec *r)
-{
-    apr_array_header_t *varies;
-    
-    varies = apr_array_make(r->pool, 5, sizeof(char *));
-    
-    /* Extract all Vary fields from the headers_out, separate each into
-     * its comma-separated fieldname values, and then add them to varies
-     * if not already present in the array.
-     */
-    apr_table_do(uniq_field_values, varies, r->headers_out, "Vary", NULL);
-    
-    /* If we found any, replace old Vary fields with unique-ified value */
-    
-    if (varies->nelts > 0) {
-        apr_table_setn(r->headers_out, "Vary",
-                       apr_array_pstrcat(r->pool, varies, ','));
-    }
-}
-
-static h2_headers *create_response(h2_task *task, request_rec *r)
-{
-    const char *clheader;
-    const char *ctype;
-
-    /*
-     * Now that we are ready to send a response, we need to combine the two
-     * header field tables into a single table.  If we don't do this, our
-     * later attempts to set or unset a given fieldname might be bypassed.
-     */
-    if (!apr_is_empty_table(r->err_headers_out)) {
-        r->headers_out = apr_table_overlay(r->pool, r->err_headers_out,
-                                           r->headers_out);
-        apr_table_clear(r->err_headers_out);
-    }
-    
-    /*
-     * Remove the 'Vary' header field if the client can't handle it.
-     * Since this will have nasty effects on HTTP/1.1 caches, force
-     * the response into HTTP/1.0 mode.
-     */
-    if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) {
-        apr_table_unset(r->headers_out, "Vary");
-        r->proto_num = HTTP_VERSION(1,0);
-        apr_table_setn(r->subprocess_env, "force-response-1.0", "1");
-    }
-    else {
-        fix_vary(r);
-    }
-    
-    /*
-     * Now remove any ETag response header field if earlier processing
-     * says so (such as a 'FileETag None' directive).
-     */
-    if (apr_table_get(r->notes, "no-etag") != NULL) {
-        apr_table_unset(r->headers_out, "ETag");
-    }
-    
-    /* determine the protocol and whether we should use keepalives. */
-    ap_set_keepalive(r);
-    
-    if (AP_STATUS_IS_HEADER_ONLY(r->status)) {
-        apr_table_unset(r->headers_out, "Transfer-Encoding");
-        apr_table_unset(r->headers_out, "Content-Length");
-        r->content_type = r->content_encoding = NULL;
-        r->content_languages = NULL;
-        r->clength = r->chunked = 0;
-    }
-    else if (r->chunked) {
-        apr_table_mergen(r->headers_out, "Transfer-Encoding", "chunked");
-        apr_table_unset(r->headers_out, "Content-Length");
-    }
-
-    ctype = ap_make_content_type(r, r->content_type);
-    if (ctype) {
-        apr_table_setn(r->headers_out, "Content-Type", ctype);
-    }
-    
-    if (r->content_encoding) {
-        apr_table_setn(r->headers_out, "Content-Encoding",
-                       r->content_encoding);
-    }
-    
-    if (!apr_is_empty_array(r->content_languages)) {
-        unsigned int i;
-        char *token;
-        char **languages = (char **)(r->content_languages->elts);
-        const char *field = apr_table_get(r->headers_out, "Content-Language");
-        
-        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]))
-                    break;
-            }
-            if (i == r->content_languages->nelts) {
-                *((char **) apr_array_push(r->content_languages)) = token;
-            }
-        }
-        
-        field = apr_array_pstrcat(r->pool, r->content_languages, ',');
-        apr_table_setn(r->headers_out, "Content-Language", field);
-    }
-    
-    /*
-     * Control cachability for non-cachable responses if not already set by
-     * some other part of the server configuration.
-     */
-    if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) {
-        char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
-        ap_recent_rfc822_date(date, r->request_time);
-        apr_table_add(r->headers_out, "Expires", date);
-    }
-    
-    /* This is a hack, but I can't find anyway around it.  The idea is that
-     * we don't want to send out 0 Content-Lengths if it is a head request.
-     * This happens when modules try to outsmart the server, and return
-     * if they see a HEAD request.  Apache 1.3 handlers were supposed to
-     * just return in that situation, and the core handled the HEAD.  In
-     * 2.0, if a handler returns, then the core sends an EOS bucket down
-     * the filter stack, and the content-length filter computes a C-L of
-     * zero and that gets put in the headers, and we end up sending a
-     * zero C-L to the client.  We can't just remove the C-L filter,
-     * because well behaved 2.0 handlers will send their data down the stack,
-     * and we will compute a real C-L for the head request. RBB
-     */
-    if (r->header_only
-        && (clheader = apr_table_get(r->headers_out, "Content-Length"))
-        && !strcmp(clheader, "0")) {
-        apr_table_unset(r->headers_out, "Content-Length");
-    }
-    
-    /*
-     * keep the set-by-proxy server and date headers, otherwise
-     * generate a new server header / date header
-     */
-    if (r->proxyreq != PROXYREQ_RESPONSE
-            || !apr_table_get(r->headers_out, "Date")) {
-        char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
-        ap_recent_rfc822_date(date, r->request_time);
-        apr_table_setn(r->headers_out, "Date", date );
-    }
-    if (r->proxyreq != PROXYREQ_RESPONSE) {
-        const char *us = ap_get_server_banner();
-        if (us) {
-            apr_table_setn(r->headers_out, "Server", us);
-        }
-    }
-    
-    return h2_headers_rcreate(r, r->status, r->headers_out, r->pool);
-}
-
-typedef enum {
-    H2_RP_STATUS_LINE,
-    H2_RP_HEADER_LINE,
-    H2_RP_DONE
-} h2_rp_state_t;
-
-typedef struct h2_response_parser {
-    h2_rp_state_t state;
-    h2_task *task;
-    int http_status;
-    apr_array_header_t *hlines;
-    apr_bucket_brigade *tmp;
-    apr_bucket_brigade *saveto;
-} h2_response_parser;
-
-static apr_status_t parse_header(h2_response_parser *parser, char *line) {
-    const char *hline;
-    if (line[0] == ' ' || line[0] == '\t') {
-        char **plast;
-        /* continuation line from the header before this */
-        while (line[0] == ' ' || line[0] == '\t') {
-            ++line;
-        }
-        
-        plast = apr_array_pop(parser->hlines);
-        if (plast == NULL) {
-            /* not well formed */
-            return APR_EINVAL;
-        }
-        hline = apr_psprintf(parser->task->pool, "%s %s", *plast, line);
-    }
-    else {
-        /* new header line */
-        hline = apr_pstrdup(parser->task->pool, line);
-    }
-    APR_ARRAY_PUSH(parser->hlines, const char*) = hline; 
-    return APR_SUCCESS;
-}
-
-static apr_status_t get_line(h2_response_parser *parser, apr_bucket_brigade *bb, 
-                             char *line, apr_size_t len)
-{
-    h2_task *task = parser->task;
-    apr_status_t status;
-    
-    if (!parser->tmp) {
-        parser->tmp = apr_brigade_create(task->pool, task->c->bucket_alloc);
-    }
-    status = apr_brigade_split_line(parser->tmp, bb, APR_BLOCK_READ, 
-                                    len);
-    if (status == APR_SUCCESS) {
-        --len;
-        status = apr_brigade_flatten(parser->tmp, line, &len);
-        if (status == APR_SUCCESS) {
-            /* we assume a non-0 containing line and remove trailing crlf. */
-            line[len] = '\0';
-            /*
-             * XXX: What to do if there is an LF but no CRLF?
-             *      Should we error out?
-             */
-            if (len >= 2 && !strcmp(H2_CRLF, line + len - 2)) {
-                len -= 2;
-                line[len] = '\0';
-                apr_brigade_cleanup(parser->tmp);
-                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
-                              "h2_task(%s): read response line: %s", 
-                              task->id, line);
-            }
-            else {
-                apr_off_t brigade_length;
-
-                /*
-                 * If the brigade parser->tmp becomes longer than our buffer
-                 * for flattening we never have a chance to get a complete
-                 * line. This can happen if we are called multiple times after
-                 * previous calls did not find a H2_CRLF and we returned
-                 * APR_EAGAIN. In this case parser->tmp (correctly) grows
-                 * with each call to apr_brigade_split_line.
-                 *
-                 * XXX: Currently a stack based buffer of HUGE_STRING_LEN is
-                 * used. This means we cannot cope with lines larger than
-                 * HUGE_STRING_LEN which might be an issue.
-                 */
-                status = apr_brigade_length(parser->tmp, 0, &brigade_length);
-                if ((status != APR_SUCCESS) || (brigade_length > len)) {
-                    ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, task->c, APLOGNO(10257)
-                                  "h2_task(%s): read response, line too long",
-                                  task->id);
-                    return APR_ENOSPC;
-                }
-                /* this does not look like a complete line yet */
-                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
-                              "h2_task(%s): read response, incomplete line: %s", 
-                              task->id, line);
-                if (!parser->saveto) {
-                    parser->saveto = apr_brigade_create(task->pool,
-                                                        task->c->bucket_alloc);
-                }
-                /*
-                 * Be on the save side and save the parser->tmp brigade
-                 * as it could contain transient buckets which could be
-                 * invalid next time we are here.
-                 *
-                 * NULL for the filter parameter is ok since we
-                 * provide our own brigade as second parameter
-                 * and ap_save_brigade does not need to create one.
-                 */
-                ap_save_brigade(NULL, &(parser->saveto), &(parser->tmp),
-                                parser->tmp->p);
-                APR_BRIGADE_CONCAT(parser->tmp, parser->saveto);
-                return APR_EAGAIN;
-            }
-        }
-    }
-    apr_brigade_cleanup(parser->tmp);
-    return status;
-}
-
-static apr_table_t *make_table(h2_response_parser *parser)
-{
-    h2_task *task = parser->task;
-    apr_array_header_t *hlines = parser->hlines;
-    if (hlines) {
-        apr_table_t *headers = apr_table_make(task->pool, hlines->nelts);        
-        int i;
-        
-        for (i = 0; i < hlines->nelts; ++i) {
-            char *hline = ((char **)hlines->elts)[i];
-            char *sep = ap_strchr(hline, ':');
-            if (!sep) {
-                ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, task->c,
-                              APLOGNO(02955) "h2_task(%s): invalid header[%d] '%s'",
-                              task->id, i, (char*)hline);
-                /* not valid format, abort */
-                return NULL;
-            }
-            (*sep++) = '\0';
-            while (*sep == ' ' || *sep == '\t') {
-                ++sep;
-            }
-            
-            if (!h2_util_ignore_header(hline)) {
-                apr_table_merge(headers, hline, sep);
-            }
-        }
-        return headers;
-    }
-    else {
-        return apr_table_make(task->pool, 0);        
-    }
-}
-
-static apr_status_t pass_response(h2_task *task, ap_filter_t *f, 
-                                  h2_response_parser *parser) 
-{
-    apr_bucket *b;
-    apr_status_t status;
-    
-    h2_headers *response = h2_headers_create(parser->http_status, 
-                                             make_table(parser),
-                                             NULL, 0, task->pool);
-    apr_brigade_cleanup(parser->tmp);
-    b = h2_bucket_headers_create(task->c->bucket_alloc, response);
-    APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
-    b = apr_bucket_flush_create(task->c->bucket_alloc);
-    APR_BRIGADE_INSERT_TAIL(parser->tmp, b);                      
-    status = ap_pass_brigade(f->next, parser->tmp);
-    apr_brigade_cleanup(parser->tmp);
-    
-    /* reset parser for possible next response */
-    parser->state = H2_RP_STATUS_LINE;
-    apr_array_clear(parser->hlines);
-
-    if (response->status >= 200) {
-        task->output.sent_response = 1;
-    }
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, 
-                  APLOGNO(03197) "h2_task(%s): passed response %d", 
-                  task->id, response->status);
-    return status;
-}
-
-static apr_status_t parse_status(h2_task *task, char *line)
-{
-    h2_response_parser *parser = task->output.rparser;
-    int sindex = (apr_date_checkmask(line, "HTTP/#.# ###*")? 9 : 
-                  (apr_date_checkmask(line, "HTTP/# ###*")? 7 : 0));
-    if (sindex > 0) {
-        int k = sindex + 3;
-        char keepchar = line[k];
-        line[k] = '\0';
-        parser->http_status = atoi(&line[sindex]);
-        line[k] = keepchar;
-        parser->state = H2_RP_HEADER_LINE;
-        
-        return APR_SUCCESS;
-    }
-    /* Seems like there is garbage on the connection. May be a leftover
-     * from a previous proxy request. 
-     * This should only happen if the H2_RESPONSE filter is not yet in 
-     * place (post_read_request has not been reached and the handler wants
-     * to write something. Probably just the interim response we are
-     * waiting for. But if there is other data hanging around before
-     * that, this needs to fail. */
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, APLOGNO(03467)
-                  "h2_task(%s): unable to parse status line: %s", 
-                  task->id, line);
-    return APR_EINVAL;
-}
-
-apr_status_t h2_from_h1_parse_response(h2_task *task, ap_filter_t *f, 
-                                       apr_bucket_brigade *bb)
-{
-    h2_response_parser *parser = task->output.rparser;
-    char line[HUGE_STRING_LEN];
-    apr_status_t status = APR_SUCCESS;
-
-    if (!parser) {
-        parser = apr_pcalloc(task->pool, sizeof(*parser));
-        parser->task = task;
-        parser->state = H2_RP_STATUS_LINE;
-        parser->hlines = apr_array_make(task->pool, 10, sizeof(char *));
-        task->output.rparser = parser;
-    }
-    
-    while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
-        switch (parser->state) {
-            case H2_RP_STATUS_LINE:
-            case H2_RP_HEADER_LINE:
-                status = get_line(parser, bb, line, sizeof(line));
-                if (status == APR_EAGAIN) {
-                    /* need more data */
-                    return APR_SUCCESS;
-                }
-                else if (status != APR_SUCCESS) {
-                    return status;
-                }
-                if (parser->state == H2_RP_STATUS_LINE) {
-                    /* instead of parsing, just take it directly */
-                    status = parse_status(task, line);
-                }
-                else if (line[0] == '\0') {
-                    /* end of headers, pass response onward */
-                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                                  "h2_task(%s): end of response", task->id);
-                    return pass_response(task, f, parser);
-                }
-                else {
-                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                                  "h2_task(%s): response header %s", task->id, line);
-                    status = parse_header(parser, line);
-                }
-                break;
-                
-            default:
-                return status;
-        }
-    }
-    return status;
-}
-
-apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb)
-{
-    h2_task *task = f->ctx;
-    request_rec *r = f->r;
-    apr_bucket *b, *bresp, *body_bucket = NULL, *next;
-    ap_bucket_error *eb = NULL;
-    h2_headers *response = NULL;
-    int headers_passing = 0;
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                  "h2_task(%s): output_filter called", task->id);
-    
-    if (!task->output.sent_response && !f->c->aborted) {
-        /* check, if we need to send the response now. Until we actually
-         * see a DATA bucket or some EOS/EOR, we do not do so. */
-        for (b = APR_BRIGADE_FIRST(bb);
-             b != APR_BRIGADE_SENTINEL(bb);
-             b = APR_BUCKET_NEXT(b))
-        {
-            if (AP_BUCKET_IS_ERROR(b) && !eb) {
-                eb = b->data;
-            }
-            else if (AP_BUCKET_IS_EOC(b)) {
-                /* If we see an EOC bucket it is a signal that we should get out
-                 * of the way doing nothing.
-                 */
-                ap_remove_output_filter(f);
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
-                              "h2_task(%s): eoc bucket passed", task->id);
-                return ap_pass_brigade(f->next, bb);
-            }
-            else if (H2_BUCKET_IS_HEADERS(b)) {
-                headers_passing = 1;
-            }
-            else if (!APR_BUCKET_IS_FLUSH(b)) { 
-                body_bucket = b;
-                break;
-            }
-        }
-        
-        if (eb) {
-            int st = eb->status;
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03047)
-                          "h2_task(%s): err bucket status=%d", task->id, st);
-            /* throw everything away and replace it with the error response
-             * generated by ap_die() */
-            apr_brigade_cleanup(bb);
-            ap_die(st, r);
-            return AP_FILTER_ERROR;
-        }
-        
-        if (body_bucket || !headers_passing) {
-            /* time to insert the response bucket before the body or if
-             * no h2_headers is passed, e.g. the response is empty */
-            response = create_response(task, r);
-            if (response == NULL) {
-                ap_log_cerror(APLOG_MARK, APLOG_NOTICE, 0, f->c, APLOGNO(03048)
-                              "h2_task(%s): unable to create response", task->id);
-                return APR_ENOMEM;
-            }
-            
-            bresp = h2_bucket_headers_create(f->c->bucket_alloc, response);
-            if (body_bucket) {
-                APR_BUCKET_INSERT_BEFORE(body_bucket, bresp);
-            }
-            else {
-                APR_BRIGADE_INSERT_HEAD(bb, bresp);
-            }
-            task->output.sent_response = 1;
-            r->sent_bodyct = 1;
-        }
-    }
-    
-    if (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status)) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                      "h2_task(%s): headers only, cleanup output brigade", 
-                      task->id);
-        b = body_bucket? body_bucket : APR_BRIGADE_FIRST(bb);
-        while (b != APR_BRIGADE_SENTINEL(bb)) {
-            next = APR_BUCKET_NEXT(b);
-            if (APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b)) {
-                break;
-            }
-            if (!H2_BUCKET_IS_HEADERS(b)) {
-                APR_BUCKET_REMOVE(b);
-                apr_bucket_destroy(b);
-            }
-            b = next;
-        }
-    }
-    else if (task->output.sent_response) {
-        /* lets get out of the way, our task is done */
-        ap_remove_output_filter(f);
-    }
-    return ap_pass_brigade(f->next, bb);
-}
-
-static void make_chunk(h2_task *task, apr_bucket_brigade *bb, 
-                       apr_bucket *first, apr_off_t chunk_len, 
-                       apr_bucket *tail)
-{
-    /* Surround the buckets [first, tail[ with new buckets carrying the
-     * HTTP/1.1 chunked encoding format. If tail is NULL, the chunk extends
-     * to the end of the brigade. */
-    char buffer[128];
-    apr_bucket *c;
-    apr_size_t len;
-    
-    len = (apr_size_t)apr_snprintf(buffer, H2_ALEN(buffer), 
-                                   "%"APR_UINT64_T_HEX_FMT"\r\n", (apr_uint64_t)chunk_len);
-    c = apr_bucket_heap_create(buffer, len, NULL, bb->bucket_alloc);
-    APR_BUCKET_INSERT_BEFORE(first, c);
-    c = apr_bucket_heap_create("\r\n", 2, NULL, bb->bucket_alloc);
-    if (tail) {
-        APR_BUCKET_INSERT_BEFORE(tail, c);
-    }
-    else {
-        APR_BRIGADE_INSERT_TAIL(bb, c);
-    }
-    task->input.chunked_total += chunk_len;
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c,
-                  "h2_task(%s): added chunk %ld, total %ld", 
-                  task->id, (long)chunk_len, (long)task->input.chunked_total);
-}
-
-static int ser_header(void *ctx, const char *name, const char *value) 
-{
-    apr_bucket_brigade *bb = ctx;
-    apr_brigade_printf(bb, NULL, NULL, "%s: %s\r\n", name, value);
-    return 1;
-}
-
-static apr_status_t read_and_chunk(ap_filter_t *f, h2_task *task,
-                                   apr_read_type_e block) {
-    request_rec *r = f->r;
-    apr_status_t status = APR_SUCCESS;
-    apr_bucket_brigade *bb = task->input.bbchunk;
-    
-    if (!bb) {
-        bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
-        task->input.bbchunk = bb;
-    }
-    
-    if (APR_BRIGADE_EMPTY(bb)) {
-        apr_bucket *b, *next, *first_data = NULL;
-        apr_bucket_brigade *tmp;
-        apr_off_t bblen = 0;
-
-        /* get more data from the lower layer filters. Always do this
-         * in larger pieces, since we handle the read modes ourself. */
-        status = ap_get_brigade(f->next, bb, 
-                                AP_MODE_READBYTES, block, 32*1024);
-        if (status == APR_EOF) {
-            if (!task->input.eos) {
-                status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
-                task->input.eos = 1;
-                return APR_SUCCESS;
-            }
-            ap_remove_input_filter(f);
-            return status;
-            
-        }
-        else if (status != APR_SUCCESS) {
-            return status;
-        }
-
-        for (b = APR_BRIGADE_FIRST(bb); 
-             b != APR_BRIGADE_SENTINEL(bb) && !task->input.eos; 
-             b = next) {
-            next = APR_BUCKET_NEXT(b);
-            if (APR_BUCKET_IS_METADATA(b)) {
-                if (first_data) {
-                    make_chunk(task, bb, first_data, bblen, b);
-                    first_data = NULL;
-                }
-                
-                if (H2_BUCKET_IS_HEADERS(b)) {
-                    h2_headers *headers = h2_bucket_headers_get(b);
-                    
-                    ap_assert(headers);
-                    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
-                                  "h2_task(%s): receiving trailers", task->id);
-                    tmp = apr_brigade_split_ex(bb, b, NULL);
-                    if (!apr_is_empty_table(headers->headers)) {
-                        status = apr_brigade_puts(bb, NULL, NULL, "0\r\n");
-                        apr_table_do(ser_header, bb, headers->headers, NULL);
-                        status = apr_brigade_puts(bb, NULL, NULL, "\r\n");
-                    }
-                    else {
-                        status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
-                    }
-                    r->trailers_in = apr_table_clone(r->pool, headers->headers);
-                    APR_BUCKET_REMOVE(b);
-                    apr_bucket_destroy(b);
-                    APR_BRIGADE_CONCAT(bb, tmp);
-                    apr_brigade_destroy(tmp);
-                    task->input.eos = 1;
-                }
-                else if (APR_BUCKET_IS_EOS(b)) {
-                    tmp = apr_brigade_split_ex(bb, b, NULL);
-                    status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
-                    APR_BRIGADE_CONCAT(bb, tmp);
-                    apr_brigade_destroy(tmp);
-                    task->input.eos = 1;
-                }
-            }
-            else if (b->length == 0) {
-                APR_BUCKET_REMOVE(b);
-                apr_bucket_destroy(b);
-            } 
-            else {
-                if (!first_data) {
-                    first_data = b;
-                    bblen = 0;
-                }
-                bblen += b->length;
-            }    
-        }
-        
-        if (first_data) {
-            make_chunk(task, bb, first_data, bblen, NULL);
-        }            
-    }
-    return status;
-}
-
-apr_status_t h2_filter_request_in(ap_filter_t* f,
-                                  apr_bucket_brigade* bb,
-                                  ap_input_mode_t mode,
-                                  apr_read_type_e block,
-                                  apr_off_t readbytes)
-{
-    h2_task *task = f->ctx;
-    request_rec *r = f->r;
-    apr_status_t status = APR_SUCCESS;
-    apr_bucket *b, *next;
-    core_server_config *conf =
-        (core_server_config *) ap_get_module_config(r->server->module_config,
-                                                    &core_module);
-
-    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, f->r,
-                  "h2_task(%s): request filter, exp=%d", task->id, r->expecting_100);
-    if (!task->request->chunked) {
-        status = ap_get_brigade(f->next, bb, mode, block, readbytes);
-        /* pipe data through, just take care of trailers */
-        for (b = APR_BRIGADE_FIRST(bb); 
-             b != APR_BRIGADE_SENTINEL(bb); b = next) {
-            next = APR_BUCKET_NEXT(b);
-            if (H2_BUCKET_IS_HEADERS(b)) {
-                h2_headers *headers = h2_bucket_headers_get(b);
-                ap_assert(headers);
-                ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
-                              "h2_task(%s): receiving trailers", task->id);
-                r->trailers_in = headers->headers;
-                if (conf && conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE) {
-                    r->headers_in = apr_table_overlay(r->pool, r->headers_in,
-                                                      r->trailers_in);                    
-                }
-                APR_BUCKET_REMOVE(b);
-                apr_bucket_destroy(b);
-                ap_remove_input_filter(f);
-                
-                if (headers->raw_bytes && h2_task_logio_add_bytes_in) {
-                    h2_task_logio_add_bytes_in(task->c, headers->raw_bytes);
-                }
-                break;
-            }
-        }
-        return status;
-    }
-
-    /* Things are more complicated. The standard HTTP input filter, which
-     * does a lot what we do not want to duplicate, also cares about chunked
-     * transfer encoding and trailers.
-     * We need to simulate chunked encoding for it to be happy.
-     */
-    if ((status = read_and_chunk(f, task, block)) != APR_SUCCESS) {
-        return status;
-    }
-    
-    if (mode == AP_MODE_EXHAUSTIVE) {
-        /* return all we have */
-        APR_BRIGADE_CONCAT(bb, task->input.bbchunk);
-    }
-    else if (mode == AP_MODE_READBYTES) {
-        status = h2_brigade_concat_length(bb, task->input.bbchunk, readbytes);
-    }
-    else if (mode == AP_MODE_SPECULATIVE) {
-        status = h2_brigade_copy_length(bb, task->input.bbchunk, readbytes);
-    }
-    else if (mode == AP_MODE_GETLINE) {
-        /* we are reading a single LF line, e.g. the HTTP headers. 
-         * this has the nasty side effect to split the bucket, even
-         * though it ends with CRLF and creates a 0 length bucket */
-        status = apr_brigade_split_line(bb, task->input.bbchunk, block, 
-                                        HUGE_STRING_LEN);
-        if (APLOGctrace1(f->c)) {
-            char buffer[1024];
-            apr_size_t len = sizeof(buffer)-1;
-            apr_brigade_flatten(bb, buffer, &len);
-            buffer[len] = 0;
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
-                          "h2_task(%s): getline: %s",
-                          task->id, buffer);
-        }
-    }
-    else {
-        /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not
-         * to support it. Seems to work. */
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
-                      APLOGNO(02942) 
-                      "h2_task, unsupported READ mode %d", mode);
-        status = APR_ENOTIMPL;
-    }
-    
-    h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2, "forwarding input", bb);
-    return status;
-}
-
-apr_status_t h2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb)
-{
-    h2_task *task = f->ctx;
-    request_rec *r = f->r;
-    apr_bucket *b, *e;
- 
-    if (task && r) {
-        /* Detect the EOS/EOR bucket and forward any trailers that may have
-         * been set to our h2_headers.
-         */
-        for (b = APR_BRIGADE_FIRST(bb);
-             b != APR_BRIGADE_SENTINEL(bb);
-             b = APR_BUCKET_NEXT(b))
-        {
-            if ((APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b))
-                && r->trailers_out && !apr_is_empty_table(r->trailers_out)) {
-                h2_headers *headers;
-                apr_table_t *trailers;
-                
-                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03049)
-                              "h2_task(%s): sending trailers", task->id);
-                trailers = apr_table_clone(r->pool, r->trailers_out);
-                headers = h2_headers_rcreate(r, HTTP_OK, trailers, r->pool);
-                e = h2_bucket_headers_create(bb->bucket_alloc, headers);
-                APR_BUCKET_INSERT_BEFORE(b, e);
-                apr_table_clear(r->trailers_out);
-                ap_remove_output_filter(f);
-                break;
-            }
-        }     
-    }
-     
-    return ap_pass_brigade(f->next, bb);
-}
-
diff -r -N -u a/modules/http2/h2_from_h1.h b/modules/http2/h2_from_h1.h
--- a/modules/http2/h2_from_h1.h	2018-02-10 16:46:12.000000000 +0100
+++ b/modules/http2/h2_from_h1.h	1970-01-01 01:00:00.000000000 +0100
@@ -1,50 +0,0 @@
-/* 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_from_h1__
-#define __mod_h2__h2_from_h1__
-
-/**
- * h2_from_h1 parses a HTTP/1.1 response into
- * - response status
- * - a list of header values
- * - a series of bytes that represent the response body alone, without
- *   any meta data, such as inserted by chunked transfer encoding.
- *
- * All data is allocated from the stream memory pool. 
- *
- * Again, see comments in h2_request: ideally we would take the headers
- * and status from the httpd structures instead of parsing them here, but
- * we need to have all handlers and filters involved in request/response
- * processing, so this seems to be the way for now.
- */
-struct h2_headers;
-struct h2_task;
-
-apr_status_t h2_from_h1_parse_response(struct h2_task *task, ap_filter_t *f, 
-                                       apr_bucket_brigade *bb);
-
-apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb);
-
-apr_status_t h2_filter_request_in(ap_filter_t* f,
-                                  apr_bucket_brigade* brigade,
-                                  ap_input_mode_t mode,
-                                  apr_read_type_e block,
-                                  apr_off_t readbytes);
-
-apr_status_t h2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb);
-
-#endif /* defined(__mod_h2__h2_from_h1__) */
diff -r -N -u a/modules/http2/h2.h b/modules/http2/h2.h
--- a/modules/http2/h2.h	2020-11-16 13:24:12.000000000 +0100
+++ b/modules/http2/h2.h	2024-10-30 21:40:01.059958617 +0100
@@ -17,6 +17,38 @@
 #ifndef __mod_h2__h2__
 #define __mod_h2__h2__
 
+#include <apr_version.h>
+#include <ap_mmn.h>
+
+#include <nghttp2/nghttp2ver.h>
+
+struct h2_session;
+struct h2_stream;
+
+/*
+ * When apr pollsets can poll file descriptors (e.g. pipes),
+ * we use it for polling stream input/output.
+ */
+#ifdef H2_NO_PIPES
+#define H2_USE_PIPES            0
+#else
+#define H2_USE_PIPES            (APR_FILES_AS_SOCKETS && APR_VERSION_AT_LEAST(1,6,0))
+#endif
+
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 129)
+#define H2_USE_POLLFD_FROM_CONN 1
+#else
+#define H2_USE_POLLFD_FROM_CONN 0
+#endif
+
+/* WebSockets support requires apr 1.7.0 for apr_encode.h, plus the
+ * WebSockets features of nghttp2 1.34.0 and later. */
+#if H2_USE_PIPES && defined(NGHTTP2_VERSION_NUM) && NGHTTP2_VERSION_NUM >= 0x012200 && APR_VERSION_AT_LEAST(1,7,0)
+#define H2_USE_WEBSOCKETS       1
+#else
+#define H2_USE_WEBSOCKETS       0
+#endif
+
 /**
  * The magic PRIamble of RFC 7540 that is always sent when starting
  * a h2 communication.
@@ -46,6 +78,8 @@
 #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 */
@@ -89,7 +123,7 @@
     H2_SESSION_ST_DONE,             /* finished, connection close */
     H2_SESSION_ST_IDLE,             /* nothing to write, expecting data inc */
     H2_SESSION_ST_BUSY,             /* read/write without stop */
-    H2_SESSION_ST_WAIT,             /* waiting for tasks reporting back */
+    H2_SESSION_ST_WAIT,             /* waiting for c1 incoming + c2s output */
     H2_SESSION_ST_CLEANUP,          /* pool is being cleaned up */
 } h2_session_state;
 
@@ -99,6 +133,7 @@
     int emitted_count;     /* the number of local streams sent */
     int emitted_max;       /* the highest local stream id sent */
     int error;             /* the last session error encountered */
+    const char *error_msg; /* the short message given on the error */
     unsigned int accepting : 1;     /* if the session is accepting new streams */
     unsigned int shutdown : 1;      /* if the final GOAWAY has been sent */
 } h2_session_props;
@@ -120,7 +155,9 @@
     H2_SEV_CLOSED_R,
     H2_SEV_CANCELLED,
     H2_SEV_EOS_SENT,
+    H2_SEV_IN_ERROR,
     H2_SEV_IN_DATA_PENDING,
+    H2_SEV_OUT_C1_BLOCK,
 } h2_stream_event_t;
 
 
@@ -129,17 +166,15 @@
  * become a request_rec to be handled by soemone.
  */
 typedef struct h2_request h2_request;
-
 struct h2_request {
     const char *method; /* pseudo header values, see ch. 8.1.2.3 */
     const char *scheme;
     const char *authority;
     const char *path;
+    const char *protocol;
     apr_table_t *headers;
 
     apr_time_t request_time;
-    unsigned int chunked : 1;   /* iff request body needs to be forwarded as chunked */
-    unsigned int serialize : 1; /* iff this request is written in HTTP/1.1 serialization */
     apr_off_t raw_bytes;        /* RAW network bytes that generated this request - if known. */
     int http_status;            /* Store a possible HTTP status code that gets
                                  * defined before creating the dummy HTTP/1.1
@@ -154,25 +189,23 @@
  */
 #define H2_HTTP_STATUS_UNSET (0)
 
-typedef struct h2_headers h2_headers;
-
-struct h2_headers {
-    int         status;
-    apr_table_t *headers;
-    apr_table_t *notes;
-    apr_off_t   raw_bytes;      /* RAW network bytes that generated this request - if known. */
-};
-
 typedef apr_status_t h2_io_data_cb(void *ctx, const char *data, apr_off_t len);
 
-typedef int h2_stream_pri_cmp(int stream_id1, int stream_id2, void *ctx);
-
-/* Note key to attach connection task id to conn_rec/request_rec instances */
+typedef int h2_stream_pri_cmp_fn(int stream_id1, int stream_id2, void *session);
+typedef struct h2_stream *h2_stream_get_fn(struct h2_session *session, int stream_id);
 
-#define H2_TASK_ID_NOTE         "http2-task-id"
-#define H2_FILTER_DEBUG_NOTE    "http2-debug"
+/* Note key to attach stream id to conn_rec/request_rec instances */
 #define H2_HDR_CONFORMANCE      "http2-hdr-conformance"
 #define H2_HDR_CONFORMANCE_UNSAFE      "unsafe"
 #define H2_PUSH_MODE_NOTE       "http2-push-mode"
 
+
+#if AP_MODULE_MAGIC_AT_LEAST(20211221, 6)
+#define AP_HAS_RESPONSE_BUCKETS     1
+
+#else /* AP_MODULE_MAGIC_AT_LEAST(20211221, 6) */
+#define AP_HAS_RESPONSE_BUCKETS     0
+
+#endif /* else AP_MODULE_MAGIC_AT_LEAST(20211221, 6) */
+
 #endif /* defined(__mod_h2__h2__) */
diff -r -N -u a/modules/http2/h2_h2.c b/modules/http2/h2_h2.c
--- a/modules/http2/h2_h2.c	2021-05-12 12:14:42.000000000 +0200
+++ b/modules/http2/h2_h2.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,737 +0,0 @@
-/* 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_strings.h>
-#include <apr_optional.h>
-#include <apr_optional_hooks.h>
-
-#include <httpd.h>
-#include <http_core.h>
-#include <http_config.h>
-#include <http_connection.h>
-#include <http_protocol.h>
-#include <http_request.h>
-#include <http_ssl.h>
-#include <http_log.h>
-
-#include "mod_http2.h"
-#include "h2_private.h"
-
-#include "h2_bucket_beam.h"
-#include "h2_stream.h"
-#include "h2_task.h"
-#include "h2_config.h"
-#include "h2_ctx.h"
-#include "h2_conn.h"
-#include "h2_filter.h"
-#include "h2_request.h"
-#include "h2_headers.h"
-#include "h2_session.h"
-#include "h2_util.h"
-#include "h2_h2.h"
-#include "mod_http2.h"
-
-const char *h2_tls_protos[] = {
-    "h2", NULL
-};
-
-const char *h2_clear_protos[] = {
-    "h2c", NULL
-};
-
-const char *H2_MAGIC_TOKEN = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
-
-/*******************************************************************************
- * HTTP/2 error stuff
- */
-static const char *h2_err_descr[] = {
-    "no error",                    /* 0x0 */
-    "protocol error",
-    "internal error",
-    "flow control error",
-    "settings timeout",
-    "stream closed",               /* 0x5 */
-    "frame size error",
-    "refused stream",
-    "cancel",
-    "compression error",
-    "connect error",               /* 0xa */
-    "enhance your calm",
-    "inadequate security",
-    "http/1.1 required",
-};
-
-const char *h2_h2_err_description(unsigned int h2_error)
-{
-    if (h2_error < (sizeof(h2_err_descr)/sizeof(h2_err_descr[0]))) {
-        return h2_err_descr[h2_error];
-    }
-    return "unknown http/2 error code";
-}
-
-/*******************************************************************************
- * Check connection security requirements of RFC 7540
- */
-
-/*
- * Black Listed Ciphers from RFC 7549 Appendix A
- *
- */
-static const char *RFC7540_names[] = {
-    /* ciphers with NULL encrpytion */
-    "NULL-MD5",                         /* TLS_NULL_WITH_NULL_NULL */
-    /* same */                          /* TLS_RSA_WITH_NULL_MD5 */
-    "NULL-SHA",                         /* TLS_RSA_WITH_NULL_SHA */
-    "NULL-SHA256",                      /* TLS_RSA_WITH_NULL_SHA256 */
-    "PSK-NULL-SHA",                     /* TLS_PSK_WITH_NULL_SHA */
-    "DHE-PSK-NULL-SHA",                 /* TLS_DHE_PSK_WITH_NULL_SHA */
-    "RSA-PSK-NULL-SHA",                 /* TLS_RSA_PSK_WITH_NULL_SHA */
-    "PSK-NULL-SHA256",                  /* TLS_PSK_WITH_NULL_SHA256 */
-    "PSK-NULL-SHA384",                  /* TLS_PSK_WITH_NULL_SHA384 */
-    "DHE-PSK-NULL-SHA256",              /* TLS_DHE_PSK_WITH_NULL_SHA256 */
-    "DHE-PSK-NULL-SHA384",              /* TLS_DHE_PSK_WITH_NULL_SHA384 */
-    "RSA-PSK-NULL-SHA256",              /* TLS_RSA_PSK_WITH_NULL_SHA256 */
-    "RSA-PSK-NULL-SHA384",              /* TLS_RSA_PSK_WITH_NULL_SHA384 */
-    "ECDH-ECDSA-NULL-SHA",              /* TLS_ECDH_ECDSA_WITH_NULL_SHA */
-    "ECDHE-ECDSA-NULL-SHA",             /* TLS_ECDHE_ECDSA_WITH_NULL_SHA */
-    "ECDH-RSA-NULL-SHA",                /* TLS_ECDH_RSA_WITH_NULL_SHA */
-    "ECDHE-RSA-NULL-SHA",               /* TLS_ECDHE_RSA_WITH_NULL_SHA */
-    "AECDH-NULL-SHA",                   /* TLS_ECDH_anon_WITH_NULL_SHA */
-    "ECDHE-PSK-NULL-SHA",               /* TLS_ECDHE_PSK_WITH_NULL_SHA */
-    "ECDHE-PSK-NULL-SHA256",            /* TLS_ECDHE_PSK_WITH_NULL_SHA256 */
-    "ECDHE-PSK-NULL-SHA384",            /* TLS_ECDHE_PSK_WITH_NULL_SHA384 */
-    
-    /* DES/3DES ciphers */
-    "PSK-3DES-EDE-CBC-SHA",             /* TLS_PSK_WITH_3DES_EDE_CBC_SHA */
-    "DHE-PSK-3DES-EDE-CBC-SHA",         /* TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA */
-    "RSA-PSK-3DES-EDE-CBC-SHA",         /* TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA */
-    "ECDH-ECDSA-DES-CBC3-SHA",          /* TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA */
-    "ECDHE-ECDSA-DES-CBC3-SHA",         /* TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA */
-    "ECDH-RSA-DES-CBC3-SHA",            /* TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA */
-    "ECDHE-RSA-DES-CBC3-SHA",           /* TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA */
-    "AECDH-DES-CBC3-SHA",               /* TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA */
-    "SRP-3DES-EDE-CBC-SHA",             /* TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA */
-    "SRP-RSA-3DES-EDE-CBC-SHA",         /* TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA */
-    "SRP-DSS-3DES-EDE-CBC-SHA",         /* TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA */
-    "ECDHE-PSK-3DES-EDE-CBC-SHA",       /* TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA */
-    "DES-CBC-SHA",                      /* TLS_RSA_WITH_DES_CBC_SHA */
-    "DES-CBC3-SHA",                     /* TLS_RSA_WITH_3DES_EDE_CBC_SHA */
-    "DHE-DSS-DES-CBC3-SHA",             /* TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA */
-    "DHE-RSA-DES-CBC-SHA",              /* TLS_DHE_RSA_WITH_DES_CBC_SHA */
-    "DHE-RSA-DES-CBC3-SHA",             /* TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA */
-    "ADH-DES-CBC-SHA",                  /* TLS_DH_anon_WITH_DES_CBC_SHA */
-    "ADH-DES-CBC3-SHA",                 /* TLS_DH_anon_WITH_3DES_EDE_CBC_SHA */
-    "EXP-DH-DSS-DES-CBC-SHA",           /* TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA */
-    "DH-DSS-DES-CBC-SHA",               /* TLS_DH_DSS_WITH_DES_CBC_SHA */
-    "DH-DSS-DES-CBC3-SHA",              /* TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA */
-    "EXP-DH-RSA-DES-CBC-SHA",           /* TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA */
-    "DH-RSA-DES-CBC-SHA",               /* TLS_DH_RSA_WITH_DES_CBC_SHA */
-    "DH-RSA-DES-CBC3-SHA",              /* TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA */
-
-    /* blacklisted EXPORT ciphers */
-    "EXP-RC4-MD5",                      /* TLS_RSA_EXPORT_WITH_RC4_40_MD5 */
-    "EXP-RC2-CBC-MD5",                  /* TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 */
-    "EXP-DES-CBC-SHA",                  /* TLS_RSA_EXPORT_WITH_DES40_CBC_SHA */
-    "EXP-DHE-DSS-DES-CBC-SHA",          /* TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA */
-    "EXP-DHE-RSA-DES-CBC-SHA",          /* TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA */
-    "EXP-ADH-DES-CBC-SHA",              /* TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA */
-    "EXP-ADH-RC4-MD5",                  /* TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 */
-
-    /* blacklisted RC4 encryption */
-    "RC4-MD5",                          /* TLS_RSA_WITH_RC4_128_MD5 */
-    "RC4-SHA",                          /* TLS_RSA_WITH_RC4_128_SHA */
-    "ADH-RC4-MD5",                      /* TLS_DH_anon_WITH_RC4_128_MD5 */
-    "KRB5-RC4-SHA",                     /* TLS_KRB5_WITH_RC4_128_SHA */
-    "KRB5-RC4-MD5",                     /* TLS_KRB5_WITH_RC4_128_MD5 */
-    "EXP-KRB5-RC4-SHA",                 /* TLS_KRB5_EXPORT_WITH_RC4_40_SHA */
-    "EXP-KRB5-RC4-MD5",                 /* TLS_KRB5_EXPORT_WITH_RC4_40_MD5 */
-    "PSK-RC4-SHA",                      /* TLS_PSK_WITH_RC4_128_SHA */
-    "DHE-PSK-RC4-SHA",                  /* TLS_DHE_PSK_WITH_RC4_128_SHA */
-    "RSA-PSK-RC4-SHA",                  /* TLS_RSA_PSK_WITH_RC4_128_SHA */
-    "ECDH-ECDSA-RC4-SHA",               /* TLS_ECDH_ECDSA_WITH_RC4_128_SHA */
-    "ECDHE-ECDSA-RC4-SHA",              /* TLS_ECDHE_ECDSA_WITH_RC4_128_SHA */
-    "ECDH-RSA-RC4-SHA",                 /* TLS_ECDH_RSA_WITH_RC4_128_SHA */
-    "ECDHE-RSA-RC4-SHA",                /* TLS_ECDHE_RSA_WITH_RC4_128_SHA */
-    "AECDH-RC4-SHA",                    /* TLS_ECDH_anon_WITH_RC4_128_SHA */
-    "ECDHE-PSK-RC4-SHA",                /* TLS_ECDHE_PSK_WITH_RC4_128_SHA */
-
-    /* blacklisted AES128 encrpytion ciphers */
-    "AES128-SHA256",                    /* TLS_RSA_WITH_AES_128_CBC_SHA */
-    "DH-DSS-AES128-SHA",                /* TLS_DH_DSS_WITH_AES_128_CBC_SHA */
-    "DH-RSA-AES128-SHA",                /* TLS_DH_RSA_WITH_AES_128_CBC_SHA */
-    "DHE-DSS-AES128-SHA",               /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA */
-    "DHE-RSA-AES128-SHA",               /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA */
-    "ADH-AES128-SHA",                   /* TLS_DH_anon_WITH_AES_128_CBC_SHA */
-    "AES128-SHA256",                    /* TLS_RSA_WITH_AES_128_CBC_SHA256 */
-    "DH-DSS-AES128-SHA256",             /* TLS_DH_DSS_WITH_AES_128_CBC_SHA256 */
-    "DH-RSA-AES128-SHA256",             /* TLS_DH_RSA_WITH_AES_128_CBC_SHA256 */
-    "DHE-DSS-AES128-SHA256",            /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 */
-    "DHE-RSA-AES128-SHA256",            /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 */
-    "ECDH-ECDSA-AES128-SHA",            /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA */
-    "ECDHE-ECDSA-AES128-SHA",           /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA */
-    "ECDH-RSA-AES128-SHA",              /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA */
-    "ECDHE-RSA-AES128-SHA",             /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA */
-    "AECDH-AES128-SHA",                 /* TLS_ECDH_anon_WITH_AES_128_CBC_SHA */
-    "ECDHE-ECDSA-AES128-SHA256",        /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 */
-    "ECDH-ECDSA-AES128-SHA256",         /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 */
-    "ECDHE-RSA-AES128-SHA256",          /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 */
-    "ECDH-RSA-AES128-SHA256",           /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 */
-    "ADH-AES128-SHA256",                /* TLS_DH_anon_WITH_AES_128_CBC_SHA256 */
-    "PSK-AES128-CBC-SHA",               /* TLS_PSK_WITH_AES_128_CBC_SHA */
-    "DHE-PSK-AES128-CBC-SHA",           /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA */
-    "RSA-PSK-AES128-CBC-SHA",           /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA */
-    "PSK-AES128-CBC-SHA256",            /* TLS_PSK_WITH_AES_128_CBC_SHA256 */
-    "DHE-PSK-AES128-CBC-SHA256",        /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 */
-    "RSA-PSK-AES128-CBC-SHA256",        /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 */
-    "ECDHE-PSK-AES128-CBC-SHA",         /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA */
-    "ECDHE-PSK-AES128-CBC-SHA256",      /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 */
-    "AES128-CCM",                       /* TLS_RSA_WITH_AES_128_CCM */
-    "AES128-CCM8",                      /* TLS_RSA_WITH_AES_128_CCM_8 */
-    "PSK-AES128-CCM",                   /* TLS_PSK_WITH_AES_128_CCM */
-    "PSK-AES128-CCM8",                  /* TLS_PSK_WITH_AES_128_CCM_8 */
-    "AES128-GCM-SHA256",                /* TLS_RSA_WITH_AES_128_GCM_SHA256 */
-    "DH-RSA-AES128-GCM-SHA256",         /* TLS_DH_RSA_WITH_AES_128_GCM_SHA256 */
-    "DH-DSS-AES128-GCM-SHA256",         /* TLS_DH_DSS_WITH_AES_128_GCM_SHA256 */
-    "ADH-AES128-GCM-SHA256",            /* TLS_DH_anon_WITH_AES_128_GCM_SHA256 */
-    "PSK-AES128-GCM-SHA256",            /* TLS_PSK_WITH_AES_128_GCM_SHA256 */
-    "RSA-PSK-AES128-GCM-SHA256",        /* TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 */
-    "ECDH-ECDSA-AES128-GCM-SHA256",     /* TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 */
-    "ECDH-RSA-AES128-GCM-SHA256",       /* TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 */
-    "SRP-AES-128-CBC-SHA",              /* TLS_SRP_SHA_WITH_AES_128_CBC_SHA */
-    "SRP-RSA-AES-128-CBC-SHA",          /* TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA */
-    "SRP-DSS-AES-128-CBC-SHA",          /* TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA */
-    
-    /* blacklisted AES256 encrpytion ciphers */
-    "AES256-SHA",                       /* TLS_RSA_WITH_AES_256_CBC_SHA */
-    "DH-DSS-AES256-SHA",                /* TLS_DH_DSS_WITH_AES_256_CBC_SHA */
-    "DH-RSA-AES256-SHA",                /* TLS_DH_RSA_WITH_AES_256_CBC_SHA */
-    "DHE-DSS-AES256-SHA",               /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA */
-    "DHE-RSA-AES256-SHA",               /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA */
-    "ADH-AES256-SHA",                   /* TLS_DH_anon_WITH_AES_256_CBC_SHA */
-    "AES256-SHA256",                    /* TLS_RSA_WITH_AES_256_CBC_SHA256 */
-    "DH-DSS-AES256-SHA256",             /* TLS_DH_DSS_WITH_AES_256_CBC_SHA256 */
-    "DH-RSA-AES256-SHA256",             /* TLS_DH_RSA_WITH_AES_256_CBC_SHA256 */
-    "DHE-DSS-AES256-SHA256",            /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 */
-    "DHE-RSA-AES256-SHA256",            /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 */
-    "ADH-AES256-SHA256",                /* TLS_DH_anon_WITH_AES_256_CBC_SHA256 */
-    "ECDH-ECDSA-AES256-SHA",            /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA */
-    "ECDHE-ECDSA-AES256-SHA",           /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA */
-    "ECDH-RSA-AES256-SHA",              /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA */
-    "ECDHE-RSA-AES256-SHA",             /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA */
-    "AECDH-AES256-SHA",                 /* TLS_ECDH_anon_WITH_AES_256_CBC_SHA */
-    "ECDHE-ECDSA-AES256-SHA384",        /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 */
-    "ECDH-ECDSA-AES256-SHA384",         /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 */
-    "ECDHE-RSA-AES256-SHA384",          /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 */
-    "ECDH-RSA-AES256-SHA384",           /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 */
-    "PSK-AES256-CBC-SHA",               /* TLS_PSK_WITH_AES_256_CBC_SHA */
-    "DHE-PSK-AES256-CBC-SHA",           /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA */
-    "RSA-PSK-AES256-CBC-SHA",           /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA */
-    "PSK-AES256-CBC-SHA384",            /* TLS_PSK_WITH_AES_256_CBC_SHA384 */
-    "DHE-PSK-AES256-CBC-SHA384",        /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 */
-    "RSA-PSK-AES256-CBC-SHA384",        /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 */
-    "ECDHE-PSK-AES256-CBC-SHA",         /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA */
-    "ECDHE-PSK-AES256-CBC-SHA384",      /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 */
-    "SRP-AES-256-CBC-SHA",              /* TLS_SRP_SHA_WITH_AES_256_CBC_SHA */
-    "SRP-RSA-AES-256-CBC-SHA",          /* TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA */
-    "SRP-DSS-AES-256-CBC-SHA",          /* TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA */
-    "AES256-CCM",                       /* TLS_RSA_WITH_AES_256_CCM */
-    "AES256-CCM8",                      /* TLS_RSA_WITH_AES_256_CCM_8 */
-    "PSK-AES256-CCM",                   /* TLS_PSK_WITH_AES_256_CCM */
-    "PSK-AES256-CCM8",                  /* TLS_PSK_WITH_AES_256_CCM_8 */
-    "AES256-GCM-SHA384",                /* TLS_RSA_WITH_AES_256_GCM_SHA384 */
-    "DH-RSA-AES256-GCM-SHA384",         /* TLS_DH_RSA_WITH_AES_256_GCM_SHA384 */
-    "DH-DSS-AES256-GCM-SHA384",         /* TLS_DH_DSS_WITH_AES_256_GCM_SHA384 */
-    "ADH-AES256-GCM-SHA384",            /* TLS_DH_anon_WITH_AES_256_GCM_SHA384 */
-    "PSK-AES256-GCM-SHA384",            /* TLS_PSK_WITH_AES_256_GCM_SHA384 */
-    "RSA-PSK-AES256-GCM-SHA384",        /* TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 */
-    "ECDH-ECDSA-AES256-GCM-SHA384",     /* TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 */
-    "ECDH-RSA-AES256-GCM-SHA384",       /* TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 */
-    
-    /* blacklisted CAMELLIA128 encrpytion ciphers */
-    "CAMELLIA128-SHA",                  /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA */
-    "DH-DSS-CAMELLIA128-SHA",           /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA */
-    "DH-RSA-CAMELLIA128-SHA",           /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA */
-    "DHE-DSS-CAMELLIA128-SHA",          /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA */
-    "DHE-RSA-CAMELLIA128-SHA",          /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA */
-    "ADH-CAMELLIA128-SHA",              /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA */
-    "ECDHE-ECDSA-CAMELLIA128-SHA256",   /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
-    "ECDH-ECDSA-CAMELLIA128-SHA256",    /* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
-    "ECDHE-RSA-CAMELLIA128-SHA256",     /* TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
-    "ECDH-RSA-CAMELLIA128-SHA256",      /* TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
-    "PSK-CAMELLIA128-SHA256",           /* TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
-    "DHE-PSK-CAMELLIA128-SHA256",       /* TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
-    "RSA-PSK-CAMELLIA128-SHA256",       /* TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
-    "ECDHE-PSK-CAMELLIA128-SHA256",     /* TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
-    "CAMELLIA128-GCM-SHA256",           /* TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
-    "DH-RSA-CAMELLIA128-GCM-SHA256",    /* TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
-    "DH-DSS-CAMELLIA128-GCM-SHA256",    /* TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 */
-    "ADH-CAMELLIA128-GCM-SHA256",       /* TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 */
-    "ECDH-ECDSA-CAMELLIA128-GCM-SHA256",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 */
-    "ECDH-RSA-CAMELLIA128-GCM-SHA256",  /* TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
-    "PSK-CAMELLIA128-GCM-SHA256",       /* TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
-    "RSA-PSK-CAMELLIA128-GCM-SHA256",   /* TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
-    "CAMELLIA128-SHA256",               /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
-    "DH-DSS-CAMELLIA128-SHA256",        /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
-    "DH-RSA-CAMELLIA128-SHA256",        /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
-    "DHE-DSS-CAMELLIA128-SHA256",       /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
-    "DHE-RSA-CAMELLIA128-SHA256",       /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
-    "ADH-CAMELLIA128-SHA256",           /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 */
-    
-    /* blacklisted CAMELLIA256 encrpytion ciphers */
-    "CAMELLIA256-SHA",                  /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA */
-    "DH-RSA-CAMELLIA256-SHA",           /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA */
-    "DH-DSS-CAMELLIA256-SHA",           /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA */
-    "DHE-DSS-CAMELLIA256-SHA",          /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA */
-    "DHE-RSA-CAMELLIA256-SHA",          /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA */
-    "ADH-CAMELLIA256-SHA",              /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA */
-    "ECDHE-ECDSA-CAMELLIA256-SHA384",   /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
-    "ECDH-ECDSA-CAMELLIA256-SHA384",    /* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
-    "ECDHE-RSA-CAMELLIA256-SHA384",     /* TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
-    "ECDH-RSA-CAMELLIA256-SHA384",      /* TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
-    "PSK-CAMELLIA256-SHA384",           /* TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
-    "DHE-PSK-CAMELLIA256-SHA384",       /* TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
-    "RSA-PSK-CAMELLIA256-SHA384",       /* TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
-    "ECDHE-PSK-CAMELLIA256-SHA384",     /* TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
-    "CAMELLIA256-SHA256",               /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
-    "DH-DSS-CAMELLIA256-SHA256",        /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
-    "DH-RSA-CAMELLIA256-SHA256",        /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
-    "DHE-DSS-CAMELLIA256-SHA256",       /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
-    "DHE-RSA-CAMELLIA256-SHA256",       /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
-    "ADH-CAMELLIA256-SHA256",           /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 */
-    "CAMELLIA256-GCM-SHA384",           /* TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
-    "DH-RSA-CAMELLIA256-GCM-SHA384",    /* TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
-    "DH-DSS-CAMELLIA256-GCM-SHA384",    /* TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 */
-    "ADH-CAMELLIA256-GCM-SHA384",       /* TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 */
-    "ECDH-ECDSA-CAMELLIA256-GCM-SHA384",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 */
-    "ECDH-RSA-CAMELLIA256-GCM-SHA384",  /* TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
-    "PSK-CAMELLIA256-GCM-SHA384",       /* TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
-    "RSA-PSK-CAMELLIA256-GCM-SHA384",   /* TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
-    
-    /* The blacklisted ARIA encrpytion ciphers */
-    "ARIA128-SHA256",                   /* TLS_RSA_WITH_ARIA_128_CBC_SHA256 */
-    "ARIA256-SHA384",                   /* TLS_RSA_WITH_ARIA_256_CBC_SHA384 */
-    "DH-DSS-ARIA128-SHA256",            /* TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 */
-    "DH-DSS-ARIA256-SHA384",            /* TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 */
-    "DH-RSA-ARIA128-SHA256",            /* TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 */
-    "DH-RSA-ARIA256-SHA384",            /* TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 */
-    "DHE-DSS-ARIA128-SHA256",           /* TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 */
-    "DHE-DSS-ARIA256-SHA384",           /* TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 */
-    "DHE-RSA-ARIA128-SHA256",           /* TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 */
-    "DHE-RSA-ARIA256-SHA384",           /* TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 */
-    "ADH-ARIA128-SHA256",               /* TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 */
-    "ADH-ARIA256-SHA384",               /* TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 */
-    "ECDHE-ECDSA-ARIA128-SHA256",       /* TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 */
-    "ECDHE-ECDSA-ARIA256-SHA384",       /* TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 */
-    "ECDH-ECDSA-ARIA128-SHA256",        /* TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 */
-    "ECDH-ECDSA-ARIA256-SHA384",        /* TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 */
-    "ECDHE-RSA-ARIA128-SHA256",         /* TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 */
-    "ECDHE-RSA-ARIA256-SHA384",         /* TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 */
-    "ECDH-RSA-ARIA128-SHA256",          /* TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 */
-    "ECDH-RSA-ARIA256-SHA384",          /* TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 */
-    "ARIA128-GCM-SHA256",               /* TLS_RSA_WITH_ARIA_128_GCM_SHA256 */
-    "ARIA256-GCM-SHA384",               /* TLS_RSA_WITH_ARIA_256_GCM_SHA384 */
-    "DH-DSS-ARIA128-GCM-SHA256",        /* TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 */
-    "DH-DSS-ARIA256-GCM-SHA384",        /* TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 */
-    "DH-RSA-ARIA128-GCM-SHA256",        /* TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 */
-    "DH-RSA-ARIA256-GCM-SHA384",        /* TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 */
-    "ADH-ARIA128-GCM-SHA256",           /* TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 */
-    "ADH-ARIA256-GCM-SHA384",           /* TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 */
-    "ECDH-ECDSA-ARIA128-GCM-SHA256",    /* TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 */
-    "ECDH-ECDSA-ARIA256-GCM-SHA384",    /* TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 */
-    "ECDH-RSA-ARIA128-GCM-SHA256",      /* TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 */
-    "ECDH-RSA-ARIA256-GCM-SHA384",      /* TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 */
-    "PSK-ARIA128-SHA256",               /* TLS_PSK_WITH_ARIA_128_CBC_SHA256 */
-    "PSK-ARIA256-SHA384",               /* TLS_PSK_WITH_ARIA_256_CBC_SHA384 */
-    "DHE-PSK-ARIA128-SHA256",           /* TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 */
-    "DHE-PSK-ARIA256-SHA384",           /* TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 */
-    "RSA-PSK-ARIA128-SHA256",           /* TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 */
-    "RSA-PSK-ARIA256-SHA384",           /* TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 */
-    "ARIA128-GCM-SHA256",               /* TLS_PSK_WITH_ARIA_128_GCM_SHA256 */
-    "ARIA256-GCM-SHA384",               /* TLS_PSK_WITH_ARIA_256_GCM_SHA384 */
-    "RSA-PSK-ARIA128-GCM-SHA256",       /* TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 */
-    "RSA-PSK-ARIA256-GCM-SHA384",       /* TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 */
-    "ECDHE-PSK-ARIA128-SHA256",         /* TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 */
-    "ECDHE-PSK-ARIA256-SHA384",         /* TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 */
-
-    /* blacklisted SEED encryptions */
-    "SEED-SHA",                         /*TLS_RSA_WITH_SEED_CBC_SHA */
-    "DH-DSS-SEED-SHA",                  /* TLS_DH_DSS_WITH_SEED_CBC_SHA */
-    "DH-RSA-SEED-SHA",                  /* TLS_DH_RSA_WITH_SEED_CBC_SHA */
-    "DHE-DSS-SEED-SHA",                 /* TLS_DHE_DSS_WITH_SEED_CBC_SHA */
-    "DHE-RSA-SEED-SHA",                 /* TLS_DHE_RSA_WITH_SEED_CBC_SHA */               
-    "ADH-SEED-SHA",                     /* TLS_DH_anon_WITH_SEED_CBC_SHA */
-
-    /* blacklisted KRB5 ciphers */
-    "KRB5-DES-CBC-SHA",                 /* TLS_KRB5_WITH_DES_CBC_SHA */
-    "KRB5-DES-CBC3-SHA",                /* TLS_KRB5_WITH_3DES_EDE_CBC_SHA */
-    "KRB5-IDEA-CBC-SHA",                /* TLS_KRB5_WITH_IDEA_CBC_SHA */
-    "KRB5-DES-CBC-MD5",                 /* TLS_KRB5_WITH_DES_CBC_MD5 */
-    "KRB5-DES-CBC3-MD5",                /* TLS_KRB5_WITH_3DES_EDE_CBC_MD5 */
-    "KRB5-IDEA-CBC-MD5",                /* TLS_KRB5_WITH_IDEA_CBC_MD5 */
-    "EXP-KRB5-DES-CBC-SHA",             /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA */
-    "EXP-KRB5-DES-CBC-MD5",             /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 */
-    "EXP-KRB5-RC2-CBC-SHA",             /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA */
-    "EXP-KRB5-RC2-CBC-MD5",             /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 */
-  
-    /* blacklisted exoticas */
-    "DHE-DSS-CBC-SHA",                  /* TLS_DHE_DSS_WITH_DES_CBC_SHA */
-    "IDEA-CBC-SHA",                     /* TLS_RSA_WITH_IDEA_CBC_SHA */
-    
-    /* not really sure if the following names are correct */
-    "SSL3_CK_SCSV",                     /* TLS_EMPTY_RENEGOTIATION_INFO_SCSV */
-    "SSL3_CK_FALLBACK_SCSV"
-};
-static size_t RFC7540_names_LEN = sizeof(RFC7540_names)/sizeof(RFC7540_names[0]);
-
-
-static apr_hash_t *BLCNames;
-
-static void cipher_init(apr_pool_t *pool)
-{
-    apr_hash_t *hash = apr_hash_make(pool);
-    const char *source;
-    unsigned int i;
-    
-    source = "rfc7540";
-    for (i = 0; i < RFC7540_names_LEN; ++i) {
-        apr_hash_set(hash, RFC7540_names[i], APR_HASH_KEY_STRING, source);
-    }
-    
-    BLCNames = hash;
-}
-
-static int cipher_is_blacklisted(const char *cipher, const char **psource)
-{   
-    *psource = apr_hash_get(BLCNames, cipher, APR_HASH_KEY_STRING);
-    return !!*psource;
-}
-
-/*******************************************************************************
- * Hooks for processing incoming connections:
- * - process_conn take over connection in case of h2
- */
-static int h2_h2_process_conn(conn_rec* c);
-static int h2_h2_pre_close_conn(conn_rec* c);
-static int h2_h2_post_read_req(request_rec *r);
-static int h2_h2_late_fixups(request_rec *r);
-
-/*******************************************************************************
- * Once per lifetime init, retrieve optional functions
- */
-apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s)
-{
-    (void)pool;
-    ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "h2_h2, child_init");
-    cipher_init(pool);
-    
-    return APR_SUCCESS;
-}
-
-int h2_is_acceptable_connection(conn_rec *c, request_rec *r, int require_all) 
-{
-    int is_tls = ap_ssl_conn_is_ssl(c);
-
-    if (is_tls && h2_config_cgeti(c, H2_CONF_MODERN_TLS_ONLY) > 0) {
-        /* Check TLS connection for modern TLS parameters, as defined in
-         * RFC 7540 and https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
-         */
-        apr_pool_t *pool = c->pool;
-        server_rec *s = c->base_server;
-        const char *val;
-
-        /* Need Tlsv1.2 or higher, rfc 7540, ch. 9.2
-         */
-        val = ap_ssl_var_lookup(pool, s, c, NULL, "SSL_PROTOCOL");
-        if (val && *val) {
-            if (strncmp("TLS", val, 3) 
-                || !strcmp("TLSv1", val) 
-                || !strcmp("TLSv1.1", val)) {
-                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03050)
-                              "h2_h2(%ld): tls protocol not suitable: %s", 
-                              (long)c->id, val);
-                return 0;
-            }
-        }
-        else if (require_all) {
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03051)
-                          "h2_h2(%ld): tls protocol is indetermined", (long)c->id);
-            return 0;
-        }
-
-        /* Check TLS cipher blacklist
-         */
-        val = ap_ssl_var_lookup(pool, s, c, NULL, "SSL_CIPHER");
-        if (val && *val) {
-            const char *source;
-            if (cipher_is_blacklisted(val, &source)) {
-                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03052)
-                              "h2_h2(%ld): tls cipher %s blacklisted by %s", 
-                              (long)c->id, val, source);
-                return 0;
-            }
-        }
-        else if (require_all) {
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03053)
-                          "h2_h2(%ld): tls cipher is indetermined", (long)c->id);
-            return 0;
-        }
-    }
-    return 1;
-}
-
-static int h2_allows_h2_direct(conn_rec *c)
-{
-    int is_tls = ap_ssl_conn_is_ssl(c);
-    const char *needed_protocol = is_tls? "h2" : "h2c";
-    int h2_direct = h2_config_cgeti(c, H2_CONF_DIRECT);
-    
-    if (h2_direct < 0) {
-        h2_direct = is_tls? 0 : 1;
-    }
-    return (h2_direct && ap_is_allowed_protocol(c, NULL, NULL, needed_protocol));
-}
-
-int h2_allows_h2_upgrade(request_rec *r)
-{
-    int h2_upgrade = h2_config_rgeti(r, H2_CONF_UPGRADE);
-    return h2_upgrade > 0 || (h2_upgrade < 0 && !ap_ssl_conn_is_ssl(r->connection));
-}
-
-/*******************************************************************************
- * Register various hooks
- */
-static const char* const mod_ssl[]        = { "mod_ssl.c", NULL};
-static const char* const mod_reqtimeout[] = { "mod_ssl.c", "mod_reqtimeout.c", NULL};
-
-void h2_h2_register_hooks(void)
-{
-    /* Our main processing needs to run quite late. Definitely after mod_ssl,
-     * as we need its connection filters, but also before reqtimeout as its
-     * method of timeouts is specific to HTTP/1.1 (as of now).
-     * The core HTTP/1 processing run as REALLY_LAST, so we will have
-     * a chance to take over before it.
-     */
-    ap_hook_process_connection(h2_h2_process_conn, 
-                               mod_reqtimeout, NULL, APR_HOOK_LAST);
-    
-    /* One last chance to properly say goodbye if we have not done so
-     * already. */
-    ap_hook_pre_close_connection(h2_h2_pre_close_conn, NULL, mod_ssl, APR_HOOK_LAST);
-
-    /* With "H2SerializeHeaders On", we install the filter in this hook
-     * that parses the response. This needs to happen before any other post
-     * read function terminates the request with an error. Otherwise we will
-     * never see the response.
-     */
-    ap_hook_post_read_request(h2_h2_post_read_req, NULL, NULL, APR_HOOK_REALLY_FIRST);
-    ap_hook_fixups(h2_h2_late_fixups, NULL, NULL, APR_HOOK_LAST);
-
-    /* special bucket type transfer through a h2_bucket_beam */
-    h2_register_bucket_beamer(h2_bucket_headers_beam);
-    h2_register_bucket_beamer(h2_bucket_observer_beam);
-}
-
-int h2_h2_process_conn(conn_rec* c)
-{
-    apr_status_t status;
-    h2_ctx *ctx;
-    server_rec *s;
-    
-    if (c->master) {
-        return DECLINED;
-    }
-    
-    ctx = h2_ctx_get(c, 0);
-    s = ctx? ctx->server : c->base_server;
-    
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn");
-    if (ctx && ctx->task) {
-        /* our stream pseudo connection */
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, task, declined");
-        return DECLINED;
-    }
-    
-    if (!ctx && c->keepalives == 0) {
-        const char *proto = ap_get_protocol(c);
-        
-        if (APLOGctrace1(c)) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn, "
-                          "new connection using protocol '%s', direct=%d, "
-                          "tls acceptable=%d", proto, h2_allows_h2_direct(c), 
-                          h2_is_acceptable_connection(c, NULL, 1));
-        }
-        
-        if (!strcmp(AP_PROTOCOL_HTTP1, proto)
-            && h2_allows_h2_direct(c) 
-            && h2_is_acceptable_connection(c, NULL, 1)) {
-            /* Fresh connection still is on http/1.1 and H2Direct is enabled. 
-             * Otherwise connection is in a fully acceptable state.
-             * -> peek at the first 24 incoming bytes
-             */
-            apr_bucket_brigade *temp;
-            char *peek = NULL;
-            apr_size_t peeklen;
-            
-            temp = apr_brigade_create(c->pool, c->bucket_alloc);
-            status = ap_get_brigade(c->input_filters, temp,
-                                    AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24);
-            
-            if (status != APR_SUCCESS) {
-                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03054)
-                              "h2_h2, error reading 24 bytes speculative");
-                apr_brigade_destroy(temp);
-                return DECLINED;
-            }
-            
-            apr_brigade_pflatten(temp, &peek, &peeklen, c->pool);
-            if ((peeklen >= 24) && !memcmp(H2_MAGIC_TOKEN, peek, 24)) {
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                              "h2_h2, direct mode detected");
-                if (!ctx) {
-                    ctx = h2_ctx_get(c, 1);
-                }
-                h2_ctx_protocol_set(ctx, ap_ssl_conn_is_ssl(c)? "h2" : "h2c");
-            }
-            else if (APLOGctrace2(c)) {
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
-                              "h2_h2, not detected in %d bytes(base64): %s", 
-                              (int)peeklen, h2_util_base64url_encode(peek, peeklen, c->pool));
-            }
-            
-            apr_brigade_destroy(temp);
-        }
-    }
-
-    if (ctx) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "process_conn");
-        
-        if (!h2_ctx_get_session(c)) {
-            status = h2_conn_setup(c, NULL, s);
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c, "conn_setup");
-            if (status != APR_SUCCESS) {
-                h2_ctx_clear(c);
-                return !OK;
-            }
-        }
-        h2_conn_run(c);
-        return OK;
-    }
-    
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined");
-    return DECLINED;
-}
-
-static int h2_h2_pre_close_conn(conn_rec *c)
-{
-    h2_ctx *ctx;
-
-    /* secondary connection? */
-    if (c->master) {
-        return DECLINED;
-    }
-
-    ctx = h2_ctx_get(c, 0);
-    if (ctx) {
-        /* If the session has been closed correctly already, we will not
-         * find a h2_ctx here. The presence indicates that the session
-         * is still ongoing. */
-        return h2_conn_pre_close(ctx, c);
-    }
-    return DECLINED;
-}
-
-static void check_push(request_rec *r, const char *tag)
-{
-    apr_array_header_t *push_list = h2_config_push_list(r);
-
-    if (!r->expecting_100 && push_list && push_list->nelts > 0) {
-        int i, old_status;
-        const char *old_line;
-        
-        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, 
-                      "%s, early announcing %d resources for push",
-                      tag, push_list->nelts);
-        for (i = 0; i < push_list->nelts; ++i) {
-            h2_push_res *push = &APR_ARRAY_IDX(push_list, i, h2_push_res);
-            apr_table_add(r->headers_out, "Link", 
-                           apr_psprintf(r->pool, "<%s>; rel=preload%s", 
-                                        push->uri_ref, push->critical? "; critical" : ""));
-        }
-        old_status = r->status;
-        old_line = r->status_line;
-        r->status = 103;
-        r->status_line = "103 Early Hints";
-        ap_send_interim_response(r, 1);
-        r->status = old_status;
-        r->status_line = old_line;
-    }
-}
-
-static int h2_h2_post_read_req(request_rec *r)
-{
-    /* secondary connection? */
-    if (r->connection->master) {
-        struct h2_task *task = h2_ctx_get_task(r->connection);
-        /* This hook will get called twice on internal redirects. Take care
-         * that we manipulate filters only once. */
-        if (task && !task->filters_set) {
-            ap_filter_t *f;
-            ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, 
-                          "h2_task(%s): adding request filters", task->id);
-
-            /* setup the correct filters to process the request for h2 */
-            ap_add_input_filter("H2_REQUEST", task, r, r->connection);
-            
-            /* replace the core http filter that formats response headers
-             * in HTTP/1 with our own that collects status and headers */
-            ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
-            ap_add_output_filter("H2_RESPONSE", task, r, r->connection);
-            
-            for (f = r->input_filters; f; f = f->next) {
-                if (!strcmp("H2_SECONDARY_IN", f->frec->name)) {
-                    f->r = r;
-                    break;
-                }
-            }
-            ap_add_output_filter("H2_TRAILERS_OUT", task, r, r->connection);
-            task->filters_set = 1;
-        }
-    }
-    return DECLINED;
-}
-
-static int h2_h2_late_fixups(request_rec *r)
-{
-    /* secondary connection? */
-    if (r->connection->master) {
-        struct h2_task *task = h2_ctx_get_task(r->connection);
-        if (task) {
-            /* check if we copy vs. setaside files in this location */
-            task->output.copy_files = h2_config_rgeti(r, H2_CONF_COPY_FILES);
-            task->output.buffered = h2_config_rgeti(r, H2_CONF_OUTPUT_BUFFER);
-            if (task->output.copy_files) {
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
-                              "h2_secondary_out(%s): copy_files on", task->id);
-                h2_beam_on_file_beam(task->output.beam, h2_beam_no_files, NULL);
-            }
-            check_push(r, "late_fixup");
-        }
-    }
-    return DECLINED;
-}
-
diff -r -N -u a/modules/http2/h2_h2.h b/modules/http2/h2_h2.h
--- a/modules/http2/h2_h2.h	2021-05-12 12:14:42.000000000 +0200
+++ b/modules/http2/h2_h2.h	1970-01-01 01:00:00.000000000 +0100
@@ -1,67 +0,0 @@
-/* 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_h2__
-#define __mod_h2__h2_h2__
-
-/**
- * List of ALPN protocol identifiers that we support in cleartext
- * negotiations. NULL terminated.
- */
-extern const char *h2_clear_protos[];
-
-/**
- * List of ALPN protocol identifiers that we support in TLS encrypted 
- * negotiations. NULL terminated.
- */
-extern const char *h2_tls_protos[];
-
-/**
- * Provide a user readable description of the HTTP/2 error code-
- * @param h2_error http/2 error code, as in rfc 7540, ch. 7
- * @return textual description of code or that it is unknown.
- */
-const char *h2_h2_err_description(unsigned int h2_error);
-
-/*
- * One time, post config initialization.
- */
-apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s);
-
-/* Register apache hooks for h2 protocol
- */
-void h2_h2_register_hooks(void);
-
-/**
- * Check if the given connection fulfills the requirements as configured.
- * @param c the connection
- * @param require_all != 0 iff any missing connection properties make
- *    the test fail. For example, a cipher might not have been selected while
- *    the handshake is still ongoing.
- * @return != 0 iff connection requirements are met
- */
-int h2_is_acceptable_connection(conn_rec *c, request_rec *r, int require_all);
-
-/**
- * Check if the "Upgrade" HTTP/1.1 mode of protocol switching is enabled
- * for the given request.
- * @param r the request to check
- * @return != 0 iff Upgrade switching is enabled
- */
-int h2_allows_h2_upgrade(request_rec *r);
-
-
-#endif /* defined(__mod_h2__h2_h2__) */
diff -r -N -u a/modules/http2/h2_headers.c b/modules/http2/h2_headers.c
--- a/modules/http2/h2_headers.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_headers.c	2024-10-30 21:40:01.059958617 +0100
@@ -27,12 +27,13 @@
 #include <nghttp2/nghttp2.h>
 
 #include "h2_private.h"
-#include "h2_h2.h"
+#include "h2_protocol.h"
 #include "h2_config.h"
 #include "h2_util.h"
 #include "h2_request.h"
 #include "h2_headers.h"
 
+#if !AP_HAS_RESPONSE_BUCKETS
 
 static int is_unsafe(server_rec *s) 
 {
@@ -64,7 +65,7 @@
 
     b = apr_bucket_shared_make(b, br, 0, 0);
     b->type = &h2_bucket_type_headers;
-    b->length = h2_headers_length(r);
+    b->length = 0;
     
     return b;
 } 
@@ -98,18 +99,11 @@
     apr_bucket_shared_copy
 };
 
-apr_bucket *h2_bucket_headers_beam(struct h2_bucket_beam *beam,
-                                    apr_bucket_brigade *dest,
-                                    const apr_bucket *src)
-{
-    if (H2_BUCKET_IS_HEADERS(src)) {
-        h2_headers *src_headers = ((h2_bucket_headers *)src->data)->headers;
-        apr_bucket *b = h2_bucket_headers_create(dest->bucket_alloc, 
-                                                 h2_headers_clone(dest->p, src_headers));
-        APR_BRIGADE_INSERT_TAIL(dest, b);
-        return b;
-    }
-    return NULL;
+apr_bucket *h2_bucket_headers_clone(apr_bucket *b, apr_pool_t *pool,
+                                    apr_bucket_alloc_t *list)
+{
+    h2_headers *hdrs = ((h2_bucket_headers *)b->data)->headers;
+    return h2_bucket_headers_create(list, h2_headers_clone(pool, hdrs));
 }
 
 
@@ -140,10 +134,19 @@
     return len;
 }
 
+apr_size_t h2_bucket_headers_headers_length(apr_bucket *b)
+{
+    h2_headers *h = h2_bucket_headers_get(b);
+    return h? h2_headers_length(h) : 0;
+}
+
 h2_headers *h2_headers_rcreate(request_rec *r, int status,
                                const apr_table_t *header, apr_pool_t *pool)
 {
     h2_headers *headers = h2_headers_create(status, header, r->notes, 0, pool);
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, headers->status, r,
+                  "h2_headers_rcreate(%ld): status=%d",
+                  (long)r->connection->id, status);
     if (headers->status == HTTP_FORBIDDEN) {
         request_rec *r_prev;
         for (r_prev = r; r_prev != NULL; r_prev = r_prev->prev) {
@@ -153,7 +156,7 @@
                  * in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
                  */
                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, headers->status, r,
-                              APLOGNO(03061)
+                              APLOGNO(10399)
                               "h2_headers(%ld): renegotiate forbidden, cause: %s",
                               (long)r->connection->id, cause);
                 headers->status = H2_ERR_HTTP_1_1_REQUIRED;
@@ -199,8 +202,9 @@
     return headers;
 }
 
-int h2_headers_are_response(h2_headers *headers)
+int h2_headers_are_final_response(h2_headers *headers)
 {
     return headers->status >= 200;
 }
 
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
diff -r -N -u a/modules/http2/h2_headers.h b/modules/http2/h2_headers.h
--- a/modules/http2/h2_headers.h	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_headers.h	2024-10-30 21:40:01.059958617 +0100
@@ -19,8 +19,19 @@
 
 #include "h2.h"
 
+#if !AP_HAS_RESPONSE_BUCKETS
+
 struct h2_bucket_beam;
 
+typedef struct h2_headers h2_headers;
+struct h2_headers {
+    int         status;
+    apr_table_t *headers;
+    apr_table_t *notes;
+    apr_off_t   raw_bytes;      /* RAW network bytes that generated this request - if known. */
+};
+
+
 extern const apr_bucket_type_t h2_bucket_type_headers;
 
 #define H2_BUCKET_IS_HEADERS(e)     (e->type == &h2_bucket_type_headers)
@@ -32,10 +43,6 @@
                                        
 h2_headers *h2_bucket_headers_get(apr_bucket *b);
 
-apr_bucket *h2_bucket_headers_beam(struct h2_bucket_beam *beam,
-                                    apr_bucket_brigade *dest,
-                                    const apr_bucket *src);
-
 /**
  * Create the headers from the given status and headers
  * @param status the headers status
@@ -77,13 +84,24 @@
  * @param pool the memory pool to use
  */
 h2_headers *h2_headers_die(apr_status_t type,
-                             const struct h2_request *req, apr_pool_t *pool);
+                           const struct h2_request *req, apr_pool_t *pool);
 
-int h2_headers_are_response(h2_headers *headers);
+int h2_headers_are_final_response(h2_headers *headers);
 
 /**
  * Give the number of bytes of all contained header strings.
  */
 apr_size_t h2_headers_length(h2_headers *headers);
 
+/**
+ * For H2HEADER buckets, return the length of all contained header strings.
+ * For all other buckets, return 0.
+ */
+apr_size_t h2_bucket_headers_headers_length(apr_bucket *b);
+
+apr_bucket *h2_bucket_headers_clone(apr_bucket *b, apr_pool_t *pool,
+                                    apr_bucket_alloc_t *list);
+
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
+
 #endif /* defined(__mod_h2__h2_headers__) */
diff -r -N -u a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c
--- a/modules/http2/h2_mplx.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_mplx.c	2024-10-30 21:40:01.059958617 +0100
@@ -26,7 +26,9 @@
 
 #include <httpd.h>
 #include <http_core.h>
+#include <http_connection.h>
 #include <http_log.h>
+#include <http_protocol.h>
 
 #include <mpm_common.h>
 
@@ -36,14 +38,14 @@
 #include "h2_private.h"
 #include "h2_bucket_beam.h"
 #include "h2_config.h"
-#include "h2_conn.h"
-#include "h2_ctx.h"
-#include "h2_h2.h"
+#include "h2_c1.h"
+#include "h2_conn_ctx.h"
+#include "h2_protocol.h"
 #include "h2_mplx.h"
 #include "h2_request.h"
 #include "h2_stream.h"
 #include "h2_session.h"
-#include "h2_task.h"
+#include "h2_c2.h"
 #include "h2_workers.h"
 #include "h2_util.h"
 
@@ -56,25 +58,37 @@
     apr_size_t count;
 } stream_iter_ctx;
 
-/**
- * Naming convention for static functions:
- * - m_*: function only called from the master connection
- * - s_*: function only called from a secondary connection
- * - t_*: function only called from a h2_task holder
- * - mst_*: function called from everyone
- */
+static conn_rec *c2_prod_next(void *baton, int *phas_more);
+static void c2_prod_done(void *baton, conn_rec *c2);
+static void workers_shutdown(void *baton, int graceful);
+
+static void s_mplx_be_happy(h2_mplx *m, conn_rec *c, h2_conn_ctx_t *conn_ctx);
+static void m_be_annoyed(h2_mplx *m);
+
+static apr_status_t mplx_pollset_create(h2_mplx *m);
+static apr_status_t mplx_pollset_poll(h2_mplx *m, apr_interval_time_t timeout,
+                            stream_ev_callback *on_stream_input,
+                            stream_ev_callback *on_stream_output,
+                            void *on_ctx);
 
-static apr_status_t s_mplx_be_happy(h2_mplx *m, h2_task *task);
-static apr_status_t m_be_annoyed(h2_mplx *m);
+static apr_pool_t *pchild;
+
+/* APR callback invoked if allocation fails. */
+static int abort_on_oom(int retcode)
+{
+    ap_abort_on_oom();
+    return retcode; /* unreachable, hopefully. */
+}
 
-apr_status_t h2_mplx_m_child_init(apr_pool_t *pool, server_rec *s)
+apr_status_t h2_mplx_c1_child_init(apr_pool_t *pool, server_rec *s)
 {
+    pchild = pool;
     return APR_SUCCESS;
 }
 
 #define H2_MPLX_ENTER(m)    \
-    do { apr_status_t rv; if ((rv = apr_thread_mutex_lock(m->lock)) != APR_SUCCESS) {\
-        return rv;\
+    do { apr_status_t rv_lock; if ((rv_lock = apr_thread_mutex_lock(m->lock)) != APR_SUCCESS) {\
+        return rv_lock;\
     } } while(0)
 
 #define H2_MPLX_LEAVE(m)    \
@@ -89,56 +103,144 @@
 #define H2_MPLX_LEAVE_MAYBE(m, dolock)    \
     if (dolock) apr_thread_mutex_unlock(m->lock)
 
-static void mst_check_data_for(h2_mplx *m, int stream_id, int mplx_is_locked);
+static void c1_input_consumed(void *ctx, h2_bucket_beam *beam, apr_off_t length)
+{
+    h2_stream_in_consumed(ctx, length);
+}
 
-static void mst_stream_input_ev(void *ctx, h2_bucket_beam *beam)
+static int stream_is_running(h2_stream *stream)
 {
-    h2_stream *stream = ctx;
-    h2_mplx *m = stream->session->mplx;
-    apr_atomic_set32(&m->event_pending, 1); 
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(stream->c2);
+    return conn_ctx && apr_atomic_read32(&conn_ctx->started) != 0
+        && apr_atomic_read32(&conn_ctx->done) == 0;
 }
 
-static void m_stream_input_consumed(void *ctx, h2_bucket_beam *beam, apr_off_t length)
+int h2_mplx_c1_stream_is_running(h2_mplx *m, h2_stream *stream)
 {
-    h2_stream_in_consumed(ctx, length);
+    int rv;
+
+    H2_MPLX_ENTER(m);
+    rv = stream_is_running(stream);
+    H2_MPLX_LEAVE(m);
+    return rv;
 }
 
-static void ms_stream_joined(h2_mplx *m, h2_stream *stream)
+static void c1c2_stream_joined(h2_mplx *m, h2_stream *stream)
 {
-    ap_assert(!h2_task_has_started(stream->task) || stream->task->worker_done);
+    ap_assert(!stream_is_running(stream));
     
-    h2_ififo_remove(m->readyq, stream->id);
     h2_ihash_remove(m->shold, stream->id);
-    h2_ihash_add(m->spurge, stream);
+    APR_ARRAY_PUSH(m->spurge, h2_stream *) = stream;
 }
 
 static void m_stream_cleanup(h2_mplx *m, h2_stream *stream)
 {
-    ap_assert(stream->state == H2_SS_CLEANUP);
+    h2_conn_ctx_t *c2_ctx = h2_conn_ctx_get(stream->c2);
 
-    if (stream->input) {
-        h2_beam_on_consumed(stream->input, NULL, NULL, NULL);
-        h2_beam_abort(stream->input);
-    }
-    if (stream->output) {
-        h2_beam_on_produced(stream->output, NULL, NULL);
-        h2_beam_leave(stream->output);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+                  H2_STRM_MSG(stream, "cleanup, unsubscribing from beam events"));
+    if (c2_ctx) {
+        if (c2_ctx->beam_out) {
+            h2_beam_on_was_empty(c2_ctx->beam_out, NULL, NULL);
+        }
+        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);
+        }
     }
-    
-    h2_stream_cleanup(stream);
 
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+                  H2_STRM_MSG(stream, "cleanup, removing from registries"));
+    ap_assert(stream->state == H2_SS_CLEANUP);
+    h2_stream_cleanup(stream);
     h2_ihash_remove(m->streams, stream->id);
     h2_iq_remove(m->q, stream->id);
-    
-    if (!h2_task_has_started(stream->task) || stream->task->done_done) {
-        ms_stream_joined(m, stream);
+
+    if (c2_ctx) {
+        if (!stream_is_running(stream)) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+                          H2_STRM_MSG(stream, "cleanup, c2 is done, move to spurge"));
+            /* processing has finished */
+            APR_ARRAY_PUSH(m->spurge, h2_stream *) = stream;
+        }
+        else {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+                          H2_STRM_MSG(stream, "cleanup, c2 is running, abort"));
+            /* c2 is still running */
+            h2_c2_abort(stream->c2, m->c1);
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+                          H2_STRM_MSG(stream, "cleanup, c2 is done, move to shold"));
+            h2_ihash_add(m->shold, stream);
+        }
     }
     else {
-        h2_ififo_remove(m->readyq, stream->id);
-        h2_ihash_add(m->shold, stream);
-        if (stream->task) {
-            stream->task->c->aborted = 1;
-        }
+        /* never started */
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+                      H2_STRM_MSG(stream, "cleanup, never started, move to spurge"));
+        APR_ARRAY_PUSH(m->spurge, h2_stream *) = stream;
+    }
+}
+
+static h2_c2_transit *c2_transit_create(h2_mplx *m)
+{
+    apr_allocator_t *allocator;
+    apr_pool_t *ptrans;
+    h2_c2_transit *transit;
+    apr_status_t rv;
+
+    /* We create a pool with its own allocator to be used for
+     * processing a request. This is the only way to have the processing
+     * independent of its parent pool in the sense that it can work in
+     * another thread.
+     */
+
+    rv = apr_allocator_create(&allocator);
+    if (rv == APR_SUCCESS) {
+        apr_allocator_max_free_set(allocator, ap_max_mem_free);
+        rv = apr_pool_create_ex(&ptrans, m->pool, NULL, allocator);
+    }
+    if (rv != APR_SUCCESS) {
+        /* maybe the log goes through, maybe not. */
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, m->c1,
+                      APLOGNO(10004) "h2_mplx: create transit pool");
+        ap_abort_on_oom();
+        return NULL; /* should never be reached. */
+    }
+
+    apr_allocator_owner_set(allocator, ptrans);
+    apr_pool_abort_set(abort_on_oom, ptrans);
+    apr_pool_tag(ptrans, "h2_c2_transit");
+
+    transit = apr_pcalloc(ptrans, sizeof(*transit));
+    transit->pool = ptrans;
+    transit->bucket_alloc = apr_bucket_alloc_create(ptrans);
+    return transit;
+}
+
+static void c2_transit_destroy(h2_c2_transit *transit)
+{
+    apr_pool_destroy(transit->pool);
+}
+
+static h2_c2_transit *c2_transit_get(h2_mplx *m)
+{
+    h2_c2_transit **ptransit = apr_array_pop(m->c2_transits);
+    if (ptransit) {
+        return *ptransit;
+    }
+    return c2_transit_create(m);
+}
+
+static void c2_transit_recycle(h2_mplx *m, h2_c2_transit *transit)
+{
+    if (m->c2_transits->nelts >= APR_INT32_MAX ||
+        (apr_uint32_t)m->c2_transits->nelts >= m->max_spare_transits) {
+        c2_transit_destroy(transit);
+    }
+    else {
+        APR_ARRAY_PUSH(m->c2_transits, h2_c2_transit*) = transit;
     }
 }
 
@@ -153,179 +255,118 @@
  *   their HTTP/1 cousins, the separate allocator seems to work better
  *   than protecting a shared h2_session one with an own lock.
  */
-h2_mplx *h2_mplx_m_create(conn_rec *c, server_rec *s, apr_pool_t *parent, 
-                          h2_workers *workers)
+h2_mplx *h2_mplx_c1_create(int child_num, apr_uint32_t id, h2_stream *stream0,
+                           server_rec *s, apr_pool_t *parent,
+                           h2_workers *workers)
 {
+    h2_conn_ctx_t *conn_ctx;
     apr_status_t status = APR_SUCCESS;
     apr_allocator_t *allocator;
-    apr_thread_mutex_t *mutex;
-    h2_mplx *m;
+    apr_thread_mutex_t *mutex = NULL;
+    h2_mplx *m = NULL;
     
     m = apr_pcalloc(parent, sizeof(h2_mplx));
-    if (m) {
-        m->id = c->id;
-        m->c = c;
-        m->s = s;
-        
-        /* We create a pool with its own allocator to be used for
-         * processing secondary connections. This is the only way to have the
-         * processing independent of its parent pool in the sense that it
-         * can work in another thread. Also, the new allocator needs its own
-         * mutex to synchronize sub-pools.
-         */
-        status = apr_allocator_create(&allocator);
-        if (status != APR_SUCCESS) {
-            return NULL;
-        }
-        apr_allocator_max_free_set(allocator, ap_max_mem_free);
-        apr_pool_create_ex(&m->pool, parent, NULL, allocator);
-        if (!m->pool) {
-            apr_allocator_destroy(allocator);
-            return NULL;
-        }
-        apr_pool_tag(m->pool, "h2_mplx");
-        apr_allocator_owner_set(allocator, m->pool);
-        status = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT,
-                                         m->pool);
-        if (status != APR_SUCCESS) {
-            apr_pool_destroy(m->pool);
-            return NULL;
-        }
-        apr_allocator_mutex_set(allocator, mutex);
-
-        status = apr_thread_mutex_create(&m->lock, APR_THREAD_MUTEX_DEFAULT,
-                                         m->pool);
-        if (status != APR_SUCCESS) {
-            apr_pool_destroy(m->pool);
-            return NULL;
-        }
-        
-        m->max_streams = h2_config_sgeti(s, H2_CONF_MAX_STREAMS);
-        m->stream_max_mem = h2_config_sgeti(s, H2_CONF_STREAM_MAX_MEM);
-
-        m->streams = h2_ihash_create(m->pool, offsetof(h2_stream,id));
-        m->shold = h2_ihash_create(m->pool, offsetof(h2_stream,id));
-        m->spurge = h2_ihash_create(m->pool, offsetof(h2_stream,id));
-        m->q = h2_iq_create(m->pool, m->max_streams);
-
-        status = h2_ififo_set_create(&m->readyq, m->pool, m->max_streams);
-        if (status != APR_SUCCESS) {
-            apr_pool_destroy(m->pool);
-            return NULL;
-        }
+    m->stream0 = stream0;
+    m->c1 = stream0->c2;
+    m->s = s;
+    m->child_num = child_num;
+    m->id = id;
+
+    /* We create a pool with its own allocator to be used for
+     * processing secondary connections. This is the only way to have the
+     * processing independent of its parent pool in the sense that it
+     * can work in another thread. Also, the new allocator needs its own
+     * mutex to synchronize sub-pools.
+     */
+    status = apr_allocator_create(&allocator);
+    if (status != APR_SUCCESS) {
+        allocator = NULL;
+        goto failure;
+    }
+
+    apr_allocator_max_free_set(allocator, ap_max_mem_free);
+    apr_pool_create_ex(&m->pool, parent, NULL, allocator);
+    if (!m->pool) goto failure;
+
+    apr_pool_tag(m->pool, "h2_mplx");
+    apr_allocator_owner_set(allocator, m->pool);
+
+    status = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT,
+                                     m->pool);
+    if (APR_SUCCESS != status) goto failure;
+    apr_allocator_mutex_set(allocator, mutex);
+
+    status = apr_thread_mutex_create(&m->lock, APR_THREAD_MUTEX_DEFAULT,
+                                     m->pool);
+    if (APR_SUCCESS != status) goto failure;
+
+    m->max_streams = h2_config_sgeti(s, H2_CONF_MAX_STREAMS);
+    m->stream_max_mem = h2_config_sgeti(s, H2_CONF_STREAM_MAX_MEM);
+
+    m->streams = h2_ihash_create(m->pool, offsetof(h2_stream,id));
+    m->shold = h2_ihash_create(m->pool, offsetof(h2_stream,id));
+    m->spurge = apr_array_make(m->pool, 10, sizeof(h2_stream*));
+    m->q = h2_iq_create(m->pool, m->max_streams);
+
+    m->workers = workers;
+    m->processing_max = H2MIN(h2_workers_get_max_workers(workers), m->max_streams);
+    m->processing_limit = 6; /* the original h1 max parallel connections */
+    m->last_mood_change = apr_time_now();
+    m->mood_update_interval = apr_time_from_msec(100);
+
+    status = mplx_pollset_create(m);
+    if (APR_SUCCESS != status) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, status, m->c1, APLOGNO(10308)
+                      "nghttp2: could not create pollset");
+        goto failure;
+    }
+    m->streams_ev_in = apr_array_make(m->pool, 10, sizeof(h2_stream*));
+    m->streams_ev_out = apr_array_make(m->pool, 10, sizeof(h2_stream*));
+
+    m->streams_input_read = h2_iq_create(m->pool, 10);
+    m->streams_output_written = h2_iq_create(m->pool, 10);
+    status = apr_thread_mutex_create(&m->poll_lock, APR_THREAD_MUTEX_DEFAULT,
+                                     m->pool);
+    if (APR_SUCCESS != status) goto failure;
+
+    conn_ctx = h2_conn_ctx_get(m->c1);
+    if (conn_ctx->pfd.reqevents) {
+        apr_pollset_add(m->pollset, &conn_ctx->pfd);
+    }
+
+    m->max_spare_transits = 3;
+    m->c2_transits = apr_array_make(m->pool, (int)m->max_spare_transits,
+                                    sizeof(h2_c2_transit*));
+
+    m->producer = h2_workers_register(workers, m->pool,
+                                      apr_psprintf(m->pool, "h2-%u",
+                                      (unsigned int)m->id),
+                                      c2_prod_next, c2_prod_done,
+                                      workers_shutdown, m);
+    return m;
 
-        m->workers = workers;
-        m->max_active = workers->max_workers;
-        m->limit_active = 6; /* the original h1 max parallel connections */
-        m->last_mood_change = apr_time_now();
-        m->mood_update_interval = apr_time_from_msec(100);
-        
-        m->spare_secondary = apr_array_make(m->pool, 10, sizeof(conn_rec*));
+failure:
+    if (m->pool) {
+        apr_pool_destroy(m->pool);
     }
-    return m;
+    else if (allocator) {
+        apr_allocator_destroy(allocator);
+    }
+    return NULL;
 }
 
-int h2_mplx_m_shutdown(h2_mplx *m)
+int h2_mplx_c1_shutdown(h2_mplx *m)
 {
-    int max_stream_started = 0;
+    int max_stream_id_started = 0;
     
     H2_MPLX_ENTER(m);
 
-    max_stream_started = m->max_stream_started;
+    max_stream_id_started = m->max_stream_id_started;
     /* Clear schedule queue, disabling existing streams from starting */ 
     h2_iq_clear(m->q);
 
     H2_MPLX_LEAVE(m);
-    return max_stream_started;
-}
-
-static int m_input_consumed_signal(h2_mplx *m, h2_stream *stream)
-{
-    if (stream->input) {
-        return h2_beam_report_consumption(stream->input);
-    }
-    return 0;
-}
-
-static int m_report_consumption_iter(void *ctx, void *val)
-{
-    h2_stream *stream = val;
-    h2_mplx *m = ctx;
-    
-    m_input_consumed_signal(m, stream);
-    if (stream->state == H2_SS_CLOSED_L
-        && (!stream->task || stream->task->worker_done)) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, 
-                      H2_STRM_LOG(APLOGNO(10026), stream, "remote close missing")); 
-        nghttp2_submit_rst_stream(stream->session->ngh2, NGHTTP2_FLAG_NONE, 
-                                  stream->id, NGHTTP2_NO_ERROR);
-    }
-    return 1;
-}
-
-static int s_output_consumed_signal(h2_mplx *m, h2_task *task)
-{
-    if (task->output.beam) {
-        return h2_beam_report_consumption(task->output.beam);
-    }
-    return 0;
-}
-
-static int m_stream_destroy_iter(void *ctx, void *val) 
-{   
-    h2_mplx *m = ctx;
-    h2_stream *stream = val;
-
-    h2_ihash_remove(m->spurge, stream->id);
-    ap_assert(stream->state == H2_SS_CLEANUP);
-    
-    if (stream->input) {
-        /* Process outstanding events before destruction */
-        m_input_consumed_signal(m, stream);    
-        h2_beam_log(stream->input, m->c, APLOG_TRACE2, "stream_destroy");
-        h2_beam_destroy(stream->input);
-        stream->input = NULL;
-    }
-
-    if (stream->task) {
-        h2_task *task = stream->task;
-        conn_rec *secondary;
-        int reuse_secondary = 0;
-        
-        stream->task = NULL;
-        secondary = task->c;
-        if (secondary) {
-            if (m->s->keep_alive_max == 0 || secondary->keepalives < m->s->keep_alive_max) {
-                reuse_secondary = ((m->spare_secondary->nelts < (m->limit_active * 3 / 2))
-                                   && !task->rst_error);
-            }
-            
-            if (reuse_secondary) {
-                h2_beam_log(task->output.beam, m->c, APLOG_DEBUG, 
-                            APLOGNO(03385) "h2_task_destroy, reuse secondary");    
-                h2_task_destroy(task);
-                APR_ARRAY_PUSH(m->spare_secondary, conn_rec*) = secondary;
-            }
-            else {
-                h2_beam_log(task->output.beam, m->c, APLOG_TRACE1, 
-                            "h2_task_destroy, destroy secondary");    
-                h2_secondary_destroy(secondary);
-            }
-        }
-    }
-    h2_stream_destroy(stream);
-    return 0;
-}
-
-static void m_purge_streams(h2_mplx *m, int lock)
-{
-    if (!h2_ihash_empty(m->spurge)) {
-        H2_MPLX_ENTER_MAYBE(m, lock);
-        while (!h2_ihash_iter(m->spurge, m_stream_destroy_iter, m)) {
-            /* repeat until empty */
-        }
-        H2_MPLX_LEAVE_MAYBE(m, lock);
-    }
+    return max_stream_id_started;
 }
 
 typedef struct {
@@ -339,7 +380,7 @@
     return x->cb(stream, x->ctx);
 }
 
-apr_status_t h2_mplx_m_stream_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx)
+apr_status_t h2_mplx_c1_streams_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx)
 {
     stream_iter_ctx_t x;
     
@@ -353,27 +394,51 @@
     return APR_SUCCESS;
 }
 
+typedef struct {
+    int stream_count;
+    int stream_want_send;
+} stream_iter_aws_t;
+
+static int m_stream_want_send_data(void *ctx, void *stream)
+{
+    stream_iter_aws_t *x = ctx;
+    ++x->stream_count;
+    if (h2_stream_wants_send_data(stream))
+      ++x->stream_want_send;
+    return 1;
+}
+
+int h2_mplx_c1_all_streams_want_send_data(h2_mplx *m)
+{
+    stream_iter_aws_t x;
+    x.stream_count = 0;
+    x.stream_want_send = 0;
+    H2_MPLX_ENTER(m);
+    h2_ihash_iter(m->streams, m_stream_want_send_data, &x);
+    H2_MPLX_LEAVE(m);
+    return x.stream_count && (x.stream_count == x.stream_want_send);
+}
+
 static int m_report_stream_iter(void *ctx, void *val) {
     h2_mplx *m = ctx;
     h2_stream *stream = val;
-    h2_task *task = stream->task;
-    if (APLOGctrace1(m->c)) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                      H2_STRM_MSG(stream, "started=%d, scheduled=%d, ready=%d, out_buffer=%ld"), 
-                      !!stream->task, stream->scheduled, h2_stream_is_ready(stream),
-                      (long)h2_beam_get_buffered(stream->output));
-    }
-    if (task) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, /* NO APLOGNO */
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(stream->c2);
+    ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c1,
+                  H2_STRM_MSG(stream, "started=%d, scheduled=%d, ready=%d, out_buffer=%ld"),
+                  !!stream->c2, stream->scheduled, h2_stream_is_ready(stream),
+                  (long)(stream->output? h2_beam_get_buffered(stream->output) : -1));
+    if (conn_ctx) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1, /* NO APLOGNO */
                       H2_STRM_MSG(stream, "->03198: %s %s %s"
-                      "[started=%d/done=%d]"), 
-                      task->request->method, task->request->authority, 
-                      task->request->path, task->worker_started, 
-                      task->worker_done);
+                      "[started=%u/done=%u]"),
+                      conn_ctx->request->method, conn_ctx->request->authority,
+                      conn_ctx->request->path,
+                      apr_atomic_read32(&conn_ctx->started),
+                      apr_atomic_read32(&conn_ctx->done));
     }
     else {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, /* NO APLOGNO */
-                      H2_STRM_MSG(stream, "->03198: no task"));
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1, /* NO APLOGNO */
+                      H2_STRM_MSG(stream, "->03198: not started"));
     }
     return 1;
 }
@@ -381,9 +446,9 @@
 static int m_unexpected_stream_iter(void *ctx, void *val) {
     h2_mplx *m = ctx;
     h2_stream *stream = val;
-    ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, /* NO APLOGNO */
+    ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c1, /* NO APLOGNO */
                   H2_STRM_MSG(stream, "unexpected, started=%d, scheduled=%d, ready=%d"), 
-                  !!stream->task, stream->scheduled, h2_stream_is_ready(stream));
+                  !!stream->c2, stream->scheduled, h2_stream_is_ready(stream));
     return 1;
 }
 
@@ -391,10 +456,6 @@
     h2_mplx *m = ctx;
     h2_stream *stream = val;
 
-    /* disabled input consumed reporting */
-    if (stream->input) {
-        h2_beam_on_consumed(stream->input, NULL, NULL, NULL);
-    }
     /* take over event monitoring */
     h2_stream_set_monitor(stream, NULL);
     /* Reset, should transit to CLOSED state */
@@ -405,32 +466,35 @@
     return 0;
 }
 
-void h2_mplx_m_release_and_join(h2_mplx *m, apr_thread_cond_t *wait)
+static void c1_purge_streams(h2_mplx *m);
+
+void h2_mplx_c1_destroy(h2_mplx *m)
 {
     apr_status_t status;
-    int i, wait_secs = 60, old_aborted;
+    unsigned int i, wait_secs = 60;
+    int old_aborted;
 
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
-                  "h2_mplx(%ld): start release", m->id);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+                  H2_MPLX_MSG(m, "start release"));
     /* How to shut down a h2 connection:
-     * 0. abort and tell the workers that no more tasks will come from us */
-    m->aborted = 1;
-    h2_workers_unregister(m->workers, m);
-    
+     * 0. abort and tell the workers that no more work will come from us */
+    m->shutdown = m->aborted = 1;
+
     H2_MPLX_ENTER_ALWAYS(m);
 
-    /* While really terminating any secondary connections, treat the master
+    /* While really terminating any c2 connections, treat the master
      * connection as aborted. It's not as if we could send any more data
      * at this point. */
-    old_aborted = m->c->aborted;
-    m->c->aborted = 1;
+    old_aborted = m->c1->aborted;
+    m->c1->aborted = 1;
 
     /* How to shut down a h2 connection:
      * 1. cancel all streams still active */
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c, 
-                  "h2_mplx(%ld): release, %d/%d/%d streams (total/hold/purge), %d active tasks", 
-                  m->id, (int)h2_ihash_count(m->streams),
-                  (int)h2_ihash_count(m->shold), (int)h2_ihash_count(m->spurge), m->tasks_active);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+                  H2_MPLX_MSG(m, "release, %u/%u/%d streams (total/hold/purge), %d streams"),
+                  h2_ihash_count(m->streams),
+                  h2_ihash_count(m->shold),
+                  m->spurge->nelts, m->processing_count);
     while (!h2_ihash_iter(m->streams, m_stream_cancel_iter, m)) {
         /* until empty */
     }
@@ -440,144 +504,144 @@
     ap_assert(h2_iq_empty(m->q));
     
     /* 3. while workers are busy on this connection, meaning they
-     *    are processing tasks from this connection, wait on them finishing
+     *    are processing streams from this connection, wait on them finishing
      *    in order to wake us and let us check again. 
-     *    Eventually, this has to succeed. */    
-    m->join_wait = wait;
-    for (i = 0; h2_ihash_count(m->shold) > 0; ++i) {        
-        status = apr_thread_cond_timedwait(wait, m->lock, apr_time_from_sec(wait_secs));
+     *    Eventually, this has to succeed. */
+    if (!m->join_wait) {
+        apr_thread_cond_create(&m->join_wait, m->pool);
+    }
+
+    for (i = 0; h2_ihash_count(m->shold) > 0; ++i) {
+        status = apr_thread_cond_timedwait(m->join_wait, m->lock, apr_time_from_sec(wait_secs));
         
         if (APR_STATUS_IS_TIMEUP(status)) {
             /* This can happen if we have very long running requests
              * that do not time out on IO. */
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, APLOGNO(03198)
-                          "h2_mplx(%ld): waited %d sec for %d tasks", 
-                          m->id, i*wait_secs, (int)h2_ihash_count(m->shold));
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1, APLOGNO(03198)
+                          H2_MPLX_MSG(m, "waited %u sec for %u streams"),
+                          i*wait_secs, h2_ihash_count(m->shold));
             h2_ihash_iter(m->shold, m_report_stream_iter, m);
         }
     }
-    m->join_wait = NULL;
+
+    H2_MPLX_LEAVE(m);
+    h2_workers_join(m->workers, m->producer);
+    H2_MPLX_ENTER_ALWAYS(m);
 
     /* 4. With all workers done, all streams should be in spurge */
-    ap_assert(m->tasks_active == 0);
+    ap_assert(m->processing_count == 0);
     if (!h2_ihash_empty(m->shold)) {
-        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, APLOGNO(03516)
-                      "h2_mplx(%ld): unexpected %d streams in hold", 
-                      m->id, (int)h2_ihash_count(m->shold));
+        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c1, APLOGNO(03516)
+                      H2_MPLX_MSG(m, "unexpected %u streams in hold"),
+                      h2_ihash_count(m->shold));
         h2_ihash_iter(m->shold, m_unexpected_stream_iter, m);
     }
-    
-    m->c->aborted = old_aborted;
+
+    c1_purge_streams(m);
+
+    m->c1->aborted = old_aborted;
     H2_MPLX_LEAVE(m);
 
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c, "h2_mplx(%ld): released", m->id);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+                  H2_MPLX_MSG(m, "released"));
 }
 
-apr_status_t h2_mplx_m_stream_cleanup(h2_mplx *m, h2_stream *stream)
+apr_status_t h2_mplx_c1_stream_cleanup(h2_mplx *m, h2_stream *stream,
+                                       unsigned int *pstream_count)
 {
     H2_MPLX_ENTER(m);
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c, 
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
                   H2_STRM_MSG(stream, "cleanup"));
-    m_stream_cleanup(m, stream);        
-    
+    m_stream_cleanup(m, stream);
+    *pstream_count = h2_ihash_count(m->streams);
     H2_MPLX_LEAVE(m);
     return APR_SUCCESS;
 }
 
-h2_stream *h2_mplx_t_stream_get(h2_mplx *m, h2_task *task)
+const h2_stream *h2_mplx_c2_stream_get(h2_mplx *m, int stream_id)
 {
     h2_stream *s = NULL;
     
     H2_MPLX_ENTER_ALWAYS(m);
-
-    s = h2_ihash_get(m->streams, task->stream_id);
-
+    s = h2_ihash_get(m->streams, stream_id);
     H2_MPLX_LEAVE(m);
+
     return s;
 }
 
-static void mst_output_produced(void *ctx, h2_bucket_beam *beam, apr_off_t bytes)
-{
-    h2_stream *stream = ctx;
-    h2_mplx *m = stream->session->mplx;
-    
-    mst_check_data_for(m, stream->id, 0);
-}
 
-static apr_status_t t_out_open(h2_mplx *m, int stream_id, h2_bucket_beam *beam)
+static void c1_purge_streams(h2_mplx *m)
 {
-    h2_stream *stream = h2_ihash_get(m->streams, stream_id);
-    
-    if (!stream || !stream->task || m->aborted) {
-        return APR_ECONNABORTED;
-    }
-    
-    ap_assert(stream->output == NULL);
-    stream->output = beam;
-    
-    if (APLOGctrace2(m->c)) {
-        h2_beam_log(beam, stream->task->c, APLOG_TRACE2, "out_open");
-    }
-    else {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->task->c,
-                      "h2_mplx(%s): out open", stream->task->id);
-    }
-    
-    h2_beam_on_produced(stream->output, mst_output_produced, stream);
-    if (stream->task->output.copy_files) {
-        h2_beam_on_file_beam(stream->output, h2_beam_no_files, NULL);
+    h2_stream *stream;
+    int i;
+
+    for (i = 0; i < m->spurge->nelts; ++i) {
+        stream = APR_ARRAY_IDX(m->spurge, i, h2_stream*);
+        ap_assert(stream->state == H2_SS_CLEANUP);
+
+        if (stream->input) {
+            h2_beam_destroy(stream->input, m->c1);
+            stream->input = NULL;
+        }
+        if (stream->c2) {
+            conn_rec *c2 = stream->c2;
+            h2_conn_ctx_t *c2_ctx = h2_conn_ctx_get(c2);
+            h2_c2_transit *transit;
+
+            stream->c2 = NULL;
+            ap_assert(c2_ctx);
+            transit = c2_ctx->transit;
+            h2_c2_destroy(c2);  /* c2_ctx is gone as well */
+            if (transit) {
+                c2_transit_recycle(m, transit);
+            }
+        }
+        h2_stream_destroy(stream);
     }
-    
-    /* we might see some file buckets in the output, see
-     * if we have enough handles reserved. */
-    mst_check_data_for(m, stream->id, 1);
-    return APR_SUCCESS;
+    apr_array_clear(m->spurge);
 }
 
-apr_status_t h2_mplx_t_out_open(h2_mplx *m, int stream_id, h2_bucket_beam *beam)
+void h2_mplx_c1_going_keepalive(h2_mplx *m)
 {
-    apr_status_t status;
-    
-    H2_MPLX_ENTER(m);
-
-    if (m->aborted) {
-        status = APR_ECONNABORTED;
-    }
-    else {
-        status = t_out_open(m, stream_id, beam);
+    H2_MPLX_ENTER_ALWAYS(m);
+    if (m->spurge->nelts) {
+        c1_purge_streams(m);
     }
-
     H2_MPLX_LEAVE(m);
-    return status;
 }
 
-static apr_status_t s_out_close(h2_mplx *m, h2_task *task)
+apr_status_t h2_mplx_c1_poll(h2_mplx *m, apr_interval_time_t timeout,
+                            stream_ev_callback *on_stream_input,
+                            stream_ev_callback *on_stream_output,
+                            void *on_ctx)
 {
-    apr_status_t status = APR_SUCCESS;
+    apr_status_t rv;
 
-    if (!task) {
-        return APR_ECONNABORTED;
-    }
-    if (task->c) {
-        ++task->c->keepalives;
+    H2_MPLX_ENTER(m);
+
+    if (m->aborted) {
+        rv = APR_ECONNABORTED;
+        goto cleanup;
     }
-    
-    if (!h2_ihash_get(m->streams, task->stream_id)) {
-        return APR_ECONNABORTED;
+    /* Purge (destroy) streams outside of pollset processing.
+     * Streams that are registered in the pollset, will be removed
+     * when they are destroyed, but the pollset works on copies
+     * of these registrations. So, if we destroy streams while
+     * processing pollset events, we might access freed memory.
+     */
+    if (m->spurge->nelts) {
+        c1_purge_streams(m);
     }
+    rv = mplx_pollset_poll(m, timeout, on_stream_input, on_stream_output, on_ctx);
 
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, task->c,
-                  "h2_mplx(%s): close", task->id);
-    status = h2_beam_close(task->output.beam);
-    h2_beam_log(task->output.beam, task->c, APLOG_TRACE2, "out_close");
-    s_output_consumed_signal(m, task);
-    mst_check_data_for(m, task->stream_id, 1);
-    return status;
+cleanup:
+    H2_MPLX_LEAVE(m);
+    return rv;
 }
 
-apr_status_t h2_mplx_m_out_trywait(h2_mplx *m, apr_interval_time_t timeout,
-                                   apr_thread_cond_t *iowait)
+apr_status_t h2_mplx_c1_reprioritize(h2_mplx *m, h2_stream_pri_cmp_fn *cmp,
+                                    h2_session *session)
 {
     apr_status_t status;
     
@@ -586,556 +650,453 @@
     if (m->aborted) {
         status = APR_ECONNABORTED;
     }
-    else if (h2_mplx_m_has_master_events(m)) {
-        status = APR_SUCCESS;
-    }
     else {
-        m_purge_streams(m, 0);
-        h2_ihash_iter(m->streams, m_report_consumption_iter, m);
-        m->added_output = iowait;
-        status = apr_thread_cond_timedwait(m->added_output, m->lock, timeout);
-        if (APLOGctrace2(m->c)) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
-                          "h2_mplx(%ld): trywait on data for %f ms)",
-                          m->id, timeout/1000.0);
-        }
-        m->added_output = NULL;
+        h2_iq_sort(m->q, cmp, session);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+                      H2_MPLX_MSG(m, "reprioritize streams"));
+        status = APR_SUCCESS;
     }
 
     H2_MPLX_LEAVE(m);
     return status;
 }
 
-static void mst_check_data_for(h2_mplx *m, int stream_id, int mplx_is_locked)
-{
-    /* If m->lock is already held, we must release during h2_ififo_push()
-     * which can wait on its not_full condition, causing a deadlock because
-     * no one would then be able to acquire m->lock to empty the fifo.
-     */
-    H2_MPLX_LEAVE_MAYBE(m, mplx_is_locked);
-    if (h2_ififo_push(m->readyq, stream_id) == APR_SUCCESS) {
-        H2_MPLX_ENTER_ALWAYS(m);
-        apr_atomic_set32(&m->event_pending, 1);
-        if (m->added_output) {
-            apr_thread_cond_signal(m->added_output);
-        }
-        H2_MPLX_LEAVE_MAYBE(m, !mplx_is_locked);
-    }
-    else {
-        H2_MPLX_ENTER_MAYBE(m, mplx_is_locked);
-    }
-}
-
-apr_status_t h2_mplx_m_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx)
+static apr_status_t c1_process_stream(h2_mplx *m,
+                                      h2_stream *stream,
+                                      h2_stream_pri_cmp_fn *cmp,
+                                      h2_session *session)
 {
-    apr_status_t status;
-    
-    H2_MPLX_ENTER(m);
+    apr_status_t rv = APR_SUCCESS;
 
     if (m->aborted) {
-        status = APR_ECONNABORTED;
+        rv = APR_ECONNABORTED;
+        goto cleanup;
+    }
+    if (!stream->request) {
+        rv = APR_EINVAL;
+        goto cleanup;
+    }
+    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%s%s"),
+                      r->protocol? r->protocol : "",
+                      r->protocol? " " : "",
+                      r->method, r->scheme? r->scheme : "",
+                      r->scheme? "://" : "",
+                      r->authority, r->path? r->path: "");
+    }
+
+    stream->scheduled = 1;
+    h2_ihash_add(m->streams, stream);
+    if (h2_stream_is_ready(stream)) {
+        /* already have a response */
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+                      H2_STRM_MSG(stream, "process, ready already"));
     }
     else {
-        h2_iq_sort(m->q, cmp, ctx);
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                      "h2_mplx(%ld): reprioritize tasks", m->id);
-        status = APR_SUCCESS;
+        /* last chance to set anything up before stream is processed
+         * by worker threads. */
+        rv = h2_stream_prepare_processing(stream);
+        if (APR_SUCCESS != rv) goto cleanup;
+        h2_iq_add(m->q, stream->id, cmp, session);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+                      H2_STRM_MSG(stream, "process, added to q"));
     }
 
-    H2_MPLX_LEAVE(m);
-    return status;
+cleanup:
+    return rv;
 }
 
-static void ms_register_if_needed(h2_mplx *m, int from_master) 
+void h2_mplx_c1_process(h2_mplx *m,
+                        h2_iqueue *ready_to_process,
+                        h2_stream_get_fn *get_stream,
+                        h2_stream_pri_cmp_fn *stream_pri_cmp,
+                        h2_session *session,
+                        unsigned int *pstream_count)
 {
-    if (!m->aborted && !m->is_registered && !h2_iq_empty(m->q)) {
-        apr_status_t status = h2_workers_register(m->workers, m); 
-        if (status == APR_SUCCESS) {
-            m->is_registered = 1;
-        }
-        else if (from_master) {
-            ap_log_cerror(APLOG_MARK, APLOG_ERR, status, m->c, APLOGNO(10021)
-                          "h2_mplx(%ld): register at workers", m->id);
-        }
-    }
-}
+    apr_status_t rv;
+    int sid;
 
-apr_status_t h2_mplx_m_process(h2_mplx *m, struct h2_stream *stream, 
-                               h2_stream_pri_cmp *cmp, void *ctx)
-{
-    apr_status_t status;
-    
-    H2_MPLX_ENTER(m);
+    H2_MPLX_ENTER_ALWAYS(m);
 
-    if (m->aborted) {
-        status = APR_ECONNABORTED;
-    }
-    else {
-        status = APR_SUCCESS;
-        h2_ihash_add(m->streams, stream);
-        if (h2_stream_is_ready(stream)) {
-            /* already have a response */
-            mst_check_data_for(m, stream->id, 1);
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                          H2_STRM_MSG(stream, "process, add to readyq")); 
+    while ((sid = h2_iq_shift(ready_to_process)) > 0) {
+        h2_stream *stream = get_stream(session, sid);
+        if (stream) {
+            ap_assert(!stream->scheduled);
+            rv = c1_process_stream(session->mplx, stream, stream_pri_cmp, session);
+            if (APR_SUCCESS != rv) {
+                h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+            }
         }
         else {
-            h2_iq_add(m->q, stream->id, cmp, ctx);
-            ms_register_if_needed(m, 1);                
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                          H2_STRM_MSG(stream, "process, added to q")); 
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+                          H2_MPLX_MSG(m, "stream %d not found to process"), sid);
         }
     }
+    if ((m->processing_count < m->processing_limit) && !h2_iq_empty(m->q)) {
+        H2_MPLX_LEAVE(m);
+        rv = h2_workers_activate(m->workers, m->producer);
+        H2_MPLX_ENTER_ALWAYS(m);
+        if (rv != APR_SUCCESS) {
+            ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, m->c1, APLOGNO(10021)
+                          H2_MPLX_MSG(m, "activate at workers"));
+        }
+    }
+    *pstream_count = h2_ihash_count(m->streams);
+
+#if APR_POOL_DEBUG
+    do {
+        apr_size_t mem_g, mem_m, mem_s, mem_c1;
+
+        mem_g = pchild? apr_pool_num_bytes(pchild, 1) : 0;
+        mem_m = apr_pool_num_bytes(m->pool, 1);
+        mem_s = apr_pool_num_bytes(session->pool, 1);
+        mem_c1 = apr_pool_num_bytes(m->c1->pool, 1);
+        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, m->c1,
+                      H2_MPLX_MSG(m, "child mem=%ld, mplx mem=%ld, session mem=%ld, c1=%ld"),
+                      (long)mem_g, (long)mem_m, (long)mem_s, (long)mem_c1);
+
+    } while (0);
+#endif
 
     H2_MPLX_LEAVE(m);
-    return status;
 }
 
-static h2_task *s_next_stream_task(h2_mplx *m)
+static void c2_beam_input_write_notify(void *ctx, h2_bucket_beam *beam)
 {
-    h2_stream *stream;
-    int sid;
-    while (!m->aborted && (m->tasks_active < m->limit_active)
-           && (sid = h2_iq_shift(m->q)) > 0) {
-        
-        stream = h2_ihash_get(m->streams, sid);
-        if (stream) {
-            conn_rec *secondary, **psecondary;
+    conn_rec *c = ctx;
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
 
-            psecondary = (conn_rec **)apr_array_pop(m->spare_secondary);
-            if (psecondary) {
-                secondary = *psecondary;
-                secondary->aborted = 0;
-            }
-            else {
-                secondary = h2_secondary_create(m->c, stream->id, m->pool);
-            }
-            
-            if (!stream->task) {
-                if (sid > m->max_stream_started) {
-                    m->max_stream_started = sid;
-                }
-                if (stream->input) {
-                    h2_beam_on_consumed(stream->input, mst_stream_input_ev, 
-                                        m_stream_input_consumed, stream);
-                }
-                
-                stream->task = h2_task_create(secondary, stream->id, 
-                                              stream->request, m, stream->input, 
-                                              stream->session->s->timeout,
-                                              m->stream_max_mem);
-                if (!stream->task) {
-                    ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, secondary,
-                                  H2_STRM_LOG(APLOGNO(02941), stream, 
-                                  "create task"));
-                    return NULL;
-                }
-            }
-            
-            stream->task->started_at = apr_time_now();
-            ++m->tasks_active;
-            return stream->task;
-        }
+    (void)beam;
+    if (conn_ctx && conn_ctx->stream_id && conn_ctx->pipe_in[H2_PIPE_IN]) {
+        apr_file_putc(1, conn_ctx->pipe_in[H2_PIPE_IN]);
     }
-    if (m->tasks_active >= m->limit_active && !h2_iq_empty(m->q)) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
-                      "h2_session(%ld): delaying request processing. "
-                      "Current limit is %d and %d workers are in use.",
-                      m->id, m->limit_active, m->tasks_active);
-    }
-    return NULL;
 }
 
-apr_status_t h2_mplx_s_pop_task(h2_mplx *m, h2_task **ptask)
+static void add_stream_poll_event(h2_mplx *m, int stream_id, h2_iqueue *q)
 {
-    apr_status_t rv = APR_EOF;
-    
-    *ptask = NULL;
-    ap_assert(m);
-    ap_assert(m->lock);
-    
-    if (APR_SUCCESS != (rv = apr_thread_mutex_lock(m->lock))) {
-        return rv;
-    }
-    
-    if (m->aborted) {
-        rv = APR_EOF;
-    }
-    else {
-        *ptask = s_next_stream_task(m);
-        rv = (*ptask != NULL && !h2_iq_empty(m->q))? APR_EAGAIN : APR_SUCCESS;
+    apr_thread_mutex_lock(m->poll_lock);
+    if (h2_iq_append(q, stream_id) && h2_iq_count(q) == 1) {
+        /* newly added first */
+        apr_pollset_wakeup(m->pollset);
     }
-    if (APR_EAGAIN != rv) {
-        m->is_registered = 0; /* h2_workers will discard this mplx */
+    apr_thread_mutex_unlock(m->poll_lock);
+}
+
+static void c2_beam_input_read_notify(void *ctx, h2_bucket_beam *beam)
+{
+    conn_rec *c = ctx;
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+
+    if (conn_ctx && conn_ctx->stream_id) {
+        add_stream_poll_event(conn_ctx->mplx, conn_ctx->stream_id,
+                              conn_ctx->mplx->streams_input_read);
     }
-    H2_MPLX_LEAVE(m);
-    return rv;
 }
 
-static void s_task_done(h2_mplx *m, h2_task *task)
+static void c2_beam_input_read_eagain(void *ctx, h2_bucket_beam *beam)
 {
-    h2_stream *stream;
-    
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
-                  "h2_mplx(%ld): task(%s) done", m->id, task->id);
-    s_out_close(m, task);
-    
-    task->worker_done = 1;
-    task->done_at = apr_time_now();
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c,
-                  "h2_mplx(%s): request done, %f ms elapsed", task->id, 
-                  (task->done_at - task->started_at) / 1000.0);
-    
-    if (task->c && !task->c->aborted && task->started_at > m->last_mood_change) {
-        s_mplx_be_happy(m, task);
+    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]);
     }
-    
-    ap_assert(task->done_done == 0);
+}
 
-    stream = h2_ihash_get(m->streams, task->stream_id);
-    if (stream) {
-        /* stream not done yet. */
-        if (!m->aborted && task->redo) {
-            /* reset and schedule again */
-            h2_task_redo(task);
-            h2_iq_add(m->q, stream->id, NULL, NULL);
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
-                          H2_STRM_MSG(stream, "redo, added to q")); 
-        }
-        else {
-            /* stream not cleaned up, stay around */
-            task->done_done = 1;
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c,
-                          H2_STRM_MSG(stream, "task_done, stream open")); 
-            if (stream->input) {
-                h2_beam_leave(stream->input);
-            }
+static void c2_beam_output_write_notify(void *ctx, h2_bucket_beam *beam)
+{
+    conn_rec *c = ctx;
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
 
-            /* more data will not arrive, resume the stream */
-            mst_check_data_for(m, stream->id, 1);
-        }
-    }
-    else if ((stream = h2_ihash_get(m->shold, task->stream_id)) != NULL) {
-        /* stream is done, was just waiting for this. */
-        task->done_done = 1;
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c,
-                      H2_STRM_MSG(stream, "task_done, in hold"));
-        if (stream->input) {
-            h2_beam_leave(stream->input);
-        }
-        ms_stream_joined(m, stream);
-    }
-    else if ((stream = h2_ihash_get(m->spurge, task->stream_id)) != NULL) {
-        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, task->c,   
-                      H2_STRM_LOG(APLOGNO(03517), stream, "already in spurge"));
-        ap_assert("stream should not be in spurge" == NULL);
-    }
-    else {
-        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, task->c, APLOGNO(03518)
-                      "h2_mplx(%s): task_done, stream not found", 
-                      task->id);
-        ap_assert("stream should still be available" == NULL);
+    if (conn_ctx && conn_ctx->stream_id) {
+        add_stream_poll_event(conn_ctx->mplx, conn_ctx->stream_id,
+                              conn_ctx->mplx->streams_output_written);
     }
 }
 
-void h2_mplx_s_task_done(h2_mplx *m, h2_task *task, h2_task **ptask)
+static apr_status_t c2_setup_io(h2_mplx *m, conn_rec *c2, h2_stream *stream, h2_c2_transit *transit)
 {
-    H2_MPLX_ENTER_ALWAYS(m);
+    h2_conn_ctx_t *conn_ctx;
+    apr_status_t rv = APR_SUCCESS;
+    const char *action = "init";
 
-    --m->tasks_active;
-    s_task_done(m, task);
-    
-    if (m->join_wait) {
-        apr_thread_cond_signal(m->join_wait);
-    }
-    if (ptask) {
-        /* caller wants another task */
-        *ptask = s_next_stream_task(m);
-    }
-    ms_register_if_needed(m, 0);
+    rv = h2_conn_ctx_init_for_c2(&conn_ctx, c2, m, stream, transit);
+    if (APR_SUCCESS != rv) goto cleanup;
 
-    H2_MPLX_LEAVE(m);
-}
+    if (!conn_ctx->beam_out) {
+        action = "create output beam";
+        rv = h2_beam_create(&conn_ctx->beam_out, c2, conn_ctx->req_pool,
+                            stream->id, "output", 0, c2->base_server->timeout);
+        if (APR_SUCCESS != rv) goto cleanup;
 
-/*******************************************************************************
- * h2_mplx DoS protection
- ******************************************************************************/
+        h2_beam_buffer_size_set(conn_ctx->beam_out, m->stream_max_mem);
+        h2_beam_on_was_empty(conn_ctx->beam_out, c2_beam_output_write_notify, c2);
+    }
 
-static int m_timed_out_busy_iter(void *data, void *val)
-{
-    stream_iter_ctx *ctx = data;
-    h2_stream *stream = val;
-    if (h2_task_has_started(stream->task) && !stream->task->worker_done
-        && (ctx->now - stream->task->started_at) > stream->task->timeout) {
-        /* timed out stream occupying a worker, found */
-        ctx->stream = stream;
-        return 0;
+    memset(&conn_ctx->pipe_in, 0, sizeof(conn_ctx->pipe_in));
+    if (stream->input) {
+        conn_ctx->beam_in = stream->input;
+        h2_beam_on_send(stream->input, c2_beam_input_write_notify, c2);
+        h2_beam_on_received(stream->input, c2_beam_input_read_notify, c2);
+        h2_beam_on_consumed(stream->input, c1_input_consumed, stream);
+#if H2_USE_PIPES
+        action = "create input write pipe";
+        rv = apr_file_pipe_create_pools(&conn_ctx->pipe_in[H2_PIPE_OUT],
+                                        &conn_ctx->pipe_in[H2_PIPE_IN],
+                                        APR_READ_BLOCK,
+                                        c2->pool, c2->pool);
+        if (APR_SUCCESS != rv) goto cleanup;
+#endif
+        h2_beam_on_eagain(stream->input, c2_beam_input_read_eagain, c2);
+        if (!h2_beam_empty(stream->input))
+            c2_beam_input_write_notify(c2, stream->input);
+    }
+
+cleanup:
+    stream->output = (APR_SUCCESS == rv)? conn_ctx->beam_out : NULL;
+    if (APR_SUCCESS != rv) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c2,
+                      H2_STRM_LOG(APLOGNO(10309), stream,
+                      "error %s"), action);
     }
-    return 1;
+    return rv;
 }
 
-static h2_stream *m_get_timed_out_busy_stream(h2_mplx *m) 
+static conn_rec *s_next_c2(h2_mplx *m)
 {
-    stream_iter_ctx ctx;
-    ctx.m = m;
-    ctx.stream = NULL;
-    ctx.now = apr_time_now();
-    h2_ihash_iter(m->streams, m_timed_out_busy_iter, &ctx);
-    return ctx.stream;
-}
+    h2_stream *stream = NULL;
+    apr_status_t rv = APR_SUCCESS;
+    apr_uint32_t sid;
+    conn_rec *c2 = NULL;
+    h2_c2_transit *transit = NULL;
 
-static int m_latest_repeatable_unsubmitted_iter(void *data, void *val)
-{
-    stream_iter_ctx *ctx = data;
-    h2_stream *stream = val;
-    
-    if (!stream->task) goto leave;
-    if (!h2_task_has_started(stream->task) || stream->task->worker_done) goto leave;
-    if (h2_stream_is_ready(stream)) goto leave;
-    if (stream->task->redo) {
-        ++ctx->count;
-        goto leave;
-    }
-    if (h2_task_can_redo(stream->task)) {
-        /* this task occupies a worker, the response has not been submitted 
-         * yet, not been cancelled and it is a repeatable request
-         * -> we could redo it later */
-        if (!ctx->stream 
-            || (ctx->stream->task->started_at < stream->task->started_at)) {
-            /* we did not have one or this one was started later */
-            ctx->stream = stream;
-        }
+    while (!m->aborted && !stream && (m->processing_count < m->processing_limit)
+           && (sid = h2_iq_shift(m->q)) > 0) {
+        stream = h2_ihash_get(m->streams, sid);
     }
-leave:
-    return 1;
-}
 
-static apr_status_t m_assess_task_to_throttle(h2_task **ptask, h2_mplx *m) 
-{
-    stream_iter_ctx ctx;
-    
-    /* count the running tasks already marked for redo and get one that could
-     * be throttled */
-    *ptask = NULL;
-    ctx.m = m;
-    ctx.stream = NULL;
-    ctx.count = 0;
-    h2_ihash_iter(m->streams, m_latest_repeatable_unsubmitted_iter, &ctx);
-    if (m->tasks_active - ctx.count > m->limit_active) {
-        /* we are above the limit of running tasks, accounting for the ones
-         * already throttled. */
-        if (ctx.stream && ctx.stream->task) {
-            *ptask = ctx.stream->task;
-            return APR_EAGAIN;
-        }
-        /* above limit, be seeing no candidate for easy throttling */
-        if (m_get_timed_out_busy_stream(m)) {
-            /* Too many busy workers, unable to cancel enough streams
-             * and with a busy, timed out stream, we tell the client
-             * to go away... */
-            return APR_TIMEUP;
+    if (!stream) {
+        if (m->processing_count >= m->processing_limit && !h2_iq_empty(m->q)) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1,
+                          H2_MPLX_MSG(m, "delaying request processing. "
+                          "Current limit is %d and %d workers are in use."),
+                          m->processing_limit, m->processing_count);
         }
+        goto cleanup;
     }
-    return APR_SUCCESS;
-}
 
-static apr_status_t m_unschedule_slow_tasks(h2_mplx *m) 
-{
-    h2_task *task;
-    apr_status_t rv;
-    
-    /* Try to get rid of streams that occupy workers. Look for safe requests
-     * that are repeatable. If none found, fail the connection.
-     */
-    while (APR_EAGAIN == (rv = m_assess_task_to_throttle(&task, m))) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c, 
-                      "h2_mplx(%s): unschedule, resetting task for redo later",
-                      task->id);
-        task->redo = 1;
-        h2_task_rst(task, H2_ERR_CANCEL);
+    if (sid > m->max_stream_id_started) {
+        m->max_stream_id_started = sid;
     }
-    return rv;
-}
 
-static apr_status_t s_mplx_be_happy(h2_mplx *m, h2_task *task)
-{
-    apr_time_t now;            
+    transit = c2_transit_get(m);
+#if AP_HAS_RESPONSE_BUCKETS
+    c2 = ap_create_secondary_connection(transit->pool, m->c1, transit->bucket_alloc);
+#else
+    c2 = h2_c2_create(m->c1, transit->pool, transit->bucket_alloc);
+#endif
+    if (!c2) goto cleanup;
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, m->c1,
+                  H2_STRM_MSG(stream, "created new c2"));
+
+    rv = c2_setup_io(m, c2, stream, transit);
+    if (APR_SUCCESS != rv) goto cleanup;
+
+    stream->c2 = c2;
+    ++m->processing_count;
 
-    --m->irritations_since;
-    now = apr_time_now();
-    if (m->limit_active < m->max_active 
-        && (now - m->last_mood_change >= m->mood_update_interval
-            || m->irritations_since < -m->limit_active)) {
-        m->limit_active = H2MIN(m->limit_active * 2, m->max_active);
-        m->last_mood_change = now;
-        m->irritations_since = 0;
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
-                      "h2_mplx(%ld): mood update, increasing worker limit to %d",
-                      m->id, m->limit_active);
+cleanup:
+    if (APR_SUCCESS != rv && c2) {
+        h2_c2_destroy(c2);
+        c2 = NULL;
     }
-    return APR_SUCCESS;
+    if (transit && !c2) {
+        c2_transit_recycle(m, transit);
+    }
+    return c2;
 }
 
-static apr_status_t m_be_annoyed(h2_mplx *m)
+static conn_rec *c2_prod_next(void *baton, int *phas_more)
 {
-    apr_status_t status = APR_SUCCESS;
-    apr_time_t now;            
+    h2_mplx *m = baton;
+    conn_rec *c = NULL;
 
-    ++m->irritations_since;
-    now = apr_time_now();
-    if (m->limit_active > 2 && 
-        ((now - m->last_mood_change >= m->mood_update_interval)
-         || (m->irritations_since >= m->limit_active))) {
-            
-        if (m->limit_active > 16) {
-            m->limit_active = 16;
-        }
-        else if (m->limit_active > 8) {
-            m->limit_active = 8;
-        }
-        else if (m->limit_active > 4) {
-            m->limit_active = 4;
-        }
-        else if (m->limit_active > 2) {
-            m->limit_active = 2;
-        }
-        m->last_mood_change = now;
-        m->irritations_since = 0;
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                      "h2_mplx(%ld): mood update, decreasing worker limit to %d",
-                      m->id, m->limit_active);
-    }
-    
-    if (m->tasks_active > m->limit_active) {
-        status = m_unschedule_slow_tasks(m);
+    H2_MPLX_ENTER_ALWAYS(m);
+    if (!m->aborted) {
+        c = s_next_c2(m);
+        *phas_more = (c != NULL && !h2_iq_empty(m->q));
     }
-    return status;
+    H2_MPLX_LEAVE(m);
+    return c;
 }
 
-apr_status_t h2_mplx_m_idle(h2_mplx *m)
+static void s_c2_done(h2_mplx *m, conn_rec *c2, h2_conn_ctx_t *conn_ctx)
 {
-    apr_status_t status = APR_SUCCESS;
-    apr_size_t scount;
+    h2_stream *stream;
+
+    ap_assert(conn_ctx);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+                  "h2_mplx(%s-%d): c2 done", conn_ctx->id, conn_ctx->stream_id);
+
+    AP_DEBUG_ASSERT(apr_atomic_read32(&conn_ctx->done) == 0);
+    apr_atomic_set32(&conn_ctx->done, 1);
+    conn_ctx->done_at = apr_time_now();
+    ++c2->keepalives;
+    /* From here on, the final handling of c2 is done by c1 processing.
+     * Which means we can give it c1's scoreboard handle for updates. */
+    c2->sbh = m->c1->sbh;
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+                  "h2_mplx(%s-%d): request done, %f ms elapsed",
+                  conn_ctx->id, conn_ctx->stream_id,
+                  (conn_ctx->done_at - conn_ctx->started_at) / 1000.0);
+    
+    if (!conn_ctx->has_final_response) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, conn_ctx->last_err, c2,
+                      "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);
+    }
     
-    H2_MPLX_ENTER(m);
+    stream = h2_ihash_get(m->streams, conn_ctx->stream_id);
+    if (stream) {
+        /* stream not done yet. trigger a potential polling on the output
+         * since nothing more will happening here. */
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+                      H2_STRM_MSG(stream, "c2_done, stream open"));
+        c2_beam_output_write_notify(c2, NULL);
+    }
+    else if ((stream = h2_ihash_get(m->shold, conn_ctx->stream_id)) != NULL) {
+        /* stream is done, was just waiting for this. */
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+                      H2_STRM_MSG(stream, "c2_done, in hold"));
+        c1c2_stream_joined(m, stream);
+    }
+    else {
+        int i;
 
-    scount = h2_ihash_count(m->streams);
-    if (scount > 0) {
-        if (m->tasks_active) {
-            /* If we have streams in connection state 'IDLE', meaning
-             * all streams are ready to sent data out, but lack
-             * WINDOW_UPDATEs. 
-             * 
-             * This is ok, unless we have streams that still occupy
-             * h2 workers. As worker threads are a scarce resource, 
-             * we need to take measures that we do not get DoSed.
-             * 
-             * This is what we call an 'idle block'. Limit the amount 
-             * of busy workers we allow for this connection until it
-             * well behaves.
-             */
-            status = m_be_annoyed(m);
-        }
-        else if (!h2_iq_empty(m->q)) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                          "h2_mplx(%ld): idle, but %d streams to process",
-                          m->id, (int)h2_iq_count(m->q));
-            status = APR_EAGAIN;
-        }
-        else {
-            /* idle, have streams, but no tasks active. what are we waiting for?
-             * WINDOW_UPDATEs from client? */
-            h2_stream *stream = NULL;
-            
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                          "h2_mplx(%ld): idle, no tasks ongoing, %d streams",
-                          m->id, (int)h2_ihash_count(m->streams));
-            h2_ihash_shift(m->streams, (void**)&stream, 1);
-            if (stream) {
-                h2_ihash_add(m->streams, stream);
-                if (stream->output && !stream->out_checked) {
-                    /* FIXME: this looks like a race between the session thinking
-                     * it is idle and the EOF on a stream not being sent.
-                     * Signal to caller to leave IDLE state.
-                     */
-                    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
-                                  H2_STRM_MSG(stream, "output closed=%d, mplx idle"
-                                              ", out has %ld bytes buffered"),
-                                  h2_beam_is_closed(stream->output),
-                                  (long)h2_beam_get_buffered(stream->output));
-                    h2_ihash_add(m->streams, stream);
-                    mst_check_data_for(m, stream->id, 1);
-                    stream->out_checked = 1;
-                    status = APR_EAGAIN;
-                }
+        for (i = 0; i < m->spurge->nelts; ++i) {
+            if (stream == APR_ARRAY_IDX(m->spurge, i, h2_stream*)) {
+                ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c2,
+                              H2_STRM_LOG(APLOGNO(03517), stream, "already in spurge"));
+                ap_assert("stream should not be in spurge" == NULL);
+                return;
             }
         }
+
+        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c2, APLOGNO(03518)
+                      "h2_mplx(%s-%d): c2_done, stream not found",
+                      conn_ctx->id, conn_ctx->stream_id);
+        ap_assert("stream should still be available" == NULL);
     }
-    ms_register_if_needed(m, 1);
+}
+
+static void c2_prod_done(void *baton, conn_rec *c2)
+{
+    h2_mplx *m = baton;
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+
+    AP_DEBUG_ASSERT(conn_ctx);
+    H2_MPLX_ENTER_ALWAYS(m);
+
+    --m->processing_count;
+    s_c2_done(m, c2, conn_ctx);
+    if (m->join_wait) apr_thread_cond_signal(m->join_wait);
 
     H2_MPLX_LEAVE(m);
-    return status;
+}
+
+static void workers_shutdown(void *baton, int graceful)
+{
+    h2_mplx *m = baton;
+
+    apr_thread_mutex_lock(m->poll_lock);
+    /* time to wakeup and assess what to do */
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+                  H2_MPLX_MSG(m, "workers shutdown, waking pollset"));
+    m->shutdown = 1;
+    if (!graceful) {
+        m->aborted = 1;
+    }
+    apr_pollset_wakeup(m->pollset);
+    apr_thread_mutex_unlock(m->poll_lock);
 }
 
 /*******************************************************************************
- * mplx master events dispatching
+ * h2_mplx DoS protection
  ******************************************************************************/
 
-int h2_mplx_m_has_master_events(h2_mplx *m)
+static void s_mplx_be_happy(h2_mplx *m, conn_rec *c, h2_conn_ctx_t *conn_ctx)
 {
-    return apr_atomic_read32(&m->event_pending) > 0;
-}
+    apr_time_t now;            
 
-apr_status_t h2_mplx_m_dispatch_master_events(h2_mplx *m, stream_ev_callback *on_resume, 
-                                              void *on_ctx)
-{
-    h2_stream *stream;
-    int n, id;
-    
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c, 
-                  "h2_mplx(%ld): dispatch events", m->id);        
-    apr_atomic_set32(&m->event_pending, 0);
-
-    /* update input windows for streams */
-    h2_ihash_iter(m->streams, m_report_consumption_iter, m);    
-    m_purge_streams(m, 1);
-    
-    n = h2_ififo_count(m->readyq);
-    while (n > 0 
-           && (h2_ififo_try_pull(m->readyq, &id) == APR_SUCCESS)) {
-        --n;
-        stream = h2_ihash_get(m->streams, id);
-        if (stream) {
-            on_resume(on_ctx, stream);
+    if (m->processing_limit < m->processing_max
+        && conn_ctx->started_at > m->last_mood_change) {
+        --m->irritations_since;
+        if (m->processing_limit < m->processing_max
+            && ((now = apr_time_now()) - m->last_mood_change >= m->mood_update_interval
+                || m->irritations_since < -m->processing_limit)) {
+            m->processing_limit = H2MIN(m->processing_limit * 2, m->processing_max);
+            m->last_mood_change = now;
+            m->irritations_since = 0;
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                          H2_MPLX_MSG(m, "mood update, increasing worker limit to %d"),
+                          m->processing_limit);
         }
     }
-    
-    return APR_SUCCESS;
 }
 
-apr_status_t h2_mplx_m_keep_active(h2_mplx *m, h2_stream *stream)
+static void m_be_annoyed(h2_mplx *m)
 {
-    mst_check_data_for(m, stream->id, 0);
-    return APR_SUCCESS;
-}
+    apr_time_t now;
 
-int h2_mplx_m_awaits_data(h2_mplx *m)
-{
-    int waiting = 1;
-     
-    H2_MPLX_ENTER_ALWAYS(m);
+    if (m->processing_limit > 2) {
+        ++m->irritations_since;
+        if (((now = apr_time_now()) - m->last_mood_change >= m->mood_update_interval)
+            || (m->irritations_since >= m->processing_limit)) {
 
-    if (h2_ihash_empty(m->streams)) {
-        waiting = 0;
-    }
-    else if (!m->tasks_active && !h2_ififo_count(m->readyq) && h2_iq_empty(m->q)) {
-        waiting = 0;
+            if (m->processing_limit > 16) {
+                m->processing_limit = 16;
+            }
+            else if (m->processing_limit > 8) {
+                m->processing_limit = 8;
+            }
+            else if (m->processing_limit > 4) {
+                m->processing_limit = 4;
+            }
+            else if (m->processing_limit > 2) {
+                m->processing_limit = 2;
+            }
+            m->last_mood_change = now;
+            m->irritations_since = 0;
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+                          H2_MPLX_MSG(m, "mood update, decreasing worker limit to %d"),
+                          m->processing_limit);
+        }
     }
-
-    H2_MPLX_LEAVE(m);
-    return waiting;
 }
 
+/*******************************************************************************
+ * mplx master events dispatching
+ ******************************************************************************/
+
 static int reset_is_acceptable(h2_stream *stream)
 {
     /* client may terminate a stream via H2 RST_STREAM message at any time.
@@ -1151,23 +1112,153 @@
      * The responses to such requests continue forever otherwise.
      *
      */
-    if (!stream->task) return 1; /* have not started or already ended for us. acceptable. */
+    if (!stream_is_running(stream)) return 1;
     if (!(stream->id & 0x01)) return 1; /* stream initiated by us. acceptable. */
-    if (!stream->has_response) return 0; /* no response headers produced yet. bad. */
+    if (!stream->response) return 0; /* no response headers produced yet. bad. */
     if (!stream->out_data_frames) return 0; /* no response body data sent yet. bad. */
     return 1; /* otherwise, be forgiving */
 }
 
-apr_status_t h2_mplx_m_client_rst(h2_mplx *m, int stream_id)
+apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id, h2_stream *stream)
 {
-    h2_stream *stream;
     apr_status_t status = APR_SUCCESS;
+    int registered;
 
     H2_MPLX_ENTER_ALWAYS(m);
-    stream = h2_ihash_get(m->streams, stream_id);
-    if (stream && !reset_is_acceptable(stream)) {
-        status = m_be_annoyed(m);
+    registered = (h2_ihash_get(m->streams, stream_id) != NULL);
+    if (!stream) {
+      /* a RST might arrive so late, we have already forgotten
+       * about it. Seems ok. */
+      ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1,
+                    H2_MPLX_MSG(m, "RST on unknown stream %d"), stream_id);
+      AP_DEBUG_ASSERT(!registered);
+    }
+    else if (!registered) {
+      /* a RST on a stream that mplx has not been told about, but
+       * which the session knows. Very early and annoying. */
+      ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1,
+                    H2_STRM_MSG(stream, "very early RST, drop"));
+      h2_stream_set_monitor(stream, NULL);
+      h2_stream_rst(stream, H2_ERR_STREAM_CLOSED);
+      h2_stream_dispatch(stream, H2_SEV_EOS_SENT);
+      m_stream_cleanup(m, stream);
+      m_be_annoyed(m);
+    }
+    else if (!reset_is_acceptable(stream)) {
+        m_be_annoyed(m);
     }
     H2_MPLX_LEAVE(m);
     return status;
 }
+
+static apr_status_t mplx_pollset_create(h2_mplx *m)
+{
+    /* stream0 output only */
+    return apr_pollset_create(&m->pollset, 1, m->pool,
+                              APR_POLLSET_WAKEABLE);
+}
+
+static apr_status_t mplx_pollset_poll(h2_mplx *m, apr_interval_time_t timeout,
+                            stream_ev_callback *on_stream_input,
+                            stream_ev_callback *on_stream_output,
+                            void *on_ctx)
+{
+    apr_status_t rv;
+    const apr_pollfd_t *results, *pfd;
+    apr_int32_t nresults, i;
+    h2_conn_ctx_t *conn_ctx;
+    h2_stream *stream;
+
+    /* Make sure we are not called recursively. */
+    ap_assert(!m->polling);
+    m->polling = 1;
+    do {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+                      H2_MPLX_MSG(m, "enter polling timeout=%d"),
+                      (int)apr_time_sec(timeout));
+
+        apr_array_clear(m->streams_ev_in);
+        apr_array_clear(m->streams_ev_out);
+
+        do {
+            /* add streams we started processing in the meantime */
+            apr_thread_mutex_lock(m->poll_lock);
+            if (!h2_iq_empty(m->streams_input_read)
+                || !h2_iq_empty(m->streams_output_written)) {
+                while ((i = h2_iq_shift(m->streams_input_read))) {
+                    stream = h2_ihash_get(m->streams, i);
+                    if (stream) {
+                        APR_ARRAY_PUSH(m->streams_ev_in, h2_stream*) = stream;
+                    }
+                }
+                while ((i = h2_iq_shift(m->streams_output_written))) {
+                    stream = h2_ihash_get(m->streams, i);
+                    if (stream) {
+                        APR_ARRAY_PUSH(m->streams_ev_out, h2_stream*) = stream;
+                    }
+                }
+                nresults = 0;
+                rv = APR_SUCCESS;
+                apr_thread_mutex_unlock(m->poll_lock);
+                break;
+            }
+            apr_thread_mutex_unlock(m->poll_lock);
+
+            H2_MPLX_LEAVE(m);
+            rv = apr_pollset_poll(m->pollset, timeout >= 0? timeout : -1, &nresults, &results);
+            H2_MPLX_ENTER_ALWAYS(m);
+            if (APR_STATUS_IS_EINTR(rv) && m->shutdown) {
+                if (!m->aborted) {
+                    rv = APR_SUCCESS;
+                }
+                goto cleanup;
+            }
+        } while (APR_STATUS_IS_EINTR(rv));
+
+        if (APR_SUCCESS != rv) {
+            if (APR_STATUS_IS_TIMEUP(rv)) {
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+                              H2_MPLX_MSG(m, "polling timed out "));
+            }
+            else {
+                ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, m->c1, APLOGNO(10310) \
+                              H2_MPLX_MSG(m, "polling failed"));
+            }
+            goto cleanup;
+        }
+
+        for (i = 0; i < nresults; i++) {
+            pfd = &results[i];
+            conn_ctx = pfd->client_data;
+
+            AP_DEBUG_ASSERT(conn_ctx);
+            if (conn_ctx->stream_id == 0) {
+                if (on_stream_input) {
+                    APR_ARRAY_PUSH(m->streams_ev_in, h2_stream*) = m->stream0;
+                }
+                continue;
+            }
+        }
+
+        if (on_stream_input && m->streams_ev_in->nelts) {
+            H2_MPLX_LEAVE(m);
+            for (i = 0; i < m->streams_ev_in->nelts; ++i) {
+                on_stream_input(on_ctx, APR_ARRAY_IDX(m->streams_ev_in, i, h2_stream*));
+            }
+            H2_MPLX_ENTER_ALWAYS(m);
+        }
+        if (on_stream_output && m->streams_ev_out->nelts) {
+            H2_MPLX_LEAVE(m);
+            for (i = 0; i < m->streams_ev_out->nelts; ++i) {
+                on_stream_output(on_ctx, APR_ARRAY_IDX(m->streams_ev_out, i, h2_stream*));
+            }
+            H2_MPLX_ENTER_ALWAYS(m);
+        }
+        break;
+    } while(1);
+
+cleanup:
+    m->polling = 0;
+    return rv;
+}
+
diff -r -N -u a/modules/http2/h2_mplx.h b/modules/http2/h2_mplx.h
--- a/modules/http2/h2_mplx.h	2020-07-08 13:53:48.000000000 +0200
+++ b/modules/http2/h2_mplx.h	2024-10-30 21:40:01.059958617 +0100
@@ -18,23 +18,16 @@
 #define __mod_h2__h2_mplx__
 
 /**
- * The stream multiplexer. It pushes buckets from the connection
- * thread to the stream threads and vice versa. It's thread-safe
- * to use.
+ * The stream multiplexer. It performs communication between the
+ * primary HTTP/2 connection (c1) to the secondary connections (c2)
+ * that process the requests, aka. HTTP/2 streams.
  *
- * There is one h2_mplx instance for each h2_session, which sits on top
- * of a particular httpd conn_rec. Input goes from the connection to
- * the stream tasks. Output goes from the stream tasks to the connection,
- * e.g. the client.
+ * There is one h2_mplx instance for each h2_session.
  *
- * For each stream, there can be at most "H2StreamMaxMemSize" output bytes
- * queued in the multiplexer. If a task thread tries to write more
- * data, it is blocked until space becomes available.
- *
- * Naming Convention: 
- * "h2_mplx_m_" are methods only to be called by the main connection
- * "h2_mplx_s_" are method only to be called by a secondary connection
- * "h2_mplx_t_" are method only to be called by a task handler (can be master or secondary)
+ * Naming Convention:
+ * "h2_mplx_c1_" are methods only to be called by the primary connection
+ * "h2_mplx_c2_" are methods only to be called by a secondary connection
+ * "h2_mplx_worker_" are methods only to be called by a h2 worker thread
  */
 
 struct apr_pool_t;
@@ -43,7 +36,6 @@
 struct h2_bucket_beam;
 struct h2_config;
 struct h2_ihash_t;
-struct h2_task;
 struct h2_stream;
 struct h2_request;
 struct apr_thread_cond_t;
@@ -52,78 +44,89 @@
 
 #include <apr_queue.h>
 
+#include "h2_workers.h"
+
+typedef struct h2_c2_transit h2_c2_transit;
+
+struct h2_c2_transit {
+    apr_pool_t *pool;
+    apr_bucket_alloc_t *bucket_alloc;
+};
+
 typedef struct h2_mplx h2_mplx;
 
 struct h2_mplx {
-    long id;
-    conn_rec *c;
+    int child_num;                  /* child this runs in */
+    apr_uint32_t id;                /* id unique per child */
+    conn_rec *c1;                   /* the main connection */
     apr_pool_t *pool;
+    struct h2_stream *stream0;      /* HTTP/2's stream 0 */
     server_rec *s;                  /* server for master conn */
 
-    unsigned int event_pending;
-    unsigned int aborted;
-    unsigned int is_registered;     /* is registered at h2_workers */
-
-    struct h2_ihash_t *streams;     /* all streams currently processing */
-    struct h2_ihash_t *shold;       /* all streams done with task ongoing */
-    struct h2_ihash_t *spurge;      /* all streams done, ready for destroy */
+    int shutdown;                   /* we are shutting down */
+    int aborted;                    /* we need to get out of here asap */
+    int polling;                    /* is waiting/processing pollset events */
+    ap_conn_producer_t *producer;   /* registered producer at h2_workers */
+
+    struct h2_ihash_t *streams;     /* all streams active */
+    struct h2_ihash_t *shold;       /* all streams done with c2 processing ongoing */
+    apr_array_header_t *spurge;     /* all streams done, ready for destroy */
     
     struct h2_iqueue *q;            /* all stream ids that need to be started */
-    struct h2_ififo *readyq;        /* all stream ids ready for output */
-        
-    struct h2_ihash_t *redo_tasks;  /* all tasks that need to be redone */
-    
-    int max_streams;        /* max # of concurrent streams */
-    int max_stream_started; /* highest stream id that started processing */
-    int tasks_active;       /* # of tasks being processed from this mplx */
-    int limit_active;       /* current limit on active tasks, dynamic */
-    int max_active;         /* max, hard limit # of active tasks in a process */
+
+    apr_size_t stream_max_mem;      /* max memory to buffer for a stream */
+    apr_uint32_t max_streams;       /* max # of concurrent streams */
+    apr_uint32_t max_stream_id_started; /* highest stream id that started processing */
+
+    apr_uint32_t processing_count;  /* # of c2 working for this mplx */
+    apr_uint32_t processing_limit;  /* current limit on processing c2s, dynamic */
+    apr_uint32_t processing_max;    /* max, hard limit of processing c2s */
     
-    apr_time_t last_mood_change; /* last time, we worker limit changed */
+    apr_time_t last_mood_change;    /* last time, processing limit changed */
     apr_interval_time_t mood_update_interval; /* how frequent we update at most */
-    int irritations_since; /* irritations (>0) or happy events (<0) since last mood change */
+    apr_uint32_t irritations_since; /* irritations (>0) or happy events (<0) since last mood change */
 
     apr_thread_mutex_t *lock;
-    struct apr_thread_cond_t *added_output;
     struct apr_thread_cond_t *join_wait;
     
-    apr_size_t stream_max_mem;
-    
-    apr_pool_t *spare_io_pool;
-    apr_array_header_t *spare_secondary; /* spare secondary connections */
-    
-    struct h2_workers *workers;
-};
+    apr_pollset_t *pollset;         /* pollset for c1/c2 IO events */
+    apr_array_header_t *streams_ev_in;
+    apr_array_header_t *streams_ev_out;
+
+    apr_thread_mutex_t *poll_lock; /* protect modifications of queues below */
+    struct h2_iqueue *streams_input_read;  /* streams whose input has been read from */
+    struct h2_iqueue *streams_output_written; /* streams whose output has been written to */
 
-/*******************************************************************************
- * From the main connection processing: h2_mplx_m_*
- ******************************************************************************/
+    struct h2_workers *workers;     /* h2 workers process wide instance */
 
-apr_status_t h2_mplx_m_child_init(apr_pool_t *pool, server_rec *s);
+    apr_uint32_t max_spare_transits; /* max number of transit pools idling */
+    apr_array_header_t *c2_transits; /* base pools for running c2 connections */
+};
+
+apr_status_t h2_mplx_c1_child_init(apr_pool_t *pool, server_rec *s);
 
 /**
  * Create the multiplexer for the given HTTP2 session. 
  * Implicitly has reference count 1.
  */
-h2_mplx *h2_mplx_m_create(conn_rec *c, server_rec *s, apr_pool_t *master, 
-                          struct h2_workers *workers);
+h2_mplx *h2_mplx_c1_create(int child_id, apr_uint32_t id,
+                           struct h2_stream *stream0,
+                           server_rec *s, apr_pool_t *master,
+                           struct h2_workers *workers);
 
 /**
- * Decreases the reference counter of this mplx and waits for it
- * to reached 0, destroy the mplx afterwards.
- * This is to be called from the thread that created the mplx in
- * the first place.
- * @param m the mplx to be released and destroyed
+ * Destroy the mplx, shutting down all ongoing processing.
+ * @param m the mplx destroyed
  * @param wait condition var to wait on for ref counter == 0
  */ 
-void h2_mplx_m_release_and_join(h2_mplx *m, struct apr_thread_cond_t *wait);
+void h2_mplx_c1_destroy(h2_mplx *m);
 
 /**
  * Shut down the multiplexer gracefully. Will no longer schedule new streams
  * but let the ongoing ones finish normally.
  * @return the highest stream id being/been processed
  */
-int h2_mplx_m_shutdown(h2_mplx *m);
+int h2_mplx_c1_shutdown(h2_mplx *m);
 
 /**
  * Notifies mplx that a stream has been completely handled on the main
@@ -131,29 +134,28 @@
  * 
  * @param m the mplx itself
  * @param stream the stream ready for cleanup
+ * @param pstream_count return the number of streams active
  */
-apr_status_t h2_mplx_m_stream_cleanup(h2_mplx *m, struct h2_stream *stream);
-
-/**
- * Waits on output data from any stream in this session to become available. 
- * Returns APR_TIMEUP if no data arrived in the given time.
- */
-apr_status_t h2_mplx_m_out_trywait(h2_mplx *m, apr_interval_time_t timeout,
-                                   struct apr_thread_cond_t *iowait);
+apr_status_t h2_mplx_c1_stream_cleanup(h2_mplx *m, struct h2_stream *stream,
+                                       unsigned int *pstream_count);
 
-apr_status_t h2_mplx_m_keep_active(h2_mplx *m, struct h2_stream *stream);
+int h2_mplx_c1_stream_is_running(h2_mplx *m, struct h2_stream *stream);
 
 /**
  * Process a stream request.
  * 
  * @param m the multiplexer
- * @param stream the identifier of the stream
- * @param r the request to be processed
+ * @param read_to_process
+ * @param input_pending
  * @param cmp the stream priority compare function
- * @param ctx context data for the compare function
+ * @param pstream_count on return the number of streams active in mplx
  */
-apr_status_t h2_mplx_m_process(h2_mplx *m, struct h2_stream *stream, 
-                               h2_stream_pri_cmp *cmp, void *ctx);
+void h2_mplx_c1_process(h2_mplx *m,
+                        struct h2_iqueue *read_to_process,
+                        h2_stream_get_fn *get_stream,
+                        h2_stream_pri_cmp_fn *cmp,
+                        struct h2_session *session,
+                        unsigned int *pstream_count);
 
 /**
  * Stream priorities have changed, reschedule pending requests.
@@ -162,62 +164,67 @@
  * @param cmp the stream priority compare function
  * @param ctx context data for the compare function
  */
-apr_status_t h2_mplx_m_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx);
+apr_status_t h2_mplx_c1_reprioritize(h2_mplx *m, h2_stream_pri_cmp_fn *cmp,
+                                    struct h2_session *session);
 
-typedef apr_status_t stream_ev_callback(void *ctx, struct h2_stream *stream);
+typedef void stream_ev_callback(void *ctx, struct h2_stream *stream);
 
 /**
- * Check if the multiplexer has events for the master connection pending.
- * @return != 0 iff there are events pending
+ * Poll the primary connection for input and the active streams for output.
+ * Invoke the callback for any stream where an event happened.
  */
-int h2_mplx_m_has_master_events(h2_mplx *m);
-
-/**
- * Dispatch events for the master connection, such as
- ± @param m the multiplexer
- * @param on_resume new output data has arrived for a suspended stream 
- * @param ctx user supplied argument to invocation.
- */
-apr_status_t h2_mplx_m_dispatch_master_events(h2_mplx *m, stream_ev_callback *on_resume, 
-                                              void *ctx);
-
-int h2_mplx_m_awaits_data(h2_mplx *m);
+apr_status_t h2_mplx_c1_poll(h2_mplx *m, apr_interval_time_t timeout,
+                            stream_ev_callback *on_stream_input,
+                            stream_ev_callback *on_stream_output,
+                            void *on_ctx);
 
-typedef int h2_mplx_stream_cb(struct h2_stream *s, void *ctx);
+void h2_mplx_c2_input_read(h2_mplx *m, conn_rec *c2);
+void h2_mplx_c2_output_written(h2_mplx *m, conn_rec *c2);
 
-apr_status_t h2_mplx_m_stream_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx);
+typedef int h2_mplx_stream_cb(struct h2_stream *s, void *userdata);
 
-apr_status_t h2_mplx_m_client_rst(h2_mplx *m, int stream_id);
+/**
+ * Iterate over all streams known to mplx from the primary connection.
+ * @param m the mplx
+ * @param cb the callback to invoke on each stream
+ * @param ctx userdata passed to the callback
+ */
+apr_status_t h2_mplx_c1_streams_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx);
 
 /**
- * Master connection has entered idle mode.
- * @param m the mplx instance of the master connection
- * @return != SUCCESS iff connection should be terminated
+ * Return != 0 iff all open streams want to send data
  */
-apr_status_t h2_mplx_m_idle(h2_mplx *m);
+int h2_mplx_c1_all_streams_want_send_data(h2_mplx *m);
 
-/*******************************************************************************
- * From a secondary connection processing: h2_mplx_s_*
- ******************************************************************************/
-apr_status_t h2_mplx_s_pop_task(h2_mplx *m, struct h2_task **ptask);
-void h2_mplx_s_task_done(h2_mplx *m, struct h2_task *task, struct h2_task **ptask);
+/**
+ * A stream has been RST_STREAM by the client. Abort
+ * any processing going on and remove from processing
+ * queue.
+ */
+apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id,
+                                   struct h2_stream *stream);
 
-/*******************************************************************************
- * From a h2_task owner: h2_mplx_s_*
- * (a task is transfered from master to secondary connection and back in
- * its normal lifetime).
- ******************************************************************************/
+/**
+ * Get readonly access to a stream for a secondary connection.
+ */
+const struct h2_stream *h2_mplx_c2_stream_get(h2_mplx *m, int stream_id);
 
 /**
- * Opens the output for the given stream with the specified response.
+ * A h2 worker asks for a secondary connection to process.
+ * @param out_c2 non-NULL, a pointer where to reveive the next
+ *               secondary connection to process.
  */
-apr_status_t h2_mplx_t_out_open(h2_mplx *mplx, int stream_id,
-                                struct h2_bucket_beam *beam);
+apr_status_t h2_mplx_worker_pop_c2(h2_mplx *m, conn_rec **out_c2);
+
 
 /**
- * Get the stream that belongs to the given task.
+ * Session processing is entering KEEPALIVE, e.g. giving control
+ * to the MPM for monitoring incoming socket events only.
+ * Last chance for maintenance work before losing control.
  */
-struct h2_stream *h2_mplx_t_stream_get(h2_mplx *m, struct h2_task *task);
+void h2_mplx_c1_going_keepalive(h2_mplx *m);
 
+#define H2_MPLX_MSG(m, msg)     \
+    "h2_mplx(%d-%lu): "msg, m->child_num, (unsigned long)m->id
 
 #endif /* defined(__mod_h2__h2_mplx__) */
diff -r -N -u a/modules/http2/h2_protocol.c b/modules/http2/h2_protocol.c
--- a/modules/http2/h2_protocol.c	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_protocol.c	2024-10-30 21:40:01.059958617 +0100
@@ -0,0 +1,485 @@
+/* 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_strings.h>
+#include <apr_optional.h>
+#include <apr_optional_hooks.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_ssl.h>
+#include <http_log.h>
+
+#include "mod_http2.h"
+#include "h2_private.h"
+
+#include "h2_bucket_beam.h"
+#include "h2_stream.h"
+#include "h2_c2.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_c1.h"
+#include "h2_request.h"
+#include "h2_headers.h"
+#include "h2_session.h"
+#include "h2_util.h"
+#include "h2_protocol.h"
+#include "mod_http2.h"
+
+const char *h2_protocol_ids_tls[] = {
+    "h2", NULL
+};
+
+const char *h2_protocol_ids_clear[] = {
+    "h2c", NULL
+};
+
+const char *H2_MAGIC_TOKEN = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
+
+/*******************************************************************************
+ * HTTP/2 error stuff
+ */
+static const char *h2_err_descr[] = {
+    "no error",                    /* 0x0 */
+    "protocol error",
+    "internal error",
+    "flow control error",
+    "settings timeout",
+    "stream closed",               /* 0x5 */
+    "frame size error",
+    "refused stream",
+    "cancel",
+    "compression error",
+    "connect error",               /* 0xa */
+    "enhance your calm",
+    "inadequate security",
+    "http/1.1 required",
+};
+
+const char *h2_protocol_err_description(unsigned int h2_error)
+{
+    if (h2_error < (sizeof(h2_err_descr)/sizeof(h2_err_descr[0]))) {
+        return h2_err_descr[h2_error];
+    }
+    return "unknown http/2 error code";
+}
+
+/*******************************************************************************
+ * Check connection security requirements of RFC 7540
+ */
+
+/*
+ * Black Listed Ciphers from RFC 7549 Appendix A
+ *
+ */
+static const char *RFC7540_names[] = {
+    /* ciphers with NULL encrpytion */
+    "NULL-MD5",                         /* TLS_NULL_WITH_NULL_NULL */
+    /* same */                          /* TLS_RSA_WITH_NULL_MD5 */
+    "NULL-SHA",                         /* TLS_RSA_WITH_NULL_SHA */
+    "NULL-SHA256",                      /* TLS_RSA_WITH_NULL_SHA256 */
+    "PSK-NULL-SHA",                     /* TLS_PSK_WITH_NULL_SHA */
+    "DHE-PSK-NULL-SHA",                 /* TLS_DHE_PSK_WITH_NULL_SHA */
+    "RSA-PSK-NULL-SHA",                 /* TLS_RSA_PSK_WITH_NULL_SHA */
+    "PSK-NULL-SHA256",                  /* TLS_PSK_WITH_NULL_SHA256 */
+    "PSK-NULL-SHA384",                  /* TLS_PSK_WITH_NULL_SHA384 */
+    "DHE-PSK-NULL-SHA256",              /* TLS_DHE_PSK_WITH_NULL_SHA256 */
+    "DHE-PSK-NULL-SHA384",              /* TLS_DHE_PSK_WITH_NULL_SHA384 */
+    "RSA-PSK-NULL-SHA256",              /* TLS_RSA_PSK_WITH_NULL_SHA256 */
+    "RSA-PSK-NULL-SHA384",              /* TLS_RSA_PSK_WITH_NULL_SHA384 */
+    "ECDH-ECDSA-NULL-SHA",              /* TLS_ECDH_ECDSA_WITH_NULL_SHA */
+    "ECDHE-ECDSA-NULL-SHA",             /* TLS_ECDHE_ECDSA_WITH_NULL_SHA */
+    "ECDH-RSA-NULL-SHA",                /* TLS_ECDH_RSA_WITH_NULL_SHA */
+    "ECDHE-RSA-NULL-SHA",               /* TLS_ECDHE_RSA_WITH_NULL_SHA */
+    "AECDH-NULL-SHA",                   /* TLS_ECDH_anon_WITH_NULL_SHA */
+    "ECDHE-PSK-NULL-SHA",               /* TLS_ECDHE_PSK_WITH_NULL_SHA */
+    "ECDHE-PSK-NULL-SHA256",            /* TLS_ECDHE_PSK_WITH_NULL_SHA256 */
+    "ECDHE-PSK-NULL-SHA384",            /* TLS_ECDHE_PSK_WITH_NULL_SHA384 */
+    
+    /* DES/3DES ciphers */
+    "PSK-3DES-EDE-CBC-SHA",             /* TLS_PSK_WITH_3DES_EDE_CBC_SHA */
+    "DHE-PSK-3DES-EDE-CBC-SHA",         /* TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA */
+    "RSA-PSK-3DES-EDE-CBC-SHA",         /* TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA */
+    "ECDH-ECDSA-DES-CBC3-SHA",          /* TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA */
+    "ECDHE-ECDSA-DES-CBC3-SHA",         /* TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA */
+    "ECDH-RSA-DES-CBC3-SHA",            /* TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA */
+    "ECDHE-RSA-DES-CBC3-SHA",           /* TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA */
+    "AECDH-DES-CBC3-SHA",               /* TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA */
+    "SRP-3DES-EDE-CBC-SHA",             /* TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA */
+    "SRP-RSA-3DES-EDE-CBC-SHA",         /* TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA */
+    "SRP-DSS-3DES-EDE-CBC-SHA",         /* TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA */
+    "ECDHE-PSK-3DES-EDE-CBC-SHA",       /* TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA */
+    "DES-CBC-SHA",                      /* TLS_RSA_WITH_DES_CBC_SHA */
+    "DES-CBC3-SHA",                     /* TLS_RSA_WITH_3DES_EDE_CBC_SHA */
+    "DHE-DSS-DES-CBC3-SHA",             /* TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA */
+    "DHE-RSA-DES-CBC-SHA",              /* TLS_DHE_RSA_WITH_DES_CBC_SHA */
+    "DHE-RSA-DES-CBC3-SHA",             /* TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA */
+    "ADH-DES-CBC-SHA",                  /* TLS_DH_anon_WITH_DES_CBC_SHA */
+    "ADH-DES-CBC3-SHA",                 /* TLS_DH_anon_WITH_3DES_EDE_CBC_SHA */
+    "EXP-DH-DSS-DES-CBC-SHA",           /* TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA */
+    "DH-DSS-DES-CBC-SHA",               /* TLS_DH_DSS_WITH_DES_CBC_SHA */
+    "DH-DSS-DES-CBC3-SHA",              /* TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA */
+    "EXP-DH-RSA-DES-CBC-SHA",           /* TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA */
+    "DH-RSA-DES-CBC-SHA",               /* TLS_DH_RSA_WITH_DES_CBC_SHA */
+    "DH-RSA-DES-CBC3-SHA",              /* TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA */
+
+    /* blacklisted EXPORT ciphers */
+    "EXP-RC4-MD5",                      /* TLS_RSA_EXPORT_WITH_RC4_40_MD5 */
+    "EXP-RC2-CBC-MD5",                  /* TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 */
+    "EXP-DES-CBC-SHA",                  /* TLS_RSA_EXPORT_WITH_DES40_CBC_SHA */
+    "EXP-DHE-DSS-DES-CBC-SHA",          /* TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA */
+    "EXP-DHE-RSA-DES-CBC-SHA",          /* TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA */
+    "EXP-ADH-DES-CBC-SHA",              /* TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA */
+    "EXP-ADH-RC4-MD5",                  /* TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 */
+
+    /* blacklisted RC4 encryption */
+    "RC4-MD5",                          /* TLS_RSA_WITH_RC4_128_MD5 */
+    "RC4-SHA",                          /* TLS_RSA_WITH_RC4_128_SHA */
+    "ADH-RC4-MD5",                      /* TLS_DH_anon_WITH_RC4_128_MD5 */
+    "KRB5-RC4-SHA",                     /* TLS_KRB5_WITH_RC4_128_SHA */
+    "KRB5-RC4-MD5",                     /* TLS_KRB5_WITH_RC4_128_MD5 */
+    "EXP-KRB5-RC4-SHA",                 /* TLS_KRB5_EXPORT_WITH_RC4_40_SHA */
+    "EXP-KRB5-RC4-MD5",                 /* TLS_KRB5_EXPORT_WITH_RC4_40_MD5 */
+    "PSK-RC4-SHA",                      /* TLS_PSK_WITH_RC4_128_SHA */
+    "DHE-PSK-RC4-SHA",                  /* TLS_DHE_PSK_WITH_RC4_128_SHA */
+    "RSA-PSK-RC4-SHA",                  /* TLS_RSA_PSK_WITH_RC4_128_SHA */
+    "ECDH-ECDSA-RC4-SHA",               /* TLS_ECDH_ECDSA_WITH_RC4_128_SHA */
+    "ECDHE-ECDSA-RC4-SHA",              /* TLS_ECDHE_ECDSA_WITH_RC4_128_SHA */
+    "ECDH-RSA-RC4-SHA",                 /* TLS_ECDH_RSA_WITH_RC4_128_SHA */
+    "ECDHE-RSA-RC4-SHA",                /* TLS_ECDHE_RSA_WITH_RC4_128_SHA */
+    "AECDH-RC4-SHA",                    /* TLS_ECDH_anon_WITH_RC4_128_SHA */
+    "ECDHE-PSK-RC4-SHA",                /* TLS_ECDHE_PSK_WITH_RC4_128_SHA */
+
+    /* blacklisted AES128 encrpytion ciphers */
+    "AES128-SHA256",                    /* TLS_RSA_WITH_AES_128_CBC_SHA */
+    "DH-DSS-AES128-SHA",                /* TLS_DH_DSS_WITH_AES_128_CBC_SHA */
+    "DH-RSA-AES128-SHA",                /* TLS_DH_RSA_WITH_AES_128_CBC_SHA */
+    "DHE-DSS-AES128-SHA",               /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA */
+    "DHE-RSA-AES128-SHA",               /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA */
+    "ADH-AES128-SHA",                   /* TLS_DH_anon_WITH_AES_128_CBC_SHA */
+    "AES128-SHA256",                    /* TLS_RSA_WITH_AES_128_CBC_SHA256 */
+    "DH-DSS-AES128-SHA256",             /* TLS_DH_DSS_WITH_AES_128_CBC_SHA256 */
+    "DH-RSA-AES128-SHA256",             /* TLS_DH_RSA_WITH_AES_128_CBC_SHA256 */
+    "DHE-DSS-AES128-SHA256",            /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 */
+    "DHE-RSA-AES128-SHA256",            /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 */
+    "ECDH-ECDSA-AES128-SHA",            /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA */
+    "ECDHE-ECDSA-AES128-SHA",           /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA */
+    "ECDH-RSA-AES128-SHA",              /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA */
+    "ECDHE-RSA-AES128-SHA",             /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA */
+    "AECDH-AES128-SHA",                 /* TLS_ECDH_anon_WITH_AES_128_CBC_SHA */
+    "ECDHE-ECDSA-AES128-SHA256",        /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 */
+    "ECDH-ECDSA-AES128-SHA256",         /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 */
+    "ECDHE-RSA-AES128-SHA256",          /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 */
+    "ECDH-RSA-AES128-SHA256",           /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 */
+    "ADH-AES128-SHA256",                /* TLS_DH_anon_WITH_AES_128_CBC_SHA256 */
+    "PSK-AES128-CBC-SHA",               /* TLS_PSK_WITH_AES_128_CBC_SHA */
+    "DHE-PSK-AES128-CBC-SHA",           /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA */
+    "RSA-PSK-AES128-CBC-SHA",           /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA */
+    "PSK-AES128-CBC-SHA256",            /* TLS_PSK_WITH_AES_128_CBC_SHA256 */
+    "DHE-PSK-AES128-CBC-SHA256",        /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 */
+    "RSA-PSK-AES128-CBC-SHA256",        /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 */
+    "ECDHE-PSK-AES128-CBC-SHA",         /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA */
+    "ECDHE-PSK-AES128-CBC-SHA256",      /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 */
+    "AES128-CCM",                       /* TLS_RSA_WITH_AES_128_CCM */
+    "AES128-CCM8",                      /* TLS_RSA_WITH_AES_128_CCM_8 */
+    "PSK-AES128-CCM",                   /* TLS_PSK_WITH_AES_128_CCM */
+    "PSK-AES128-CCM8",                  /* TLS_PSK_WITH_AES_128_CCM_8 */
+    "AES128-GCM-SHA256",                /* TLS_RSA_WITH_AES_128_GCM_SHA256 */
+    "DH-RSA-AES128-GCM-SHA256",         /* TLS_DH_RSA_WITH_AES_128_GCM_SHA256 */
+    "DH-DSS-AES128-GCM-SHA256",         /* TLS_DH_DSS_WITH_AES_128_GCM_SHA256 */
+    "ADH-AES128-GCM-SHA256",            /* TLS_DH_anon_WITH_AES_128_GCM_SHA256 */
+    "PSK-AES128-GCM-SHA256",            /* TLS_PSK_WITH_AES_128_GCM_SHA256 */
+    "RSA-PSK-AES128-GCM-SHA256",        /* TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 */
+    "ECDH-ECDSA-AES128-GCM-SHA256",     /* TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 */
+    "ECDH-RSA-AES128-GCM-SHA256",       /* TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 */
+    "SRP-AES-128-CBC-SHA",              /* TLS_SRP_SHA_WITH_AES_128_CBC_SHA */
+    "SRP-RSA-AES-128-CBC-SHA",          /* TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA */
+    "SRP-DSS-AES-128-CBC-SHA",          /* TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA */
+    
+    /* blacklisted AES256 encrpytion ciphers */
+    "AES256-SHA",                       /* TLS_RSA_WITH_AES_256_CBC_SHA */
+    "DH-DSS-AES256-SHA",                /* TLS_DH_DSS_WITH_AES_256_CBC_SHA */
+    "DH-RSA-AES256-SHA",                /* TLS_DH_RSA_WITH_AES_256_CBC_SHA */
+    "DHE-DSS-AES256-SHA",               /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA */
+    "DHE-RSA-AES256-SHA",               /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA */
+    "ADH-AES256-SHA",                   /* TLS_DH_anon_WITH_AES_256_CBC_SHA */
+    "AES256-SHA256",                    /* TLS_RSA_WITH_AES_256_CBC_SHA256 */
+    "DH-DSS-AES256-SHA256",             /* TLS_DH_DSS_WITH_AES_256_CBC_SHA256 */
+    "DH-RSA-AES256-SHA256",             /* TLS_DH_RSA_WITH_AES_256_CBC_SHA256 */
+    "DHE-DSS-AES256-SHA256",            /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 */
+    "DHE-RSA-AES256-SHA256",            /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 */
+    "ADH-AES256-SHA256",                /* TLS_DH_anon_WITH_AES_256_CBC_SHA256 */
+    "ECDH-ECDSA-AES256-SHA",            /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA */
+    "ECDHE-ECDSA-AES256-SHA",           /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA */
+    "ECDH-RSA-AES256-SHA",              /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA */
+    "ECDHE-RSA-AES256-SHA",             /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA */
+    "AECDH-AES256-SHA",                 /* TLS_ECDH_anon_WITH_AES_256_CBC_SHA */
+    "ECDHE-ECDSA-AES256-SHA384",        /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 */
+    "ECDH-ECDSA-AES256-SHA384",         /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 */
+    "ECDHE-RSA-AES256-SHA384",          /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 */
+    "ECDH-RSA-AES256-SHA384",           /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 */
+    "PSK-AES256-CBC-SHA",               /* TLS_PSK_WITH_AES_256_CBC_SHA */
+    "DHE-PSK-AES256-CBC-SHA",           /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA */
+    "RSA-PSK-AES256-CBC-SHA",           /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA */
+    "PSK-AES256-CBC-SHA384",            /* TLS_PSK_WITH_AES_256_CBC_SHA384 */
+    "DHE-PSK-AES256-CBC-SHA384",        /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 */
+    "RSA-PSK-AES256-CBC-SHA384",        /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 */
+    "ECDHE-PSK-AES256-CBC-SHA",         /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA */
+    "ECDHE-PSK-AES256-CBC-SHA384",      /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 */
+    "SRP-AES-256-CBC-SHA",              /* TLS_SRP_SHA_WITH_AES_256_CBC_SHA */
+    "SRP-RSA-AES-256-CBC-SHA",          /* TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA */
+    "SRP-DSS-AES-256-CBC-SHA",          /* TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA */
+    "AES256-CCM",                       /* TLS_RSA_WITH_AES_256_CCM */
+    "AES256-CCM8",                      /* TLS_RSA_WITH_AES_256_CCM_8 */
+    "PSK-AES256-CCM",                   /* TLS_PSK_WITH_AES_256_CCM */
+    "PSK-AES256-CCM8",                  /* TLS_PSK_WITH_AES_256_CCM_8 */
+    "AES256-GCM-SHA384",                /* TLS_RSA_WITH_AES_256_GCM_SHA384 */
+    "DH-RSA-AES256-GCM-SHA384",         /* TLS_DH_RSA_WITH_AES_256_GCM_SHA384 */
+    "DH-DSS-AES256-GCM-SHA384",         /* TLS_DH_DSS_WITH_AES_256_GCM_SHA384 */
+    "ADH-AES256-GCM-SHA384",            /* TLS_DH_anon_WITH_AES_256_GCM_SHA384 */
+    "PSK-AES256-GCM-SHA384",            /* TLS_PSK_WITH_AES_256_GCM_SHA384 */
+    "RSA-PSK-AES256-GCM-SHA384",        /* TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 */
+    "ECDH-ECDSA-AES256-GCM-SHA384",     /* TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 */
+    "ECDH-RSA-AES256-GCM-SHA384",       /* TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 */
+    
+    /* blacklisted CAMELLIA128 encrpytion ciphers */
+    "CAMELLIA128-SHA",                  /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA */
+    "DH-DSS-CAMELLIA128-SHA",           /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA */
+    "DH-RSA-CAMELLIA128-SHA",           /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA */
+    "DHE-DSS-CAMELLIA128-SHA",          /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA */
+    "DHE-RSA-CAMELLIA128-SHA",          /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA */
+    "ADH-CAMELLIA128-SHA",              /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA */
+    "ECDHE-ECDSA-CAMELLIA128-SHA256",   /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
+    "ECDH-ECDSA-CAMELLIA128-SHA256",    /* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
+    "ECDHE-RSA-CAMELLIA128-SHA256",     /* TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+    "ECDH-RSA-CAMELLIA128-SHA256",      /* TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+    "PSK-CAMELLIA128-SHA256",           /* TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
+    "DHE-PSK-CAMELLIA128-SHA256",       /* TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
+    "RSA-PSK-CAMELLIA128-SHA256",       /* TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
+    "ECDHE-PSK-CAMELLIA128-SHA256",     /* TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
+    "CAMELLIA128-GCM-SHA256",           /* TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
+    "DH-RSA-CAMELLIA128-GCM-SHA256",    /* TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
+    "DH-DSS-CAMELLIA128-GCM-SHA256",    /* TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 */
+    "ADH-CAMELLIA128-GCM-SHA256",       /* TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 */
+    "ECDH-ECDSA-CAMELLIA128-GCM-SHA256",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 */
+    "ECDH-RSA-CAMELLIA128-GCM-SHA256",  /* TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
+    "PSK-CAMELLIA128-GCM-SHA256",       /* TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
+    "RSA-PSK-CAMELLIA128-GCM-SHA256",   /* TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
+    "CAMELLIA128-SHA256",               /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+    "DH-DSS-CAMELLIA128-SHA256",        /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
+    "DH-RSA-CAMELLIA128-SHA256",        /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+    "DHE-DSS-CAMELLIA128-SHA256",       /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
+    "DHE-RSA-CAMELLIA128-SHA256",       /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+    "ADH-CAMELLIA128-SHA256",           /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 */
+    
+    /* blacklisted CAMELLIA256 encrpytion ciphers */
+    "CAMELLIA256-SHA",                  /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA */
+    "DH-RSA-CAMELLIA256-SHA",           /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA */
+    "DH-DSS-CAMELLIA256-SHA",           /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA */
+    "DHE-DSS-CAMELLIA256-SHA",          /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA */
+    "DHE-RSA-CAMELLIA256-SHA",          /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA */
+    "ADH-CAMELLIA256-SHA",              /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA */
+    "ECDHE-ECDSA-CAMELLIA256-SHA384",   /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
+    "ECDH-ECDSA-CAMELLIA256-SHA384",    /* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
+    "ECDHE-RSA-CAMELLIA256-SHA384",     /* TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
+    "ECDH-RSA-CAMELLIA256-SHA384",      /* TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
+    "PSK-CAMELLIA256-SHA384",           /* TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
+    "DHE-PSK-CAMELLIA256-SHA384",       /* TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
+    "RSA-PSK-CAMELLIA256-SHA384",       /* TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
+    "ECDHE-PSK-CAMELLIA256-SHA384",     /* TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
+    "CAMELLIA256-SHA256",               /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
+    "DH-DSS-CAMELLIA256-SHA256",        /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
+    "DH-RSA-CAMELLIA256-SHA256",        /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
+    "DHE-DSS-CAMELLIA256-SHA256",       /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
+    "DHE-RSA-CAMELLIA256-SHA256",       /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
+    "ADH-CAMELLIA256-SHA256",           /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 */
+    "CAMELLIA256-GCM-SHA384",           /* TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
+    "DH-RSA-CAMELLIA256-GCM-SHA384",    /* TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
+    "DH-DSS-CAMELLIA256-GCM-SHA384",    /* TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 */
+    "ADH-CAMELLIA256-GCM-SHA384",       /* TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 */
+    "ECDH-ECDSA-CAMELLIA256-GCM-SHA384",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 */
+    "ECDH-RSA-CAMELLIA256-GCM-SHA384",  /* TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
+    "PSK-CAMELLIA256-GCM-SHA384",       /* TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
+    "RSA-PSK-CAMELLIA256-GCM-SHA384",   /* TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
+    
+    /* The blacklisted ARIA encrpytion ciphers */
+    "ARIA128-SHA256",                   /* TLS_RSA_WITH_ARIA_128_CBC_SHA256 */
+    "ARIA256-SHA384",                   /* TLS_RSA_WITH_ARIA_256_CBC_SHA384 */
+    "DH-DSS-ARIA128-SHA256",            /* TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 */
+    "DH-DSS-ARIA256-SHA384",            /* TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 */
+    "DH-RSA-ARIA128-SHA256",            /* TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 */
+    "DH-RSA-ARIA256-SHA384",            /* TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 */
+    "DHE-DSS-ARIA128-SHA256",           /* TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 */
+    "DHE-DSS-ARIA256-SHA384",           /* TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 */
+    "DHE-RSA-ARIA128-SHA256",           /* TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 */
+    "DHE-RSA-ARIA256-SHA384",           /* TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 */
+    "ADH-ARIA128-SHA256",               /* TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 */
+    "ADH-ARIA256-SHA384",               /* TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 */
+    "ECDHE-ECDSA-ARIA128-SHA256",       /* TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 */
+    "ECDHE-ECDSA-ARIA256-SHA384",       /* TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 */
+    "ECDH-ECDSA-ARIA128-SHA256",        /* TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 */
+    "ECDH-ECDSA-ARIA256-SHA384",        /* TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 */
+    "ECDHE-RSA-ARIA128-SHA256",         /* TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 */
+    "ECDHE-RSA-ARIA256-SHA384",         /* TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 */
+    "ECDH-RSA-ARIA128-SHA256",          /* TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 */
+    "ECDH-RSA-ARIA256-SHA384",          /* TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 */
+    "ARIA128-GCM-SHA256",               /* TLS_RSA_WITH_ARIA_128_GCM_SHA256 */
+    "ARIA256-GCM-SHA384",               /* TLS_RSA_WITH_ARIA_256_GCM_SHA384 */
+    "DH-DSS-ARIA128-GCM-SHA256",        /* TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 */
+    "DH-DSS-ARIA256-GCM-SHA384",        /* TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 */
+    "DH-RSA-ARIA128-GCM-SHA256",        /* TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 */
+    "DH-RSA-ARIA256-GCM-SHA384",        /* TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 */
+    "ADH-ARIA128-GCM-SHA256",           /* TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 */
+    "ADH-ARIA256-GCM-SHA384",           /* TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 */
+    "ECDH-ECDSA-ARIA128-GCM-SHA256",    /* TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 */
+    "ECDH-ECDSA-ARIA256-GCM-SHA384",    /* TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 */
+    "ECDH-RSA-ARIA128-GCM-SHA256",      /* TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 */
+    "ECDH-RSA-ARIA256-GCM-SHA384",      /* TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 */
+    "PSK-ARIA128-SHA256",               /* TLS_PSK_WITH_ARIA_128_CBC_SHA256 */
+    "PSK-ARIA256-SHA384",               /* TLS_PSK_WITH_ARIA_256_CBC_SHA384 */
+    "DHE-PSK-ARIA128-SHA256",           /* TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 */
+    "DHE-PSK-ARIA256-SHA384",           /* TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 */
+    "RSA-PSK-ARIA128-SHA256",           /* TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 */
+    "RSA-PSK-ARIA256-SHA384",           /* TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 */
+    "ARIA128-GCM-SHA256",               /* TLS_PSK_WITH_ARIA_128_GCM_SHA256 */
+    "ARIA256-GCM-SHA384",               /* TLS_PSK_WITH_ARIA_256_GCM_SHA384 */
+    "RSA-PSK-ARIA128-GCM-SHA256",       /* TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 */
+    "RSA-PSK-ARIA256-GCM-SHA384",       /* TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 */
+    "ECDHE-PSK-ARIA128-SHA256",         /* TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 */
+    "ECDHE-PSK-ARIA256-SHA384",         /* TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 */
+
+    /* blacklisted SEED encryptions */
+    "SEED-SHA",                         /*TLS_RSA_WITH_SEED_CBC_SHA */
+    "DH-DSS-SEED-SHA",                  /* TLS_DH_DSS_WITH_SEED_CBC_SHA */
+    "DH-RSA-SEED-SHA",                  /* TLS_DH_RSA_WITH_SEED_CBC_SHA */
+    "DHE-DSS-SEED-SHA",                 /* TLS_DHE_DSS_WITH_SEED_CBC_SHA */
+    "DHE-RSA-SEED-SHA",                 /* TLS_DHE_RSA_WITH_SEED_CBC_SHA */               
+    "ADH-SEED-SHA",                     /* TLS_DH_anon_WITH_SEED_CBC_SHA */
+
+    /* blacklisted KRB5 ciphers */
+    "KRB5-DES-CBC-SHA",                 /* TLS_KRB5_WITH_DES_CBC_SHA */
+    "KRB5-DES-CBC3-SHA",                /* TLS_KRB5_WITH_3DES_EDE_CBC_SHA */
+    "KRB5-IDEA-CBC-SHA",                /* TLS_KRB5_WITH_IDEA_CBC_SHA */
+    "KRB5-DES-CBC-MD5",                 /* TLS_KRB5_WITH_DES_CBC_MD5 */
+    "KRB5-DES-CBC3-MD5",                /* TLS_KRB5_WITH_3DES_EDE_CBC_MD5 */
+    "KRB5-IDEA-CBC-MD5",                /* TLS_KRB5_WITH_IDEA_CBC_MD5 */
+    "EXP-KRB5-DES-CBC-SHA",             /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA */
+    "EXP-KRB5-DES-CBC-MD5",             /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 */
+    "EXP-KRB5-RC2-CBC-SHA",             /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA */
+    "EXP-KRB5-RC2-CBC-MD5",             /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 */
+  
+    /* blacklisted exoticas */
+    "DHE-DSS-CBC-SHA",                  /* TLS_DHE_DSS_WITH_DES_CBC_SHA */
+    "IDEA-CBC-SHA",                     /* TLS_RSA_WITH_IDEA_CBC_SHA */
+    
+    /* not really sure if the following names are correct */
+    "SSL3_CK_SCSV",                     /* TLS_EMPTY_RENEGOTIATION_INFO_SCSV */
+    "SSL3_CK_FALLBACK_SCSV"
+};
+static size_t RFC7540_names_LEN = sizeof(RFC7540_names)/sizeof(RFC7540_names[0]);
+
+
+static apr_hash_t *BLCNames;
+
+static void cipher_init(apr_pool_t *pool)
+{
+    apr_hash_t *hash = apr_hash_make(pool);
+    const char *source;
+    unsigned int i;
+    
+    source = "rfc7540";
+    for (i = 0; i < RFC7540_names_LEN; ++i) {
+        apr_hash_set(hash, RFC7540_names[i], APR_HASH_KEY_STRING, source);
+    }
+    
+    BLCNames = hash;
+}
+
+static int cipher_is_blacklisted(const char *cipher, const char **psource)
+{   
+    *psource = apr_hash_get(BLCNames, cipher, APR_HASH_KEY_STRING);
+    return !!*psource;
+}
+
+apr_status_t h2_protocol_init(apr_pool_t *pool, server_rec *s)
+{
+    (void)pool;
+    ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "h2_h2, child_init");
+    cipher_init(pool);
+    
+    return APR_SUCCESS;
+}
+
+int h2_protocol_is_acceptable_c1(conn_rec *c, request_rec *r, int require_all)
+{
+    int is_tls = ap_ssl_conn_is_ssl(c);
+
+    if (is_tls && h2_config_cgeti(c, H2_CONF_MODERN_TLS_ONLY) > 0) {
+        /* Check TLS connection for modern TLS parameters, as defined in
+         * RFC 7540 and https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
+         */
+        apr_pool_t *pool = c->pool;
+        server_rec *s = c->base_server;
+        const char *val;
+
+        /* Need Tlsv1.2 or higher, rfc 7540, ch. 9.2
+         */
+        val = ap_ssl_var_lookup(pool, s, c, NULL, "SSL_PROTOCOL");
+        if (val && *val) {
+            if (strncmp("TLS", val, 3) 
+                || !strcmp("TLSv1", val) 
+                || !strcmp("TLSv1.1", val)) {
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03050)
+                              "h2_h2(%ld): tls protocol not suitable: %s", 
+                              (long)c->id, val);
+                return 0;
+            }
+        }
+        else if (require_all) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03051)
+                          "h2_h2(%ld): tls protocol is indetermined", (long)c->id);
+            return 0;
+        }
+
+        if (val && !strcmp("TLSv1.2", val)) {
+            /* Check TLS cipher blacklist, defined pre-TLSv1.3, so only
+             * checking for 1.2 */
+            val = ap_ssl_var_lookup(pool, s, c, NULL, "SSL_CIPHER");
+            if (val && *val) {
+                const char *source;
+                if (cipher_is_blacklisted(val, &source)) {
+                    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03052)
+                                  "h2_h2(%ld): tls cipher %s blacklisted by %s",
+                                  (long)c->id, val, source);
+                    return 0;
+                }
+            }
+            else if (require_all) {
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03053)
+                              "h2_h2(%ld): tls cipher is indetermined", (long)c->id);
+                return 0;
+            }
+        }
+    }
+    return 1;
+}
+
diff -r -N -u a/modules/http2/h2_protocol.h b/modules/http2/h2_protocol.h
--- a/modules/http2/h2_protocol.h	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_protocol.h	2024-10-30 21:40:01.059958617 +0100
@@ -0,0 +1,56 @@
+/* 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_protocol__
+#define __mod_h2__h2_protocol__
+
+/**
+ * List of protocol identifiers that we support in cleartext
+ * negotiations. NULL terminated.
+ */
+extern const char *h2_protocol_ids_clear[];
+
+/**
+ * List of protocol identifiers that we support in TLS encrypted
+ * negotiations (ALPN). NULL terminated.
+ */
+extern const char *h2_protocol_ids_tls[];
+
+/**
+ * Provide a user readable description of the HTTP/2 error code-
+ * @param h2_error http/2 error code, as in rfc 7540, ch. 7
+ * @return textual description of code or that it is unknown.
+ */
+const char *h2_protocol_err_description(unsigned int h2_error);
+
+/*
+ * One time, post config initialization.
+ */
+apr_status_t h2_protocol_init(apr_pool_t *pool, server_rec *s);
+
+/**
+ * Check if the given primary connection fulfills the protocol
+ * requirements for HTTP/2.
+ * @param c the connection
+ * @param require_all != 0 iff any missing connection properties make
+ *    the test fail. For example, a cipher might not have been selected while
+ *    the handshake is still ongoing.
+ * @return != 0 iff protocol requirements are met
+ */
+int h2_protocol_is_acceptable_c1(conn_rec *c, request_rec *r, int require_all);
+
+
+#endif /* defined(__mod_h2__h2_protocol__) */
diff -r -N -u a/modules/http2/h2_proxy_session.c b/modules/http2/h2_proxy_session.c
--- a/modules/http2/h2_proxy_session.c	2024-10-30 21:39:44.611620539 +0100
+++ b/modules/http2/h2_proxy_session.c	2024-10-30 21:40:01.059958617 +0100
@@ -20,6 +20,7 @@
 
 #include <mpm_common.h>
 #include <httpd.h>
+#include <http_protocol.h>
 #include <mod_proxy.h>
 
 #include "mod_http2.h"
@@ -36,6 +37,7 @@
 
     const char *url;
     request_rec *r;
+    conn_rec *cfront;
     h2_proxy_request *req;
     const char *real_server_uri;
     const char *p_server_uri;
@@ -400,7 +402,7 @@
             char *s = apr_pstrndup(stream->r->pool, v, vlen);
             
             apr_table_setn(stream->r->notes, "proxy-status", s);
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c, 
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
                           "h2_proxy_stream(%s-%d): got status %s", 
                           stream->session->id, stream->id, s);
             stream->r->status = (int)apr_atoi64(s);
@@ -412,7 +414,7 @@
         return APR_SUCCESS;
     }
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c, 
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
                   "h2_proxy_stream(%s-%d): on_header %s: %s", 
                   stream->session->id, stream->id, n, v);
     if (!h2_proxy_res_ignore_header(n, nlen)) {
@@ -424,7 +426,7 @@
         h2_proxy_util_camel_case_header(hname, nlen);
         hvalue = apr_pstrndup(stream->pool, v, vlen);
         
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c, 
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
                       "h2_proxy_stream(%s-%d): got header %s: %s", 
                       stream->session->id, stream->id, hname, hvalue);
         process_proxy_header(headers, stream, hname, hvalue);
@@ -531,22 +533,21 @@
         h2_proxy_stream_end_headers_out(stream);
     }
     stream->data_received += len;
-    
-    b = apr_bucket_transient_create((const char*)data, len, 
-                                    stream->r->connection->bucket_alloc);
+    b = apr_bucket_transient_create((const char*)data, len,
+                                    stream->cfront->bucket_alloc);
     APR_BRIGADE_INSERT_TAIL(stream->output, b);
     /* always flush after a DATA frame, as we have no other indication
      * of buffer use */
-    b = apr_bucket_flush_create(stream->r->connection->bucket_alloc);
+    b = apr_bucket_flush_create(stream->cfront->bucket_alloc);
     APR_BRIGADE_INSERT_TAIL(stream->output, b);
-    
+
     status = ap_pass_brigade(stream->r->output_filters, stream->output);
     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, stream->r, APLOGNO(03359)
                   "h2_proxy_session(%s): stream=%d, response DATA %ld, %ld"
                   " total", session->id, stream_id, (long)len,
                   (long)stream->data_received);
     if (status != APR_SUCCESS) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO(03344)
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, stream->r, APLOGNO(03344)
                       "h2_proxy_session(%s): passing output on stream %d", 
                       session->id, stream->id);
         nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE,
@@ -636,7 +637,7 @@
     }
 
     if (status == APR_SUCCESS) {
-        ssize_t readlen = 0;
+        size_t readlen = 0;
         while (status == APR_SUCCESS 
                && (readlen < length)
                && !APR_BRIGADE_EMPTY(stream->input)) {
@@ -655,7 +656,7 @@
                 status = apr_bucket_read(b, &bdata, &blen, APR_BLOCK_READ);
                 
                 if (status == APR_SUCCESS && blen > 0) {
-                    ssize_t copylen = H2MIN(length - readlen, blen);
+                    size_t copylen = H2MIN(length - readlen, blen);
                     memcpy(buf, bdata, copylen);
                     buf += copylen;
                     readlen += copylen;
@@ -698,7 +699,7 @@
 }
 
 #ifdef H2_NG2_INVALID_HEADER_CB
-static int on_invalid_header_cb(nghttp2_session *ngh2, 
+static int on_invalid_header_cb(nghttp2_session *ngh2,
                                 const nghttp2_frame *frame, 
                                 const uint8_t *name, size_t namelen, 
                                 const uint8_t *value, size_t valuelen, 
@@ -761,7 +762,6 @@
 #ifdef H2_NG2_INVALID_HEADER_CB
         nghttp2_session_callbacks_set_on_invalid_header_callback(cbs, on_invalid_header_cb);
 #endif
-        
         nghttp2_option_new(&option);
         nghttp2_option_set_peer_max_concurrent_streams(option, 100);
         nghttp2_option_set_no_auto_window_update(option, 0);
@@ -818,7 +818,7 @@
 {
     h2_proxy_stream *stream;
     apr_uri_t puri;
-    const char *authority, *scheme, *path;
+    const char *authority, *scheme, *path, *orig_host;
     apr_status_t status;
     proxy_dir_conf *dconf;
 
@@ -827,24 +827,29 @@
     stream->pool = r->pool;
     stream->url = url;
     stream->r = r;
+    stream->cfront = r->connection;
     stream->standalone = standalone;
     stream->session = session;
     stream->state = H2_STREAM_ST_IDLE;
     
-    stream->input = apr_brigade_create(stream->pool, session->c->bucket_alloc);
-    stream->output = apr_brigade_create(stream->pool, session->c->bucket_alloc);
+    stream->input = apr_brigade_create(stream->pool, stream->cfront->bucket_alloc);
+    stream->output = apr_brigade_create(stream->pool, stream->cfront->bucket_alloc);
     
-    stream->req = h2_proxy_req_create(1, stream->pool, 0);
+    stream->req = h2_proxy_req_create(1, stream->pool);
 
     status = apr_uri_parse(stream->pool, url, &puri);
     if (status != APR_SUCCESS)
         return status;
     
     scheme = (strcmp(puri.scheme, "h2")? "http" : "https");
-    
+    orig_host = apr_table_get(r->headers_in, "Host");
+    if (orig_host == NULL) {
+        orig_host = r->hostname;
+    }
+
     dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
     if (dconf->preserve_host) {
-        authority = r->hostname;
+        authority = orig_host;
     }
     else {
         authority = puri.hostname;
@@ -853,22 +858,27 @@
             /* port info missing and port is not default for scheme: append */
             authority = apr_psprintf(stream->pool, "%s:%d", authority, puri.port);
         }
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
+                      "authority=%s from uri.hostname=%s and uri.port=%d",
+                      authority, puri.hostname, puri.port);
+    }
+    /* See #235, we use only :authority when available and remove Host:
+     * since differing values are not acceptable, see RFC 9113 ch. 8.3.1 */
+    if (authority && strlen(authority)) {
+        apr_table_unset(r->headers_in, "Host");
     }
-    
+
     /* we need this for mapping relative uris in headers ("Link") back
      * to local uris */
     stream->real_server_uri = apr_psprintf(stream->pool, "%s://%s", scheme, authority); 
     stream->p_server_uri = apr_psprintf(stream->pool, "%s://%s", puri.scheme, authority); 
     path = apr_uri_unparse(stream->pool, &puri, APR_URI_UNP_OMITSITEPART);
 
-
     h2_proxy_req_make(stream->req, stream->pool, r->method, scheme,
                 authority, path, r->headers_in);
 
     if (dconf->add_forwarded_headers) {
         if (PROXYREQ_REVERSE == r->proxyreq) {
-            const char *buf;
-
             /* Add X-Forwarded-For: so that the upstream has a chance to
              * determine, where the original request came from.
              */
@@ -878,8 +888,9 @@
             /* Add X-Forwarded-Host: so that upstream knows what the
              * original request hostname was.
              */
-            if ((buf = apr_table_get(r->headers_in, "Host"))) {
-                apr_table_mergen(stream->req->headers, "X-Forwarded-Host", buf);
+            if (orig_host) {
+                apr_table_mergen(stream->req->headers, "X-Forwarded-Host",
+                                 orig_host);
             }
 
             /* Add X-Forwarded-Server: so that upstream knows what the
@@ -890,7 +901,7 @@
                              r->server->server_hostname);
         }
     }
-    
+
     /* Tuck away all already existing cookies */
     stream->saves = apr_table_make(r->pool, 2);
     apr_table_do(add_header, stream->saves, r->headers_out, "Set-Cookie", NULL);
@@ -933,7 +944,7 @@
     rv = nghttp2_submit_request(session->ngh2, NULL, 
                                 hd->nv, hd->nvlen, pp, stream);
                                 
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03363)
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->cfront, APLOGNO(03363)
                   "h2_proxy_session(%s): submit %s%s -> %d", 
                   session->id, stream->req->authority, stream->req->path,
                   rv);
@@ -963,7 +974,7 @@
     apr_status_t status = APR_SUCCESS;
     apr_size_t readlen = 0;
     ssize_t n;
-    
+
     while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)) {
         apr_bucket* b = APR_BRIGADE_FIRST(bb);
         
@@ -986,9 +997,10 @@
                     }
                 }
                 else {
-                    readlen += n;
-                    if (n < blen) {
-                        apr_bucket_split(b, n);
+                    size_t rlen = (size_t)n;
+                    readlen += rlen;
+                    if (rlen < blen) {
+                        apr_bucket_split(b, rlen);
                     }
                 }
             }
@@ -1077,7 +1089,7 @@
 static void stream_resume(h2_proxy_stream *stream)
 {
     h2_proxy_session *session = stream->session;
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
                   "h2_proxy_stream(%s-%d): resuming", 
                   session->id, stream->id);
     stream->suspended = 0;
@@ -1118,7 +1130,7 @@
                 return APR_SUCCESS;
             }
             else if (status != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(status)) {
-                ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, session->c, 
+                ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, stream->cfront,
                               APLOGNO(03382) "h2_proxy_stream(%s-%d): check input", 
                               session->id, stream_id);
                 stream_resume(stream);
@@ -1146,7 +1158,7 @@
     if (!err && reason) {
         err = nghttp2_strerror(reason);
     }
-    nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 0, 
+    nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 0,
                           reason, (uint8_t*)err, err? strlen(err):0);
     status = nghttp2_session_send(session->ngh2);
     dispatch_event(session, H2_PROXYS_EV_LOCAL_GOAWAY, reason, err);
@@ -1348,39 +1360,56 @@
                            const char *msg)
 {
     h2_proxy_stream *stream;
-    
+    apr_bucket *b;
+
     stream = nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
     if (stream) {
-        int touched = (stream->data_sent || 
-                       stream_id <= session->last_stream_id);
+        /* if the stream's connection is aborted, do not send anything
+         * more on it. */
         apr_status_t status = (stream->error_code == 0)? APR_SUCCESS : APR_EINVAL;
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03364)
-                      "h2_proxy_sesssion(%s): stream(%d) closed "
-                      "(touched=%d, error=%d)", 
-                      session->id, stream_id, touched, stream->error_code);
-        
-        if (status != APR_SUCCESS) {
-            stream->r->status = 500;
-        }
-        else if (!stream->data_received) {
-            apr_bucket *b;
-            /* if the response had no body, this is the time to flush
-             * an empty brigade which will also write the resonse
-             * headers */
-            h2_proxy_stream_end_headers_out(stream);
-            stream->data_received = 1;
-            b = apr_bucket_flush_create(stream->r->connection->bucket_alloc);
-            APR_BRIGADE_INSERT_TAIL(stream->output, b);
-            b = apr_bucket_eos_create(stream->r->connection->bucket_alloc);
-            APR_BRIGADE_INSERT_TAIL(stream->output, b);
-            ap_pass_brigade(stream->r->output_filters, stream->output);
+        int touched = (stream->data_sent || stream->data_received ||
+                       stream_id <= session->last_stream_id);
+        if (!stream->cfront->aborted) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->cfront, APLOGNO(03364)
+                          "h2_proxy_sesssion(%s): stream(%d) closed "
+                          "(touched=%d, error=%d)",
+                          session->id, stream_id, touched, stream->error_code);
+
+            if (status != APR_SUCCESS) {
+              /* stream failed. If we have received (and forwarded) response
+               * data already, we need to append an error buckt to inform
+               * consumers.
+               * Otherwise, we have an early fail on the connection and may
+               * retry this request on a new one. In that case, keep the
+               * output virgin so that a new attempt can be made. */
+              if (stream->data_received) {
+                int http_status = ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+                b = ap_bucket_error_create(http_status, NULL, stream->r->pool,
+                                           stream->cfront->bucket_alloc);
+                APR_BRIGADE_INSERT_TAIL(stream->output, b);
+                b = apr_bucket_eos_create(stream->cfront->bucket_alloc);
+                APR_BRIGADE_INSERT_TAIL(stream->output, b);
+                ap_pass_brigade(stream->r->output_filters, stream->output);
+              }
+            }
+            else if (!stream->data_received) {
+                /* if the response had no body, this is the time to flush
+                 * an empty brigade which will also write the response headers */
+                h2_proxy_stream_end_headers_out(stream);
+                stream->data_received = 1;
+                b = apr_bucket_flush_create(stream->cfront->bucket_alloc);
+                APR_BRIGADE_INSERT_TAIL(stream->output, b);
+                b = apr_bucket_eos_create(stream->cfront->bucket_alloc);
+                APR_BRIGADE_INSERT_TAIL(stream->output, b);
+                ap_pass_brigade(stream->r->output_filters, stream->output);
+            }
         }
-        
+
         stream->state = H2_STREAM_ST_CLOSED;
         h2_proxy_ihash_remove(session->streams, stream_id);
         h2_proxy_iq_remove(session->suspended, stream_id);
         if (session->done) {
-            session->done(session, stream->r, status, touched);
+            session->done(session, stream->r, status, touched, stream->error_code);
         }
     }
     
@@ -1650,9 +1679,19 @@
 {
     cleanup_iter_ctx *ctx = udata;
     h2_proxy_stream *stream = val;
-    int touched = (stream->data_sent || 
+    int touched = (stream->data_sent || stream->data_received ||
                    stream->id <= ctx->session->last_stream_id);
-    ctx->done(ctx->session, stream->r, APR_ECONNABORTED, touched);
+    if (touched && stream->output) {
+      apr_bucket *b = ap_bucket_error_create(HTTP_BAD_GATEWAY, NULL,
+                                             stream->r->pool,
+                                             stream->cfront->bucket_alloc);
+      APR_BRIGADE_INSERT_TAIL(stream->output, b);
+      b = apr_bucket_eos_create(stream->cfront->bucket_alloc);
+      APR_BRIGADE_INSERT_TAIL(stream->output, b);
+      ap_pass_brigade(stream->r->output_filters, stream->output);
+    }
+    ctx->done(ctx->session, stream->r, APR_ECONNABORTED, touched,
+              stream->error_code);
     return 1;
 }
 
@@ -1671,6 +1710,12 @@
     }
 }
 
+int h2_proxy_session_is_reusable(h2_proxy_session *session)
+{
+    return (session->state != H2_PROXYS_ST_DONE) &&
+           h2_proxy_ihash_empty(session->streams);
+}
+
 static int ping_arrived_iter(void *udata, void *val)
 {
     h2_proxy_stream *stream = val;
diff -r -N -u a/modules/http2/h2_proxy_session.h b/modules/http2/h2_proxy_session.h
--- a/modules/http2/h2_proxy_session.h	2020-06-08 09:30:49.000000000 +0200
+++ b/modules/http2/h2_proxy_session.h	2024-10-30 21:40:01.059958617 +0100
@@ -68,7 +68,8 @@
 
 typedef struct h2_proxy_session h2_proxy_session;
 typedef void h2_proxy_request_done(h2_proxy_session *s, request_rec *r,
-                                   apr_status_t status, int touched);
+                                   apr_status_t status, int touched,
+                                   int error_code);
 
 struct h2_proxy_session {
     const char *id;
@@ -130,4 +131,6 @@
 
 #define H2_PROXY_REQ_URL_NOTE   "h2-proxy-req-url"
 
+int h2_proxy_session_is_reusable(h2_proxy_session *s);
+
 #endif /* h2_proxy_session_h */
diff -r -N -u a/modules/http2/h2_proxy_util.c b/modules/http2/h2_proxy_util.c
--- a/modules/http2/h2_proxy_util.c	2019-07-02 13:11:08.000000000 +0200
+++ b/modules/http2/h2_proxy_util.c	2024-10-30 21:40:01.059958617 +0100
@@ -496,7 +496,7 @@
                          const char *name, size_t nlen)
 {
     const literal *lit;
-    int i;
+    size_t i;
     
     for (i = 0; i < llen; ++i) {
         lit = &lits[i];
@@ -583,8 +583,7 @@
 
 static h2_proxy_request *h2_proxy_req_createn(int id, apr_pool_t *pool, const char *method, 
                                   const char *scheme, const char *authority, 
-                                  const char *path, apr_table_t *header, 
-                                  int serialize)
+                                  const char *path, apr_table_t *header)
 {
     h2_proxy_request *req = apr_pcalloc(pool, sizeof(h2_proxy_request));
     
@@ -594,14 +593,13 @@
     req->path           = path;
     req->headers        = header? header : apr_table_make(pool, 10);
     req->request_time   = apr_time_now();
-    req->serialize      = serialize;
-    
+
     return req;
 }
 
-h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool, int serialize)
+h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool)
 {
-    return h2_proxy_req_createn(id, pool, NULL, NULL, NULL, NULL, NULL, serialize);
+    return h2_proxy_req_createn(id, pool, NULL, NULL, NULL, NULL, NULL);
 }
 
 typedef struct {
@@ -953,7 +951,7 @@
 {
     if (ctx->link_start < ctx->link_end) {
         char buffer[HUGE_STRING_LEN];
-        int need_len, link_len, buffer_len, prepend_p_server; 
+        size_t need_len, link_len, buffer_len, prepend_p_server;
         const char *mapped;
         
         buffer[0] = '\0';
diff -r -N -u a/modules/http2/h2_proxy_util.h b/modules/http2/h2_proxy_util.h
--- a/modules/http2/h2_proxy_util.h	2020-02-21 01:33:40.000000000 +0100
+++ b/modules/http2/h2_proxy_util.h	2024-10-30 21:40:01.059958617 +0100
@@ -185,11 +185,10 @@
     
     apr_time_t request_time;
     
-    unsigned int chunked : 1;   /* iff request body needs to be forwarded as chunked */
-    unsigned int serialize : 1; /* iff this request is written in HTTP/1.1 serialization */
+    int chunked;   /* iff request body needs to be forwarded as chunked */
 };
 
-h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool, int serialize);
+h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool);
 apr_status_t h2_proxy_req_make(h2_proxy_request *req, apr_pool_t *pool,
                                const char *method, const char *scheme, 
                                const char *authority, const char *path, 
diff -r -N -u a/modules/http2/h2_push.c b/modules/http2/h2_push.c
--- a/modules/http2/h2_push.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_push.c	2024-10-30 21:40:01.059958617 +0100
@@ -29,13 +29,13 @@
 #include <httpd.h>
 #include <http_core.h>
 #include <http_log.h>
+#include <http_protocol.h>
 
 #include "h2_private.h"
-#include "h2_h2.h"
+#include "h2_protocol.h"
 #include "h2_util.h"
 #include "h2_push.h"
 #include "h2_request.h"
-#include "h2_headers.h"
 #include "h2_session.h"
 #include "h2_stream.h"
 
@@ -348,11 +348,10 @@
                 }
                 headers = apr_table_make(ctx->pool, 5);
                 apr_table_do(set_push_header, headers, ctx->req->headers, NULL);
-                req = h2_req_create(0, ctx->pool, method, ctx->req->scheme,
-                                    ctx->req->authority, path, headers,
-                                    ctx->req->serialize);
+                req = h2_request_create(0, ctx->pool, method, ctx->req->scheme,
+                                        ctx->req->authority, path, headers);
                 /* atm, we do not push on pushes */
-                h2_request_end_headers(req, ctx->pool, 1, 0);
+                h2_request_end_headers(req, ctx->pool, 0);
                 push->req = req;
                 if (has_param(ctx, "critical")) {
                     h2_priority *prio = apr_pcalloc(ctx->pool, sizeof(*prio));
@@ -427,14 +426,23 @@
 
 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;
 }
 
-apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req,
-                                    apr_uint32_t push_policy, const h2_headers *res)
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+                                    const struct h2_request *req,
+                                    apr_uint32_t push_policy,
+                                    const ap_bucket_response *res)
+#else
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+                                    const struct h2_request *req,
+                                    apr_uint32_t push_policy,
+                                    const struct h2_headers *res)
+#endif
 {
     if (req && push_policy != H2_PUSH_NONE) {
         /* Collect push candidates from the request/response pair.
@@ -482,8 +490,7 @@
     EVP_MD_CTX *md;
     apr_uint64_t val;
     unsigned char hash[EVP_MAX_MD_SIZE];
-    unsigned len;
-    int i;
+    unsigned len, i;
 
     md = EVP_MD_CTX_create();
     ap_assert(md != NULL);
@@ -495,6 +502,7 @@
     sha256_update(md, push->req->authority);
     sha256_update(md, push->req->path);
     EVP_DigestFinal(md, hash, &len);
+    EVP_MD_CTX_destroy(md);
 
     val = 0;
     for (i = 0; i != len; ++i)
@@ -600,7 +608,7 @@
 {
     h2_push_diary_entry *entries = (h2_push_diary_entry*)diary->entries->elts;
     h2_push_diary_entry e;
-    int lastidx;
+    apr_size_t lastidx;
     
     /* Move an existing entry to the last place */
     if (diary->entries->nelts <= 0)
@@ -657,13 +665,13 @@
             idx = h2_push_diary_find(session->push_diary, e.hash);
             if (idx >= 0) {
                 /* Intentional no APLOGNO */
-                ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c,
+                ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c1,
                               "push_diary_update: already there PUSH %s", push->req->path);
                 move_to_last(session->push_diary, (apr_size_t)idx);
             }
             else {
                 /* Intentional no APLOGNO */
-                ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c,
+                ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c1,
                               "push_diary_update: adding PUSH %s", push->req->path);
                 if (!npushes) {
                     npushes = apr_array_make(pushes->pool, 5, sizeof(h2_push_diary_entry*));
@@ -676,9 +684,15 @@
     return npushes;
 }
     
-apr_array_header_t *h2_push_collect_update(h2_stream *stream, 
-                                           const struct h2_request *req, 
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+                                           const struct h2_request *req,
+                                           const ap_bucket_response *res)
+#else
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+                                           const struct h2_request *req,
                                            const struct h2_headers *res)
+#endif
 {
     apr_array_header_t *pushes;
     
@@ -793,11 +807,11 @@
                                       int maxP, const char *authority, 
                                       const char **pdata, apr_size_t *plen)
 {
-    int nelts, N, i;
+    int nelts, N;
     unsigned char log2n, log2pmax;
     gset_encoder encoder;
     apr_uint64_t *hashes;
-    apr_size_t hash_count;
+    apr_size_t hash_count, i;
     
     nelts = diary->entries->nelts;
     N = ceil_power_of_2(nelts);
diff -r -N -u a/modules/http2/h2_push.h b/modules/http2/h2_push.h
--- a/modules/http2/h2_push.h	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_push.h	2024-10-30 21:40:01.059958617 +0100
@@ -17,10 +17,12 @@
 #ifndef __mod_h2__h2_push__
 #define __mod_h2__h2_push__
 
+#include <http_protocol.h>
+
 #include "h2.h"
+#include "h2_headers.h"
 
 struct h2_request;
-struct h2_headers;
 struct h2_ngheader;
 struct h2_session;
 struct h2_stream;
@@ -97,14 +99,21 @@
  * @param res the response from the server
  * @return array of h2_push addresses or NULL
  */
-apr_array_header_t *h2_push_collect(apr_pool_t *p, 
-                                    const struct h2_request *req, 
-                                    apr_uint32_t push_policy, 
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+                                    const struct h2_request *req,
+                                    apr_uint32_t push_policy,
+                                    const ap_bucket_response *res);
+#else
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+                                    const struct h2_request *req,
+                                    apr_uint32_t push_policy,
                                     const struct h2_headers *res);
+#endif
 
 /**
  * Create a new push diary for the given maximum number of entries.
- * 
+ *
  * @param p the pool to use
  * @param N the max number of entries, rounded up to 2^x
  * @return the created diary, might be NULL of max_entries is 0
@@ -121,14 +130,21 @@
  * Collect pushes for the given request/response pair, enter them into the
  * diary and return those pushes newly entered.
  */
-apr_array_header_t *h2_push_collect_update(struct h2_stream *stream, 
-                                           const struct h2_request *req, 
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+                                           const struct h2_request *req,
+                                           const ap_bucket_response *res);
+#else
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+                                           const struct h2_request *req,
                                            const struct h2_headers *res);
+#endif
+
 /**
  * Get a cache digest as described in 
  * https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
  * from the contents of the push diary.
- * 
+ *
  * @param diary the diary to calculdate the digest from
  * @param p the pool to use
  * @param authority the authority to get the data for, use NULL/"*" for all
diff -r -N -u a/modules/http2/h2_request.c b/modules/http2/h2_request.c
--- a/modules/http2/h2_request.c	2024-10-30 21:39:44.367615523 +0100
+++ b/modules/http2/h2_request.c	2024-10-30 21:40:01.059958617 +0100
@@ -16,7 +16,11 @@
  
 #include <assert.h>
 
-#include <apr_strings.h>
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_strmatch.h"
+
 #include <ap_mmn.h>
 
 #include <httpd.h>
@@ -25,6 +29,7 @@
 #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>
@@ -33,11 +38,28 @@
 
 #include "h2_private.h"
 #include "h2_config.h"
+#include "h2_conn_ctx.h"
 #include "h2_push.h"
 #include "h2_request.h"
 #include "h2_util.h"
 
 
+h2_request *h2_request_create(int id, apr_pool_t *pool, const char *method,
+                              const char *scheme, const char *authority,
+                              const char *path, apr_table_t *header)
+{
+    h2_request *req = apr_pcalloc(pool, sizeof(h2_request));
+
+    req->method         = method;
+    req->scheme         = scheme;
+    req->authority      = authority;
+    req->path           = path;
+    req->headers        = header? header : apr_table_make(pool, 10);
+    req->request_time   = apr_time_now();
+
+    return req;
+}
+
 typedef struct {
     apr_table_t *headers;
     apr_pool_t *pool;
@@ -69,15 +91,28 @@
         return APR_EINVAL;
     }
 
-    if (!ap_strchr_c(authority, ':') && r->server && r->server->port) {
-        apr_port_t defport = apr_uri_port_of_scheme(scheme);
-        if (defport != r->server->port) {
-            /* port info missing and port is not default for scheme: append */
-            authority = apr_psprintf(pool, "%s:%d", authority,
-                                     (int)r->server->port);
+    /* The authority we carry in h2_request is the 'authority' part of
+     * the URL for the request. r->hostname has stripped any port info that
+     * might have been present. Do we need to add it?
+     */
+    if (!ap_strchr_c(authority, ':')) {
+        if (r->parsed_uri.port_str) {
+            /* Yes, it was there, add it again. */
+            authority = apr_pstrcat(pool, authority, ":", r->parsed_uri.port_str, NULL);
+        }
+        else if (!r->parsed_uri.hostname && r->server && r->server->port) {
+            /* If there was no hostname in the parsed URL, the URL was relative.
+             * In that case, we restore port from our server->port, if it
+             * is known and not the default port for the scheme. */
+            apr_port_t defport = apr_uri_port_of_scheme(scheme);
+            if (defport != r->server->port) {
+                /* port info missing and port is not default for scheme: append */
+                authority = apr_psprintf(pool, "%s:%d", authority,
+                                         (int)r->server->port);
+            }
         }
     }
-    
+
     req = apr_pcalloc(pool, sizeof(*req));
     req->method      = apr_pstrdup(pool, r->method);
     req->scheme      = scheme;
@@ -85,40 +120,37 @@
     req->path        = path;
     req->headers     = apr_table_make(pool, 10);
     req->http_status = H2_HTTP_STATUS_UNSET;
-    if (r->server) {
-        req->serialize = h2_config_rgeti(r, H2_CONF_SER_HEADERS);
-    }
 
     x.pool = pool;
     x.headers = req->headers;
     x.status = APR_SUCCESS;
     apr_table_do(set_h1_header, &x, r->headers_in, NULL);
-    
+
     *preq = req;
     return x.status;
 }
 
-apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, 
+apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
                                    const char *name, size_t nlen,
                                    const char *value, size_t vlen,
                                    size_t max_field_len, int *pwas_added)
 {
     apr_status_t status = APR_SUCCESS;
-    
+
     *pwas_added = 0;
     if (nlen <= 0) {
         return status;
     }
-    
+
     if (name[0] == ':') {
         /* pseudo header, see ch. 8.1.2.3, always should come first */
         if (!apr_is_empty_table(req->headers)) {
             ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool,
-                          APLOGNO(02917) 
+                          APLOGNO(02917)
                           "h2_request: pseudo header after request start");
             return APR_EGENERAL;
         }
-        
+
         if (H2_HEADER_METHOD_LEN == nlen
             && !strncmp(H2_HEADER_METHOD, name, nlen)) {
             req->method = apr_pstrndup(pool, value, vlen);
@@ -135,33 +167,36 @@
                  && !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);
             strncpy(buffer, name, (nlen > 31)? 31 : nlen);
             ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, pool,
-                          APLOGNO(02954) 
+                          APLOGNO(02954)
                           "h2_request: ignoring unknown pseudo header %s",
                           buffer);
         }
     }
     else {
         /* non-pseudo header, add to table */
-        status = h2_req_add_header(req->headers, pool, name, nlen, value, vlen, 
+        status = h2_req_add_header(req->headers, pool, name, nlen, value, vlen,
                                    max_field_len, pwas_added);
     }
-    
+
     return status;
 }
 
-apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos, size_t raw_bytes)
+apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool,
+                                    size_t raw_bytes)
 {
-    const char *s;
-    
-    /* rfc7540, ch. 8.1.2.3:
-     * - if we have :authority, it overrides any Host header 
-     * - :authority MUST be omitted when converting h1->h2, so we
-     *   might get a stream without, but then Host needs to be there */
+    /* rfc7540, ch. 8.1.2.3: without :authority, Host: must be there */
+    if (req->authority && !strlen(req->authority)) {
+        req->authority = NULL;
+    }
     if (!req->authority) {
         const char *host = apr_table_get(req->headers, "Host");
         if (!host) {
@@ -172,30 +207,8 @@
     else {
         apr_table_setn(req->headers, "Host", req->authority);
     }
-
-    s = apr_table_get(req->headers, "Content-Length");
-    if (!s) {
-        /* HTTP/2 does not need a Content-Length for framing, but our
-         * internal request processing is used to HTTP/1.1, so we
-         * need to either add a Content-Length or a Transfer-Encoding
-         * if any content can be expected. */
-        if (!eos) {
-            /* We have not seen a content-length and have no eos,
-             * simulate a chunked encoding for our HTTP/1.1 infrastructure,
-             * in case we have "H2SerializeHeaders on" here
-             */
-            req->chunked = 1;
-            apr_table_mergen(req->headers, "Transfer-Encoding", "chunked");
-        }
-        else if (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");
-        }
-    }
     req->raw_bytes += raw_bytes;
-    
+
     return APR_SUCCESS;
 }
 
@@ -206,6 +219,7 @@
     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;
 }
@@ -267,9 +281,96 @@
 }
 #endif
 
-request_rec *h2_request_create_rec(const h2_request *req, conn_rec *c)
+#if AP_HAS_RESPONSE_BUCKETS
+apr_bucket *h2_request_create_bucket(const h2_request *req, request_rec *r)
 {
-    int access_status = HTTP_OK;    
+    conn_rec *c = r->connection;
+    apr_table_t *headers = apr_table_clone(r->pool, req->headers);
+    const char *uri = req->path;
+
+    AP_DEBUG_ASSERT(req->method);
+    AP_DEBUG_ASSERT(req->authority);
+    if (!ap_cstr_casecmp("CONNECT", req->method))  {
+        uri = req->authority;
+    }
+    else if (h2_config_cgeti(c, H2_CONF_PROXY_REQUESTS)) {
+        /* Forward proxying: always absolute uris */
+        uri = apr_psprintf(r->pool, "%s://%s%s",
+                           req->scheme, req->authority,
+                           req->path ? req->path : "");
+    }
+    else if (req->scheme && ap_cstr_casecmp(req->scheme, "http")
+             && ap_cstr_casecmp(req->scheme, "https")) {
+        /* Client sent a non-http ':scheme', use an absolute URI */
+        uri = apr_psprintf(r->pool, "%s://%s%s",
+                           req->scheme, req->authority, req->path ? req->path : "");
+    }
+
+    return ap_bucket_request_create(req->method, uri, "HTTP/2.0", headers,
+                                    r->pool, c->bucket_alloc);
+}
+#endif
+
+static void assign_headers(request_rec *r, const h2_request *req,
+                           int no_body, int is_connect)
+{
+    const char *cl;
+
+    r->headers_in = apr_table_clone(r->pool, req->headers);
+
+    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,
+         * we are more lenient and just replace any Host: if we have
+         * an :authority.
+         */
+        const char *orig_host = apr_table_get(req->headers, "Host");
+        if (orig_host && strcmp(req->authority, orig_host)) {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10401)
+                          "overwriting 'Host: %s' with :authority: %s'",
+                          orig_host, req->authority);
+            apr_table_setn(r->subprocess_env, "H2_ORIGINAL_HOST", orig_host);
+        }
+        apr_table_setn(r->headers_in, "Host", req->authority);
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+                      "set 'Host: %s' from :authority", req->authority);
+    }
+
+    /* 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");
+        }
+#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);
@@ -278,13 +379,78 @@
 #endif
 
 #if AP_MODULE_MAGIC_AT_LEAST(20120211, 107)
+    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;
-    r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
-                                  req->method, req->path ? req->path : "");
-    r->headers_in = apr_table_clone(r->pool, req->headers);
+    AP_DEBUG_ASSERT(req->authority);
+    if (req->http_status != H2_HTTP_STATUS_UNSET) {
+        access_status = req->http_status;
+        goto die;
+    }
+    else if (is_connect) {
+      /* CONNECT MUST NOT have scheme or path */
+        r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+                                      req->method, req->authority);
+        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;
+        }
+        else 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;
+        }
+    }
+    else if (req->protocol) {
+      ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10470)
+                    "':protocol: %s' header present in %s request",
+                    req->protocol, req->method);
+      access_status = HTTP_BAD_REQUEST;
+      goto die;
+    }
+    else if (h2_config_cgeti(c, H2_CONF_PROXY_REQUESTS)) {
+        if (!req->scheme) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10468)
+                          "H2ProxyRequests on, but request misses :scheme");
+            access_status = HTTP_BAD_REQUEST;
+            goto die;
+        }
+        if (!req->authority) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10469)
+                          "H2ProxyRequests on, but request misses :authority");
+            access_status = HTTP_BAD_REQUEST;
+            goto die;
+        }
+        r->the_request = apr_psprintf(r->pool, "%s %s://%s%s HTTP/2.0",
+                                      req->method, req->scheme, req->authority,
+                                      req->path ? req->path : "");
+    }
+    else if (req->scheme && ap_cstr_casecmp(req->scheme, "http")
+             && ap_cstr_casecmp(req->scheme, "https")) {
+        /* Client sent a ':scheme' pseudo header for something else
+         * 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 : "");
+    }
+    else if (req->path) {
+        r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+                                      req->method, req->path);
+    }
+    else {
+        /* We should only come here on a request that is errored already.
+         * create a request line that passes parsing, we'll die anyway.
+         */
+        AP_DEBUG_ASSERT(req->http_status != H2_HTTP_STATUS_UNSET);
+        r->the_request = apr_psprintf(r->pool, "%s / HTTP/2.0", req->method);
+    }
 
     /* Start with r->hostname = NULL, ap_check_request_header() will get it
      * form Host: header, otherwise we get complains about port numbers.
@@ -310,7 +476,7 @@
     {
         const char *s;
 
-        r->headers_in = apr_table_clone(r->pool, req->headers);
+        assign_headers(r, req, no_body, is_connect);
         ap_run_pre_read_request(r, c);
 
         /* Time to populate r with the data we have. */
@@ -369,7 +535,7 @@
      */
     ap_add_input_filter_handle(ap_http_input_filter_handle,
                                NULL, r, r->connection);
-    
+
     if ((access_status = ap_post_read_request(r))) {
         /* Request check post hooks failed. An example of this would be a
          * request for a vhost where h2 is disabled --> 421.
@@ -380,12 +546,24 @@
         goto die;
     }
 
-    AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method, 
-                            (char *)r->uri, (char *)r->server->defn_name, 
+    AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method,
+                            (char *)r->uri, (char *)r->server->defn_name,
                             r->status);
     return r;
 
 die:
+    if (!r->method) {
+        /* if we fail early, `r` is not properly initialized for error
+         * processing which accesses fields in message generation.
+         * Make a best effort. */
+        if (!r->the_request) {
+                r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+                                      req->method, req->path);
+        }
+        ap_parse_request_line(r);
+    }
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+                  "ap_die(%d) for %s", access_status, r->the_request);
     ap_die(access_status, r);
 
     /* ap_die() sent the response through the output filters, we must now
@@ -412,6 +590,3 @@
     AP_READ_REQUEST_FAILURE((uintptr_t)r);
     return NULL;
 }
-
-
-
diff -r -N -u a/modules/http2/h2_request.h b/modules/http2/h2_request.h
--- a/modules/http2/h2_request.h	2020-07-15 16:59:43.000000000 +0200
+++ b/modules/http2/h2_request.h	2024-10-30 21:40:01.059958617 +0100
@@ -19,7 +19,11 @@
 
 #include "h2.h"
 
-apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, 
+h2_request *h2_request_create(int id, apr_pool_t *pool, const char *method,
+                              const char *scheme, const char *authority,
+                              const char *path, apr_table_t *header);
+
+apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool,
                                 request_rec *r);
 
 apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
@@ -31,7 +35,8 @@
                                     const char *name, size_t nlen,
                                     const char *value, size_t vlen);
 
-apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos, size_t raw_bytes);
+apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool,
+                                     size_t raw_bytes);
 
 h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src);
 
@@ -41,9 +46,14 @@
  *
  * @param req the h2 request to process
  * @param conn the connection to process the request on
+ * @param no_body != 0 iff the request is known to have no body
  * @return the request_rec representing the request
  */
-request_rec *h2_request_create_rec(const h2_request *req, conn_rec *conn);
+request_rec *h2_create_request_rec(const h2_request *req, conn_rec *conn,
+                                   int no_body);
 
+#if AP_HAS_RESPONSE_BUCKETS
+apr_bucket *h2_request_create_bucket(const h2_request *req, request_rec *r);
+#endif
 
 #endif /* defined(__mod_h2__h2_request__) */
diff -r -N -u a/modules/http2/h2_session.c b/modules/http2/h2_session.c
--- a/modules/http2/h2_session.c	2024-10-30 21:39:44.639621115 +0100
+++ b/modules/http2/h2_session.c	2024-10-30 21:40:01.059958617 +0100
@@ -17,6 +17,7 @@
 #include <assert.h>
 #include <stddef.h>
 #include <apr_thread_cond.h>
+#include <apr_atomic.h>
 #include <apr_base64.h>
 #include <apr_strings.h>
 
@@ -26,33 +27,35 @@
 #include <http_core.h>
 #include <http_config.h>
 #include <http_log.h>
+#include <http_protocol.h>
 #include <scoreboard.h>
 
 #include <mpm_common.h>
 
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>         /* for getpid() */
+#endif
+
 #include "h2_private.h"
 #include "h2.h"
 #include "h2_bucket_beam.h"
 #include "h2_bucket_eos.h"
 #include "h2_config.h"
-#include "h2_ctx.h"
-#include "h2_filter.h"
-#include "h2_h2.h"
+#include "h2_conn_ctx.h"
+#include "h2_protocol.h"
 #include "h2_mplx.h"
 #include "h2_push.h"
 #include "h2_request.h"
 #include "h2_headers.h"
 #include "h2_stream.h"
-#include "h2_task.h"
+#include "h2_c2.h"
 #include "h2_session.h"
 #include "h2_util.h"
 #include "h2_version.h"
 #include "h2_workers.h"
 
 
-static apr_status_t dispatch_master(h2_session *session);
-static apr_status_t h2_session_read(h2_session *session, int block);
-static void transit(h2_session *session, const char *action, 
+static void transit(h2_session *session, const char *action,
                     h2_session_state nstate);
 
 static void on_stream_state_enter(void *ctx, h2_stream *stream);
@@ -78,18 +81,15 @@
     return nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
 }
 
-static void dispatch_event(h2_session *session, h2_session_event_t ev, 
-                             int err, const char *msg);
-
-void h2_session_event(h2_session *session, h2_session_event_t ev, 
+void h2_session_event(h2_session *session, h2_session_event_t ev,
                              int err, const char *msg)
 {
-    dispatch_event(session, ev, err, msg);
+    h2_session_dispatch_event(session, ev, err, msg);
 }
 
 static int rst_unprocessed_stream(h2_stream *stream, void *ctx)
 {
-    int unprocessed = (!h2_stream_was_closed(stream)
+    int unprocessed = (!h2_stream_is_at_or_past(stream, H2_SS_CLOSED)
                        && (H2_STREAM_CLIENT_INITIATED(stream->id)? 
                            (!stream->session->local.accepting
                             && stream->id > stream->session->local.accepted_max)
@@ -106,7 +106,7 @@
 
 static void cleanup_unprocessed_streams(h2_session *session)
 {
-    h2_mplx_m_stream_do(session->mplx, rst_unprocessed_stream, session);
+    h2_mplx_c1_streams_do(session->mplx, rst_unprocessed_stream, session);
 }
 
 static h2_stream *h2_session_open_stream(h2_session *session, int stream_id,
@@ -127,7 +127,7 @@
 }
 
 /**
- * Determine the importance of streams when scheduling tasks.
+ * Determine the priority order of streams.
  * - if both stream depend on the same one, compare weights
  * - if one stream is closer to the root, prioritize that one
  * - if both are on the same level, use the weight of their root
@@ -187,20 +187,26 @@
                        int flags, void *userp)
 {
     h2_session *session = (h2_session *)userp;
-    apr_status_t status;
+    apr_status_t rv;
     (void)ngh2;
     (void)flags;
-    
-    status = h2_conn_io_write(&session->io, (const char *)data, length);
-    if (status == APR_SUCCESS) {
+
+    if (h2_c1_io_needs_flush(&session->io)) {
+        return NGHTTP2_ERR_WOULDBLOCK;
+    }
+
+    rv = h2_c1_io_add_data(&session->io, (const char *)data, length);
+    if (APR_SUCCESS == rv) {
         return length;
     }
-    if (APR_STATUS_IS_EAGAIN(status)) {
+    else if (APR_STATUS_IS_EAGAIN(rv)) {
         return NGHTTP2_ERR_WOULDBLOCK;
     }
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO(03062)
-                  "h2_session: send error");
-    return h2_session_status_from_apr_status(status);
+    else {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, session->c1,
+                      APLOGNO(03062) "h2_session: send error");
+        return h2_session_status_from_apr_status(rv);
+    }
 }
 
 static int on_invalid_frame_recv_cb(nghttp2_session *ngh2,
@@ -210,11 +216,11 @@
     h2_session *session = (h2_session *)userp;
     (void)ngh2;
     
-    if (APLOGcdebug(session->c)) {
+    if (APLOGcdebug(session->c1)) {
         char buffer[256];
         
         h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
                       H2_SSSN_LOG(APLOGNO(03063), session, 
                       "recv invalid FRAME[%s], frames=%ld/%ld (r/s)"),
                       buffer, (long)session->frames_received,
@@ -234,13 +240,15 @@
     
     stream = get_stream(session, stream_id);
     if (stream) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+                      H2_SSSN_STRM_MSG(session, stream_id, "write %ld bytes of DATA"),
+                      (long)len);
         status = h2_stream_recv_DATA(stream, flags, data, len);
-        dispatch_event(session, H2_SESSION_EV_STREAM_CHANGE, 0, "stream data rcvd");
     }
     else {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03064)
-                      "h2_stream(%ld-%d): on_data_chunk for unknown stream",
-                      session->id, (int)stream_id);
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03064)
+                      H2_SSSN_STRM_MSG(session, stream_id,
+                      "on_data_chunk for unknown stream"));
         rv = NGHTTP2_ERR_CALLBACK_FAILURE;
     }
     
@@ -261,10 +269,10 @@
     stream = get_stream(session, stream_id);
     if (stream) {
         if (error_code) {
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
                           H2_STRM_LOG(APLOGNO(03065), stream, 
                           "closing with err=%d %s"), 
-                          (int)error_code, h2_h2_err_description(error_code));
+                          (int)error_code, h2_protocol_err_description(error_code));
             h2_stream_rst(stream, error_code);
         }
     }
@@ -275,7 +283,7 @@
                                const nghttp2_frame *frame, void *userp)
 {
     h2_session *session = (h2_session *)userp;
-    h2_stream *s;
+    h2_stream *s = NULL;
     
     /* We may see HEADERs at the start of a stream or after all DATA
      * streams to carry trailers. */
@@ -284,7 +292,7 @@
     if (s) {
         /* nop */
     }
-    else {
+    else if (session->local.accepting) {
         s = h2_session_open_stream(userp, frame->hd.stream_id, 0);
     }
     return s? 0 : NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
@@ -303,21 +311,17 @@
     (void)flags;
     stream = get_stream(session, frame->hd.stream_id);
     if (!stream) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(02920) 
-                      "h2_stream(%ld-%d): on_header unknown stream",
-                      session->id, (int)frame->hd.stream_id);
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(02920)
+                      H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
+                      "on_header unknown stream"));
         return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
     }
     
     status = h2_stream_add_header(stream, (const char *)name, namelen,
                                   (const char *)value, valuelen);
-    if (status != APR_SUCCESS &&
-        (!stream->rtmp ||
-         stream->rtmp->http_status == H2_HTTP_STATUS_UNSET ||
-         /* We accept a certain amount of failures in order to reply
-          * with an informative HTTP error response like 413. But if the
-          * client is too wrong, we fail the request a RESET of the stream */
-         stream->request_headers_failed > 100)) {
+    if (status != APR_SUCCESS
+        && (!stream->rtmp
+            || stream->rtmp->http_status == H2_HTTP_STATUS_UNSET)) {
         return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
     }
     return 0;
@@ -336,15 +340,25 @@
     h2_stream *stream;
     apr_status_t rv = APR_SUCCESS;
     
-    if (APLOGcdebug(session->c)) {
+    stream = frame->hd.stream_id? get_stream(session, frame->hd.stream_id) : NULL;
+    if (APLOGcdebug(session->c1)) {
         char buffer[256];
-        
+
         h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
-                      H2_SSSN_LOG(APLOGNO(03066), session, 
-                      "recv FRAME[%s], frames=%ld/%ld (r/s)"),
-                      buffer, (long)session->frames_received,
-                     (long)session->frames_sent);
+        if (stream) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+                          H2_STRM_LOG(APLOGNO(10302), stream,
+                          "recv FRAME[%s], frames=%ld/%ld (r/s)"),
+                          buffer, (long)session->frames_received,
+                         (long)session->frames_sent);
+        }
+        else {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+                          H2_SSSN_LOG(APLOGNO(03066), session,
+                          "recv FRAME[%s], frames=%ld/%ld (r/s)"),
+                          buffer, (long)session->frames_received,
+                         (long)session->frames_sent);
+        }
     }
 
     ++session->frames_received;
@@ -353,16 +367,14 @@
             /* This can be HEADERS for a new stream, defining the request,
              * or HEADER may come after DATA at the end of a stream as in
              * trailers */
-            stream = get_stream(session, frame->hd.stream_id);
             if (stream) {
                 rv = h2_stream_recv_frame(stream, NGHTTP2_HEADERS, frame->hd.flags, 
                     frame->hd.length + H2_FRAME_HDR_LEN);
             }
             break;
         case NGHTTP2_DATA:
-            stream = get_stream(session, frame->hd.stream_id);
             if (stream) {
-                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,  
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
                               H2_STRM_LOG(APLOGNO(02923), stream, 
                               "DATA, len=%ld, flags=%d"), 
                               (long)frame->hd.length, frame->hd.flags);
@@ -372,29 +384,28 @@
             break;
         case NGHTTP2_PRIORITY:
             session->reprioritize = 1;
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                          "h2_stream(%ld-%d): PRIORITY frame "
-                          " weight=%d, dependsOn=%d, exclusive=%d", 
-                          session->id, (int)frame->hd.stream_id,
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+                          H2_SSSN_STRM_MSG(session, frame->hd.stream_id, "PRIORITY frame "
+                          " weight=%d, dependsOn=%d, exclusive=%d"),
                           frame->priority.pri_spec.weight,
                           frame->priority.pri_spec.stream_id,
                           frame->priority.pri_spec.exclusive);
             break;
         case NGHTTP2_WINDOW_UPDATE:
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                          "h2_stream(%ld-%d): WINDOW_UPDATE incr=%d", 
-                          session->id, (int)frame->hd.stream_id,
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+                          H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
+                          "WINDOW_UPDATE incr=%d"),
                           frame->window_update.window_size_increment);
-            if (nghttp2_session_want_write(session->ngh2)) {
-                dispatch_event(session, H2_SESSION_EV_FRAME_RCVD, 0, "window update");
-            }
             break;
         case NGHTTP2_RST_STREAM:
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03067)
-                          "h2_stream(%ld-%d): RST_STREAM by client, error=%d",
-                          session->id, (int)frame->hd.stream_id,
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03067)
+                          H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
+                          "RST_STREAM by client, error=%d"),
                           (int)frame->rst_stream.error_code);
-            stream = get_stream(session, frame->hd.stream_id);
+            if (stream) {
+                rv = h2_stream_recv_frame(stream, NGHTTP2_RST_STREAM, frame->hd.flags,
+                    frame->hd.length + H2_FRAME_HDR_LEN);
+            }
             if (stream && stream->initiated_on) {
                 /* A stream reset on a request we sent it. Normal, when the
                  * client does not want it. */
@@ -403,9 +414,10 @@
             else {
                 /* A stream reset on a request it sent us. Could happen in a browser
                  * when the user navigates away or cancels loading - maybe. */
-                h2_mplx_m_client_rst(session->mplx, frame->hd.stream_id);
-                ++session->streams_reset;
+                h2_mplx_c1_client_rst(session->mplx, frame->hd.stream_id,
+                                      stream);
             }
+            ++session->streams_reset;
             break;
         case NGHTTP2_GOAWAY:
             if (frame->goaway.error_code == 0 
@@ -415,23 +427,21 @@
             }
             else {
                 session->remote.accepted_max = frame->goaway.last_stream_id;
-                dispatch_event(session, H2_SESSION_EV_REMOTE_GOAWAY, 
+                h2_session_dispatch_event(session, H2_SESSION_EV_REMOTE_GOAWAY,
                                frame->goaway.error_code, NULL);
             }
             break;
         case NGHTTP2_SETTINGS:
-            if (APLOGctrace2(session->c)) {
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                              H2_SSSN_MSG(session, "SETTINGS, len=%ld"), (long)frame->hd.length);
-            }
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+                          H2_SSSN_MSG(session, "SETTINGS, len=%ld"), (long)frame->hd.length);
             break;
         default:
-            if (APLOGctrace2(session->c)) {
+            if (APLOGctrace2(session->c1)) {
                 char buffer[256];
                 
                 h2_util_frame_print(frame, buffer,
                                     sizeof(buffer)/sizeof(buffer[0]));
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
                               H2_SSSN_MSG(session, "on_frame_rcv %s"), buffer);
             }
             break;
@@ -447,7 +457,7 @@
          * become in serving this connection. This is expressed in increasing "idle_delays".
          * Eventually, the connection will timeout and we'll close it. */
         session->idle_frames = H2MIN(session->idle_frames + 1, session->frames_received);
-            ap_log_cerror( APLOG_MARK, APLOG_TRACE2, 0, session->c,
+            ap_log_cerror( APLOG_MARK, APLOG_TRACE2, 0, session->c1,
                           H2_SSSN_MSG(session, "session has %ld idle frames"), 
                           (long)session->idle_frames);
         if (session->idle_frames > 10) {
@@ -472,16 +482,6 @@
     return 0;
 }
 
-static int h2_session_continue_data(h2_session *session) {
-    if (h2_mplx_m_has_master_events(session->mplx)) {
-        return 0;
-    }
-    if (h2_conn_io_needs_flush(&session->io)) {
-        return 0;
-    }
-    return 1;
-}
-
 static char immortal_zeros[H2_MAX_PADLEN];
 
 static int on_send_data_cb(nghttp2_session *ngh2, 
@@ -502,47 +502,42 @@
     
     (void)ngh2;
     (void)source;
-    if (!h2_session_continue_data(session)) {
-        return NGHTTP2_ERR_WOULDBLOCK;
-    }
-
     ap_assert(frame->data.padlen <= (H2_MAX_PADLEN+1));
     padlen = (unsigned char)frame->data.padlen;
     
     stream = get_stream(session, stream_id);
     if (!stream) {
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c1,
                       APLOGNO(02924) 
-                      "h2_stream(%ld-%d): send_data, stream not found",
-                      session->id, (int)stream_id);
+                      H2_SSSN_STRM_MSG(session, stream_id, "send_data, stream not found"));
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
                   H2_STRM_MSG(stream, "send_data_cb for %ld bytes"),
                   (long)length);
                   
-    status = h2_conn_io_write(&session->io, (const char *)framehd, H2_FRAME_HDR_LEN);
+    status = h2_c1_io_add_data(&session->io, (const char *)framehd, H2_FRAME_HDR_LEN);
     if (padlen && status == APR_SUCCESS) {
         --padlen;
-        status = h2_conn_io_write(&session->io, (const char *)&padlen, 1);
+        status = h2_c1_io_add_data(&session->io, (const char *)&padlen, 1);
     }
     
     if (status != APR_SUCCESS) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c1,
                       H2_STRM_MSG(stream, "writing frame header"));
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
     
     status = h2_stream_read_to(stream, session->bbtmp, &len, &eos);
     if (status != APR_SUCCESS) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c1,
                       H2_STRM_MSG(stream, "send_data_cb, reading stream"));
         apr_brigade_cleanup(session->bbtmp);
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
-    else if (len != length) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
+    else if (len != (apr_off_t)length) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c1,
                       H2_STRM_MSG(stream, "send_data_cb, wanted %ld bytes, "
                       "got %ld from stream"), (long)length, (long)len);
         apr_brigade_cleanup(session->bbtmp);
@@ -551,20 +546,23 @@
     
     if (padlen) {
         b = apr_bucket_immortal_create(immortal_zeros, padlen, 
-                                       session->c->bucket_alloc);
+                                       session->c1->bucket_alloc);
         APR_BRIGADE_INSERT_TAIL(session->bbtmp, b);
     }
     
-    status = h2_conn_io_pass(&session->io, session->bbtmp);
+    status = h2_c1_io_append(&session->io, session->bbtmp);
     apr_brigade_cleanup(session->bbtmp);
     
     if (status == APR_SUCCESS) {
         stream->out_data_frames++;
         stream->out_data_octets += length;
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+                      H2_STRM_MSG(stream, "sent data length=%ld, total=%ld"),
+                      (long)length, (long)stream->out_data_octets);
         return 0;
     }
     else {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,  
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1,
                       H2_STRM_LOG(APLOGNO(02925), stream, "failed send_data_cb"));
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
@@ -588,18 +586,27 @@
             break;
     }
     
-    if (APLOGcdebug(session->c)) {
+    stream = get_stream(session, stream_id);
+    if (APLOGcdebug(session->c1)) {
         char buffer[256];
         
         h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
-                      H2_SSSN_LOG(APLOGNO(03068), session, 
-                      "sent FRAME[%s], frames=%ld/%ld (r/s)"),
-                      buffer, (long)session->frames_received,
-                     (long)session->frames_sent);
+        if (stream) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+                          H2_STRM_LOG(APLOGNO(10303), stream,
+                          "sent FRAME[%s], frames=%ld/%ld (r/s)"),
+                          buffer, (long)session->frames_received,
+                         (long)session->frames_sent);
+        }
+        else {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+                          H2_SSSN_LOG(APLOGNO(03068), session,
+                          "sent FRAME[%s], frames=%ld/%ld (r/s)"),
+                          buffer, (long)session->frames_received,
+                         (long)session->frames_sent);
+        }
     }
     
-    stream = get_stream(session, stream_id);
     if (stream) {
         h2_stream_send_frame(stream, frame->hd.type, frame->hd.flags, 
             frame->hd.length + H2_FRAME_HDR_LEN);
@@ -608,7 +615,7 @@
 }
 
 #ifdef H2_NG2_INVALID_HEADER_CB
-static int on_invalid_header_cb(nghttp2_session *ngh2, 
+static int on_invalid_header_cb(nghttp2_session *ngh2,
                                 const nghttp2_frame *frame, 
                                 const uint8_t *name, size_t namelen, 
                                 const uint8_t *value, size_t valuelen, 
@@ -617,13 +624,10 @@
     h2_session *session = user_data;
     h2_stream *stream;
     
-    if (APLOGcdebug(session->c)) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03456)
-                      "h2_stream(%ld-%d): invalid header '%s: %s'", 
-                      session->id, (int)frame->hd.stream_id,
-                      apr_pstrndup(session->pool, (const char *)name, namelen),
-                      apr_pstrndup(session->pool, (const char *)value, valuelen));
-    }
+    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'"),
+                  (int)namelen, name, (int)valuelen, value);
     stream = get_stream(session, frame->hd.stream_id);
     if (stream) {
         h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
@@ -637,8 +641,8 @@
                                  size_t max_payloadlen, void *user_data)
 {
     h2_session *session = user_data;
-    ssize_t frame_len = frame->hd.length + H2_FRAME_HDR_LEN; /* the total length without padding */
-    ssize_t padded_len = frame_len;
+    size_t frame_len = frame->hd.length + H2_FRAME_HDR_LEN; /* the total length without padding */
+    size_t padded_len = frame_len;
 
     /* Determine # of padding bytes to append to frame. Unless session->padding_always
      * the number my be capped by the ui.write_size that currently applies. 
@@ -654,12 +658,10 @@
             && (frame_len <= session->io.write_size)) {
             padded_len = session->io.write_size;
         }
-        if (APLOGctrace2(session->c)) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                          "select padding from [%d, %d]: %d (frame length: 0x%04x, write size: %d)", 
-                          (int)frame_len, (int)max_payloadlen+H2_FRAME_HDR_LEN, 
-                          (int)(padded_len - frame_len), (int)padded_len, (int)session->io.write_size);
-        }
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+                      "select padding from [%d, %d]: %d (frame length: 0x%04x, write size: %d)",
+                      (int)frame_len, (int)max_payloadlen+H2_FRAME_HDR_LEN,
+                      (int)(padded_len - frame_len), (int)padded_len, (int)session->io.write_size);
         return padded_len - H2_FRAME_HDR_LEN;
     }
     return frame->hd.length;
@@ -694,6 +696,33 @@
     return APR_SUCCESS;
 }
 
+static void update_child_status(h2_session *session, int status,
+                                const char *msg, const h2_stream *stream)
+{
+    /* Assume that we also change code/msg when something really happened and
+     * avoid updating the scoreboard in between */
+    if (session->last_status_code != status
+        || session->last_status_msg != msg) {
+        char sbuffer[1024];
+        sbuffer[0] = '\0';
+        if (stream) {
+            apr_snprintf(sbuffer, sizeof(sbuffer),
+                         ": stream %d, %s %s",
+                         stream->id,
+                         stream->request? stream->request->method : "",
+                         stream->request? stream->request->path : "");
+        }
+        apr_snprintf(session->status, sizeof(session->status),
+                     "[%d/%d] %s%s",
+                     (int)(session->remote.emitted_count + session->pushes_submitted),
+                     (int)session->streams_done,
+                     msg? msg : "-", sbuffer);
+        ap_update_child_status_from_server(session->c1->sbh, status,
+                                           session->c1, session->s);
+        ap_update_child_status_descr(session->c1->sbh, status, session->status);
+    }
+}
+
 static apr_status_t h2_session_shutdown_notice(h2_session *session)
 {
     apr_status_t status;
@@ -707,9 +736,9 @@
     session->local.accepting = 0;
     status = nghttp2_session_send(session->ngh2);
     if (status == APR_SUCCESS) {
-        status = h2_conn_io_flush(&session->io);
+        status = h2_c1_io_assure_flushed(&session->io);
     }
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
                   H2_SSSN_LOG(APLOGNO(03457), session, "sent shutdown notice"));
     return status;
 }
@@ -723,10 +752,13 @@
     if (session->local.shutdown) {
         return APR_SUCCESS;
     }
-    if (!msg && error) {
-        msg = nghttp2_strerror(error);
+
+    if (error && !msg) {
+        if (APR_STATUS_IS_EPIPE(error)) {
+            msg = "remote close";
+        }
     }
-    
+
     if (error || force_close) {
         /* not a graceful shutdown, we want to leave... 
          * Do not start further streams that are waiting to be scheduled. 
@@ -735,8 +767,9 @@
          * Remove all streams greater than this number without submitting
          * a RST_STREAM frame, since that should be clear from the GOAWAY
          * we send. */
-        session->local.accepted_max = h2_mplx_m_shutdown(session->mplx);
+        session->local.accepted_max = h2_mplx_c1_shutdown(session->mplx);
         session->local.error = error;
+        session->local.error_msg = msg;
     }
     else {
         /* graceful shutdown. we will continue processing all streams
@@ -746,25 +779,25 @@
     
     session->local.accepting = 0;
     session->local.shutdown = 1;
-    if (!session->c->aborted) {
+    if (!session->c1->aborted) {
         nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 
                               session->local.accepted_max, 
                               error, (uint8_t*)msg, msg? strlen(msg):0);
         status = nghttp2_session_send(session->ngh2);
         if (status == APR_SUCCESS) {
-            status = h2_conn_io_flush(&session->io);
+            status = h2_c1_io_assure_flushed(&session->io);
         }
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
                       H2_SSSN_LOG(APLOGNO(03069), session, 
                                   "sent GOAWAY, err=%d, msg=%s"), error, msg? msg : "");
     }
-    dispatch_event(session, H2_SESSION_EV_LOCAL_GOAWAY, error, msg);
+    h2_session_dispatch_event(session, H2_SESSION_EV_LOCAL_GOAWAY, error, msg);
     return status;
 }
 
 static apr_status_t session_cleanup(h2_session *session, const char *trigger)
 {
-    conn_rec *c = session->c;
+    conn_rec *c = session->c1;
     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                   H2_SSSN_MSG(session, "pool_cleanup"));
     
@@ -784,25 +817,36 @@
                       "goodbye, clients will be confused, should not happen"));
     }
 
+    if (!h2_iq_empty(session->ready_to_process)) {
+        int sid;
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+                      H2_SSSN_LOG(APLOGNO(10485), session,
+                      "cleanup, resetting %d streams in ready-to-process"),
+                      h2_iq_count(session->ready_to_process));
+        while ((sid = h2_iq_shift(session->ready_to_process)) > 0) {
+          h2_mplx_c1_client_rst(session->mplx, sid, get_stream(session, sid));
+        }
+    }
+
     transit(session, trigger, H2_SESSION_ST_CLEANUP);
-    h2_mplx_m_release_and_join(session->mplx, session->iowait);
+    h2_mplx_c1_destroy(session->mplx);
     session->mplx = NULL;
 
     ap_assert(session->ngh2);
     nghttp2_session_del(session->ngh2);
     session->ngh2 = NULL;
-    h2_ctx_clear(c);
-    
-    
+    h2_conn_ctx_detach(c);
+
     return APR_SUCCESS;
 }
 
 static apr_status_t session_pool_cleanup(void *data)
 {
     conn_rec *c = data;
-    h2_session *session;
-    
-    if ((session = h2_ctx_get_session(c))) {
+    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+    h2_session *session = conn_ctx? conn_ctx->session : NULL;
+
+    if (session) {
         int mpm_state = 0;
         int level;
 
@@ -814,7 +858,7 @@
          * However, when the server is stopping, it may shutdown connections
          * without running the pre_close hooks. Do not want about that. */
         ap_log_cerror(APLOG_MARK, level, 0, c,
-                      H2_SSSN_LOG(APLOGNO(10020), session, 
+                      H2_SSSN_LOG(APLOGNO(10020), session,
                       "session cleanup triggered by pool cleanup. "
                       "this should have happened earlier already."));
         return session_cleanup(session, "pool cleanup");
@@ -822,47 +866,46 @@
     return APR_SUCCESS;
 }
 
+static /* atomic */ apr_uint32_t next_id;
+
 apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *r,
                                server_rec *s, h2_workers *workers)
 {
     nghttp2_session_callbacks *callbacks = NULL;
     nghttp2_option *options = NULL;
-    apr_allocator_t *allocator;
-    apr_thread_mutex_t *mutex;
     uint32_t n;
+    int thread_num;
     apr_pool_t *pool = NULL;
     h2_session *session;
+    h2_stream *stream0;
     apr_status_t status;
     int rv;
 
     *psession = NULL;
-    status = apr_allocator_create(&allocator);
-    if (status != APR_SUCCESS) {
-        return status;
-    }
-    apr_allocator_max_free_set(allocator, ap_max_mem_free);
-    apr_pool_create_ex(&pool, c->pool, NULL, allocator);
-    if (!pool) {
-        apr_allocator_destroy(allocator);
-        return APR_ENOMEM;
-    }
+    apr_pool_create(&pool, c->pool);
     apr_pool_tag(pool, "h2_session");
-    apr_allocator_owner_set(allocator, pool);
-    status = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, pool);
-    if (status != APR_SUCCESS) {
-        apr_pool_destroy(pool);
-        return APR_ENOMEM;
-    }
-    apr_allocator_mutex_set(allocator, mutex);
-    
     session = apr_pcalloc(pool, sizeof(h2_session));
     if (!session) {
         return APR_ENOMEM;
     }
     
     *psession = session;
-    session->id = c->id;
-    session->c = c;
+    /* c->id does not give a unique id for the lifetime of the session.
+     * mpms like event change c->id when re-activating a keepalive
+     * connection based on the child_num+thread_num of the worker
+     * processing it.
+     * We'd like to have an id that remains constant and unique bc
+     * h2 streams can live through keepalive periods. While double id
+     * will not lead to processing failures, it will confuse log analysis.
+     */
+#if AP_MODULE_MAGIC_AT_LEAST(20211221, 8)
+    ap_sb_get_child_thread(c->sbh, &session->child_num, &thread_num);
+#else
+    (void)thread_num;
+    session->child_num = (int)getpid();
+#endif
+    session->id = apr_atomic_inc32(&next_id);
+    session->c1 = c;
     session->r = r;
     session->s = s;
     session->pool = pool;
@@ -874,42 +917,27 @@
     
     session->max_stream_count = h2_config_sgeti(s, H2_CONF_MAX_STREAMS);
     session->max_stream_mem = h2_config_sgeti(s, H2_CONF_STREAM_MAX_MEM);
-    
-    status = apr_thread_cond_create(&session->iowait, session->pool);
-    if (status != APR_SUCCESS) {
-        apr_pool_destroy(pool);
-        return status;
-    }
-    
-    session->in_pending = h2_iq_create(session->pool, (int)session->max_stream_count);
-    if (session->in_pending == NULL) {
-        apr_pool_destroy(pool);
-        return APR_ENOMEM;
-    }
+    session->max_data_frame_len = h2_config_sgeti(s, H2_CONF_MAX_DATA_FRAME_LEN);
+
+    session->out_c1_blocked = h2_iq_create(session->pool, (int)session->max_stream_count);
+    session->ready_to_process = h2_iq_create(session->pool, (int)session->max_stream_count);
 
-    session->in_process = h2_iq_create(session->pool, (int)session->max_stream_count);
-    if (session->in_process == NULL) {
-        apr_pool_destroy(pool);
-        return APR_ENOMEM;
-    }
-    
     session->monitor = apr_pcalloc(pool, sizeof(h2_stream_monitor));
-    if (session->monitor == NULL) {
-        apr_pool_destroy(pool);
-        return APR_ENOMEM;
-    }
     session->monitor->ctx = session;
     session->monitor->on_state_enter = on_stream_state_enter;
     session->monitor->on_state_event = on_stream_state_event;
     session->monitor->on_event = on_stream_event;
-    
-    session->mplx = h2_mplx_m_create(c, s, session->pool, workers);
-    
-    /* connection input filter that feeds the session */
-    session->cin = h2_filter_cin_create(session);
-    ap_add_input_filter("H2_IN", session->cin, r, c);
-    
-    h2_conn_io_init(&session->io, c, s);
+
+    stream0 = h2_stream_create(0, session->pool, session, NULL, 0);
+    stream0->c2 = session->c1;  /* stream0's connection is the main connection */
+    session->mplx = h2_mplx_c1_create(session->child_num, session->id,
+                                      stream0, s, session->pool, workers);
+    if (!session->mplx) {
+        apr_pool_destroy(pool);
+        return APR_ENOTIMPL;
+    }
+
+    h2_c1_io_init(&session->io, session);
     session->padding_max = h2_config_sgeti(s, H2_CONF_PADDING_BITS);
     if (session->padding_max) {
         session->padding_max = (0x01 << session->padding_max) - 1; 
@@ -937,7 +965,19 @@
     /* We need to handle window updates ourself, otherwise we
      * get flooded by nghttp2. */
     nghttp2_option_set_no_auto_window_update(options, 1);
-    
+#ifdef H2_NG2_NO_CLOSED_STREAMS
+    /* We do not want nghttp2 to keep information about closed streams as
+     * that accumulates memory on long connections. This makes PRIORITY
+     * setting in relation to older streams non-working. */
+    nghttp2_option_set_no_closed_streams(options, 1);
+#endif
+#ifdef H2_NG2_RFC9113_STRICTNESS
+    /* nghttp2 v1.50.0 introduces the strictness checks on leading/trailing
+     * whitespace of RFC 9113 for fields. But, by default, it RST streams
+     * carrying such. We do not want that. We want to strip the ws and
+     * handle them, just like the HTTP/1.1 parser does. */
+    nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(options, 1);
+#endif
     rv = nghttp2_session_server_new2(&session->ngh2, callbacks,
                                      session, options);
     nghttp2_session_callbacks_del(callbacks);
@@ -959,13 +999,15 @@
                       H2_SSSN_LOG(APLOGNO(03200), session, 
                                   "created, max_streams=%d, stream_mem=%d, "
                                   "workers_limit=%d, workers_max=%d, "
-                                  "push_diary(type=%d,N=%d)"),
+                                  "push_diary(type=%d,N=%d), "
+                                  "max_data_frame_len=%d"),
                       (int)session->max_stream_count, 
                       (int)session->max_stream_mem,
-                      session->mplx->limit_active, 
-                      session->mplx->max_active, 
+                      session->mplx->processing_limit,
+                      session->mplx->processing_max,
                       session->push_diary->dtype, 
-                      (int)session->push_diary->N);
+                      (int)session->push_diary->N,
+                      (int)session->max_data_frame_len);
     }
     
     apr_pool_pre_cleanup_register(pool, c, session_pool_cleanup);
@@ -976,7 +1018,7 @@
 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;
     
@@ -1043,8 +1085,15 @@
         settings[slen].value = win_size;
         ++slen;
     }
-    
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, 
+#if H2_USE_WEBSOCKETS
+    if (h2_config_sgeti(session->s, H2_CONF_WEBSOCKETS)) {
+      settings[slen].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
+      settings[slen].value = 1;
+      ++slen;
+    }
+#endif
+
+    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"), 
                   (long)win_size, (int)session->max_stream_count);
@@ -1052,7 +1101,7 @@
                                   settings, slen);
     if (*rv != 0) {
         status = APR_EGENERAL;
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c1,
                       H2_SSSN_LOG(APLOGNO(02935), session, 
                       "nghttp2_submit_settings: %s"), nghttp2_strerror(*rv));
     }
@@ -1070,7 +1119,7 @@
                                            0, NGHTTP2_MAX_WINDOW_SIZE - win_size);
         if (*rv != 0) {
             status = APR_EGENERAL;
-            ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
+            ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c1,
                           H2_SSSN_LOG(APLOGNO(02970), session,
                           "nghttp2_submit_window_update: %s"), 
                           nghttp2_strerror(*rv));        
@@ -1080,87 +1129,6 @@
     return status;
 }
 
-static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream,  
-                                      h2_headers *headers, apr_off_t len,
-                                      int eos);
-
-static ssize_t stream_data_cb(nghttp2_session *ng2s,
-                              int32_t stream_id,
-                              uint8_t *buf,
-                              size_t length,
-                              uint32_t *data_flags,
-                              nghttp2_data_source *source,
-                              void *puser)
-{
-    h2_session *session = (h2_session *)puser;
-    apr_off_t nread = length;
-    int eos = 0;
-    apr_status_t status;
-    h2_stream *stream;
-    ap_assert(session);
-    
-    /* The session wants to send more DATA for the stream. We need
-     * to find out how much of the requested length we can send without
-     * blocking.
-     * Indicate EOS when we encounter it or DEFERRED if the stream
-     * should be suspended. Beware of trailers.
-     */
- 
-    (void)ng2s;
-    (void)buf;
-    (void)source;
-    stream = get_stream(session, stream_id);
-    if (!stream) {
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
-                      APLOGNO(02937) 
-                      "h2_stream(%ld-%d): data_cb, stream not found",
-                      session->id, (int)stream_id);
-        return NGHTTP2_ERR_CALLBACK_FAILURE;
-    }
-
-    status = h2_stream_out_prepare(stream, &nread, &eos, NULL);
-    if (nread) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
-                      H2_STRM_MSG(stream, "prepared no_copy, len=%ld, eos=%d"),
-                      (long)nread, eos);
-        *data_flags |=  NGHTTP2_DATA_FLAG_NO_COPY;
-    }
-    
-    switch (status) {
-        case APR_SUCCESS:
-            break;
-            
-        case APR_EOF:
-            eos = 1;
-            break;
-            
-        case APR_ECONNRESET:
-        case APR_ECONNABORTED:
-            return NGHTTP2_ERR_CALLBACK_FAILURE;
-            
-        case APR_EAGAIN:
-            /* If there is no data available, our session will automatically
-             * suspend this stream and not ask for more data until we resume
-             * it. Remember at our h2_stream that we need to do this.
-             */
-            nread = 0;
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
-                          H2_STRM_LOG(APLOGNO(03071), stream, "suspending"));
-            return NGHTTP2_ERR_DEFERRED;
-            
-        default:
-            nread = 0;
-            ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, 
-                          H2_STRM_LOG(APLOGNO(02938), stream, "reading data"));
-            return NGHTTP2_ERR_CALLBACK_FAILURE;
-    }
-    
-    if (eos) {
-        *data_flags |= NGHTTP2_DATA_FLAG_EOF;
-    }
-    return (ssize_t)nread;
-}
-
 struct h2_stream *h2_session_push(h2_session *session, h2_stream *is,
                                   h2_push *push)
 {
@@ -1175,20 +1143,20 @@
                                           ngh->nv, ngh->nvlen, NULL);
     }
     if (status != APR_SUCCESS || nid <= 0) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, 
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1,
                       H2_STRM_LOG(APLOGNO(03075), is, 
                       "submitting push promise fail: %s"), nghttp2_strerror(nid));
         return NULL;
     }
     ++session->pushes_promised;
     
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
                   H2_STRM_LOG(APLOGNO(03076), is, "SERVER_PUSH %d for %s %s on %d"),
                   nid, push->req->method, push->req->path, is->id);
                   
     stream = h2_session_open_stream(session, nid, is->id);
     if (!stream) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
                       H2_STRM_LOG(APLOGNO(03077), is,
                       "failed to create stream obj %d"), nid);
         /* kill the push_promise */
@@ -1199,7 +1167,6 @@
     
     h2_session_set_prio(session, stream, push->priority);
     h2_stream_set_request(stream, push->req);
-    ++session->unsent_promises;
     return stream;
 }
 
@@ -1214,7 +1181,6 @@
                                  const h2_priority *prio)
 {
     apr_status_t status = APR_SUCCESS;
-#ifdef H2_NG2_CHANGE_PRIO
     nghttp2_stream *s_grandpa, *s_parent, *s;
     
     if (prio == NULL) {
@@ -1223,7 +1189,7 @@
     }
     s = nghttp2_session_find_stream(session->ngh2, stream->id);
     if (!s) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
                       H2_STRM_MSG(stream, "lookup of nghttp2_stream failed"));
         return APR_EINVAL;
     }
@@ -1272,10 +1238,10 @@
                 id_grandpa = nghttp2_stream_get_stream_id(s_grandpa);
                 rv = nghttp2_session_change_stream_priority(session->ngh2, id_parent, &ps);
                 if (rv < 0) {
-                    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03202)
-                                  "h2_stream(%ld-%d): PUSH BEFORE, weight=%d, "
-                                  "depends=%d, returned=%d",
-                                  session->id, id_parent, ps.weight, ps.stream_id, rv);
+                    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03202)
+                                  H2_SSSN_STRM_MSG(session, id_parent,
+                                  "PUSH BEFORE, weight=%d, depends=%d, returned=%d"),
+                                  ps.weight, ps.stream_id, rv);
                     return APR_EGENERAL;
                 }
                 nghttp2_priority_spec_init(&ps, id_grandpa, w, 0);
@@ -1294,18 +1260,13 @@
 
 
         rv = nghttp2_session_change_stream_priority(session->ngh2, stream->id, &ps);
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
                       H2_STRM_LOG(APLOGNO(03203), stream, 
                       "PUSH %s, weight=%d, depends=%d, returned=%d"),
                       ptype, ps.weight, ps.stream_id, rv);
         status = (rv < 0)? APR_EGENERAL : APR_SUCCESS;
     }
-#else
-    (void)session;
-    (void)stream;
-    (void)prio;
-    (void)valid_weight;
-#endif
+
     return status;
 }
 
@@ -1318,338 +1279,84 @@
                    NGHTTP2_SETTINGS_ENABLE_PUSH));
 }
 
-static apr_status_t h2_session_send(h2_session *session)
+static int h2_session_want_send(h2_session *session)
 {
-    apr_interval_time_t saved_timeout;
-    int rv;
-    apr_socket_t *socket;
-    
-    socket = ap_get_conn_socket(session->c);
-    if (socket) {
-        apr_socket_timeout_get(socket, &saved_timeout);
-        apr_socket_timeout_set(socket, session->s->timeout);
-    }
-    
-    rv = nghttp2_session_send(session->ngh2);
-    
-    if (socket) {
-        apr_socket_timeout_set(socket, saved_timeout);
-    }
-    session->have_written = 1;
-    if (rv != 0 && rv != NGHTTP2_ERR_WOULDBLOCK) {
-        if (nghttp2_is_fatal(rv)) {
-            dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, rv, nghttp2_strerror(rv));
-            return APR_EGENERAL;
-        }
-    }
-    
-    session->unsent_promises = 0;
-    session->unsent_submits = 0;
-    
-    return APR_SUCCESS;
+    return nghttp2_session_want_write(session->ngh2)
+        || h2_c1_io_pending(&session->io);
 }
 
-/**
- * headers for the stream are ready.
- */
-static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream,  
-                                      h2_headers *headers, apr_off_t len,
-                                      int eos)
+static apr_status_t h2_session_send(h2_session *session)
 {
-    apr_status_t status = APR_SUCCESS;
-    const char *s;
-    int rv = 0;
+    int ngrv, pending = 0;
+    apr_status_t rv = APR_SUCCESS;
 
-    ap_assert(session);
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
-                  H2_STRM_MSG(stream, "on_headers"));
-    if (headers->status < 100) {
-        h2_stream_rst(stream, headers->status);
-        goto leave;
+    while (nghttp2_session_want_write(session->ngh2)) {
+        ngrv = nghttp2_session_send(session->ngh2);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+                      "nghttp2_session_send: %d", (int)ngrv);
+        pending = 1;
+        if (ngrv != 0 && ngrv != NGHTTP2_ERR_WOULDBLOCK) {
+            if (nghttp2_is_fatal(ngrv)) {
+                h2_session_dispatch_event(session, H2_SESSION_EV_PROTO_ERROR,
+                               ngrv, nghttp2_strerror(ngrv));
+                rv = APR_EGENERAL;
+                goto cleanup;
+            }
+        }
+        if (h2_c1_io_needs_flush(&session->io) ||
+            ngrv == NGHTTP2_ERR_WOULDBLOCK) {
+            rv = h2_c1_io_assure_flushed(&session->io);
+            if (rv != APR_SUCCESS)
+                goto cleanup;
+            pending = 0;
+        }
+    }
+    if (pending) {
+        rv = h2_c1_io_pass(&session->io);
+    }
+cleanup:
+    if (rv != APR_SUCCESS) {
+        h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, rv, NULL);
     }
-    else if (stream->has_response) {
-        h2_ngheader *nh;
-        
-        status = h2_res_create_ngtrailer(&nh, stream->pool, headers);
-        
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, 
-                      H2_STRM_LOG(APLOGNO(03072), stream, "submit %d trailers"), 
-                      (int)nh->nvlen);
-        if (status == APR_SUCCESS) {
-            rv = nghttp2_submit_trailer(session->ngh2, stream->id, 
-                                        nh->nv, nh->nvlen);
-        }
-        else {
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
-                          H2_STRM_LOG(APLOGNO(10024), stream, "invalid trailers"));
-            h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
-        }
-        goto leave;
-    }
-    else {
-        nghttp2_data_provider provider, *pprovider = NULL;
-        h2_ngheader *ngh;
-        const char *note;
-        
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
-                      H2_STRM_LOG(APLOGNO(03073), stream, "submit response %d, REMOTE_WINDOW_SIZE=%u"),
-                      headers->status,
-                      (unsigned int)nghttp2_session_get_stream_remote_window_size(session->ngh2, stream->id));
-        
-        if (!eos || len > 0) {
-            memset(&provider, 0, sizeof(provider));
-            provider.source.fd = stream->id;
-            provider.read_callback = stream_data_cb;
-            pprovider = &provider;
-        }
-        
-        /* If this stream is not a pushed one itself,
-         * and HTTP/2 server push is enabled here,
-         * and the response HTTP status is not sth >= 400,
-         * and the remote side has pushing enabled,
-         * -> find and perform any pushes on this stream
-         *    *before* we submit the stream response itself.
-         *    This helps clients avoid opening new streams on Link
-         *    headers that get pushed right afterwards.
-         * 
-         * *) the response code is relevant, as we do not want to 
-         *    make pushes on 401 or 403 codes and friends. 
-         *    And if we see a 304, we do not push either
-         *    as the client, having this resource in its cache, might
-         *    also have the pushed ones as well.
-         */
-        if (!stream->initiated_on
-            && !stream->has_response
-            && stream->request && stream->request->method
-            && !strcmp("GET", stream->request->method)
-            && (headers->status < 400)
-            && (headers->status != 304)
-            && h2_session_push_enabled(session)) {
-            /* PUSH is possible and enabled on server, unless the request
-             * denies it, submit resources to push */
-            s = apr_table_get(headers->notes, H2_PUSH_MODE_NOTE);
-            if (!s || strcmp(s, "0")) {
-                h2_stream_submit_pushes(stream, headers);
-            }
-        }
-        
-        if (!stream->pref_priority) {
-            stream->pref_priority = h2_stream_get_priority(stream, headers);
-        }
-        h2_session_set_prio(session, stream, stream->pref_priority);
-        
-        note = apr_table_get(headers->notes, H2_FILTER_DEBUG_NOTE);
-        if (note && !strcmp("on", note)) {
-            int32_t connFlowIn, connFlowOut;
-
-            connFlowIn = nghttp2_session_get_effective_local_window_size(session->ngh2); 
-            connFlowOut = nghttp2_session_get_remote_window_size(session->ngh2);
-            headers = h2_headers_copy(stream->pool, headers);
-            apr_table_setn(headers->headers, "conn-flow-in", 
-                           apr_itoa(stream->pool, connFlowIn));
-            apr_table_setn(headers->headers, "conn-flow-out", 
-                           apr_itoa(stream->pool, connFlowOut));
-        }
-        
-        if (headers->status == 103 
-            && !h2_config_sgeti(session->s, H2_CONF_EARLY_HINTS)) {
-            /* suppress sending this to the client, it might have triggered 
-             * pushes and served its purpose nevertheless */
-            rv = 0;
-            goto leave;
-        }
-        
-        status = h2_res_create_ngheader(&ngh, stream->pool, headers);
-        if (status == APR_SUCCESS) {
-            rv = nghttp2_submit_response(session->ngh2, stream->id,
-                                         ngh->nv, ngh->nvlen, pprovider);
-            stream->has_response = h2_headers_are_response(headers);
-            session->have_written = 1;
-            
-            if (stream->initiated_on) {
-                ++session->pushes_submitted;
-            }
-            else {
-                ++session->responses_submitted;
-            }
-        }
-        else {
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
-                          H2_STRM_LOG(APLOGNO(10025), stream, "invalid response"));
-            h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
-        }
-    }
-    
-leave:
-    if (nghttp2_is_fatal(rv)) {
-        status = APR_EGENERAL;
-        dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, rv, nghttp2_strerror(rv));
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
-                      APLOGNO(02940) "submit_response: %s", 
-                      nghttp2_strerror(rv));
-    }
-    
-    ++session->unsent_submits;
-    
-    /* Unsent push promises are written immediately, as nghttp2
-     * 1.5.0 realizes internal stream data structures only on 
-     * send and we might need them for other submits. 
-     * Also, to conserve memory, we send at least every 10 submits
-     * so that nghttp2 does not buffer all outbound items too 
-     * long.
-     */
-    if (status == APR_SUCCESS 
-        && (session->unsent_promises || session->unsent_submits > 10)) {
-        status = h2_session_send(session);
-    }
-    return status;
+    return rv;
 }
 
 /**
- * A stream was resumed as new response/output data arrived.
+ * A streams input state has changed.
  */
-static apr_status_t on_stream_resume(void *ctx, h2_stream *stream)
+static void on_stream_input(void *ctx, h2_stream *stream)
 {
     h2_session *session = ctx;
-    apr_status_t status = APR_EAGAIN;
-    int rv;
-    apr_off_t len = 0;
-    int eos = 0;
-    h2_headers *headers;
-    
+
     ap_assert(stream);
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
-                  H2_STRM_MSG(stream, "on_resume"));
-    
-send_headers:
-    headers = NULL;
-    status = h2_stream_out_prepare(stream, &len, &eos, &headers);
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c, 
-                  H2_STRM_MSG(stream, "prepared len=%ld, eos=%d"), 
-                  (long)len, eos);
-    if (headers) {
-        status = on_stream_headers(session, stream, headers, len, eos);
-        if (status != APR_SUCCESS || stream->rst_error) {
-            return status;
-        }
-        goto send_headers;
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+                  H2_STRM_MSG(stream, "on_input change"));
+    update_child_status(session, SERVER_BUSY_READ, "read", stream);
+    if (stream->id == 0) {
+        /* input on primary connection available? read */
+        h2_c1_read(session);
     }
-    else if (status != APR_EAGAIN) {
-        /* we have DATA to send */
-        if (!stream->has_response) {
-            /* but no response */
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
-                          H2_STRM_LOG(APLOGNO(03466), stream, 
-                          "no response, RST_STREAM"));
-            h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR);
-            return APR_SUCCESS;
-        } 
-        rv = nghttp2_session_resume_data(session->ngh2, stream->id);
-        session->have_written = 1;
-        ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
-                      APLOG_ERR : APLOG_DEBUG, 0, session->c,  
-                      H2_STRM_LOG(APLOGNO(02936), stream, "resumed"));
+    else {
+        h2_stream_on_input_change(stream);
     }
-    return status;
 }
 
-static void h2_session_in_flush(h2_session *session)
+/**
+ * A streams output state has changed.
+ */
+static void on_stream_output(void *ctx, h2_stream *stream)
 {
-    int id;
-    
-    while ((id = h2_iq_shift(session->in_process)) > 0) {
-        h2_stream *stream = get_stream(session, id);
-        if (stream) {
-            ap_assert(!stream->scheduled);
-            if (h2_stream_prep_processing(stream) == APR_SUCCESS) {
-                h2_mplx_m_process(session->mplx, stream, stream_pri_cmp, session);
-            }
-            else {
-                h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
-            }
-        }
-    }
-
-    while ((id = h2_iq_shift(session->in_pending)) > 0) {
-        h2_stream *stream = get_stream(session, id);
-        if (stream) {
-            h2_stream_flush_input(stream);
-        }
-    }
-}
+    h2_session *session = ctx;
 
-static apr_status_t session_read(h2_session *session, apr_size_t readlen, int block)
-{
-    apr_status_t status, rstatus = APR_EAGAIN;
-    conn_rec *c = session->c;
-    apr_off_t read_start = session->io.bytes_read;
-    
-    while (1) {
-        /* H2_IN filter handles all incoming data against the session.
-         * We just pull at the filter chain to make it happen */
-        status = ap_get_brigade(c->input_filters,
-                                session->bbtmp, AP_MODE_READBYTES,
-                                block? APR_BLOCK_READ : APR_NONBLOCK_READ,
-                                H2MAX(APR_BUCKET_BUFF_SIZE, readlen));
-        /* get rid of any possible data we do not expect to get */
-        apr_brigade_cleanup(session->bbtmp); 
-
-        switch (status) {
-            case APR_SUCCESS:
-                /* successful read, reset our idle timers */
-                rstatus = APR_SUCCESS;
-                if (block) {
-                    /* successful blocked read, try unblocked to
-                     * get more. */
-                    block = 0;
-                }
-                break;
-            case APR_EAGAIN:
-                return rstatus;
-            case APR_TIMEUP:
-                return status;
-            default:
-                if (session->io.bytes_read == read_start) {
-                    /* first attempt failed */
-                    if (APR_STATUS_IS_ETIMEDOUT(status)
-                        || APR_STATUS_IS_ECONNABORTED(status)
-                        || APR_STATUS_IS_ECONNRESET(status)
-                        || APR_STATUS_IS_EOF(status)
-                        || APR_STATUS_IS_EBADF(status)) {
-                        /* common status for a client that has left */
-                        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
-                                      H2_SSSN_MSG(session, "input gone"));
-                    }
-                    else {
-                        /* uncommon status, log on INFO so that we see this */
-                        ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, c,
-                                      H2_SSSN_LOG(APLOGNO(02950), session, 
-                                      "error reading, terminating"));
-                    }
-                    return status;
-                }
-                /* subsequent failure after success(es), return initial
-                 * status. */
-                return rstatus;
-        }
-        if ((session->io.bytes_read - read_start) > readlen) {
-            /* read enough in one go, give write a chance */
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
-                          H2_SSSN_MSG(session, "read enough, returning"));
-            break;
-        }
+    ap_assert(stream);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+                  H2_STRM_MSG(stream, "on_output change"));
+    if (stream->id != 0) {
+        update_child_status(session, SERVER_BUSY_WRITE, "write", stream);
+        h2_stream_on_output_change(stream);
     }
-    return rstatus;
 }
 
-static apr_status_t h2_session_read(h2_session *session, int block)
-{
-    apr_status_t status = session_read(session, session->max_stream_mem
-                                       * H2MAX(2, session->open_streams), 
-                                       block);
-    h2_session_in_flush(session);
-    return status;
-}
 
 static const char *StateNames[] = {
     "INIT",      /* H2_SESSION_ST_INIT */
@@ -1668,40 +1375,14 @@
     return StateNames[state];
 }
 
-static void update_child_status(h2_session *session, int status, const char *msg)
-{
-    /* Assume that we also change code/msg when something really happened and
-     * avoid updating the scoreboard in between */
-    if (session->last_status_code != status 
-        || session->last_status_msg != msg) {
-        apr_snprintf(session->status, sizeof(session->status),
-                     "%s, streams: %d/%d/%d/%d/%d (open/recv/resp/push/rst)", 
-                     msg? msg : "-",
-                     (int)session->open_streams, 
-                     (int)session->remote.emitted_count,
-                     (int)session->responses_submitted,
-                     (int)session->pushes_submitted,
-                     (int)session->pushes_reset + session->streams_reset);
-        ap_update_child_status_descr(session->c->sbh, status, session->status);
-    }
-}
-
 static void transit(h2_session *session, const char *action, h2_session_state nstate)
 {
-    apr_time_t timeout;
-    int ostate, loglvl;
-    const char *s;
-    
+    int ostate;
+
     if (session->state != nstate) {
         ostate = session->state;
-        session->state = nstate;
-        
-        loglvl = APLOG_DEBUG;
-        if ((ostate == H2_SESSION_ST_BUSY && nstate == H2_SESSION_ST_WAIT)
-            || (ostate == H2_SESSION_ST_WAIT && nstate == H2_SESSION_ST_BUSY)){
-            loglvl = APLOG_TRACE1;
-        }
-        ap_log_cerror(APLOG_MARK, loglvl, 0, session->c, 
+
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
                       H2_SSSN_LOG(APLOGNO(03078), session, 
                       "transit [%s] -- %s --> [%s]"), 
                       h2_session_state_str(ostate), action, 
@@ -1716,35 +1397,21 @@
                      * If we return to mpm right away, this connection has the
                      * same chance of being cleaned up by the mpm as connections
                      * that already served requests - not fair. */
-                    session->idle_sync_until = apr_time_now() + apr_time_from_sec(1);
-                    s = "timeout";
-                    timeout = session->s->timeout;
-                    update_child_status(session, SERVER_BUSY_READ, "idle");
-                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, 
-                                  H2_SSSN_LOG("", session, "enter idle, timeout = %d sec"),
-                                  (int)apr_time_sec(timeout));
-                }
-                else if (session->open_streams) {
-                    s = "timeout";
-                    timeout = session->s->timeout;
-                    update_child_status(session, SERVER_BUSY_READ, "idle");
+                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
+                                  H2_SSSN_LOG("", session, "enter idle"));
                 }
                 else {
                     /* normal keepalive setup */
-                    s = "keepalive";
-                    timeout = session->s->keep_alive_timeout;
-                    update_child_status(session, SERVER_BUSY_KEEPALIVE, "idle");
+                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
+                                  H2_SSSN_LOG("", session, "enter keepalive"));
                 }
-                session->idle_until = apr_time_now() + timeout; 
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, 
-                              H2_SSSN_LOG("", session, "enter idle, %s = %d sec"), 
-                              s, (int)apr_time_sec(timeout));
+                session->state = nstate;
                 break;
             case H2_SESSION_ST_DONE:
-                update_child_status(session, SERVER_CLOSING, "done");
                 break;
             default:
                 /* nop */
+                session->state = nstate;
                 break;
         }
     }
@@ -1762,12 +1429,45 @@
     }
 }
 
+static void h2_session_ev_input_pending(h2_session *session, int arg, const char *msg)
+{
+    switch (session->state) {
+        case H2_SESSION_ST_INIT:
+        case H2_SESSION_ST_IDLE:
+        case H2_SESSION_ST_WAIT:
+            transit(session, "input read", H2_SESSION_ST_BUSY);
+            break;
+        default:
+            break;
+    }
+}
+
+static void h2_session_ev_input_exhausted(h2_session *session, int arg, const char *msg)
+{
+    switch (session->state) {
+        case H2_SESSION_ST_BUSY:
+            if (!h2_session_want_send(session)) {
+                if (session->open_streams == 0) {
+                    transit(session, "input exhausted, no streams", H2_SESSION_ST_IDLE);
+                }
+                else {
+                    transit(session, "input exhausted", H2_SESSION_ST_WAIT);
+                }
+            }
+            break;
+        case H2_SESSION_ST_WAIT:
+            if (session->open_streams == 0) {
+                transit(session, "input exhausted, no streams", H2_SESSION_ST_IDLE);
+            }
+            break;
+        default:
+            break;
+    }
+}
+
 static void h2_session_ev_local_goaway(h2_session *session, int arg, const char *msg)
 {
     cleanup_unprocessed_streams(session);
-    if (!session->remote.shutdown) {
-        update_child_status(session, SERVER_CLOSING, "local goaway");
-    }
     transit(session, "local goaway", H2_SESSION_ST_DONE);
 }
 
@@ -1778,7 +1478,6 @@
         session->remote.accepting = 0;
         session->remote.shutdown = 1;
         cleanup_unprocessed_streams(session);
-        update_child_status(session, SERVER_CLOSING, "remote goaway");
         transit(session, "remote goaway", H2_SESSION_ST_DONE);
     }
 }
@@ -1793,7 +1492,7 @@
             break;
         
         default:
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
                           H2_SSSN_LOG(APLOGNO(03401), session, 
                           "conn error -> shutdown"));
             h2_session_shutdown(session, arg, msg, 0);
@@ -1804,7 +1503,7 @@
 static void h2_session_ev_proto_error(h2_session *session, int arg, const char *msg)
 {
     if (!session->local.shutdown) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
                       H2_SSSN_LOG(APLOGNO(03402), session, 
                       "proto error -> shutdown"));
         h2_session_shutdown(session, arg, msg, 0);
@@ -1819,83 +1518,6 @@
     }
 }
 
-static void h2_session_ev_no_io(h2_session *session, int arg, const char *msg)
-{
-    switch (session->state) {
-        case H2_SESSION_ST_BUSY:
-            /* Nothing to READ, nothing to WRITE on the master connection.
-             * Possible causes:
-             * - we wait for the client to send us sth
-             * - we wait for started tasks to produce output
-             * - we have finished all streams and the client has sent GO_AWAY
-             */
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                          H2_SSSN_MSG(session, "NO_IO event, %d streams open"), 
-                          session->open_streams);
-            h2_conn_io_flush(&session->io);
-            if (session->open_streams > 0) {
-                if (h2_mplx_m_awaits_data(session->mplx)) {
-                    /* waiting for at least one stream to produce data */
-                    transit(session, "no io", H2_SESSION_ST_WAIT);
-                }
-                else {
-                    /* we have streams open, and all are submitted and none
-                     * is suspended. The only thing keeping us from WRITEing
-                     * more must be the flow control.
-                     * This means we only wait for WINDOW_UPDATE from the 
-                     * client and can block on READ. */
-                    transit(session, "no io (flow wait)", H2_SESSION_ST_IDLE);
-                    /* Make sure we have flushed all previously written output
-                     * so that the client will react. */
-                    if (h2_conn_io_flush(&session->io) != APR_SUCCESS) {
-                        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
-                        return;
-                    }
-                }
-            }
-            else if (session->local.accepting) {
-                /* When we have no streams, but accept new, switch to idle */
-                transit(session, "no io (keepalive)", H2_SESSION_ST_IDLE);
-            }
-            else {
-                /* We are no longer accepting new streams and there are
-                 * none left. Time to leave. */
-                h2_session_shutdown(session, arg, msg, 0);
-                transit(session, "no io", H2_SESSION_ST_DONE);
-            }
-            break;
-        default:
-            /* nop */
-            break;
-    }
-}
-
-static void h2_session_ev_frame_rcvd(h2_session *session, int arg, const char *msg)
-{
-    switch (session->state) {
-        case H2_SESSION_ST_IDLE:
-        case H2_SESSION_ST_WAIT:
-            transit(session, "frame received", H2_SESSION_ST_BUSY);
-            break;
-        default:
-            /* nop */
-            break;
-    }
-}
-
-static void h2_session_ev_stream_change(h2_session *session, int arg, const char *msg)
-{
-    switch (session->state) {
-        case H2_SESSION_ST_IDLE:
-        case H2_SESSION_ST_WAIT:
-            transit(session, "stream change", H2_SESSION_ST_BUSY);
-            break;
-        default:
-            /* nop */
-            break;
-    }
-}
-
 static void h2_session_ev_ngh2_done(h2_session *session, int arg, const char *msg)
 {
     switch (session->state) {
@@ -1928,9 +1550,59 @@
     h2_session_shutdown(session, arg, msg, 1);
 }
 
+static void h2_session_ev_no_more_streams(h2_session *session)
+{
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+                  H2_SSSN_LOG(APLOGNO(10304), session, "no more streams"));
+    switch (session->state) {
+        case H2_SESSION_ST_BUSY:
+        case H2_SESSION_ST_WAIT:
+            if (!h2_session_want_send(session)) {
+                if (session->local.accepting) {
+                    /* We wait for new frames on c1 only. */
+                    transit(session, "all streams done", H2_SESSION_ST_IDLE);
+                }
+                else {
+                    /* We are no longer accepting new streams.
+                     * Time to leave. */
+                    h2_session_shutdown(session, 0, "done", 0);
+                    transit(session, "c1 done after goaway", H2_SESSION_ST_DONE);
+                }
+            }
+            else {
+                transit(session, "no more streams", H2_SESSION_ST_WAIT);
+            }
+            break;
+        default:
+            /* nop */
+            break;
+    }
+}
+
+static void ev_stream_created(h2_session *session, h2_stream *stream)
+{
+    /* nop */
+}
+
 static void ev_stream_open(h2_session *session, h2_stream *stream)
 {
-    h2_iq_append(session->in_process, stream->id);
+    if (H2_STREAM_CLIENT_INITIATED(stream->id)) {
+        ++session->remote.emitted_count;
+        if (stream->id > session->remote.emitted_max) {
+            session->remote.emitted_max = stream->id;
+            session->local.accepted_max = stream->id;
+        }
+    }
+    else {
+        if (stream->id > session->local.emitted_max) {
+            ++session->local.emitted_count;
+            session->remote.emitted_max = stream->id;
+        }
+    }
+    /* Stream state OPEN means we have received all request headers
+     * and can start processing the stream. */
+    h2_iq_append(session->ready_to_process, stream->id);
+    update_child_status(session, SERVER_BUSY_READ, "schedule", stream);
 }
 
 static void ev_stream_closed(h2_session *session, h2_stream *stream)
@@ -1941,76 +1613,72 @@
         && (stream->id > session->local.completed_max)) {
         session->local.completed_max = stream->id;
     }
-    switch (session->state) {
-        case H2_SESSION_ST_IDLE:
-            break;
-        default:
-            break;
-    }
-    
     /* The stream might have data in the buffers of the main connection.
      * We can only free the allocated resources once all had been written.
      * Send a special buckets on the connection that gets destroyed when
      * all preceding data has been handled. On its destruction, it is safe
      * to purge all resources of the stream. */
-    b = h2_bucket_eos_create(session->c->bucket_alloc, stream);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+                  H2_STRM_MSG(stream, "adding h2_eos to c1 out"));
+    b = h2_bucket_eos_create(session->c1->bucket_alloc, stream);
     APR_BRIGADE_INSERT_TAIL(session->bbtmp, b);
-    h2_conn_io_pass(&session->io, session->bbtmp);
+    h2_c1_io_append(&session->io, session->bbtmp);
     apr_brigade_cleanup(session->bbtmp);
 }
 
 static void on_stream_state_enter(void *ctx, h2_stream *stream)
 {
     h2_session *session = ctx;
-    /* stream entered a new state */
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
                   H2_STRM_MSG(stream, "entered state"));
     switch (stream->state) {
         case H2_SS_IDLE: /* stream was created */
-            ++session->open_streams;
-            if (H2_STREAM_CLIENT_INITIATED(stream->id)) {
-                ++session->remote.emitted_count;
-                if (stream->id > session->remote.emitted_max) {
-                    session->remote.emitted_max = stream->id;
-                    session->local.accepted_max = stream->id;
-                }
-            }
-            else {
-                if (stream->id > session->local.emitted_max) {
-                    ++session->local.emitted_count;
-                    session->remote.emitted_max = stream->id;
-                }
-            }
+            ev_stream_created(session, stream);
             break;
         case H2_SS_OPEN: /* stream has request headers */
-        case H2_SS_RSVD_L: /* stream has request headers */
+        case H2_SS_RSVD_L:
             ev_stream_open(session, stream);
             break;
-        case H2_SS_CLOSED_L: /* stream output was closed */
+        case H2_SS_CLOSED_L: /* stream output was closed, but remote end is not */
+            /* If the stream is still being processed, it could still be reading
+             * its input (theoretically, http request hangling does not normally).
+             * But when processing is done, we need to cancel the stream as no
+             * one is consuming the input any longer.
+             * This happens, for example, on a large POST when the response
+             * is ready early due to the POST being denied. */
+            if (!h2_mplx_c1_stream_is_running(session->mplx, stream)) {
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+                              H2_STRM_LOG(APLOGNO(10305), stream, "remote close missing"));
+                nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
+                                          stream->id, H2_ERR_NO_ERROR);
+            }
             break;
         case H2_SS_CLOSED_R: /* stream input was closed */
             break;
         case H2_SS_CLOSED: /* stream in+out were closed */
-            --session->open_streams;
             ev_stream_closed(session, stream);
             break;
         case H2_SS_CLEANUP:
             nghttp2_session_set_stream_user_data(session->ngh2, stream->id, NULL);
-            h2_mplx_m_stream_cleanup(session->mplx, stream);
+            h2_mplx_c1_stream_cleanup(session->mplx, stream, &session->open_streams);
+            ++session->streams_done;
+            update_child_status(session, SERVER_BUSY_WRITE, "done", stream);
             break;
         default:
             break;
     }
-    dispatch_event(session, H2_SESSION_EV_STREAM_CHANGE, 0, "stream state change");
 }
 
-static void on_stream_event(void *ctx, h2_stream *stream, 
-                                  h2_stream_event_t ev)
+static void on_stream_event(void *ctx, h2_stream *stream, h2_stream_event_t ev)
 {
     h2_session *session = ctx;
     switch (ev) {
         case H2_SEV_IN_DATA_PENDING:
-            h2_iq_append(session->in_pending, stream->id);
+            session->input_flushed = 1;
+            break;
+        case H2_SEV_OUT_C1_BLOCK:
+            h2_iq_append(session->out_c1_blocked, stream->id);
             break;
         default:
             /* NOP */
@@ -2035,13 +1703,19 @@
     }
 }
 
-static void dispatch_event(h2_session *session, h2_session_event_t ev, 
-                      int arg, const char *msg)
+void h2_session_dispatch_event(h2_session *session, h2_session_event_t ev,
+                               apr_status_t arg, const char *msg)
 {
     switch (ev) {
         case H2_SESSION_EV_INIT:
             h2_session_ev_init(session, arg, msg);
             break;            
+        case H2_SESSION_EV_INPUT_PENDING:
+            h2_session_ev_input_pending(session, arg, msg);
+            break;
+        case H2_SESSION_EV_INPUT_EXHAUSTED:
+            h2_session_ev_input_exhausted(session, arg, msg);
+            break;
         case H2_SESSION_EV_LOCAL_GOAWAY:
             h2_session_ev_local_goaway(session, arg, msg);
             break;
@@ -2057,12 +1731,6 @@
         case H2_SESSION_EV_CONN_TIMEOUT:
             h2_session_ev_conn_timeout(session, arg, msg);
             break;
-        case H2_SESSION_EV_NO_IO:
-            h2_session_ev_no_io(session, arg, msg);
-            break;
-        case H2_SESSION_EV_FRAME_RCVD:
-            h2_session_ev_frame_rcvd(session, arg, msg);
-            break;
         case H2_SESSION_EV_NGH2_DONE:
             h2_session_ev_ngh2_done(session, arg, msg);
             break;
@@ -2072,319 +1740,265 @@
         case H2_SESSION_EV_PRE_CLOSE:
             h2_session_ev_pre_close(session, arg, msg);
             break;
-        case H2_SESSION_EV_STREAM_CHANGE:
-            h2_session_ev_stream_change(session, arg, msg);
+        case H2_SESSION_EV_NO_MORE_STREAMS:
+            h2_session_ev_no_more_streams(session);
             break;
         default:
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
                           H2_SSSN_MSG(session, "unknown event %d"), ev);
             break;
     }
 }
 
-/* trigger window updates, stream resumes and submits */
-static apr_status_t dispatch_master(h2_session *session) {
-    apr_status_t status;
-    
-    status = h2_mplx_m_dispatch_master_events(session->mplx, 
-                                            on_stream_resume, session);
-    if (status == APR_EAGAIN) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c,
-                      H2_SSSN_MSG(session, "no master event available"));
-    }
-    else if (status != APR_SUCCESS) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c,
-                      H2_SSSN_MSG(session, "dispatch error"));
-        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 
-                       H2_ERR_INTERNAL_ERROR, "dispatch error");
+static void unblock_c1_out(h2_session *session) {
+    int sid;
+
+    while ((sid = h2_iq_shift(session->out_c1_blocked)) > 0) {
+        nghttp2_session_resume_data(session->ngh2, sid);
     }
-    return status;
 }
 
-static const int MAX_WAIT_MICROS = 200 * 1000;
-
 apr_status_t h2_session_process(h2_session *session, int async)
 {
     apr_status_t status = APR_SUCCESS;
-    conn_rec *c = session->c;
+    conn_rec *c = session->c1;
     int rv, mpm_state, trace = APLOGctrace3(c);
-    apr_time_t now;
-    
+
     if (trace) {
         ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
                       H2_SSSN_MSG(session, "process start, async=%d"), async);
     }
-                  
+
+    if (H2_SESSION_ST_INIT == session->state) {
+        if (!h2_protocol_is_acceptable_c1(c, session->r, 1)) {
+            const char *msg = nghttp2_strerror(NGHTTP2_INADEQUATE_SECURITY);
+            update_child_status(session, SERVER_BUSY_READ, msg, NULL);
+            h2_session_shutdown(session, APR_EINVAL, msg, 1);
+        }
+        else {
+            update_child_status(session, SERVER_BUSY_READ, "init", NULL);
+            status = h2_session_start(session, &rv);
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
+                          H2_SSSN_LOG(APLOGNO(03079), session,
+                          "started on %s:%d"),
+                          session->s->server_hostname,
+                          c->local_addr->port);
+            if (status != APR_SUCCESS) {
+                h2_session_dispatch_event(session,
+                               H2_SESSION_EV_CONN_ERROR, status, NULL);
+            }
+            else {
+                h2_session_dispatch_event(session, H2_SESSION_EV_INIT, 0, NULL);
+            }
+        }
+    }
+
     while (session->state != H2_SESSION_ST_DONE) {
-        now = apr_time_now();
-        session->have_read = session->have_written = 0;
 
-        if (session->local.accepting 
+        /* PR65731: we may get a new connection to process while the
+         * MPM already is stopping. For example due to having reached
+         * MaxRequestsPerChild limit.
+         * Since this is supposed to handle things gracefully, we need to:
+         * a) fully initialize the session before GOAWAYing
+         * b) give the client the chance to submit at least one request
+         */
+        if (session->state != H2_SESSION_ST_INIT /* no longer intializing */
+            && session->local.accepted_max > 0   /* have gotten at least one stream */
+            && session->local.accepting          /* have not already locally shut down */
             && !ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
             if (mpm_state == AP_MPMQ_STOPPING) {
-                dispatch_event(session, H2_SESSION_EV_MPM_STOPPING, 0, NULL);
+                h2_session_dispatch_event(session, H2_SESSION_EV_MPM_STOPPING, 0, NULL);
             }
         }
-        
+
         session->status[0] = '\0';
         
+        if (h2_session_want_send(session)) {
+            h2_session_send(session);
+        }
+        else if (!nghttp2_session_want_read(session->ngh2)) {
+            h2_session_dispatch_event(session, H2_SESSION_EV_NGH2_DONE, 0, NULL);
+        }
+
+        if (!h2_iq_empty(session->ready_to_process)) {
+            h2_mplx_c1_process(session->mplx, session->ready_to_process,
+                               get_stream, stream_pri_cmp, session,
+                               &session->open_streams);
+            transit(session, "scheduled stream", H2_SESSION_ST_BUSY);
+        }
+
+        if (session->input_flushed) {
+            transit(session, "forwarded input", H2_SESSION_ST_BUSY);
+            session->input_flushed = 0;
+        }
+
+        if (!h2_iq_empty(session->out_c1_blocked)) {
+            unblock_c1_out(session);
+            transit(session, "unblocked output", H2_SESSION_ST_BUSY);
+        }
+
+        if (session->reprioritize) {
+            h2_mplx_c1_reprioritize(session->mplx, stream_pri_cmp, session);
+            session->reprioritize = 0;
+        }
+
+        if (h2_session_want_send(session)) {
+            h2_session_send(session);
+        }
+
+        status = h2_c1_io_assure_flushed(&session->io);
+        if (APR_SUCCESS != status) {
+            h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
+        }
+
         switch (session->state) {
-            case H2_SESSION_ST_INIT:
-                ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c);
-                if (!h2_is_acceptable_connection(c, session->r, 1)) {
-                    update_child_status(session, SERVER_BUSY_READ, 
-                                        "inadequate security");
-                    h2_session_shutdown(session, 
-                                        NGHTTP2_INADEQUATE_SECURITY, NULL, 1);
-                } 
-                else {
-                    update_child_status(session, SERVER_BUSY_READ, "init");
-                    status = h2_session_start(session, &rv);
-                    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, 
-                                  H2_SSSN_LOG(APLOGNO(03079), session, 
-                                  "started on %s:%d"), 
-                                  session->s->server_hostname,
-                                  c->local_addr->port);
-                    if (status != APR_SUCCESS) {
-                        dispatch_event(session, 
-                                       H2_SESSION_EV_CONN_ERROR, 0, NULL);
-                    }
-                    dispatch_event(session, H2_SESSION_EV_INIT, 0, NULL);
-                }
-                break;
-                
-            case H2_SESSION_ST_IDLE:
-                if (session->idle_until && (now + session->idle_delay) > session->idle_until) {
-                    ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, c,
-                                  H2_SSSN_MSG(session, "idle, timeout reached, closing"));
-                    if (session->idle_delay) {
-                        apr_table_setn(session->c->notes, "short-lingering-close", "1"); 
-                    }
-                    dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, 0, "timeout");
-                    goto out;
-                }
-                
-                if (session->idle_delay) {
-                    /* we are less interested in spending time on this connection */
-                    ap_log_cerror( APLOG_MARK, APLOG_TRACE2, status, c,
-                                  H2_SSSN_MSG(session, "session is idle (%ld ms), idle wait %ld sec left"), 
-                                  (long)apr_time_as_msec(session->idle_delay),
-                                  (long)apr_time_sec(session->idle_until - now));
-                    apr_sleep(session->idle_delay);
-                    session->idle_delay = 0;
-                }
+        case H2_SESSION_ST_INIT:
+            ap_assert(0);
+            h2_c1_read(session);
+            break;
 
-                h2_conn_io_flush(&session->io);
-                if (async && !session->r && (now > session->idle_sync_until)) {
-                    if (trace) {
-                        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
-                                      H2_SSSN_MSG(session, 
-                                      "nonblock read, %d streams open"), 
-                                      session->open_streams);
-                    }
-                    status = h2_session_read(session, 0);
-                    
-                    if (status == APR_SUCCESS) {
-                        session->have_read = 1;
-                    }
-                    else if (APR_STATUS_IS_EAGAIN(status) || APR_STATUS_IS_TIMEUP(status)) {
-                        status = h2_mplx_m_idle(session->mplx);
-                        if (status == APR_EAGAIN) {
-                            break;
-                        }
-                        else if (status != APR_SUCCESS) {
-                            dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 
-                                           H2_ERR_ENHANCE_YOUR_CALM, "less is more");
-                        }
-                        status = APR_EAGAIN;
-                        goto out;
-                    }
-                    else {
+        case H2_SESSION_ST_IDLE:
+            ap_assert(session->open_streams == 0);
+            ap_assert(nghttp2_session_want_read(session->ngh2));
+            if (!h2_session_want_send(session)) {
+                /* Give any new incoming request a short grace period to
+                 * arrive while we are still hot and return to the mpm
+                 * connection handling when nothing really happened. */
+                h2_c1_read(session);
+                if (H2_SESSION_ST_IDLE == session->state) {
+                    if (async) {
                         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
-                                      H2_SSSN_LOG(APLOGNO(03403), session, 
-                                      "no data, error"));
-                        dispatch_event(session, 
-                                       H2_SESSION_EV_CONN_ERROR, 0, "timeout");
-                    }
-                }
-                else {
-                    /* make certain, we send everything before we idle */
-                    if (trace) {
-                        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
-                                      H2_SSSN_MSG(session, 
-                                      "sync, stutter 1-sec, %d streams open"), 
-                                      session->open_streams);
-                    }
-                    /* We wait in smaller increments, using a 1 second timeout.
-                     * That gives us the chance to check for MPMQ_STOPPING often. 
-                     */
-                    status = h2_mplx_m_idle(session->mplx);
-                    if (status == APR_EAGAIN) {
-                        break;
-                    }
-                    else if (status != APR_SUCCESS) {
-                        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 
-                                       H2_ERR_ENHANCE_YOUR_CALM, "less is more");
-                    }
-                    h2_filter_cin_timeout_set(session->cin, apr_time_from_sec(1));
-                    status = h2_session_read(session, 1);
-                    if (status == APR_SUCCESS) {
-                        session->have_read = 1;
-                    }
-                    else if (status == APR_EAGAIN) {
-                        /* nothing to read */
-                    }
-                    else if (APR_STATUS_IS_TIMEUP(status)) {
-                        /* continue reading handling */
-                    }
-                    else if (APR_STATUS_IS_ECONNABORTED(status)
-                             || APR_STATUS_IS_ECONNRESET(status)
-                             || APR_STATUS_IS_EOF(status)
-                             || APR_STATUS_IS_EBADF(status)) {
-                        ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
-                                      H2_SSSN_MSG(session, "input gone"));
-                        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+                                      H2_SSSN_LOG(APLOGNO(10306), session,
+                                      "returning to mpm c1 monitoring"));
+                        goto leaving;
                     }
                     else {
-                        ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
-                                      H2_SSSN_MSG(session, 
-                                      "(1 sec timeout) read failed"));
-                        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, "error");
-                    }
-                }
-                if (nghttp2_session_want_write(session->ngh2)) {
-                    ap_update_child_status(session->c->sbh, SERVER_BUSY_WRITE, NULL);
-                    status = h2_session_send(session);
-                    if (status == APR_SUCCESS) {
-                        status = h2_conn_io_flush(&session->io);
-                    }
-                    if (status != APR_SUCCESS) {
-                        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 
-                                       H2_ERR_INTERNAL_ERROR, "writing");
-                        break;
+                        /* Not an async mpm, we must continue waiting
+                         * for client data to arrive until the configured
+                         * server Timeout/KeepAliveTimeout happens */
+                        apr_time_t timeout = (session->open_streams == 0)?
+                            session->s->keep_alive_timeout :
+                            session->s->timeout;
+                        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
+                                      H2_SSSN_MSG(session, "polling timeout=%d"),
+                                      (int)apr_time_sec(timeout));
+                        status = h2_mplx_c1_poll(session->mplx, timeout,
+                                                 on_stream_input,
+                                                 on_stream_output, session);
+                        if (APR_STATUS_IS_TIMEUP(status)) {
+                            if (session->open_streams == 0) {
+                                h2_session_dispatch_event(session,
+                                    H2_SESSION_EV_CONN_TIMEOUT, status, NULL);
+                                break;
+                            }
+                        }
+                        else if (APR_SUCCESS != status) {
+                            h2_session_dispatch_event(session,
+                                H2_SESSION_EV_CONN_ERROR, status, NULL);
+                            break;
+                        }
                     }
                 }
+            }
+            else {
+                transit(session, "c1 io pending", H2_SESSION_ST_BUSY);
+            }
+            break;
+
+        case H2_SESSION_ST_BUSY:
+            /* IO happening in and out. Make sure we react to c2 events
+             * inbetween send and receive. */
+            status = h2_mplx_c1_poll(session->mplx, 0,
+                                     on_stream_input, on_stream_output, session);
+            if (APR_SUCCESS != status && !APR_STATUS_IS_TIMEUP(status)) {
+                h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
                 break;
-                
-            case H2_SESSION_ST_BUSY:
-                if (nghttp2_session_want_read(session->ngh2)) {
-                    ap_update_child_status(session->c->sbh, SERVER_BUSY_READ, NULL);
-                    h2_filter_cin_timeout_set(session->cin, session->s->timeout);
-                    status = h2_session_read(session, 0);
-                    if (status == APR_SUCCESS) {
-                        session->have_read = 1;
-                    }
-                    else if (status == APR_EAGAIN) {
-                        /* nothing to read */
-                    }
-                    else if (APR_STATUS_IS_TIMEUP(status)) {
-                        dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, 0, NULL);
-                        break;
-                    }
-                    else {
-                        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
-                    }
-                }
+            }
+            h2_c1_read(session);
+            break;
 
-                status = dispatch_master(session);
-                if (status != APR_SUCCESS && status != APR_EAGAIN) {
+        case H2_SESSION_ST_WAIT:
+            status = h2_c1_io_assure_flushed(&session->io);
+            if (APR_SUCCESS != status) {
+                h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
+                break;
+            }
+            if (session->open_streams == 0) {
+                h2_session_dispatch_event(session, H2_SESSION_EV_NO_MORE_STREAMS,
+                                          0, "streams really done");
+                if (session->state != H2_SESSION_ST_WAIT) {
                     break;
                 }
-                
-                if (nghttp2_session_want_write(session->ngh2)) {
-                    ap_update_child_status(session->c->sbh, SERVER_BUSY_WRITE, NULL);
-                    status = h2_session_send(session);
-                    if (status == APR_SUCCESS) {
-                        status = h2_conn_io_flush(&session->io);
-                    }
-                    if (status != APR_SUCCESS) {
-                        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 
-                                       H2_ERR_INTERNAL_ERROR, "writing");
-                        break;
-                    }
-                }
-                
-                if (session->have_read || session->have_written) {
-                    if (session->wait_us) {
-                        session->wait_us = 0;
-                    }
-                }
-                else if (!nghttp2_session_want_write(session->ngh2)) {
-                    dispatch_event(session, H2_SESSION_EV_NO_IO, 0, NULL);
+            }
+            /* No IO happening and input is exhausted. Make sure we have
+             * flushed any possibly pending output and then wait with
+             * the c1 connection timeout for sth to happen in our c1/c2 sockets/pipes */
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
+                          H2_SSSN_MSG(session, "polling timeout=%d, open_streams=%d"),
+                          (int)apr_time_sec(session->s->timeout), session->open_streams);
+            status = h2_mplx_c1_poll(session->mplx, session->s->timeout,
+                                     on_stream_input, on_stream_output, session);
+            if (APR_STATUS_IS_TIMEUP(status)) {
+                /* If we timeout without streams open, no new request from client
+                 * arrived.
+                 * If we timeout without nghttp2 wanting to write something, but
+                 * all open streams have something to send, it means we are
+                 * blocked on HTTP/2 flow control and the client did not send
+                 * WINDOW_UPDATEs to us. */
+                if (session->open_streams == 0 ||
+                    (!h2_session_want_send(session) &&
+                     h2_mplx_c1_all_streams_want_send_data(session->mplx))) {
+                    h2_session_dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, status, NULL);
+                    break;
                 }
+            }
+            else if (APR_SUCCESS != status) {
+                h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
                 break;
-                
-            case H2_SESSION_ST_WAIT:
-                if (session->wait_us <= 0) {
-                    session->wait_us = 10;
-                    if (h2_conn_io_flush(&session->io) != APR_SUCCESS) {
-                        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
-                        break;
-                    }
-                }
-                else {
-                    /* repeating, increase timer for graceful backoff */
-                    session->wait_us = H2MIN(session->wait_us*2, MAX_WAIT_MICROS);
-                }
+            }
+            break;
 
-                if (trace) {
-                    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
-                                  "h2_session: wait for data, %ld micros", 
-                                  (long)session->wait_us);
-                }
-                status = h2_mplx_m_out_trywait(session->mplx, session->wait_us, 
-                                             session->iowait);
-                if (status == APR_SUCCESS) {
-                    session->wait_us = 0;
-                        dispatch_event(session, H2_SESSION_EV_STREAM_CHANGE, 0, NULL);
-                }
-                else if (APR_STATUS_IS_TIMEUP(status)) {
-                    /* go back to checking all inputs again */
-                    transit(session, "wait cycle", session->local.shutdown? 
-                            H2_SESSION_ST_DONE : H2_SESSION_ST_BUSY);
-                }
-                else if (APR_STATUS_IS_ECONNRESET(status) 
-                         || APR_STATUS_IS_ECONNABORTED(status)) {
-                    dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
-                }
-                else {
-                    ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, c,
-                                  H2_SSSN_LOG(APLOGNO(03404), session, 
-                                  "waiting on conditional"));
-                    h2_session_shutdown(session, H2_ERR_INTERNAL_ERROR, 
-                                        "cond wait error", 0);
-                }
-                break;
-                
-            default:
-                ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
-                              H2_SSSN_LOG(APLOGNO(03080), session, 
-                              "unknown state"));
-                dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, 0, NULL);
-                break;
-        }
+        case H2_SESSION_ST_DONE:
+            h2_c1_read(session);
+            break;
 
-        if (!nghttp2_session_want_read(session->ngh2) 
-                 && !nghttp2_session_want_write(session->ngh2)) {
-            dispatch_event(session, H2_SESSION_EV_NGH2_DONE, 0, NULL); 
-        }
-        if (session->reprioritize) {
-            h2_mplx_m_reprioritize(session->mplx, stream_pri_cmp, session);
-            session->reprioritize = 0;
+        default:
+            ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
+                          H2_SSSN_LOG(APLOGNO(03080), session,
+                          "unknown state"));
+            h2_session_dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, APR_EGENERAL, NULL);
+            break;
         }
     }
-    
-out:
+
+leaving:
     if (trace) {
         ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
                       H2_SSSN_MSG(session, "process returns")); 
     }
-    
-    if ((session->state != H2_SESSION_ST_DONE)
-        && (APR_STATUS_IS_EOF(status)
+    h2_mplx_c1_going_keepalive(session->mplx);
+
+    if (session->state == H2_SESSION_ST_DONE) {
+        if (session->local.error) {
+            char buffer[128];
+            const char *msg;
+            if (session->local.error_msg) {
+                msg = session->local.error_msg;
+            }
+            else {
+                msg = apr_strerror(session->local.error, buffer, sizeof(buffer));
+            }
+            update_child_status(session, SERVER_CLOSING, msg, NULL);
+        }
+        else {
+            update_child_status(session, SERVER_CLOSING, "done", NULL);
+        }
+    }
+    else if (APR_STATUS_IS_EOF(status)
             || APR_STATUS_IS_ECONNRESET(status) 
-            || APR_STATUS_IS_ECONNABORTED(status))) {
-        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+            || APR_STATUS_IS_ECONNABORTED(status)) {
+        h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
+        update_child_status(session, SERVER_CLOSING, "error", NULL);
     }
 
     return (session->state == H2_SESSION_ST_DONE)? APR_EOF : APR_SUCCESS;
@@ -2394,14 +2008,14 @@
 {
     apr_status_t status;
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, 
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
                   H2_SSSN_MSG(session, "pre_close"));
-    dispatch_event(session, H2_SESSION_EV_PRE_CLOSE, 0, 
+    h2_session_dispatch_event(session, H2_SESSION_EV_PRE_CLOSE, 0,
         (session->state == H2_SESSION_ST_IDLE)? "timeout" : NULL);
     status = session_cleanup(session, "pre_close");
     if (status == APR_SUCCESS) {
         /* no one should hold a reference to this session any longer and
-         * the h2_ctx was removed from the connection.
+         * the h2_conn_ctx_twas removed from the connection.
          * Take the pool (and thus all subpools etc. down now, instead of
          * during cleanup of main connection pool. */
         apr_pool_destroy(session->pool);
diff -r -N -u a/modules/http2/h2_session.h b/modules/http2/h2_session.h
--- a/modules/http2/h2_session.h	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_session.h	2024-10-30 21:40:01.059958617 +0100
@@ -17,25 +17,15 @@
 #ifndef __mod_h2__h2_session__
 #define __mod_h2__h2_session__
 
-#include "h2_conn_io.h"
+#include "h2_c1_io.h"
 
 /**
  * A HTTP/2 connection, a session with a specific client.
  * 
  * h2_session sits on top of a httpd conn_rec* instance and takes complete
  * control of the connection data. It receives protocol frames from the
- * client. For new HTTP/2 streams it creates h2_task(s) that are sent
- * via callback to a dispatcher (see h2_conn.c).
- * h2_session keeps h2_io's for each ongoing stream which buffer the
- * payload for that stream.
- *
- * New incoming HEADER frames are converted into a h2_stream+h2_task instance
- * that both represent a HTTP/2 stream, but may have separate lifetimes. This
- * allows h2_task to be scheduled in other threads without semaphores
- * all over the place. It allows task memory to be freed independent of
- * session lifetime and sessions may close down while tasks are still running.
- *
- *
+ * client. For new HTTP/2 streams it creates secondary connections
+ * to execute the requests in h2 workers.
  */
 
 #include "h2.h"
@@ -44,7 +34,6 @@
 struct apr_thread_cond_t;
 struct h2_ctx;
 struct h2_config;
-struct h2_filter_cin;
 struct h2_ihash_t;
 struct h2_mplx;
 struct h2_priority;
@@ -53,39 +42,38 @@
 struct h2_session;
 struct h2_stream;
 struct h2_stream_monitor;
-struct h2_task;
 struct h2_workers;
 
 struct nghttp2_session;
 
 typedef enum {
     H2_SESSION_EV_INIT,             /* session was initialized */
+    H2_SESSION_EV_INPUT_PENDING,    /* c1 input may have data pending */
+    H2_SESSION_EV_INPUT_EXHAUSTED,  /* c1 input exhausted */
     H2_SESSION_EV_LOCAL_GOAWAY,     /* we send a GOAWAY */
     H2_SESSION_EV_REMOTE_GOAWAY,    /* remote send us a GOAWAY */
     H2_SESSION_EV_CONN_ERROR,       /* connection error */
     H2_SESSION_EV_PROTO_ERROR,      /* protocol error */
     H2_SESSION_EV_CONN_TIMEOUT,     /* connection timeout */
-    H2_SESSION_EV_NO_IO,            /* nothing has been read or written */
-    H2_SESSION_EV_FRAME_RCVD,       /* a frame has been received */
     H2_SESSION_EV_NGH2_DONE,        /* nghttp2 wants neither read nor write anything */
     H2_SESSION_EV_MPM_STOPPING,     /* the process is stopping */
     H2_SESSION_EV_PRE_CLOSE,        /* connection will close after this */
-    H2_SESSION_EV_STREAM_CHANGE,    /* a stream (state/input/output) changed */
+    H2_SESSION_EV_NO_MORE_STREAMS,  /* no more streams to process */
 } h2_session_event_t;
 
 typedef struct h2_session {
-    long id;                        /* identifier of this session, unique
-                                     * inside a httpd process */
-    conn_rec *c;                    /* the connection this session serves */
+    int child_num;                  /* child number this session runs in */
+    apr_uint32_t id;                /* identifier of this session, unique per child */
+    conn_rec *c1;                   /* the main connection this session serves */
     request_rec *r;                 /* the request that started this in case
                                      * of 'h2c', NULL otherwise */
     server_rec *s;                  /* server/vhost we're starting on */
     apr_pool_t *pool;               /* pool to use in session */
     struct h2_mplx *mplx;           /* multiplexer for stream data */
-    struct h2_workers *workers;     /* for executing stream tasks */
-    struct h2_filter_cin *cin;      /* connection input filter context */
-    h2_conn_io io;                  /* io on httpd conn filters */
-    int padding_max;                /* max number of padding bytes */
+    struct h2_workers *workers;     /* for executing streams */
+    struct h2_c1_io_in_ctx_t *cin;  /* connection input filter context */
+    h2_c1_io io;                    /* io on httpd conn filters */
+    unsigned int padding_max;       /* max number of padding bytes */
     int padding_always;             /* padding has precedence over I/O optimizations */
     struct nghttp2_session *ngh2;   /* the nghttp2 session (internal use) */
 
@@ -96,43 +84,39 @@
     
     unsigned int reprioritize  : 1; /* scheduled streams priority changed */
     unsigned int flush         : 1; /* flushing output necessary */
-    unsigned int have_read     : 1; /* session has read client data */
-    unsigned int have_written  : 1; /* session did write data to client */
     apr_interval_time_t  wait_us;   /* timeout during BUSY_WAIT state, micro secs */
     
     struct h2_push_diary *push_diary; /* remember pushes, avoid duplicates */
     
     struct h2_stream_monitor *monitor;/* monitor callbacks for streams */
-    int open_streams;               /* number of client streams open */
-    int unsent_submits;             /* number of submitted, but not yet written responses. */
-    int unsent_promises;            /* number of submitted, but not yet written push promises */
-                                         
-    int responses_submitted;        /* number of http/2 responses submitted */
-    int streams_reset;              /* number of http/2 streams reset by client */
-    int pushes_promised;            /* number of http/2 push promises submitted */
-    int pushes_submitted;           /* number of http/2 pushed responses submitted */
-    int pushes_reset;               /* number of http/2 pushed reset by client */
+    unsigned int open_streams;      /* number of streams processing */
+
+    unsigned int streams_done;      /* number of http/2 streams handled */
+    unsigned int responses_submitted; /* number of http/2 responses submitted */
+    unsigned int streams_reset;     /* number of http/2 streams reset by client */
+    unsigned int pushes_promised;   /* number of http/2 push promises submitted */
+    unsigned int pushes_submitted;  /* number of http/2 pushed responses submitted */
+    unsigned int pushes_reset;      /* number of http/2 pushed reset by client */
     
     apr_size_t frames_received;     /* number of http/2 frames received */
     apr_size_t frames_sent;         /* number of http/2 frames sent */
     
     apr_size_t max_stream_count;    /* max number of open streams */
     apr_size_t max_stream_mem;      /* max buffer memory for a single stream */
-    
-    apr_time_t idle_until;          /* Time we shut down due to sheer boredom */
-    apr_time_t idle_sync_until;     /* Time we sync wait until keepalive handling kicks in */
+    apr_size_t max_data_frame_len;  /* max amount of bytes for a single DATA frame */
+
     apr_size_t idle_frames;         /* number of rcvd frames that kept session in idle state */
     apr_interval_time_t idle_delay; /* Time we delay processing rcvd frames in idle state */
     
     apr_bucket_brigade *bbtmp;      /* brigade for keeping temporary data */
-    struct apr_thread_cond_t *iowait; /* our cond when trywaiting for data */
-    
+
     char status[64];                /* status message for scoreboard */
     int last_status_code;           /* the one already reported */
     const char *last_status_msg;    /* the one already reported */
-    
-    struct h2_iqueue *in_pending;   /* all streams with input pending */
-    struct h2_iqueue *in_process;   /* all streams ready for processing on a secondary */
+
+    int input_flushed;              /* stream input was flushed */
+    struct h2_iqueue *out_c1_blocked;  /* all streams with output blocked on c1 buffer full */
+    struct h2_iqueue *ready_to_process;  /* all streams ready for processing */
 
 } h2_session;
 
@@ -153,7 +137,7 @@
                                struct h2_workers *workers);
 
 void h2_session_event(h2_session *session, h2_session_event_t ev, 
-                             int err, const char *msg);
+                      int err, const char *msg);
 
 /**
  * Process the given HTTP/2 session until it is ended or a fatal
@@ -177,11 +161,6 @@
 void h2_session_abort(h2_session *session, apr_status_t reason);
 
 /**
- * Close and deallocate the given session.
- */
-void h2_session_close(h2_session *session);
-
-/**
  * Returns if client settings have push enabled.
  * @param != 0 iff push is enabled in client settings
  */
@@ -203,10 +182,25 @@
                                  struct h2_stream *stream, 
                                  const struct h2_priority *prio);
 
+/**
+ * Dispatch a event happending during session processing.
+ * @param session the sessiont
+ * @param ev the event that happened
+ * @param arg integer argument (event type dependant)
+ * @param msg destriptive message
+ */
+void h2_session_dispatch_event(h2_session *session, h2_session_event_t ev,
+                               int arg, const char *msg);
+
+
 #define H2_SSSN_MSG(s, msg)     \
-    "h2_session(%ld,%s,%d): "msg, s->id, h2_session_state_str(s->state), \
+    "h2_session(%d-%lu,%s,%d): "msg, s->child_num, (unsigned long)s->id, \
+                            h2_session_state_str(s->state), \
                             s->open_streams
 
 #define H2_SSSN_LOG(aplogno, s, msg)    aplogno H2_SSSN_MSG(s, msg)
 
+#define H2_SSSN_STRM_MSG(s, stream_id, msg)     \
+    "h2_stream(%d-%lu-%d): "msg, s->child_num, (unsigned long)s->id, stream_id
+
 #endif /* defined(__mod_h2__h2_session__) */
diff -r -N -u a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c
--- a/modules/http2/h2_stream.c	2024-10-30 21:39:44.639621115 +0100
+++ b/modules/http2/h2_stream.c	2024-10-30 21:40:01.059958617 +0100
@@ -17,34 +17,39 @@
 #include <assert.h>
 #include <stddef.h>
 
-#include <apr_strings.h>
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_strmatch.h"
 
 #include <httpd.h>
 #include <http_core.h>
 #include <http_connection.h>
 #include <http_log.h>
+#include <http_protocol.h>
+#include <http_ssl.h>
 
 #include <nghttp2/nghttp2.h>
 
 #include "h2_private.h"
 #include "h2.h"
 #include "h2_bucket_beam.h"
-#include "h2_conn.h"
+#include "h2_c1.h"
 #include "h2_config.h"
-#include "h2_h2.h"
+#include "h2_protocol.h"
 #include "h2_mplx.h"
 #include "h2_push.h"
 #include "h2_request.h"
 #include "h2_headers.h"
 #include "h2_session.h"
 #include "h2_stream.h"
-#include "h2_task.h"
-#include "h2_ctx.h"
-#include "h2_task.h"
+#include "h2_c2.h"
+#include "h2_conn_ctx.h"
+#include "h2_c2.h"
 #include "h2_util.h"
 
 
-static const char *h2_ss_str(h2_stream_state_t state)
+static const char *h2_ss_str(const h2_stream_state_t state)
 {
     switch (state) {
         case H2_SS_IDLE:
@@ -68,7 +73,7 @@
     }
 }
 
-const char *h2_stream_state_str(h2_stream *stream) 
+const char *h2_stream_state_str(const h2_stream *stream)
 {
     return h2_ss_str(stream->state);
 }
@@ -120,7 +125,8 @@
 { S_XXX, S_ERR,  S_ERR,  S_CL_L, S_CLS,  S_XXX,  S_XXX,  S_XXX, },/* EV_CLOSED_L*/
 { S_ERR, S_ERR,  S_ERR,  S_CL_R, S_ERR,  S_CLS,  S_NOP,  S_NOP, },/* EV_CLOSED_R*/
 { S_CLS, S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_NOP,  S_NOP, },/* EV_CANCELLED*/
-{ S_NOP, S_XXX,  S_XXX,  S_XXX,  S_XXX,  S_CLS,  S_CLN,  S_XXX, },/* EV_EOS_SENT*/
+{ S_NOP, S_XXX,  S_XXX,  S_XXX,  S_XXX,  S_CLS,  S_CLN,  S_NOP, },/* EV_EOS_SENT*/
+{ S_NOP, S_XXX,  S_CLS,  S_XXX,  S_XXX,  S_CLS,  S_XXX,  S_XXX, },/* EV_IN_ERROR*/
 };
 
 static int on_map(h2_stream_state_t state, int map[H2_SS_MAX])
@@ -142,7 +148,7 @@
 {
     ap_assert(frame_type >= 0);
     ap_assert(state >= 0);
-    if (frame_type >= maxlen) {
+    if ((apr_size_t)frame_type >= maxlen) {
         return state; /* NOP, ignore unknown frame types */
     }
     return on_map(state, frame_map[frame_type]);
@@ -160,6 +166,7 @@
 
 static int on_event(h2_stream* stream, h2_stream_event_t ev)
 {
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     if (stream->monitor && stream->monitor->on_event) {
         stream->monitor->on_event(stream->monitor->ctx, stream, ev);
     }
@@ -169,10 +176,18 @@
     return stream->state;
 }
 
+static ssize_t stream_data_cb(nghttp2_session *ng2s,
+                              int32_t stream_id,
+                              uint8_t *buf,
+                              size_t length,
+                              uint32_t *data_flags,
+                              nghttp2_data_source *source,
+                              void *puser);
+
 static void H2_STREAM_OUT_LOG(int lvl, h2_stream *s, const char *tag)
 {
-    if (APLOG_C_IS_LEVEL(s->session->c, lvl)) {
-        conn_rec *c = s->session->c;
+    if (APLOG_C_IS_LEVEL(s->session->c1, lvl)) {
+        conn_rec *c = s->session->c1;
         char buffer[4 * 1024];
         apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]);
         
@@ -182,76 +197,116 @@
     }
 }
 
-static apr_status_t setup_input(h2_stream *stream) {
-    if (stream->input == NULL) {
-        int empty = (stream->input_eof 
-                     && (!stream->in_buffer 
-                         || APR_BRIGADE_EMPTY(stream->in_buffer)));
-        if (!empty) {
-            h2_beam_create(&stream->input, stream->pool, stream->id, 
-                           "input", H2_BEAM_OWNER_SEND, 0, 
-                           stream->session->s->timeout);
-            h2_beam_send_from(stream->input, stream->pool);
-        }
+static void stream_setup_input(h2_stream *stream)
+{
+    if (stream->input != NULL) return;
+    ap_assert(!stream->input_closed);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+                  H2_STRM_MSG(stream, "setup input beam"));
+    h2_beam_create(&stream->input, stream->session->c1,
+                   stream->pool, stream->id,
+                   "input", 0, stream->session->s->timeout);
+}
+
+apr_status_t h2_stream_prepare_processing(h2_stream *stream)
+{
+    /* Right before processing starts, last chance to decide if
+     * there is need to an input beam. */
+    if (!stream->input_closed) {
+        stream_setup_input(stream);
     }
     return APR_SUCCESS;
 }
 
-static apr_status_t close_input(h2_stream *stream)
+static int input_buffer_is_empty(h2_stream *stream)
+{
+    return !stream->in_buffer || APR_BRIGADE_EMPTY(stream->in_buffer);
+}
+
+static apr_status_t input_flush(h2_stream *stream)
 {
-    conn_rec *c = stream->session->c;
     apr_status_t status = APR_SUCCESS;
+    apr_off_t written;
 
-    stream->input_eof = 1;
-    if (stream->input && h2_beam_is_closed(stream->input)) {
-        return APR_SUCCESS;
-    }
-    
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
-                  H2_STRM_MSG(stream, "closing input"));
-    if (stream->rst_error) {
-        return APR_ECONNRESET;
+    if (input_buffer_is_empty(stream)) goto cleanup;
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+                  H2_STRM_MSG(stream, "flush input"));
+    status = h2_beam_send(stream->input, stream->session->c1,
+                          stream->in_buffer, APR_BLOCK_READ, &written);
+    stream->in_last_write = apr_time_now();
+    if (APR_SUCCESS != status && h2_stream_is_at(stream, H2_SS_CLOSED_L)) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, stream->session->c1,
+                      H2_STRM_MSG(stream, "send input error"));
+        h2_stream_dispatch(stream, H2_SEV_IN_ERROR);
     }
-    
-    if (stream->trailers && !apr_is_empty_table(stream->trailers)) {
-        apr_bucket *b;
-        h2_headers *r;
-        
-        if (!stream->in_buffer) {
-            stream->in_buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
-        }
-        
-        r = h2_headers_create(HTTP_OK, stream->trailers, NULL, 
-            stream->in_trailer_octets, stream->pool);
-        stream->trailers = NULL;        
-        b = h2_bucket_headers_create(c->bucket_alloc, r);
-        APR_BRIGADE_INSERT_TAIL(stream->in_buffer, b);
-        
-        b = apr_bucket_eos_create(c->bucket_alloc);
-        APR_BRIGADE_INSERT_TAIL(stream->in_buffer, b);
-        
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
-                      H2_STRM_MSG(stream, "added trailers"));
-        h2_stream_dispatch(stream, H2_SEV_IN_DATA_PENDING);
+cleanup:
+    return status;
+}
+
+static void input_append_bucket(h2_stream *stream, apr_bucket *b)
+{
+    if (!stream->in_buffer) {
+        stream_setup_input(stream);
+        stream->in_buffer = apr_brigade_create(
+            stream->pool, stream->session->c1->bucket_alloc);
     }
-    if (stream->input) {
-        h2_stream_flush_input(stream);
-        return h2_beam_close(stream->input);
+    APR_BRIGADE_INSERT_TAIL(stream->in_buffer, b);
+}
+
+static void input_append_data(h2_stream *stream, const char *data, apr_size_t len)
+{
+    if (!stream->in_buffer) {
+        stream_setup_input(stream);
+        stream->in_buffer = apr_brigade_create(
+            stream->pool, stream->session->c1->bucket_alloc);
     }
-    return status;
+    apr_brigade_write(stream->in_buffer, NULL, NULL, data, len);
 }
 
-static apr_status_t close_output(h2_stream *stream)
+
+static apr_status_t close_input(h2_stream *stream)
 {
-    if (!stream->output || h2_beam_is_closed(stream->output)) {
-        return APR_SUCCESS;
+    conn_rec *c = stream->session->c1;
+    apr_status_t rv = APR_SUCCESS;
+    apr_bucket *b;
+
+    if (stream->input_closed) goto cleanup;
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
+                  H2_STRM_MSG(stream, "closing input"));
+    if (!stream->rst_error
+        && stream->trailers_in
+        && !apr_is_empty_table(stream->trailers_in)) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+                      H2_STRM_MSG(stream, "adding trailers"));
+#if AP_HAS_RESPONSE_BUCKETS
+        b = ap_bucket_headers_create(stream->trailers_in,
+                                     stream->pool, c->bucket_alloc);
+#else
+        b = h2_bucket_headers_create(c->bucket_alloc,
+            h2_headers_create(HTTP_OK, stream->trailers_in, NULL,
+                              stream->in_trailer_octets, stream->pool));
+#endif
+        input_append_bucket(stream, b);
+        stream->trailers_in = NULL;
     }
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
-                  H2_STRM_MSG(stream, "closing output"));
-    return h2_beam_leave(stream->output);
+
+    stream->input_closed = 1;
+    if (stream->input) {
+        b = apr_bucket_eos_create(c->bucket_alloc);
+        input_append_bucket(stream, b);
+        input_flush(stream);
+        h2_stream_dispatch(stream, H2_SEV_IN_DATA_PENDING);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+                      H2_STRM_MSG(stream, "input flush + EOS"));
+    }
+
+cleanup:
+    return rv;
 }
 
-static void on_state_enter(h2_stream *stream) 
+static void on_state_enter(h2_stream *stream)
 {
     if (stream->monitor && stream->monitor->on_state_enter) {
         stream->monitor->on_state_enter(stream->monitor->ctx, stream);
@@ -271,7 +326,7 @@
         stream->monitor->on_state_invalid(stream->monitor->ctx, stream);
     }
     /* stream got an event/frame invalid in its state */
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
                   H2_STRM_MSG(stream, "invalid state event")); 
     switch (stream->state) {
         case H2_SS_OPEN:
@@ -288,17 +343,17 @@
 
 static apr_status_t transit(h2_stream *stream, int new_state)
 {
-    if (new_state == stream->state) {
+    if ((h2_stream_state_t)new_state == stream->state) {
         return APR_SUCCESS;
     }
     else if (new_state < 0) {
-        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c, 
+        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c1,
                       H2_STRM_LOG(APLOGNO(03081), stream, "invalid transition"));
         on_state_invalid(stream);
         return APR_EINVAL;
     }
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c, 
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
                   H2_STRM_MSG(stream, "transit to [%s]"), h2_ss_str(new_state));
     stream->state = new_state;
     switch (new_state) {
@@ -312,14 +367,12 @@
         case H2_SS_OPEN:
             break;
         case H2_SS_CLOSED_L:
-            close_output(stream);
             break;
         case H2_SS_CLOSED_R:
             close_input(stream);
             break;
         case H2_SS_CLOSED:
             close_input(stream);
-            close_output(stream);
             if (stream->out_buffer) {
                 apr_brigade_cleanup(stream->out_buffer);
             }
@@ -340,19 +393,20 @@
 {
     int new_state;
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
                   H2_STRM_MSG(stream, "dispatch event %d"), ev);
     new_state = on_event(stream, ev);
     if (new_state < 0) {
-        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c, 
+        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c1,
                       H2_STRM_LOG(APLOGNO(10002), stream, "invalid event %d"), ev);
         on_state_invalid(stream);
         AP_DEBUG_ASSERT(new_state > S_XXX);
         return;
     }
-    else if (new_state == stream->state) {
+    else if ((h2_stream_state_t)new_state == stream->state) {
         /* nop */
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
                       H2_STRM_MSG(stream, "non-state event %d"), ev);
         return;
     }
@@ -366,7 +420,6 @@
 {
     int enabled = h2_session_push_enabled(stream->session);
     stream->push_policy = h2_push_policy_determine(r->headers, stream->pool, enabled);
-    r->serialize = h2_config_sgeti(stream->session->s, H2_CONF_SER_HEADERS);
 }
 
 apr_status_t h2_stream_send_frame(h2_stream *stream, int ftype, int flags, size_t frame_len)
@@ -374,9 +427,10 @@
     apr_status_t status = APR_SUCCESS;
     int new_state, eos = 0;
 
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     new_state = on_frame_send(stream->state, ftype);
     if (new_state < 0) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c, 
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
                       H2_STRM_MSG(stream, "invalid frame %d send"), ftype);
         AP_DEBUG_ASSERT(new_state > S_XXX);
         return transit(stream, new_state);
@@ -384,6 +438,12 @@
 
     ++stream->out_frames;
     stream->out_frame_octets += frame_len;
+    if(stream->c2) {
+      h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(stream->c2);
+      if(conn_ctx)
+        conn_ctx->bytes_sent = stream->out_frame_octets;
+    }
+
     switch (ftype) {
         case NGHTTP2_DATA:
             eos = (flags & NGHTTP2_FLAG_END_STREAM);
@@ -404,8 +464,6 @@
         default:
             break;
     }
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c, 
-                  H2_STRM_MSG(stream, "send frame %d, eos=%d"), ftype, eos);
     status = transit(stream, new_state);
     if (status == APR_SUCCESS && eos) {
         status = transit(stream, on_event(stream, H2_SEV_CLOSED_L));
@@ -419,9 +477,10 @@
     apr_status_t status = APR_SUCCESS;
     int new_state, eos = 0;
 
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     new_state = on_frame_recv(stream->state, ftype);
     if (new_state < 0) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c, 
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
                       H2_STRM_MSG(stream, "invalid frame %d recv"), ftype);
         AP_DEBUG_ASSERT(new_state > S_XXX);
         return transit(stream, new_state);
@@ -434,7 +493,7 @@
             
         case NGHTTP2_HEADERS:
             eos = (flags & NGHTTP2_FLAG_END_STREAM);
-            if (stream->state == H2_SS_OPEN) {
+            if (h2_stream_is_at_or_past(stream, H2_SS_OPEN)) {
                 /* trailer HEADER */
                 if (!eos) {
                     h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR);
@@ -467,63 +526,65 @@
     return status;
 }
 
-apr_status_t h2_stream_flush_input(h2_stream *stream)
-{
-    apr_status_t status = APR_SUCCESS;
-    
-    if (stream->in_buffer && !APR_BRIGADE_EMPTY(stream->in_buffer)) {
-        setup_input(stream);
-        status = h2_beam_send(stream->input, stream->in_buffer, APR_BLOCK_READ);
-        stream->in_last_write = apr_time_now();
-    }
-    if (stream->input_eof 
-        && stream->input && !h2_beam_is_closed(stream->input)) {
-        status = h2_beam_close(stream->input);
-    }
-    return status;
-}
-
 apr_status_t h2_stream_recv_DATA(h2_stream *stream, uint8_t flags,
                                     const uint8_t *data, size_t len)
 {
     h2_session *session = stream->session;
     apr_status_t status = APR_SUCCESS;
     
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     stream->in_data_frames++;
     if (len > 0) {
-        if (APLOGctrace3(session->c)) {
+        if (APLOGctrace3(session->c1)) {
             const char *load = apr_pstrndup(stream->pool, (const char *)data, len);
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, session->c,
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, session->c1,
                           H2_STRM_MSG(stream, "recv DATA, len=%d: -->%s<--"), 
                           (int)len, load);
         }
         else {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c,
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c1,
                           H2_STRM_MSG(stream, "recv DATA, len=%d"), (int)len);
         }
         stream->in_data_octets += len;
-        if (!stream->in_buffer) {
-            stream->in_buffer = apr_brigade_create(stream->pool, 
-                                                   session->c->bucket_alloc);
-        }
-        apr_brigade_write(stream->in_buffer, NULL, NULL, (const char *)data, len);
+        input_append_data(stream, (const char*)data, len);
+        input_flush(stream);
         h2_stream_dispatch(stream, H2_SEV_IN_DATA_PENDING);
     }
     return status;
 }
 
-static void prep_output(h2_stream *stream) {
-    conn_rec *c = stream->session->c;
-    if (!stream->out_buffer) {
-        stream->out_buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
+#ifdef AP_DEBUG
+static apr_status_t stream_pool_destroy(void *data)
+{
+    h2_stream *stream = data;
+    switch (stream->magic) {
+    case H2_STRM_MAGIC_OK:
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, stream->session->c1,
+                      H2_STRM_MSG(stream, "was not destroyed explicitly"));
+        AP_DEBUG_ASSERT(0);
+        break;
+    case H2_STRM_MAGIC_SDEL:
+        /* stream has been explicitly destroyed, as it should */
+        H2_STRM_ASSIGN_MAGIC(stream, H2_STRM_MAGIC_PDEL);
+        break;
+    case H2_STRM_MAGIC_PDEL:
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, stream->session->c1,
+                      H2_STRM_MSG(stream, "already pool destroyed"));
+        AP_DEBUG_ASSERT(0);
+        break;
+    default:
+        AP_DEBUG_ASSERT(0);
     }
+    return APR_SUCCESS;
 }
+#endif
 
 h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session,
                             h2_stream_monitor *monitor, int initiated_on)
 {
     h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));
-    
+
+    H2_STRM_ASSIGN_MAGIC(stream, H2_STRM_MAGIC_OK);
     stream->id           = id;
     stream->initiated_on = initiated_on;
     stream->created      = apr_time_now();
@@ -531,15 +592,21 @@
     stream->pool         = pool;
     stream->session      = session;
     stream->monitor      = monitor;
-    stream->max_mem      = session->max_stream_mem;
-    
-#ifdef H2_NG2_LOCAL_WIN_SIZE
-    stream->in_window_size = 
-        nghttp2_session_get_stream_local_window_size(
-            stream->session->ngh2, stream->id);
+#ifdef AP_DEBUG
+    if (id) { /* stream 0 has special lifetime */
+        apr_pool_cleanup_register(pool, stream, stream_pool_destroy,
+                                  apr_pool_cleanup_null);
+    }
 #endif
 
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+#ifdef H2_NG2_LOCAL_WIN_SIZE
+    if (id) {
+        stream->in_window_size =
+            nghttp2_session_get_stream_local_window_size(
+                stream->session->ngh2, stream->id);
+    }
+#endif
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
                   H2_STRM_LOG(APLOGNO(03082), stream, "created"));
     on_state_enter(stream);
     return stream;
@@ -547,59 +614,35 @@
 
 void h2_stream_cleanup(h2_stream *stream)
 {
-    apr_status_t status;
-    
+    /* Stream is done on c1. There might still be processing on a c2
+     * going on. The input/output beams get aborted and the stream's
+     * end of the in/out notifications get closed.
+     */
     ap_assert(stream);
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     if (stream->out_buffer) {
-        /* remove any left over output buckets that may still have
-         * references into request pools */
         apr_brigade_cleanup(stream->out_buffer);
     }
-    if (stream->input) {
-        h2_beam_abort(stream->input);
-        status = h2_beam_wait_empty(stream->input, APR_NONBLOCK_READ);
-        if (status == APR_EAGAIN) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c, 
-                          H2_STRM_MSG(stream, "wait on input drain"));
-            status = h2_beam_wait_empty(stream->input, APR_BLOCK_READ);
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, stream->session->c, 
-                          H2_STRM_MSG(stream, "input drain returned"));
-        }
-    }
 }
 
 void h2_stream_destroy(h2_stream *stream)
 {
     ap_assert(stream);
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, stream->session->c, 
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, stream->session->c1,
                   H2_STRM_MSG(stream, "destroy"));
+    H2_STRM_ASSIGN_MAGIC(stream, H2_STRM_MAGIC_SDEL);
     apr_pool_destroy(stream->pool);
 }
 
-apr_status_t h2_stream_prep_processing(h2_stream *stream)
-{
-    if (stream->request) {
-        const h2_request *r = stream->request;
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
-                      H2_STRM_MSG(stream, "schedule %s %s://%s%s chunked=%d"),
-                      r->method, r->scheme, r->authority, r->path, r->chunked);
-        setup_input(stream);
-        stream->scheduled = 1;
-        return APR_SUCCESS;
-    }
-    return APR_EINVAL;
-}
-
 void h2_stream_rst(h2_stream *stream, int error_code)
 {
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     stream->rst_error = error_code;
-    if (stream->input) {
-        h2_beam_abort(stream->input);
-    }
-    if (stream->output) {
-        h2_beam_leave(stream->output);
+    if (stream->c2) {
+        h2_c2_abort(stream->c2, stream->session->c1);
     }
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
                   H2_STRM_MSG(stream, "reset, error=%d"), error_code);
     h2_stream_dispatch(stream, H2_SEV_CANCELLED);
 }
@@ -610,6 +653,7 @@
     h2_request *req;
     apr_status_t status;
 
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     ap_assert(stream->request == NULL);
     ap_assert(stream->rtmp == NULL);
     if (stream->rst_error) {
@@ -631,6 +675,7 @@
 
 void h2_stream_set_request(h2_stream *stream, const h2_request *r)
 {
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     ap_assert(stream->request == NULL);
     ap_assert(stream->rtmp == NULL);
     stream->rtmp = h2_request_clone(stream->pool, r);
@@ -648,7 +693,7 @@
                                 const char *value, size_t vlen,
                                 size_t max_field_len, int *pwas_added)
 {
-    conn_rec *c = stream->session->c;
+    conn_rec *c = stream->session->c1;
     char *hname, *hvalue;
     const char *existing;
 
@@ -659,15 +704,15 @@
                       "pseudo header in trailer"));
         return APR_EINVAL;
     }
-    if (h2_req_ignore_trailer(name, nlen)) {
+    if (h2_ignore_req_trailer(name, nlen)) {
         return APR_SUCCESS;
     }
-    if (!stream->trailers) {
-        stream->trailers = apr_table_make(stream->pool, 5);
+    if (!stream->trailers_in) {
+        stream->trailers_in = apr_table_make(stream->pool, 5);
     }
     hname = apr_pstrndup(stream->pool, name, nlen);
     h2_util_camel_case_header(hname, nlen);
-    existing = apr_table_get(stream->trailers, hname);
+    existing = apr_table_get(stream->trailers_in, hname);
     if (max_field_len 
         && ((existing? strlen(existing)+2 : 0) + vlen + nlen + 2 > max_field_len)) {
         /* "key: (oldval, )?nval" is too long */
@@ -675,7 +720,7 @@
     }
     if (!existing) *pwas_added = 1;
     hvalue = apr_pstrndup(stream->pool, value, vlen);
-    apr_table_mergen(stream->trailers, hname, hvalue);
+    apr_table_mergen(stream->trailers_in, hname, hvalue);
     ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, 
                   H2_STRM_MSG(stream, "added trailer '%s: %s'"), hname, hvalue);
     
@@ -690,15 +735,16 @@
     int error = 0, was_added = 0;
     apr_status_t status = APR_SUCCESS;
     
-    if (stream->has_response) {
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+    if (stream->response) {
         return APR_EINVAL;    
     }
 
     if (name[0] == ':') {
-        if ((vlen) > session->s->limit_req_line) {
+        if (vlen > APR_INT32_MAX || (int)vlen > session->s->limit_req_line) {
             /* pseudo header: approximation of request line size check */
             if (!h2_stream_is_ready(stream)) {
-                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,
+                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c1,
                               H2_STRM_LOG(APLOGNO(10178), stream,
                                           "Request pseudo header exceeds "
                                           "LimitRequestFieldSize: %s"), name);
@@ -715,12 +761,15 @@
     }
     else if (H2_SS_IDLE == stream->state) {
         if (!stream->rtmp) {
-            stream->rtmp = h2_req_create(stream->id, stream->pool, 
-                                         NULL, NULL, NULL, NULL, NULL, 0);
+            stream->rtmp = h2_request_create(stream->id, stream->pool,
+                                             NULL, NULL, NULL, NULL, NULL);
         }
         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) {
@@ -736,7 +785,7 @@
     if (APR_EINVAL == status) {
         /* header too long */
         if (!h2_stream_is_ready(stream)) {
-            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,
+            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c1,
                           H2_STRM_LOG(APLOGNO(10180), stream,"Request header exceeds "
                                       "LimitRequestFieldSize: %.*s"),
                           (int)H2MIN(nlen, 80), name);
@@ -754,7 +803,7 @@
             return APR_ECONNRESET;
         }
         if (!h2_stream_is_ready(stream)) {
-            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,
+            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c1,
                           H2_STRM_LOG(APLOGNO(10181), stream, "Number of request headers "
                                       "exceeds LimitRequestFields"));
         }
@@ -764,12 +813,11 @@
     
 cleanup:
     if (error) {
-        ++stream->request_headers_failed;
         set_error_response(stream, error);
         return APR_EINVAL; 
     }
     else if (status != APR_SUCCESS) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
                       H2_STRM_MSG(stream, "header %s not accepted"), name);
         h2_stream_dispatch(stream, H2_SEV_CANCELLED);
     }
@@ -794,239 +842,376 @@
 {
     apr_status_t status;
     val_len_check_ctx ctx;
-    
-    status = h2_request_end_headers(stream->rtmp, stream->pool, eos, raw_bytes);
+    int is_http_or_https;
+    h2_request *req = stream->rtmp;
+
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+    status = h2_request_end_headers(req, stream->pool, raw_bytes);
+    if (APR_SUCCESS != status || req->http_status != H2_HTTP_STATUS_UNSET) {
+        goto cleanup;
+    }
+
+    /* keep on returning APR_SUCCESS for error responses, so that we
+     * send it and do not RST the stream.
+     */
+    set_policy_for(stream, req);
+
+    ctx.maxlen = stream->session->s->limit_req_fieldsize;
+    ctx.failed_key = NULL;
+    apr_table_do(table_check_val_len, &ctx, req->headers, NULL);
+    if (ctx.failed_key) {
+        if (!h2_stream_is_ready(stream)) {
+            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+                          H2_STRM_LOG(APLOGNO(10230), stream,"Request header exceeds "
+                                      "LimitRequestFieldSize: %.*s"),
+                          (int)H2MIN(strlen(ctx.failed_key), 80), ctx.failed_key);
+        }
+        set_error_response(stream, HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE);
+        goto cleanup;
+    }
+
+    /* http(s) scheme. rfc7540, ch. 8.1.2.3:
+     * This [:path] pseudo-header field MUST NOT be empty for "http" or "https"
+     * URIs; "http" or "https" URIs that do not contain a path component
+     * MUST include a value of '/'.  The exception to this rule is an
+     * OPTIONS request for an "http" or "https" URI that does not include
+     * a path component; these MUST include a ":path" pseudo-header field
+     * with a value of '*'
+     *
+     * All HTTP/2 requests MUST include exactly one valid value for the
+     * ":method", ":scheme", and ":path" pseudo-header fields, unless it is
+     * a CONNECT request.
+     */
+    is_http_or_https = (!req->scheme
+            || !(ap_cstr_casecmpn(req->scheme, "http", 4) != 0
+                 || (req->scheme[4] != '\0'
+                     && (apr_tolower(req->scheme[4]) != 's'
+                         || req->scheme[5] != '\0'))));
+
+    /* CONNECT. rfc7540, ch. 8.3:
+     * In HTTP/2, the CONNECT method is used to establish a tunnel over a
+     * single HTTP/2 stream to a remote host for similar purposes.  The HTTP
+     * header field mapping works as defined in Section 8.1.2.3 ("Request
+     * Pseudo-Header Fields"), with a few differences.  Specifically:
+     *   o  The ":method" pseudo-header field is set to "CONNECT".
+     *   o  The ":scheme" and ":path" pseudo-header fields MUST be omitted.
+     *   o  The ":authority" pseudo-header field contains the host and port to
+     *      connect to (equivalent to the authority-form of the request-target
+     *      of CONNECT requests (see [RFC7230], Section 5.3)).
+     */
+    if (!ap_cstr_casecmp(req->method, "CONNECT")) {
+        if (req->protocol) {
+            if (!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"));
+                    set_error_response(stream, HTTP_BAD_REQUEST);
+                    goto cleanup;
+                }
+            }
+            else {
+                /* do not know that protocol */
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c1, APLOGNO(10460)
+                              "':protocol: %s' header present in %s request",
+                              req->protocol, req->method);
+                set_error_response(stream, HTTP_NOT_IMPLEMENTED);
+                goto cleanup;
+            }
+        }
+        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"));
+            set_error_response(stream, HTTP_BAD_REQUEST);
+            goto cleanup;
+        }
+    }
+    else if (is_http_or_https) {
+        if (!req->path) {
+            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+                          H2_STRM_LOG(APLOGNO(10385), stream, "Request for http(s) "
+                          "resource without :path, sending 400 answer"));
+            set_error_response(stream, HTTP_BAD_REQUEST);
+            goto cleanup;
+        }
+        if (!req->scheme) {
+            req->scheme = ap_ssl_conn_is_ssl(stream->session->c1)? "https" : "http";
+        }
+    }
+
+    if (req->scheme && (req->path && req->path[0] != '/')) {
+        /* We still have a scheme, which means we need to pass an absolute URI into
+         * our HTTP protocol handling and the missing '/' at the start will prevent
+         * us from doing so (as it then confuses path and authority). */
+        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+                      H2_STRM_LOG(APLOGNO(10379), stream, "Request :scheme '%s' and "
+                      "path '%s' do not allow creating an absolute URL. Failing "
+                      "request with 400."), req->scheme, req->path);
+        set_error_response(stream, HTTP_BAD_REQUEST);
+        goto cleanup;
+    }
+
+cleanup:
     if (APR_SUCCESS == status) {
-        set_policy_for(stream, stream->rtmp);
-        stream->request = stream->rtmp;
+        stream->request = req;
         stream->rtmp = NULL;
-        
-        ctx.maxlen = stream->session->s->limit_req_fieldsize;
-        ctx.failed_key = NULL;
-        apr_table_do(table_check_val_len, &ctx, stream->request->headers, NULL);
-        if (ctx.failed_key) {
-            if (!h2_stream_is_ready(stream)) {
-                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c,
-                              H2_STRM_LOG(APLOGNO(10230), stream,"Request header exceeds "
-                                          "LimitRequestFieldSize: %.*s"),
-                              (int)H2MIN(strlen(ctx.failed_key), 80), ctx.failed_key);
-            }
-            set_error_response(stream, HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE);
-            /* keep on returning APR_SUCCESS, so that we send a HTTP response and
-             * do not RST the stream. */
+
+        if (APLOGctrace4(stream->session->c1)) {
+            int i;
+            const apr_array_header_t *t_h = apr_table_elts(req->headers);
+            const apr_table_entry_t *t_elt = (apr_table_entry_t *)t_h->elts;
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, stream->session->c1,
+                          H2_STRM_MSG(stream,"headers received from client:"));
+            for (i = 0; i < t_h->nelts; i++, t_elt++) {
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, stream->session->c1,
+                              H2_STRM_MSG(stream, "  %s: %s"),
+                              ap_escape_logitem(stream->pool, t_elt->key),
+                              ap_escape_logitem(stream->pool, t_elt->val));
+            }
         }
     }
     return status;
 }
 
-static apr_bucket *get_first_headers_bucket(apr_bucket_brigade *bb)
+static apr_bucket *get_first_response_bucket(apr_bucket_brigade *bb)
 {
     if (bb) {
         apr_bucket *b = APR_BRIGADE_FIRST(bb);
         while (b != APR_BRIGADE_SENTINEL(bb)) {
+#if AP_HAS_RESPONSE_BUCKETS
+            if (AP_BUCKET_IS_RESPONSE(b)) {
+                return b;
+            }
+#else
             if (H2_BUCKET_IS_HEADERS(b)) {
                 return b;
             }
+#endif
             b = APR_BUCKET_NEXT(b);
         }
     }
     return NULL;
 }
 
-static apr_status_t add_buffered_data(h2_stream *stream, apr_off_t requested,
-                                      apr_off_t *plen, int *peos, int *is_all, 
-                                      h2_headers **pheaders)
+static void stream_do_error_bucket(h2_stream *stream, apr_bucket *b)
 {
-    apr_bucket *b, *e;
-    
-    *peos = 0;
-    *plen = 0;
-    *is_all = 0;
-    if (pheaders) {
-        *pheaders = NULL;
+    int err = ((ap_bucket_error *)(b->data))->status;
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
+                  H2_STRM_MSG(stream, "error bucket received, err=%d"), err);
+    if (err >= 500) {
+        err = NGHTTP2_INTERNAL_ERROR;
     }
-
-    H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "add_buffered_data");
-    b = APR_BRIGADE_FIRST(stream->out_buffer);
-    while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
-        e = APR_BUCKET_NEXT(b);
-        if (APR_BUCKET_IS_METADATA(b)) {
-            if (APR_BUCKET_IS_FLUSH(b)) {
-                APR_BUCKET_REMOVE(b);
-                apr_bucket_destroy(b);
-            }
-            else if (APR_BUCKET_IS_EOS(b)) {
-                *peos = 1;
-                return APR_SUCCESS;
-            }
-            else if (H2_BUCKET_IS_HEADERS(b)) {
-                if (*plen > 0) {
-                    /* data before the response, can only return up to here */
-                    return APR_SUCCESS;
-                }
-                else if (pheaders) {
-                    *pheaders = h2_bucket_headers_get(b);
-                    APR_BUCKET_REMOVE(b);
-                    apr_bucket_destroy(b);
-                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
-                                  H2_STRM_MSG(stream, "prep, -> response %d"), 
-                                  (*pheaders)->status);
-                    return APR_SUCCESS;
-                }
-                else {
-                    return APR_EAGAIN;
-                }
-            }
-        }
-        else if (b->length == 0) {
-            APR_BUCKET_REMOVE(b);
-            apr_bucket_destroy(b);
-        }
-        else {
-            ap_assert(b->length != (apr_size_t)-1);
-            *plen += b->length;
-            if (*plen >= requested) {
-                *plen = requested;
-                return APR_SUCCESS;
-            }
-        }
-        b = e;
+    else if (err >= 400) {
+        err = NGHTTP2_STREAM_CLOSED;
     }
-    *is_all = 1;
-    return APR_SUCCESS;
+    else {
+        err = NGHTTP2_PROTOCOL_ERROR;
+    }
+    h2_stream_rst(stream, err);
 }
 
-apr_status_t h2_stream_out_prepare(h2_stream *stream, apr_off_t *plen, 
-                                   int *peos, h2_headers **pheaders)
+static apr_status_t buffer_output_receive(h2_stream *stream)
 {
-    apr_status_t status = APR_SUCCESS;
-    apr_off_t requested, missing, max_chunk = H2_DATA_CHUNK_SIZE;
-    conn_rec *c;
-    int complete, was_closed = 0;
+    apr_status_t rv = APR_EAGAIN;
+    apr_off_t buf_len;
+    conn_rec *c1 = stream->session->c1;
+    apr_bucket *b, *e;
 
-    ap_assert(stream);
-    
+    if (!stream->output) {
+        goto cleanup;
+    }
     if (stream->rst_error) {
-        *plen = 0;
-        *peos = 1;
-        return APR_ECONNRESET;
+        rv = APR_ECONNRESET;
+        goto cleanup;
     }
-    
-    c = stream->session->c;
-    prep_output(stream);
 
-    /* determine how much we'd like to send. We cannot send more than
-     * is requested. But we can reduce the size in case the master
-     * connection operates in smaller chunks. (TSL warmup) */
-    if (stream->session->io.write_size > 0) {
-        max_chunk = stream->session->io.write_size - H2_FRAME_HDR_LEN; 
-    }
-    requested = (*plen > 0)? H2MIN(*plen, max_chunk) : max_chunk;
-    
-    /* count the buffered data until eos or a headers bucket */
-    status = add_buffered_data(stream, requested, plen, peos, &complete, pheaders);
-    
-    if (status == APR_EAGAIN) {
-        /* TODO: ugly, someone needs to retrieve the response first */
-        h2_mplx_m_keep_active(stream->session->mplx, stream);
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                      H2_STRM_MSG(stream, "prep, response eagain"));
-        return status;
+    if (!stream->out_buffer) {
+        stream->out_buffer = apr_brigade_create(stream->pool, c1->bucket_alloc);
+        buf_len = 0;
     }
-    else if (status != APR_SUCCESS) {
-        return status;
+    else {
+        /* if the brigade contains a file bucket, its normal report length
+         * might be megabytes, but the memory used is tiny. For buffering,
+         * we are only interested in the memory footprint. */
+        buf_len = h2_brigade_mem_size(stream->out_buffer);
+    }
+
+    if (buf_len > APR_INT32_MAX
+        || (apr_size_t)buf_len >= stream->session->max_stream_mem) {
+        /* we have buffered enough. No need to read more.
+         * However, we have now output pending for which we may not
+         * receive another poll event. We need to make sure that this
+         * stream is not suspended so we keep on processing output.
+         */
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+                      H2_STRM_MSG(stream, "out_buffer, already has %ld length"),
+                      (long)buf_len);
+        rv = APR_SUCCESS;
+        goto cleanup;
     }
-    
-    if (pheaders && *pheaders) {
-        return APR_SUCCESS;
+
+    if (stream->output_eos) {
+        rv = APR_BRIGADE_EMPTY(stream->out_buffer)? APR_EOF : APR_SUCCESS;
     }
-    
-    /* If there we do not have enough buffered data to satisfy the requested
-     * length *and* we counted the _complete_ buffer (and did not stop in the middle
-     * because of meta data there), lets see if we can read more from the
-     * output beam */
-    missing = H2MIN(requested, stream->max_mem) - *plen;
-    if (complete && !*peos && missing > 0) {
-        apr_status_t rv = APR_EOF;
-        
-        if (stream->output) {
-            H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "pre");
-            h2_beam_log(stream->output, c, APLOG_TRACE2, "pre read output");
-            rv = h2_beam_receive(stream->output, stream->out_buffer,
-                                 APR_NONBLOCK_READ, stream->max_mem - *plen, &was_closed);
-            H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "post");
-            h2_beam_log(stream->output, c, APLOG_TRACE2, "post read output");
-        }
-        
-        if (rv == APR_SUCCESS) {
-            /* count the buffer again, now that we have read output */
-            status = add_buffered_data(stream, requested, plen, peos, &complete, pheaders);
-        }
-        else if (APR_STATUS_IS_EOF(rv)) {
-            apr_bucket *eos = apr_bucket_eos_create(c->bucket_alloc);
-            APR_BRIGADE_INSERT_TAIL(stream->out_buffer, eos);
-            *peos = 1;
-        }
-        else if (APR_STATUS_IS_EAGAIN(rv)) {
-            /* we set this is the status of this call only if there
-             * is no buffered data, see check below */
-        }
-        else {
-            /* real error reading. Give this back directly, even though
-             * we may have something buffered. */
-            status = rv;
+    else {
+        H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "pre");
+        rv = h2_beam_receive(stream->output, stream->session->c1, stream->out_buffer,
+                             APR_NONBLOCK_READ, stream->session->max_stream_mem - buf_len);
+        if (APR_SUCCESS != rv) {
+            if (APR_EAGAIN != rv) {
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+                              H2_STRM_MSG(stream, "out_buffer, receive unsuccessful"));
+            }
         }
     }
-    
-    if (status == APR_SUCCESS) {
-        if (*peos || *plen) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                          H2_STRM_MSG(stream, "prepare, len=%ld eos=%d"),
-                          (long)*plen, *peos);
-        }
-        else {
-            status = was_closed? APR_EOF : APR_EAGAIN;
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                          H2_STRM_MSG(stream, "prepare, no data"));
+
+    /* get rid of buckets we have no need for */
+    if (!APR_BRIGADE_EMPTY(stream->out_buffer)) {
+        b = APR_BRIGADE_FIRST(stream->out_buffer);
+        while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+            e = APR_BUCKET_NEXT(b);
+            if (APR_BUCKET_IS_METADATA(b)) {
+                if (APR_BUCKET_IS_FLUSH(b)) {  /* we flush any c1 data already */
+                    APR_BUCKET_REMOVE(b);
+                    apr_bucket_destroy(b);
+                }
+                else if (APR_BUCKET_IS_EOS(b)) {
+                    stream->output_eos = 1;
+                }
+                else if (AP_BUCKET_IS_ERROR(b)) {
+                    stream_do_error_bucket(stream, b);
+                    break;
+                }
+            }
+            else if (b->length == 0) {  /* zero length data */
+                APR_BUCKET_REMOVE(b);
+                apr_bucket_destroy(b);
+            }
+            b = e;
         }
     }
-    return status;
+    H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "out_buffer, after receive");
+
+cleanup:
+    return rv;
 }
 
-static int is_not_headers(apr_bucket *b)
+static int bucket_pass_to_c1(apr_bucket *b)
 {
-    return !H2_BUCKET_IS_HEADERS(b);
+#if AP_HAS_RESPONSE_BUCKETS
+    return !AP_BUCKET_IS_RESPONSE(b)
+           && !AP_BUCKET_IS_HEADERS(b)
+           && !APR_BUCKET_IS_EOS(b);
+#else
+    return !H2_BUCKET_IS_HEADERS(b) && !APR_BUCKET_IS_EOS(b);
+#endif
 }
 
 apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb, 
                                apr_off_t *plen, int *peos)
 {
-    conn_rec *c = stream->session->c;
-    apr_status_t status = APR_SUCCESS;
+    apr_status_t rv = APR_SUCCESS;
 
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     if (stream->rst_error) {
         return APR_ECONNRESET;
     }
-    status = h2_append_brigade(bb, stream->out_buffer, plen, peos, is_not_headers);
-    if (status == APR_SUCCESS && !*peos && !*plen) {
-        status = APR_EAGAIN;
-    }
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
-                  H2_STRM_MSG(stream, "read_to, len=%ld eos=%d"),
-                  (long)*plen, *peos);
-    return status;
+    rv = h2_append_brigade(bb, stream->out_buffer, plen, peos, bucket_pass_to_c1);
+    if (APR_SUCCESS  == rv && !*peos && !*plen) {
+        rv = APR_EAGAIN;
+    }
+    return rv;
 }
 
+static apr_status_t stream_do_trailers(h2_stream *stream)
+{
+    conn_rec *c1 = stream->session->c1;
+    int ngrv;
+    h2_ngheader *nh = NULL;
+    apr_bucket *b, *e;
+#if AP_HAS_RESPONSE_BUCKETS
+    ap_bucket_headers *headers = NULL;
+#else
+    h2_headers *headers = NULL;
+#endif
+    apr_status_t rv;
+
+    ap_assert(stream->response);
+    ap_assert(stream->out_buffer);
+
+    b = APR_BRIGADE_FIRST(stream->out_buffer);
+    while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+        e = APR_BUCKET_NEXT(b);
+        if (APR_BUCKET_IS_METADATA(b)) {
+#if AP_HAS_RESPONSE_BUCKETS
+            if (AP_BUCKET_IS_HEADERS(b)) {
+                headers = b->data;
+#else /* AP_HAS_RESPONSE_BUCKETS */
+            if (H2_BUCKET_IS_HEADERS(b)) {
+                headers = h2_bucket_headers_get(b);
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+                APR_BUCKET_REMOVE(b);
+                apr_bucket_destroy(b);
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
+                              H2_STRM_MSG(stream, "process trailers"));
+                break;
+            }
+            else if (APR_BUCKET_IS_EOS(b)) {
+                break;
+            }
+        }
+        else {
+            break;
+        }
+        b = e;
+    }
+
+    if (!headers) {
+        rv = APR_EAGAIN;
+        goto cleanup;
+    }
+
+    rv = h2_res_create_ngtrailer(&nh, stream->pool, headers);
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+                  H2_STRM_LOG(APLOGNO(03072), stream, "submit %d trailers"),
+                  (int)nh->nvlen);
+    if (APR_SUCCESS != rv) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+                      H2_STRM_LOG(APLOGNO(10024), stream, "invalid trailers"));
+        h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+        goto cleanup;
+    }
 
+    ngrv = nghttp2_submit_trailer(stream->session->ngh2, stream->id, nh->nv, nh->nvlen);
+    if (nghttp2_is_fatal(ngrv)) {
+        rv = APR_EGENERAL;
+        h2_session_dispatch_event(stream->session,
+                                 H2_SESSION_EV_PROTO_ERROR, ngrv, nghttp2_strerror(rv));
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
+                      APLOGNO(02940) "submit_response: %s",
+                      nghttp2_strerror(rv));
+    }
+    stream->sent_trailers = 1;
+
+cleanup:
+    return rv;
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+apr_status_t h2_stream_submit_pushes(h2_stream *stream, ap_bucket_response *response)
+#else
 apr_status_t h2_stream_submit_pushes(h2_stream *stream, h2_headers *response)
+#endif
 {
     apr_status_t status = APR_SUCCESS;
     apr_array_header_t *pushes;
     int i;
     
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     pushes = h2_push_collect_update(stream, stream->request, response);
     if (pushes && !apr_is_empty_array(pushes)) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
                       H2_STRM_MSG(stream, "found %d push candidates"),
                       pushes->nelts);
         for (i = 0; i < pushes->nelts; ++i) {
@@ -1043,17 +1228,24 @@
 
 apr_table_t *h2_stream_get_trailers(h2_stream *stream)
 {
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     return NULL;
 }
 
-const h2_priority *h2_stream_get_priority(h2_stream *stream, 
+#if AP_HAS_RESPONSE_BUCKETS
+const h2_priority *h2_stream_get_priority(h2_stream *stream,
+                                          ap_bucket_response *response)
+#else
+const h2_priority *h2_stream_get_priority(h2_stream *stream,
                                           h2_headers *response)
+#endif
 {
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     if (response && stream->initiated_on) {
         const char *ctype = apr_table_get(response->headers, "content-type");
         if (ctype) {
             /* FIXME: Not good enough, config needs to come from request->server */
-            return h2_cconfig_get_priority(stream->session->c, ctype);
+            return h2_cconfig_get_priority(stream->session->c1, ctype);
         }
     }
     return NULL;
@@ -1061,21 +1253,47 @@
 
 int h2_stream_is_ready(h2_stream *stream)
 {
-    if (stream->has_response) {
+    /* Have we sent a response or do we have the response in our buffer? */
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+    if (stream->response) {
         return 1;
     }
-    else if (stream->out_buffer && get_first_headers_bucket(stream->out_buffer)) {
+    else if (stream->out_buffer && get_first_response_bucket(stream->out_buffer)) {
         return 1;
     }
     return 0;
 }
 
-int h2_stream_was_closed(const h2_stream *stream)
+int h2_stream_wants_send_data(h2_stream *stream)
 {
-    switch (stream->state) {
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+    return h2_stream_is_ready(stream) &&
+           ((stream->out_buffer && !APR_BRIGADE_EMPTY(stream->out_buffer)) ||
+            (stream->output && !h2_beam_empty(stream->output)));
+}
+
+int h2_stream_is_at(const h2_stream *stream, h2_stream_state_t state)
+{
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+    return stream->state == state;
+}
+
+int h2_stream_is_at_or_past(const h2_stream *stream, h2_stream_state_t state)
+{
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+    switch (state) {
+        case H2_SS_IDLE:
+            return 1; /* by definition */
+        case H2_SS_RSVD_R: /*fall through*/
+        case H2_SS_RSVD_L: /*fall through*/
+        case H2_SS_OPEN:
+            return stream->state == state || stream->state >= H2_SS_OPEN;
+        case H2_SS_CLOSED_R: /*fall through*/
+        case H2_SS_CLOSED_L: /*fall through*/
         case H2_SS_CLOSED:
+            return stream->state == state || stream->state >= H2_SS_CLOSED;
         case H2_SS_CLEANUP:
-            return 1;
+            return stream->state == state;
         default:
             return 0;
     }
@@ -1085,6 +1303,7 @@
 {
     h2_session *session = stream->session;
     
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     if (amount > 0) {
         apr_off_t consumed = amount;
         
@@ -1132,13 +1351,477 @@
                 nghttp2_session_set_local_window_size(session->ngh2, 
                         NGHTTP2_FLAG_NONE, stream->id, win);
             } 
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                          "h2_stream(%ld-%d): consumed %ld bytes, window now %d/%d",
-                          session->id, stream->id, (long)amount, 
-                          cur_size, stream->in_window_size);
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+                          H2_STRM_MSG(stream, "consumed %ld bytes, window now %d/%d"),
+                          (long)amount, cur_size, stream->in_window_size);
         }
-#endif
+#endif /* #ifdef H2_NG2_LOCAL_WIN_SIZE */
     }
     return APR_SUCCESS;   
 }
 
+static apr_off_t output_data_buffered(h2_stream *stream, int *peos, int *pheader_blocked)
+{
+    /* How much data do we have in our buffers that we can write? */
+    apr_off_t buf_len = 0;
+    apr_bucket *b;
+
+    *peos = *pheader_blocked = 0;
+    if (stream->out_buffer) {
+        b = APR_BRIGADE_FIRST(stream->out_buffer);
+        while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+            if (APR_BUCKET_IS_METADATA(b)) {
+                if (APR_BUCKET_IS_EOS(b)) {
+                    *peos = 1;
+                    break;
+                }
+#if AP_HAS_RESPONSE_BUCKETS
+                else if (AP_BUCKET_IS_RESPONSE(b)) {
+                    break;
+                }
+                else if (AP_BUCKET_IS_HEADERS(b)) {
+                    *pheader_blocked = 1;
+                    break;
+                }
+#else
+                else if (H2_BUCKET_IS_HEADERS(b)) {
+                    *pheader_blocked = 1;
+                    break;
+                }
+#endif
+            }
+            else {
+                buf_len += b->length;
+            }
+            b = APR_BUCKET_NEXT(b);
+        }
+    }
+    return buf_len;
+}
+
+static ssize_t stream_data_cb(nghttp2_session *ng2s,
+                              int32_t stream_id,
+                              uint8_t *buf,
+                              size_t length,
+                              uint32_t *data_flags,
+                              nghttp2_data_source *source,
+                              void *puser)
+{
+    h2_session *session = (h2_session *)puser;
+    conn_rec *c1 = session->c1;
+    apr_off_t buf_len;
+    int eos, header_blocked;
+    apr_status_t rv;
+    h2_stream *stream;
+
+    /* nghttp2 wants to send more DATA for the stream.
+     * we should have submitted the final response at this time
+     * after receiving output via stream_do_responses() */
+    ap_assert(session);
+    (void)ng2s;
+    (void)buf;
+    (void)source;
+    stream = nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
+
+    if (!stream) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c1,
+                      APLOGNO(02937)
+                      H2_SSSN_STRM_MSG(session, stream_id, "data_cb, stream not found"));
+        return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+    if (!stream->output || !stream->response || !stream->out_buffer) {
+        return NGHTTP2_ERR_DEFERRED;
+    }
+    if (stream->rst_error) {
+        return NGHTTP2_ERR_DEFERRED;
+    }
+    if (h2_c1_io_needs_flush(&session->io)) {
+        rv = h2_c1_io_pass(&session->io);
+        if (APR_STATUS_IS_EAGAIN(rv)) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
+                          H2_SSSN_STRM_MSG(session, stream_id, "suspending on c1 out needs flush"));
+            h2_stream_dispatch(stream, H2_SEV_OUT_C1_BLOCK);
+            return NGHTTP2_ERR_DEFERRED;
+        }
+        else if (rv) {
+            h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, rv, NULL);
+            return NGHTTP2_ERR_CALLBACK_FAILURE;
+        }
+    }
+
+    /* determine how much we'd like to send. We cannot send more than
+     * is requested. But we can reduce the size in case the master
+     * connection operates in smaller chunks. (TSL warmup) */
+    if (stream->session->io.write_size > 0) {
+        apr_size_t chunk_len = stream->session->io.write_size - H2_FRAME_HDR_LEN;
+        if (length > chunk_len) {
+            length = chunk_len;
+        }
+    }
+    /* We allow configurable max DATA frame length. */
+    if (stream->session->max_data_frame_len > 0
+        && length > stream->session->max_data_frame_len) {
+      length = stream->session->max_data_frame_len;
+    }
+
+    /* How much data do we have in our buffers that we can write?
+     * if not enough, receive more. */
+    buf_len = output_data_buffered(stream, &eos, &header_blocked);
+    if (buf_len < (apr_off_t)length && !eos
+           && !header_blocked && !stream->rst_error) {
+        /* read more? */
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
+                      H2_SSSN_STRM_MSG(session, stream_id,
+                      "need more (read len=%ld, %ld in buffer)"),
+                      (long)length, (long)buf_len);
+        rv = buffer_output_receive(stream);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+                      H2_SSSN_STRM_MSG(session, stream_id,
+                      "buffer_output_received"));
+        if (APR_STATUS_IS_EAGAIN(rv)) {
+            /* currently, no more is available */
+        }
+        else if (APR_SUCCESS == rv) {
+            /* got some, re-assess */
+            buf_len = output_data_buffered(stream, &eos, &header_blocked);
+        }
+        else if (APR_EOF == rv) {
+            if (!stream->output_eos) {
+                /* Seeing APR_EOF without an EOS bucket received before indicates
+                 * that stream output is incomplete. Commonly, we expect to see
+                 * an ERROR bucket to have been generated. But faulty handlers
+                 * may not have generated one.
+                 * We need to RST the stream bc otherwise the client thinks
+                 * 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_STREAM_CLOSED);
+                 return NGHTTP2_ERR_DEFERRED;
+            }
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+                          H2_SSSN_STRM_MSG(session, stream_id,
+                          "eof on receive (read len=%ld, %ld in buffer)"),
+                          (long)length, (long)buf_len);
+            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(10471), 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"));
+            h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+            return NGHTTP2_ERR_DEFERRED;
+        }
+    }
+
+    if (stream->rst_error) {
+        return NGHTTP2_ERR_DEFERRED;
+    }
+
+    if (buf_len == 0 && header_blocked) {
+        rv = stream_do_trailers(stream);
+        if (APR_SUCCESS != rv && !APR_STATUS_IS_EAGAIN(rv)) {
+            ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
+                          H2_STRM_LOG(APLOGNO(10300), stream,
+                          "data_cb, error processing trailers"));
+            return NGHTTP2_ERR_CALLBACK_FAILURE;
+        }
+        length = 0;
+        eos = 0;
+    }
+    else if (buf_len > (apr_off_t)length) {
+        eos = 0;  /* Any EOS we have in the buffer does not apply yet */
+    }
+    else {
+        length = (size_t)buf_len;
+    }
+
+    if (length) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
+                      H2_STRM_MSG(stream, "data_cb, sending len=%ld, eos=%d"),
+                      (long)length, eos);
+        *data_flags |=  NGHTTP2_DATA_FLAG_NO_COPY;
+    }
+    else if (!eos && !stream->sent_trailers) {
+        /* We have not reached the end of DATA yet, DEFER sending */
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c1,
+                      H2_STRM_LOG(APLOGNO(03071), stream, "data_cb, suspending"));
+        return NGHTTP2_ERR_DEFERRED;
+    }
+
+    if (eos) {
+        *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+    }
+    return length;
+}
+
+static apr_status_t stream_do_response(h2_stream *stream)
+{
+    conn_rec *c1 = stream->session->c1;
+    apr_status_t rv = APR_EAGAIN;
+    int ngrv, is_empty = 0;
+    h2_ngheader *nh = NULL;
+    apr_bucket *b, *e;
+#if AP_HAS_RESPONSE_BUCKETS
+    ap_bucket_response *resp = NULL;
+#else
+    h2_headers *resp = NULL;
+#endif
+    nghttp2_data_provider provider, *pprovider = NULL;
+
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+    ap_assert(!stream->response);
+    ap_assert(stream->out_buffer);
+
+    b = APR_BRIGADE_FIRST(stream->out_buffer);
+    while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+        e = APR_BUCKET_NEXT(b);
+        if (APR_BUCKET_IS_METADATA(b)) {
+#if AP_HAS_RESPONSE_BUCKETS
+            if (AP_BUCKET_IS_RESPONSE(b)) {
+                resp = b->data;
+#else /* AP_HAS_RESPONSE_BUCKETS */
+            if (H2_BUCKET_IS_HEADERS(b)) {
+                resp = h2_bucket_headers_get(b);
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+                APR_BUCKET_REMOVE(b);
+                apr_bucket_destroy(b);
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
+                              H2_STRM_MSG(stream, "process response %d"),
+                              resp->status);
+                is_empty = (e != APR_BRIGADE_SENTINEL(stream->out_buffer)
+                            && APR_BUCKET_IS_EOS(e));
+                break;
+            }
+            else if (APR_BUCKET_IS_EOS(b)) {
+                h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+                rv = APR_EINVAL;
+                goto cleanup;
+            }
+            else if (AP_BUCKET_IS_ERROR(b)) {
+                stream_do_error_bucket(stream, b);
+                rv = APR_EINVAL;
+                goto cleanup;
+            }
+        }
+        else {
+            /* data buckets before response headers, an error */
+            h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+            rv = APR_EINVAL;
+            goto cleanup;
+        }
+        b = e;
+    }
+
+    if (!resp) {
+        rv = APR_EAGAIN;
+        goto cleanup;
+    }
+
+    if (resp->status < 100) {
+        h2_stream_rst(stream, resp->status);
+        goto cleanup;
+    }
+
+    if (resp->status == HTTP_FORBIDDEN && resp->notes) {
+        const char *cause = apr_table_get(resp->notes, "ssl-renegotiate-forbidden");
+        if (cause) {
+            /* This request triggered a TLS renegotiation that is not allowed
+             * in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
+             */
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, resp->status, c1,
+                          H2_STRM_LOG(APLOGNO(03061), stream,
+                          "renegotiate forbidden, cause: %s"), cause);
+            h2_stream_rst(stream, H2_ERR_HTTP_1_1_REQUIRED);
+            goto cleanup;
+        }
+    }
+
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c1,
+                  H2_STRM_LOG(APLOGNO(03073), stream,
+                  "submit response %d"), resp->status);
+
+    /* If this stream is not a pushed one itself,
+     * and HTTP/2 server push is enabled here,
+     * and the response HTTP status is not sth >= 400,
+     * and the remote side has pushing enabled,
+     * -> find and perform any pushes on this stream
+     *    *before* we submit the stream response itself.
+     *    This helps clients avoid opening new streams on Link
+     *    resp that get pushed right afterwards.
+     *
+     * *) the response code is relevant, as we do not want to
+     *    make pushes on 401 or 403 codes and friends.
+     *    And if we see a 304, we do not push either
+     *    as the client, having this resource in its cache, might
+     *    also have the pushed ones as well.
+     */
+    if (!stream->initiated_on
+        && !stream->response
+        && stream->request && stream->request->method
+        && !strcmp("GET", stream->request->method)
+        && (resp->status < 400)
+        && (resp->status != 304)
+        && h2_session_push_enabled(stream->session)) {
+        /* PUSH is possible and enabled on server, unless the request
+         * denies it, submit resources to push */
+        const char *s = apr_table_get(resp->notes, H2_PUSH_MODE_NOTE);
+        if (!s || strcmp(s, "0")) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
+                          H2_STRM_MSG(stream, "submit pushes, note=%s"), s);
+            h2_stream_submit_pushes(stream, resp);
+        }
+    }
+
+    if (!stream->pref_priority) {
+        stream->pref_priority = h2_stream_get_priority(stream, resp);
+    }
+    h2_session_set_prio(stream->session, stream, stream->pref_priority);
+
+    if (resp->status == 103
+        && !h2_config_sgeti(stream->session->s, H2_CONF_EARLY_HINTS)) {
+        /* suppress sending this to the client, it might have triggered
+         * pushes and served its purpose nevertheless */
+        rv = APR_SUCCESS;
+        goto cleanup;
+    }
+    if (resp->status >= 200) {
+        stream->response = resp;
+    }
+
+    if (!is_empty) {
+        memset(&provider, 0, sizeof(provider));
+        provider.source.fd = stream->id;
+        provider.read_callback = stream_data_cb;
+        pprovider = &provider;
+    }
+
+    rv = h2_res_create_ngheader(&nh, stream->pool, resp);
+    if (APR_SUCCESS != rv) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+                      H2_STRM_LOG(APLOGNO(10025), stream, "invalid response"));
+        h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+        goto cleanup;
+    }
+
+    ngrv = nghttp2_submit_response(stream->session->ngh2, stream->id,
+                                   nh->nv, nh->nvlen, pprovider);
+    if (nghttp2_is_fatal(ngrv)) {
+        rv = APR_EGENERAL;
+        h2_session_dispatch_event(stream->session,
+                                 H2_SESSION_EV_PROTO_ERROR, ngrv, nghttp2_strerror(rv));
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
+                      APLOGNO(10402) "submit_response: %s",
+                      nghttp2_strerror(rv));
+        goto cleanup;
+    }
+
+    if (stream->initiated_on) {
+        ++stream->session->pushes_submitted;
+    }
+    else {
+        ++stream->session->responses_submitted;
+    }
+
+cleanup:
+    return rv;
+}
+
+static void stream_do_responses(h2_stream *stream)
+{
+    h2_session *session = stream->session;
+    conn_rec *c1 = session->c1;
+    apr_status_t rv;
+
+    ap_assert(!stream->response);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
+                  H2_STRM_MSG(stream, "do_response"));
+    rv = buffer_output_receive(stream);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+                  H2_SSSN_STRM_MSG(session, stream->id,
+                  "buffer_output_received2"));
+    if (APR_SUCCESS != rv && APR_EAGAIN != rv) {
+        h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+    }
+    else {
+        /* process all headers sitting at the buffer head. */
+        do {
+            rv = stream_do_response(stream);
+        } while (APR_SUCCESS == rv
+                 && !stream->rst_error
+                 && !stream->response);
+    }
+}
+
+void h2_stream_on_output_change(h2_stream *stream)
+{
+    conn_rec *c1 = stream->session->c1;
+    apr_status_t rv = APR_EAGAIN;
+
+    /* stream->pout_recv_write signalled a change. Check what has happend, read
+     * from it and act on seeing a response/data. */
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+    if (!stream->output) {
+        /* c2 has not assigned the output beam to the stream (yet). */
+        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c1,
+                      H2_STRM_MSG(stream, "read_output, no output beam registered"));
+    }
+    else if (h2_stream_is_at_or_past(stream, H2_SS_CLOSED)) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+                      H2_STRM_LOG(APLOGNO(10301), stream, "already closed"));
+    }
+    else if (h2_stream_is_at(stream, H2_SS_CLOSED_L)) {
+        /* We have delivered a response to a stream that was not closed
+         * by the client. This could be a POST with body that we negate
+         * and we need to RST_STREAM to end if. */
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c1,
+                      H2_STRM_LOG(APLOGNO(10313), stream, "remote close missing"));
+        h2_stream_rst(stream, H2_ERR_NO_ERROR);
+    }
+    else {
+        /* stream is not closed, a change in output happened. There are
+         * two modes of operation here:
+         * 1) the final response has been submitted. nghttp2 is invoking
+         *    stream_data_cb() to progress the stream. This handles DATA,
+         *    trailers, EOS and ERRORs.
+         *    When stream_data_cb() runs out of things to send, it returns
+         *    NGHTTP2_ERR_DEFERRED and nghttp2 *suspends* further processing
+         *    until we tell it to resume.
+         * 2) We have not seen the *final* response yet. The stream can not
+         *    send any response DATA. The nghttp2 stream_data_cb() is not
+         *    invoked. We need to receive output, expecting not DATA but
+         *    RESPONSEs (intermediate may arrive) and submit those. On
+         *    the final response, nghttp2 will start calling stream_data_cb().
+         */
+        if (stream->response) {
+            nghttp2_session_resume_data(stream->session->ngh2, stream->id);
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
+                          H2_STRM_MSG(stream, "resumed"));
+        }
+        else {
+            stream_do_responses(stream);
+            if (!stream->rst_error) {
+                nghttp2_session_resume_data(stream->session->ngh2, stream->id);
+            }
+        }
+    }
+}
+
+void h2_stream_on_input_change(h2_stream *stream)
+{
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+    ap_assert(stream->input);
+    h2_beam_report_consumption(stream->input);
+    if (h2_stream_is_at(stream, H2_SS_CLOSED_L)
+        && !h2_mplx_c1_stream_is_running(stream->session->mplx, stream)) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c1,
+                      H2_STRM_LOG(APLOGNO(10026), stream, "remote close missing"));
+        h2_stream_rst(stream, H2_ERR_NO_ERROR);
+    }
+}
diff -r -N -u a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h
--- a/modules/http2/h2_stream.h	2024-10-30 21:39:44.639621115 +0100
+++ b/modules/http2/h2_stream.h	2024-10-30 21:40:01.059958617 +0100
@@ -17,7 +17,10 @@
 #ifndef __mod_h2__h2_stream__
 #define __mod_h2__h2_stream__
 
+#include <http_protocol.h>
+
 #include "h2.h"
+#include "h2_headers.h"
 
 /**
  * A HTTP/2 stream, e.g. a client request+response in HTTP/1.1 terms.
@@ -26,8 +29,8 @@
  * connection to the client. The h2_session writes to the h2_stream,
  * adding HEADERS and DATA and finally an EOS. When headers are done,
  * h2_stream is scheduled for handling, which is expected to produce
- * a response h2_headers at least.
- * 
+ * h2_headers/RESPONSE buckets.
+ *
  * The h2_headers may be followed by more h2_headers (interim responses) and
  * by DATA frames read from the h2_stream until EOS is reached. Trailers
  * are send when a last h2_headers is received. This always closes the stream
@@ -37,9 +40,7 @@
 struct h2_mplx;
 struct h2_priority;
 struct h2_request;
-struct h2_headers;
 struct h2_session;
-struct h2_task;
 struct h2_bucket_beam;
 
 typedef struct h2_stream h2_stream;
@@ -62,7 +63,22 @@
                                              trigger a state change */
 } h2_stream_monitor;
 
+#ifdef AP_DEBUG
+#define H2_STRM_MAGIC_OK     0x5354524d
+#define H2_STRM_MAGIC_SDEL   0x5344454c
+#define H2_STRM_MAGIC_PDEL   0x5044454c
+
+#define H2_STRM_ASSIGN_MAGIC(s,m)  ((s)->magic = m)
+#define H2_STRM_ASSERT_MAGIC(s,m)  ap_assert((s)->magic == m)
+#else
+#define H2_STRM_ASSIGN_MAGIC(s,m)  ((void)0)
+#define H2_STRM_ASSERT_MAGIC(s,m)  ((void)0)
+#endif
+
 struct h2_stream {
+#ifdef AP_DEBUG
+    uint32_t magic;
+#endif
     int id;                     /* http2 stream identifier */
     int initiated_on;           /* initiating stream id (PUSH) or 0 */
     apr_pool_t *pool;           /* the memory pool for this stream */
@@ -73,10 +89,15 @@
     
     const struct h2_request *request; /* the request made in this stream */
     struct h2_request *rtmp;    /* request being assembled */
-    apr_table_t *trailers;      /* optional incoming trailers */
+    apr_table_t *trailers_in;   /* optional, incoming trailers */
     int request_headers_added;  /* number of request headers added */
-    int request_headers_failed; /* number of request headers failed to add */
-    
+
+#if AP_HAS_RESPONSE_BUCKETS
+    ap_bucket_response *response; /* the final, non-interim response or NULL */
+#else
+    struct h2_headers *response; /* the final, non-interim response or NULL */
+#endif
+
     struct h2_bucket_beam *input;
     apr_bucket_brigade *in_buffer;
     int in_window_size;
@@ -84,18 +105,16 @@
     
     struct h2_bucket_beam *output;
     apr_bucket_brigade *out_buffer;
-    apr_size_t max_mem;         /* maximum amount of data buffered */
 
     int rst_error;              /* stream error for RST_STREAM */
     unsigned int aborted   : 1; /* was aborted */
     unsigned int scheduled : 1; /* stream has been scheduled */
-    unsigned int has_response : 1; /* response headers are known */
-    unsigned int input_eof : 1; /* no more request data coming */
-    unsigned int out_checked : 1; /* output eof was double checked */
+    unsigned int input_closed : 1; /* no more request data/trailers coming */
     unsigned int push_policy;   /* which push policy to use for this request */
-    unsigned int input_buffering : 1; /* buffer request bodies for efficiency */
+    unsigned int sent_trailers : 1; /* trailers have been submitted */
+    unsigned int output_eos : 1; /* output EOS in buffer/sent */
 
-    struct h2_task *task;       /* assigned task to fullfill request */
+    conn_rec *c2;               /* connection processing stream */
     
     const h2_priority *pref_priority; /* preferred priority for this stream */
     apr_off_t out_frames;       /* # of frames sent out */
@@ -134,13 +153,9 @@
 void h2_stream_destroy(h2_stream *stream);
 
 /**
- * Prepare the stream so that processing may start.
- * 
- * This is the time to allocated resources not needed before.
- * 
- * @param stream the stream to prep 
+ * Perform any late initialization before stream starts processing.
  */
-apr_status_t h2_stream_prep_processing(h2_stream *stream);
+apr_status_t h2_stream_prepare_processing(h2_stream *stream);
 
 /*
  * Set a new monitor for this stream, replacing any existing one. Can
@@ -156,6 +171,22 @@
 void h2_stream_dispatch(h2_stream *stream, h2_stream_event_t ev);
 
 /**
+ * Determine if stream is at given state.
+ * @param stream the stream to check
+ * @param state the state to look for
+ * @return != 0 iff stream is at given state.
+ */
+int h2_stream_is_at(const h2_stream *stream, h2_stream_state_t state);
+
+/**
+ * Determine if stream is reached given state or is past this state.
+ * @param stream the stream to check
+ * @param state the state to look for
+ * @return != 0 iff stream is at or past given state.
+ */
+int h2_stream_is_at_or_past(const h2_stream *stream, h2_stream_state_t state);
+
+/**
  * Cleanup references into requst processing.
  *
  * @param stream the stream to cleanup
@@ -219,8 +250,6 @@
 apr_status_t h2_stream_recv_DATA(h2_stream *stream, uint8_t flags,
                                  const uint8_t *data, size_t len);
 
-apr_status_t h2_stream_flush_input(h2_stream *stream);
-
 /**
  * Reset the stream. Stream write/reads will return errors afterwards.
  *
@@ -230,31 +259,16 @@
 void h2_stream_rst(h2_stream *stream, int error_code);
 
 /**
- * Determine if stream was closed already. This is true for
- * states H2_SS_CLOSED, H2_SS_CLEANUP. But not true
- * for H2_SS_CLOSED_L and H2_SS_CLOSED_R.
- *
- * @param stream the stream to check on
- * @return != 0 iff stream has been closed
+ * Stream input signals change. Take necessary actions.
+ * @param stream the stream to read output for
  */
-int h2_stream_was_closed(const h2_stream *stream);
+void h2_stream_on_input_change(h2_stream *stream);
 
 /**
- * Do a speculative read on the stream output to determine the 
- * amount of data that can be read.
- * 
- * @param stream the stream to speculatively read from
- * @param plen (in-/out) number of bytes requested and on return amount of bytes that
- *        may be read without blocking
- * @param peos (out) != 0 iff end of stream will be reached when reading plen
- *        bytes (out value).
- * @param presponse (out) the response of one became available
- * @return APR_SUCCESS if out information was computed successfully.
- *         APR_EAGAIN if not data is available and end of stream has not been
- *         reached yet.
+ * Stream output signals change. Take necessary actions.
+ * @param stream the stream to read output for
  */
-apr_status_t h2_stream_out_prepare(h2_stream *stream, apr_off_t *plen, 
-                                   int *peos, h2_headers **presponse);
+void h2_stream_on_output_change(h2_stream *stream);
 
 /**
  * Read a maximum number of bytes into the bucket brigade.
@@ -283,23 +297,34 @@
 
 /**
  * Submit any server push promises on this stream and schedule
- * the tasks connection with these.
+ * the streams for these.
  *
  * @param stream the stream for which to submit
  */
-apr_status_t h2_stream_submit_pushes(h2_stream *stream, h2_headers *response);
+#if AP_HAS_RESPONSE_BUCKETS
+apr_status_t h2_stream_submit_pushes(h2_stream *stream,
+                                     ap_bucket_response *response);
+#else
+apr_status_t h2_stream_submit_pushes(h2_stream *stream,
+                                     struct h2_headers *response);
+#endif
 
 /**
  * Get priority information set for this stream.
  */
-const struct h2_priority *h2_stream_get_priority(h2_stream *stream, 
-                                                 h2_headers *response);
+#if AP_HAS_RESPONSE_BUCKETS
+const struct h2_priority *h2_stream_get_priority(h2_stream *stream,
+                                                 ap_bucket_response *response);
+#else
+const struct h2_priority *h2_stream_get_priority(h2_stream *stream,
+                                                 struct h2_headers *response);
+#endif
 
 /**
  * Return a textual representation of the stream state as in RFC 7540
  * nomenclator, all caps, underscores.
  */
-const char *h2_stream_state_str(h2_stream *stream);
+const char *h2_stream_state_str(const h2_stream *stream);
 
 /**
  * Determine if stream is ready for submitting a response or a RST
@@ -307,8 +332,11 @@
  */
 int h2_stream_is_ready(h2_stream *stream);
 
+int h2_stream_wants_send_data(h2_stream *stream);
+
 #define H2_STRM_MSG(s, msg)     \
-    "h2_stream(%ld-%d,%s): "msg, s->session->id, s->id, h2_stream_state_str(s)
+    "h2_stream(%d-%lu-%d,%s): "msg, s->session->child_num, \
+    (unsigned long)s->session->id, s->id, h2_stream_state_str(s)
 
 #define H2_STRM_LOG(aplogno, s, msg)    aplogno H2_STRM_MSG(s, msg)
 
diff -r -N -u a/modules/http2/h2_switch.c b/modules/http2/h2_switch.c
--- a/modules/http2/h2_switch.c	2021-05-12 12:14:42.000000000 +0200
+++ b/modules/http2/h2_switch.c	2024-10-30 21:40:01.059958617 +0100
@@ -29,11 +29,13 @@
 #include <http_log.h>
 
 #include "h2_private.h"
+#include "h2.h"
 
 #include "h2_config.h"
-#include "h2_ctx.h"
-#include "h2_conn.h"
-#include "h2_h2.h"
+#include "h2_conn_ctx.h"
+#include "h2_c1.h"
+#include "h2_c2.h"
+#include "h2_protocol.h"
 #include "h2_switch.h"
 
 /*******************************************************************************
@@ -54,7 +56,7 @@
 {
     int proposed = 0;
     int is_tls = ap_ssl_conn_is_ssl(c);
-    const char **protos = is_tls? h2_tls_protos : h2_clear_protos;
+    const char **protos = is_tls? h2_protocol_ids_tls : h2_protocol_ids_clear;
     
     if (!h2_mpm_supported()) {
         return DECLINED;
@@ -68,7 +70,7 @@
         return DECLINED;
     }
     
-    if (!h2_is_acceptable_connection(c, r, 0)) {
+    if (!h2_protocol_is_acceptable_c1(c, r, 0)) {
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03084)
                       "protocol propose: connection requirements not met");
         return DECLINED;
@@ -81,7 +83,7 @@
          */
         const char *p;
         
-        if (!h2_allows_h2_upgrade(r)) {
+        if (!h2_c1_can_upgrade(r)) {
             return DECLINED;
         }
          
@@ -102,9 +104,10 @@
         /* We also allow switching only for requests that have no body.
          */
         p = apr_table_get(r->headers_in, "Content-Length");
-        if (p && strcmp(p, "0")) {
+        if ((p && strcmp(p, "0"))
+            || (!p && apr_table_get(r->headers_in, "Transfer-Encoding"))) {
             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03087)
-                          "upgrade with content-length: %s, declined", p);
+                          "upgrade with body declined");
             return DECLINED;
         }
     }
@@ -124,11 +127,35 @@
     return proposed? DECLINED : OK;
 }
 
+#if AP_HAS_RESPONSE_BUCKETS
+static void remove_output_filters_below(ap_filter_t *f, ap_filter_type ftype)
+{
+    ap_filter_t *fnext;
+
+    while (f && f->frec->ftype < ftype) {
+        fnext = f->next;
+        ap_remove_output_filter(f);
+        f = fnext;
+    }
+}
+
+static void remove_input_filters_below(ap_filter_t *f, ap_filter_type ftype)
+{
+    ap_filter_t *fnext;
+
+    while (f && f->frec->ftype < ftype) {
+        fnext = f->next;
+        ap_remove_input_filter(f);
+        f = fnext;
+    }
+}
+#endif
+
 static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
                               const char *protocol)
 {
     int found = 0;
-    const char **protos = ap_ssl_conn_is_ssl(c)? h2_tls_protos : h2_clear_protos;
+    const char **protos = ap_ssl_conn_is_ssl(c)? h2_protocol_ids_tls : h2_protocol_ids_clear;
     const char **p = protos;
     
     (void)s;
@@ -145,15 +172,22 @@
     }
     
     if (found) {
-        h2_ctx *ctx = h2_ctx_get(c, 1);
-        
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                       "switching protocol to '%s'", protocol);
-        h2_ctx_protocol_set(ctx, protocol);
-        h2_ctx_server_update(ctx, s);
-        
+        h2_conn_ctx_create_for_c1(c, s, protocol);
+
         if (r != NULL) {
             apr_status_t status;
+#if AP_HAS_RESPONSE_BUCKETS
+            /* Switching in the middle of a request means that
+             * we have to send out the response to this one in h2
+             * format. So we need to take over the connection
+             * and remove all old filters with type up to the
+             * CONNEDCTION/NETWORK ones.
+             */
+            remove_input_filters_below(r->input_filters, AP_FTYPE_CONNECTION);
+            remove_output_filters_below(r->output_filters, AP_FTYPE_CONNECTION);
+#else
             /* Switching in the middle of a request means that
              * we have to send out the response to this one in h2
              * format. So we need to take over the connection
@@ -161,18 +195,18 @@
              */
             ap_remove_input_filter_byhandle(r->input_filters, "http_in");
             ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
-            
+#endif
             /* Ok, start an h2_conn on this one. */
-            status = h2_conn_setup(c, r, s);
+            status = h2_c1_setup(c, r, s);
             
             if (status != APR_SUCCESS) {
                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(03088)
                               "session setup");
-                h2_ctx_clear(c);
+                h2_conn_ctx_detach(c);
                 return !OK;
             }
             
-            h2_conn_run(c);
+            h2_c1_run(c);
         }
         return OK;
     }
@@ -182,7 +216,13 @@
 
 static const char *h2_protocol_get(const conn_rec *c)
 {
-    return h2_ctx_protocol_get(c);
+    h2_conn_ctx_t *ctx;
+
+    if (c->master) {
+        c = c->master;
+    }
+    ctx = h2_conn_ctx_get(c);
+    return ctx? ctx->protocol : NULL;
 }
 
 void h2_switch_register_hooks(void)
diff -r -N -u a/modules/http2/h2_task.c b/modules/http2/h2_task.c
--- a/modules/http2/h2_task.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_task.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,725 +0,0 @@
-/* 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 <stddef.h>
-
-#include <apr_atomic.h>
-#include <apr_strings.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_vhost.h>
-#include <util_filter.h>
-#include <ap_mpm.h>
-#include <mod_core.h>
-#include <scoreboard.h>
-
-#include "h2_private.h"
-#include "h2.h"
-#include "h2_bucket_beam.h"
-#include "h2_conn.h"
-#include "h2_config.h"
-#include "h2_ctx.h"
-#include "h2_from_h1.h"
-#include "h2_h2.h"
-#include "h2_mplx.h"
-#include "h2_request.h"
-#include "h2_headers.h"
-#include "h2_session.h"
-#include "h2_stream.h"
-#include "h2_task.h"
-#include "h2_util.h"
-
-static void H2_TASK_OUT_LOG(int lvl, h2_task *task, apr_bucket_brigade *bb, 
-                            const char *tag)
-{
-    if (APLOG_C_IS_LEVEL(task->c, lvl)) {
-        conn_rec *c = task->c;
-        char buffer[4 * 1024];
-        const char *line = "(null)";
-        apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]);
-        
-        len = h2_util_bb_print(buffer, bmax, tag, "", bb);
-        ap_log_cerror(APLOG_MARK, lvl, 0, c, "bb_dump(%s): %s", 
-                      task->id, len? buffer : line);
-    }
-}
-
-/*******************************************************************************
- * task input handling
- ******************************************************************************/
-
-static int input_ser_header(void *ctx, const char *name, const char *value) 
-{
-    h2_task *task = ctx;
-    apr_brigade_printf(task->input.bb, NULL, NULL, "%s: %s\r\n", name, value);
-    return 1;
-}
-
-/*******************************************************************************
- * task output handling
- ******************************************************************************/
-
-static apr_status_t open_output(h2_task *task)
-{
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, APLOGNO(03348)
-                  "h2_task(%s): open output to %s %s %s",
-                  task->id, task->request->method, 
-                  task->request->authority, 
-                  task->request->path);
-    task->output.opened = 1;
-    return h2_mplx_t_out_open(task->mplx, task->stream_id, task->output.beam);
-}
-
-static void output_consumed(void *ctx, h2_bucket_beam *beam, apr_off_t length)
-{
-    h2_task *task = ctx;
-    if (task && h2_task_logio_add_bytes_out) {
-        h2_task_logio_add_bytes_out(task->c, length);
-    }
-}
-
-static apr_status_t send_out(h2_task *task, apr_bucket_brigade* bb, int block)
-{
-    apr_off_t written, left;
-    apr_status_t status;
-
-    apr_brigade_length(bb, 0, &written);
-    H2_TASK_OUT_LOG(APLOG_TRACE2, task, bb, "h2_task send_out");
-    h2_beam_log(task->output.beam, task->c, APLOG_TRACE2, "send_out(before)");
-
-    status = h2_beam_send(task->output.beam, bb, 
-                          block? APR_BLOCK_READ : APR_NONBLOCK_READ);
-    h2_beam_log(task->output.beam, task->c, APLOG_TRACE2, "send_out(after)");
-    
-    if (APR_STATUS_IS_EAGAIN(status)) {
-        apr_brigade_length(bb, 0, &left);
-        written -= left;
-        status = APR_SUCCESS;
-    }
-    if (status == APR_SUCCESS) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c, 
-                      "h2_task(%s): send_out done", task->id);
-    }
-    else {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, task->c,
-                      "h2_task(%s): send_out (%ld bytes)", 
-                      task->id, (long)written);
-    }
-    return status;
-}
-
-/* Bring the data from the brigade (which represents the result of the
- * request_rec out filter chain) into the h2_mplx for further sending
- * on the master connection. 
- */
-static apr_status_t secondary_out(h2_task *task, ap_filter_t* f, 
-                                  apr_bucket_brigade* bb)
-{
-    apr_bucket *b;
-    apr_status_t rv = APR_SUCCESS;
-    int flush = 0, blocking;
-    
-send:
-    /* we send block once we opened the output, so someone is there reading it */
-    blocking = task->output.opened;
-    for (b = APR_BRIGADE_FIRST(bb);
-         b != APR_BRIGADE_SENTINEL(bb);
-         b = APR_BUCKET_NEXT(b)) {
-        if (APR_BUCKET_IS_FLUSH(b) || APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b)) {
-            flush = 1;
-            break;
-        }
-    }
-    
-    if (task->output.bb && !APR_BRIGADE_EMPTY(task->output.bb)) {
-        /* still have data buffered from previous attempt.
-         * setaside and append new data and try to pass the complete data */
-        if (!APR_BRIGADE_EMPTY(bb)) {
-            if (APR_SUCCESS != (rv = ap_save_brigade(f, &task->output.bb, &bb, task->pool))) {
-                goto out;
-            }
-        }
-        rv = send_out(task, task->output.bb, blocking);
-    }
-    else {
-        /* no data buffered previously, pass brigade directly */
-        rv = send_out(task, bb, blocking);
-
-        if (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(bb)) {
-            /* output refused to buffer it all, time to open? */
-            if (!task->output.opened && APR_SUCCESS == (rv = open_output(task))) {
-                /* Make another attempt to send the data. With the output open,
-                 * the call might be blocking and send all data, so we do not need
-                 * to save the brigade */
-                goto send;
-            }
-            else if (blocking && flush) {
-                /* Need to keep on doing this. */
-                goto send;
-            }
-            
-            if (APR_SUCCESS == rv) {
-                /* could not write all, buffer the rest */
-                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, task->c, APLOGNO(03405)
-                              "h2_secondary_out(%s): saving brigade", task->id);
-                ap_assert(NULL);
-                rv = ap_save_brigade(f, &task->output.bb, &bb, task->pool);
-                flush = 1;
-            }
-        }
-    }
-    
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c,
-                  "h2_secondary_out(%s): buffered=%d", task->id, task->output.buffered);
-    if (APR_SUCCESS == rv && !task->output.opened && (flush || !task->output.buffered)) {
-        /* got a flush or could not write all, time to tell someone to read */
-        rv = open_output(task);
-    }
-out:
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, task->c, 
-                  "h2_secondary_out(%s): secondary_out leave", task->id);    
-    return rv;
-}
-
-static apr_status_t output_finish(h2_task *task)
-{
-    if (!task->output.opened) {
-        return open_output(task);
-    }
-    return APR_SUCCESS;
-}
-
-/*******************************************************************************
- * task secondary connection filters
- ******************************************************************************/
-
-static apr_status_t h2_filter_secondary_in(ap_filter_t* f,
-                                           apr_bucket_brigade* bb,
-                                           ap_input_mode_t mode,
-                                           apr_read_type_e block,
-                                           apr_off_t readbytes)
-{
-    h2_task *task;
-    apr_status_t status = APR_SUCCESS;
-    apr_bucket *b, *next;
-    apr_off_t bblen;
-    const int trace1 = APLOGctrace1(f->c);
-    apr_size_t rmax = ((readbytes <= APR_SIZE_MAX)? 
-                       (apr_size_t)readbytes : APR_SIZE_MAX);
-    
-    task = h2_ctx_get_task(f->c);
-    ap_assert(task);
-
-    if (trace1) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                      "h2_secondary_in(%s): read, mode=%d, block=%d, readbytes=%ld", 
-                      task->id, mode, block, (long)readbytes);
-    }
-    
-    if (mode == AP_MODE_INIT) {
-        return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
-    }
-    
-    if (f->c->aborted) {
-        return APR_ECONNABORTED;
-    }
-    
-    if (!task->input.bb) {
-        return APR_EOF;
-    }
-    
-    /* Cleanup brigades from those nasty 0 length non-meta buckets
-     * that apr_brigade_split_line() sometimes produces. */
-    for (b = APR_BRIGADE_FIRST(task->input.bb);
-         b != APR_BRIGADE_SENTINEL(task->input.bb); b = next) {
-        next = APR_BUCKET_NEXT(b);
-        if (b->length == 0 && !APR_BUCKET_IS_METADATA(b)) {
-            apr_bucket_delete(b);
-        } 
-    }
-    
-    while (APR_BRIGADE_EMPTY(task->input.bb)) {
-        /* Get more input data for our request. */
-        if (trace1) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
-                          "h2_secondary_in(%s): get more data from mplx, block=%d, "
-                          "readbytes=%ld", task->id, block, (long)readbytes);
-        }
-        if (task->input.beam) {
-            status = h2_beam_receive(task->input.beam, task->input.bb, block, 
-                                     128*1024, NULL);
-        }
-        else {
-            status = APR_EOF;
-        }
-        
-        if (trace1) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c,
-                          "h2_secondary_in(%s): read returned", task->id);
-        }
-        if (APR_STATUS_IS_EAGAIN(status) 
-            && (mode == AP_MODE_GETLINE || block == APR_BLOCK_READ)) {
-            /* chunked input handling does not seem to like it if we
-             * return with APR_EAGAIN from a GETLINE read... 
-             * upload 100k test on test-ser.example.org hangs */
-            status = APR_SUCCESS;
-        }
-        else if (APR_STATUS_IS_EOF(status)) {
-            break;
-        }
-        else if (status != APR_SUCCESS) {
-            return status;
-        }
-
-        if (trace1) {
-            h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2, 
-                        "input.beam recv raw", task->input.bb);
-        }
-        if (h2_task_logio_add_bytes_in) {
-            apr_brigade_length(bb, 0, &bblen);
-            h2_task_logio_add_bytes_in(f->c, bblen);
-        }
-    }
-    
-    /* Nothing there, no more data to get. Return. */
-    if (status == APR_EOF && APR_BRIGADE_EMPTY(task->input.bb)) {
-        return status;
-    }
-
-    if (trace1) {
-        h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2, 
-                    "task_input.bb", task->input.bb);
-    }
-           
-    if (APR_BRIGADE_EMPTY(task->input.bb)) {
-        if (trace1) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                          "h2_secondary_in(%s): no data", task->id);
-        }
-        return (block == APR_NONBLOCK_READ)? APR_EAGAIN : APR_EOF;
-    }
-    
-    if (mode == AP_MODE_EXHAUSTIVE) {
-        /* return all we have */
-        APR_BRIGADE_CONCAT(bb, task->input.bb);
-    }
-    else if (mode == AP_MODE_READBYTES) {
-        status = h2_brigade_concat_length(bb, task->input.bb, rmax);
-    }
-    else if (mode == AP_MODE_SPECULATIVE) {
-        status = h2_brigade_copy_length(bb, task->input.bb, rmax);
-    }
-    else if (mode == AP_MODE_GETLINE) {
-        /* we are reading a single LF line, e.g. the HTTP headers. 
-         * this has the nasty side effect to split the bucket, even
-         * though it ends with CRLF and creates a 0 length bucket */
-        status = apr_brigade_split_line(bb, task->input.bb, block, 
-                                        HUGE_STRING_LEN);
-        if (APLOGctrace1(f->c)) {
-            char buffer[1024];
-            apr_size_t len = sizeof(buffer)-1;
-            apr_brigade_flatten(bb, buffer, &len);
-            buffer[len] = 0;
-            if (trace1) {
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
-                              "h2_secondary_in(%s): getline: %s",
-                              task->id, buffer);
-            }
-        }
-    }
-    else {
-        /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not
-         * to support it. Seems to work. */
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
-                      APLOGNO(03472) 
-                      "h2_secondary_in(%s), unsupported READ mode %d", 
-                      task->id, mode);
-        status = APR_ENOTIMPL;
-    }
-    
-    if (trace1) {
-        apr_brigade_length(bb, 0, &bblen);
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
-                      "h2_secondary_in(%s): %ld data bytes", task->id, (long)bblen);
-    }
-    return status;
-}
-
-static apr_status_t h2_filter_secondary_output(ap_filter_t* filter,
-                                               apr_bucket_brigade* brigade)
-{
-    h2_task *task = h2_ctx_get_task(filter->c);
-    apr_status_t status;
-    
-    ap_assert(task);
-    status = secondary_out(task, filter, brigade);
-    if (status != APR_SUCCESS) {
-        h2_task_rst(task, H2_ERR_INTERNAL_ERROR);
-    }
-    return status;
-}
-
-static apr_status_t h2_filter_parse_h1(ap_filter_t* f, apr_bucket_brigade* bb)
-{
-    h2_task *task = h2_ctx_get_task(f->c);
-    apr_status_t status;
-    
-    ap_assert(task);
-    /* There are cases where we need to parse a serialized http/1.1 
-     * response. One example is a 100-continue answer in serialized mode
-     * or via a mod_proxy setup */
-    while (bb && !task->c->aborted && !task->output.sent_response) {
-        status = h2_from_h1_parse_response(task, f, bb);
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c,
-                      "h2_task(%s): parsed response", task->id);
-        if (APR_BRIGADE_EMPTY(bb) || status != APR_SUCCESS) {
-            return status;
-        }
-    }
-    
-    return ap_pass_brigade(f->next, bb);
-}
-
-/*******************************************************************************
- * task things
- ******************************************************************************/
- 
-int h2_task_can_redo(h2_task *task) {
-    if (task->input.beam && h2_beam_was_received(task->input.beam)) {
-        /* cannot repeat that. */
-        return 0;
-    }
-    return (!strcmp("GET", task->request->method)
-            || !strcmp("HEAD", task->request->method)
-            || !strcmp("OPTIONS", task->request->method));
-}
-
-int h2_task_has_started(h2_task *task)
-{
-    return task && task->started_at != 0;
-}
-
-void h2_task_redo(h2_task *task)
-{
-    task->started_at = 0;
-    task->worker_done = 0;
-    task->rst_error = 0;
-}
-
-void h2_task_rst(h2_task *task, int error)
-{
-    task->rst_error = error;
-    if (task->input.beam) {
-        h2_beam_leave(task->input.beam);
-    }
-    if (!task->worker_done) {
-        h2_beam_abort(task->output.beam);
-    }
-    if (task->c) {
-        task->c->aborted = 1;
-    }
-}
-
-/*******************************************************************************
- * Register various hooks
- */
-static const char *const mod_ssl[]        = { "mod_ssl.c", NULL};
-static int h2_task_pre_conn(conn_rec* c, void *arg);
-static int h2_task_process_conn(conn_rec* c);
-
-APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_task_logio_add_bytes_in;
-APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_task_logio_add_bytes_out;
-
-void h2_task_register_hooks(void)
-{
-    /* This hook runs on new connections before mod_ssl has a say.
-     * Its purpose is to prevent mod_ssl from touching our pseudo-connections
-     * for streams.
-     */
-    ap_hook_pre_connection(h2_task_pre_conn,
-                           NULL, mod_ssl, APR_HOOK_FIRST);
-    /* When the connection processing actually starts, we might 
-     * take over, if the connection is for a task.
-     */
-    ap_hook_process_connection(h2_task_process_conn, 
-                               NULL, NULL, APR_HOOK_FIRST);
-
-    ap_register_input_filter("H2_SECONDARY_IN", h2_filter_secondary_in,
-                             NULL, AP_FTYPE_NETWORK);
-    ap_register_output_filter("H2_SECONDARY_OUT", h2_filter_secondary_output,
-                              NULL, AP_FTYPE_NETWORK);
-    ap_register_output_filter("H2_PARSE_H1", h2_filter_parse_h1,
-                              NULL, AP_FTYPE_NETWORK);
-
-    ap_register_input_filter("H2_REQUEST", h2_filter_request_in,
-                             NULL, AP_FTYPE_PROTOCOL);
-    ap_register_output_filter("H2_RESPONSE", h2_filter_headers_out,
-                              NULL, AP_FTYPE_PROTOCOL);
-    ap_register_output_filter("H2_TRAILERS_OUT", h2_filter_trailers_out,
-                              NULL, AP_FTYPE_PROTOCOL);
-}
-
-/* post config init */
-apr_status_t h2_task_init(apr_pool_t *pool, server_rec *s)
-{
-    h2_task_logio_add_bytes_in = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_in);
-    h2_task_logio_add_bytes_out = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_out);
-
-    return APR_SUCCESS;
-}
-
-static int h2_task_pre_conn(conn_rec* c, void *arg)
-{
-    h2_ctx *ctx;
-    
-    if (!c->master) {
-        return OK;
-    }
-    
-    ctx = h2_ctx_get(c, 0);
-    (void)arg;
-    if (ctx->task) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
-                      "h2_secondary(%s), pre_connection, adding filters", c->log_id);
-        ap_add_input_filter("H2_SECONDARY_IN", NULL, NULL, c);
-        ap_add_output_filter("H2_PARSE_H1", NULL, NULL, c);
-        ap_add_output_filter("H2_SECONDARY_OUT", NULL, NULL, c);
-    }
-    return OK;
-}
-
-h2_task *h2_task_create(conn_rec *secondary, int stream_id,
-                        const h2_request *req, h2_mplx *m,
-                        h2_bucket_beam *input, 
-                        apr_interval_time_t timeout,
-                        apr_size_t output_max_mem)
-{
-    apr_pool_t *pool;
-    h2_task *task;
-    
-    ap_assert(secondary);
-    ap_assert(req);
-
-    apr_pool_create(&pool, secondary->pool);
-    apr_pool_tag(pool, "h2_task");
-    task = apr_pcalloc(pool, sizeof(h2_task));
-    if (task == NULL) {
-        return NULL;
-    }
-    task->id          = "000";
-    task->stream_id   = stream_id;
-    task->c           = secondary;
-    task->mplx        = m;
-    task->pool        = pool;
-    task->request     = req;
-    task->timeout     = timeout;
-    task->input.beam  = input;
-    task->output.max_buffer = output_max_mem;
-
-    return task;
-}
-
-void h2_task_destroy(h2_task *task)
-{
-    if (task->output.beam) {
-        h2_beam_log(task->output.beam, task->c, APLOG_TRACE2, "task_destroy");
-        h2_beam_destroy(task->output.beam);
-        task->output.beam = NULL;
-    }
-    
-    if (task->eor) {
-        apr_bucket_destroy(task->eor);
-    }
-    if (task->pool) {
-        apr_pool_destroy(task->pool);
-    }
-}
-
-apr_status_t h2_task_do(h2_task *task, apr_thread_t *thread, int worker_id)
-{
-    conn_rec *c;
-    
-    ap_assert(task);
-    c = task->c;
-    task->worker_started = 1;
-    
-    if (c->master) {
-        /* See the discussion at <https://github.com/icing/mod_h2/issues/195>
-         *
-         * Each conn_rec->id is supposed to be unique at a point in time. Since
-         * some modules (and maybe external code) uses this id as an identifier
-         * for the request_rec they handle, it needs to be unique for secondary 
-         * connections also.
-         *
-         * The MPM module assigns the connection ids and mod_unique_id is using
-         * that one to generate identifier for requests. While the implementation
-         * works for HTTP/1.x, the parallel execution of several requests per
-         * connection will generate duplicate identifiers on load.
-         * 
-         * The original implementation for secondary connection identifiers used 
-         * to shift the master connection id up and assign the stream id to the 
-         * lower bits. This was cramped on 32 bit systems, but on 64bit there was
-         * enough space.
-         * 
-         * As issue 195 showed, mod_unique_id only uses the lower 32 bit of the
-         * connection id, even on 64bit systems. Therefore collisions in request ids.
-         *
-         * The way master connection ids are generated, there is some space "at the
-         * top" of the lower 32 bits on allmost all systems. If you have a setup
-         * with 64k threads per child and 255 child processes, you live on the edge.
-         *
-         * The new implementation shifts 8 bits and XORs in the worker
-         * id. This will experience collisions with > 256 h2 workers and heavy
-         * load still. There seems to be no way to solve this in all possible 
-         * configurations by mod_h2 alone. 
-         */
-        task->c->id = (c->master->id << 8)^worker_id;
-        task->id = apr_psprintf(task->pool, "%ld-%d", task->mplx->id,
-                                task->stream_id);
-    }
-        
-    h2_beam_create(&task->output.beam, c->pool, task->stream_id, "output", 
-                   H2_BEAM_OWNER_SEND, 0, task->timeout);
-    if (!task->output.beam) {
-        return APR_ENOMEM;
-    }
-    
-    h2_beam_buffer_size_set(task->output.beam, task->output.max_buffer);
-    h2_beam_send_from(task->output.beam, task->pool);
-    h2_beam_on_consumed(task->output.beam, NULL, output_consumed, task);
-
-    h2_ctx_create_for(c, task);
-    apr_table_setn(c->notes, H2_TASK_ID_NOTE, task->id);
-
-    h2_secondary_run_pre_connection(c, ap_get_conn_socket(c));            
-
-    task->input.bb = apr_brigade_create(task->pool, c->bucket_alloc);
-    if (task->request->serialize) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                      "h2_task(%s): serialize request %s %s", 
-                      task->id, task->request->method, task->request->path);
-        apr_brigade_printf(task->input.bb, NULL, 
-                           NULL, "%s %s HTTP/1.1\r\n", 
-                           task->request->method, task->request->path);
-        apr_table_do(input_ser_header, task, task->request->headers, NULL);
-        apr_brigade_puts(task->input.bb, NULL, NULL, "\r\n");
-    }
-    
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                  "h2_task(%s): process connection", task->id);
-                  
-    task->c->current_thread = thread; 
-    ap_run_process_connection(c);
-    
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                  "h2_task(%s): processing done", task->id);
-    return output_finish(task);
-}
-
-static apr_status_t h2_task_process_request(h2_task *task, conn_rec *c)
-{
-    const h2_request *req = task->request;
-    conn_state_t *cs = c->cs;
-    request_rec *r;
-
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                  "h2_task(%s): create request_rec", task->id);
-    r = h2_request_create_rec(req, c);
-    if (r && (r->status == HTTP_OK)) {
-        /* set timeouts for virtual host of request */
-        if (task->timeout != r->server->timeout) {
-            task->timeout = r->server->timeout;
-            h2_beam_timeout_set(task->output.beam, task->timeout);
-            if (task->input.beam) {
-                h2_beam_timeout_set(task->input.beam, task->timeout);
-            }
-        }
-        
-        ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, r);
-        
-        if (cs) {
-            cs->state = CONN_STATE_HANDLER;
-        }
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                      "h2_task(%s): start process_request", task->id);
-    
-        /* Add the raw bytes of the request (e.g. header frame lengths to
-         * the logio for this request. */
-        if (req->raw_bytes && h2_task_logio_add_bytes_in) {
-            h2_task_logio_add_bytes_in(c, req->raw_bytes);
-        }
-        
-        ap_process_request(r);
-        
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                      "h2_task(%s): process_request done", task->id);
-        
-        /* After the call to ap_process_request, the
-         * request pool may have been deleted.  We set
-         * r=NULL here to ensure that any dereference
-         * of r that might be added later in this function
-         * will result in a segfault immediately instead
-         * of nondeterministic failures later.
-         */
-        if (cs) 
-            cs->state = CONN_STATE_WRITE_COMPLETION;
-        r = NULL;
-    }
-    else if (!r) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                      "h2_task(%s): create request_rec failed, r=NULL", task->id);
-    }
-    else {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                      "h2_task(%s): create request_rec failed, r->status=%d", 
-                      task->id, r->status);
-    }
-
-    return APR_SUCCESS;
-}
-
-static int h2_task_process_conn(conn_rec* c)
-{
-    h2_ctx *ctx;
-    
-    if (!c->master) {
-        return DECLINED;
-    }
-    
-    ctx = h2_ctx_get(c, 0);
-    if (ctx->task) {
-        if (!ctx->task->request->serialize) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, 
-                          "h2_h2, processing request directly");
-            h2_task_process_request(ctx->task, c);
-            return DONE;
-        }
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, 
-                      "h2_task(%s), serialized handling", ctx->task->id);
-    }
-    else {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, 
-                      "secondary_conn(%ld): has no task", c->id);
-    }
-    return DECLINED;
-}
-
diff -r -N -u a/modules/http2/h2_task.h b/modules/http2/h2_task.h
--- a/modules/http2/h2_task.h	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_task.h	1970-01-01 01:00:00.000000000 +0100
@@ -1,122 +0,0 @@
-/* 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_task__
-#define __mod_h2__h2_task__
-
-#include <http_core.h>
-
-/**
- * A h2_task fakes a HTTP/1.1 request from the data in a HTTP/2 stream 
- * (HEADER+CONT.+DATA) the module receives.
- *
- * In order to answer a HTTP/2 stream, we want all Apache httpd infrastructure
- * to be involved as usual, as if this stream can as a separate HTTP/1.1
- * request. The basic trickery to do so was derived from google's mod_spdy
- * source. Basically, we fake a new conn_rec object, even with its own
- * socket and give it to ap_process_connection().
- *
- * Since h2_task instances are executed in separate threads, we may have
- * different lifetimes than our h2_stream or h2_session instances. Basically,
- * we would like to be as standalone as possible.
- *
- * Finally, to keep certain connection level filters, such as ourselves and
- * especially mod_ssl ones, from messing with our data, we need a filter
- * of our own to disable those.
- */
-
-struct h2_bucket_beam;
-struct h2_conn;
-struct h2_mplx;
-struct h2_task;
-struct h2_request;
-struct h2_response_parser;
-struct h2_stream;
-struct h2_worker;
-
-typedef struct h2_task h2_task;
-
-struct h2_task {
-    const char *id;
-    int stream_id;
-    conn_rec *c;
-    apr_pool_t *pool;
-    
-    const struct h2_request *request;
-    apr_interval_time_t timeout;
-    int rst_error;                   /* h2 related stream abort error */
-    
-    struct {
-        struct h2_bucket_beam *beam;
-        unsigned int eos : 1;
-        apr_bucket_brigade *bb;
-        apr_bucket_brigade *bbchunk;
-        apr_off_t chunked_total;
-    } input;
-    struct {
-        struct h2_bucket_beam *beam;
-        unsigned int opened : 1;
-        unsigned int sent_response : 1;
-        unsigned int copy_files : 1;
-        unsigned int buffered : 1;
-        struct h2_response_parser *rparser;
-        apr_bucket_brigade *bb;
-        apr_size_t max_buffer;
-    } output;
-    
-    struct h2_mplx *mplx;
-    
-    unsigned int filters_set    : 1;
-    unsigned int worker_started : 1; /* h2_worker started processing */
-    unsigned int redo : 1;           /* was throttled, should be restarted later */
-    
-    int worker_done;                 /* h2_worker finished */
-    int done_done;                   /* task_done has been handled */
-    
-    apr_time_t started_at;           /* when processing started */
-    apr_time_t done_at;              /* when processing was done */
-    apr_bucket *eor;
-};
-
-h2_task *h2_task_create(conn_rec *secondary, int stream_id,
-                        const h2_request *req, struct h2_mplx *m, 
-                        struct h2_bucket_beam *input, 
-                        apr_interval_time_t timeout,
-                        apr_size_t output_max_mem);
-
-void h2_task_destroy(h2_task *task);
-
-apr_status_t h2_task_do(h2_task *task, apr_thread_t *thread, int worker_id);
-
-void h2_task_redo(h2_task *task);
-int h2_task_can_redo(h2_task *task);
-int h2_task_has_started(h2_task *task);
-
-/**
- * Reset the task with the given error code, resets all input/output.
- */
-void h2_task_rst(h2_task *task, int error);
-
-void h2_task_register_hooks(void);
-/*
- * One time, post config initialization.
- */
-apr_status_t h2_task_init(apr_pool_t *pool, server_rec *s);
-
-extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_task_logio_add_bytes_in;
-extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_task_logio_add_bytes_out;
-
-#endif /* defined(__mod_h2__h2_task__) */
diff -r -N -u a/modules/http2/h2_util.c b/modules/http2/h2_util.c
--- a/modules/http2/h2_util.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_util.c	2024-10-30 21:40:01.059958617 +0100
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- 
+
 #include <assert.h>
 #include <apr_strings.h>
 #include <apr_thread_mutex.h>
@@ -22,11 +22,13 @@
 #include <httpd.h>
 #include <http_core.h>
 #include <http_log.h>
+#include <http_protocol.h>
 #include <http_request.h>
 
 #include <nghttp2/nghttp2.h>
 
 #include "h2.h"
+#include "h2_headers.h"
 #include "h2_util.h"
 
 /* h2_log2(n) iff n is a power of 2 */
@@ -55,7 +57,7 @@
     if (!(n & 0x80000000u)) {
         lz += 1;
     }
-    
+
     return 31 - lz;
 }
 
@@ -75,26 +77,6 @@
     return strlen(buffer);
 }
 
-size_t h2_util_header_print(char *buffer, size_t maxlen,
-                            const char *name, size_t namelen,
-                            const char *value, size_t valuelen)
-{
-    size_t offset = 0;
-    size_t i;
-    for (i = 0; i < namelen && offset < maxlen; ++i, ++offset) {
-        buffer[offset] = name[i];
-    }
-    for (i = 0; i < 2 && offset < maxlen; ++i, ++offset) {
-        buffer[offset] = ": "[i];
-    }
-    for (i = 0; i < valuelen && offset < maxlen; ++i, ++offset) {
-        buffer[offset] = value[i];
-    }
-    buffer[offset] = '\0';
-    return offset;
-}
-
-
 void h2_util_camel_case_header(char *s, size_t len)
 {
     size_t start = 1;
@@ -104,7 +86,7 @@
             if (s[i] >= 'a' && s[i] <= 'z') {
                 s[i] -= 'a' - 'A';
             }
-            
+
             start = 0;
         }
         else if (s[i] == '-') {
@@ -120,9 +102,9 @@
 static const unsigned int BASE64URL_UINT6[] = {
 /*   0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f        */
     N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  0 */
-    N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  1 */ 
+    N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  1 */
     N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, 62, N6, N6, /*  2 */
-    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, N6, N6, N6, N6, N6, N6, /*  3 */ 
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, N6, N6, N6, N6, N6, N6, /*  3 */
     N6, 0,  1,  2,  3,  4,  5,  6,   7,  8,  9, 10, 11, 12, 13, 14, /*  4 */
     15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, N6, N6, N6, N6, 63, /*  5 */
     N6, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /*  6 */
@@ -148,7 +130,7 @@
 
 #define BASE64URL_CHAR(x)    BASE64URL_CHARS[ (unsigned int)(x) & 0x3fu ]
 
-apr_size_t h2_util_base64url_decode(const char **decoded, const char *encoded, 
+apr_size_t h2_util_base64url_decode(const char **decoded, const char *encoded,
                                     apr_pool_t *pool)
 {
     const unsigned char *e = (const unsigned char *)encoded;
@@ -156,14 +138,14 @@
     unsigned char *d;
     unsigned int n;
     long len, mlen, remain, i;
-    
+
     while (*p && BASE64URL_UINT6[ *p ] != N6) {
         ++p;
     }
     len = (int)(p - e);
     mlen = (len/4)*4;
     *decoded = apr_pcalloc(pool, (apr_size_t)len + 1);
-    
+
     i = 0;
     d = (unsigned char*)*decoded;
     for (; i < mlen; i += 4) {
@@ -197,14 +179,14 @@
     return (apr_size_t)(mlen/4*3 + remain);
 }
 
-const char *h2_util_base64url_encode(const char *data, 
+const char *h2_util_base64url_encode(const char *data,
                                      apr_size_t dlen, apr_pool_t *pool)
 {
     int i, len = (int)dlen;
     apr_size_t slen = ((dlen+2)/3)*4 + 1; /* 0 terminated */
     const unsigned char *udata = (const unsigned char*)data;
     unsigned char *enc, *p = apr_pcalloc(pool, slen);
-    
+
     enc = p;
     for (i = 0; i < len-2; i+= 3) {
         *p++ = BASE64URL_CHAR( (udata[i]   >> 2) );
@@ -212,7 +194,7 @@
         *p++ = BASE64URL_CHAR( (udata[i+1] << 2) + (udata[i+2] >> 6) );
         *p++ = BASE64URL_CHAR( (udata[i+2]) );
     }
-    
+
     if (i < len) {
         *p++ = BASE64URL_CHAR( (udata[i] >> 2) );
         if (i == (len - 1)) {
@@ -248,7 +230,7 @@
     return ih;
 }
 
-size_t h2_ihash_count(h2_ihash_t *ih)
+unsigned int h2_ihash_count(h2_ihash_t *ih)
 {
     return apr_hash_count(ih->hash);
 }
@@ -268,7 +250,7 @@
     void *ctx;
 } iter_ctx;
 
-static int ihash_iter(void *ctx, const void *key, apr_ssize_t klen, 
+static int ihash_iter(void *ctx, const void *key, apr_ssize_t klen,
                      const void *val)
 {
     iter_ctx *ictx = ctx;
@@ -326,7 +308,7 @@
 {
     collect_ctx ctx;
     size_t i;
-    
+
     ctx.ih = ih;
     ctx.buffer = buffer;
     ctx.max = max;
@@ -344,19 +326,17 @@
 
 static void iq_grow(h2_iqueue *q, int nlen);
 static void iq_swap(h2_iqueue *q, int i, int j);
-static int iq_bubble_up(h2_iqueue *q, int i, int top, 
+static int iq_bubble_up(h2_iqueue *q, int i, int top,
                         h2_iq_cmp *cmp, void *ctx);
-static int iq_bubble_down(h2_iqueue *q, int i, int bottom, 
+static int iq_bubble_down(h2_iqueue *q, int i, int bottom,
                           h2_iq_cmp *cmp, void *ctx);
 
 h2_iqueue *h2_iq_create(apr_pool_t *pool, int capacity)
 {
     h2_iqueue *q = apr_pcalloc(pool, sizeof(h2_iqueue));
-    if (q) {
-        q->pool = pool;
-        iq_grow(q, capacity);
-        q->nelts = 0;
-    }
+    q->pool = pool;
+    iq_grow(q, capacity);
+    q->nelts = 0;
     return q;
 }
 
@@ -374,7 +354,7 @@
 int h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx)
 {
     int i;
-    
+
     if (h2_iq_contains(q, sid)) {
         return 0;
     }
@@ -384,7 +364,7 @@
     i = (q->head + q->nelts) % q->nalloc;
     q->elts[i] = sid;
     ++q->nelts;
-    
+
     if (cmp) {
         /* bubble it to the front of the queue */
         iq_bubble_up(q, i, q->head, cmp, ctx);
@@ -405,7 +385,7 @@
             break;
         }
     }
-    
+
     if (i < q->nelts) {
         ++i;
         for (; i < q->nelts; ++i) {
@@ -430,18 +410,18 @@
      */
     if (q->nelts > 0) {
         int i, ni, prev, last;
-        
+
         /* Start at the end of the queue and create a tail of sorted
          * entries. Make that tail one element longer in each iteration.
          */
         last = i = (q->head + q->nelts - 1) % q->nalloc;
         while (i != q->head) {
             prev = (q->nalloc + i - 1) % q->nalloc;
-            
+
             ni = iq_bubble_up(q, i, prev, cmp, ctx);
             if (ni == prev) {
                 /* i bubbled one up, bubble the new i down, which
-                 * keeps all tasks below i sorted. */
+                 * keeps all ints below i sorted. */
                 iq_bubble_down(q, i, last, cmp, ctx);
             }
             i = prev;
@@ -453,21 +433,21 @@
 int h2_iq_shift(h2_iqueue *q)
 {
     int sid;
-    
+
     if (q->nelts <= 0) {
         return 0;
     }
-    
+
     sid = q->elts[q->head];
     q->head = (q->head + 1) % q->nalloc;
     q->nelts--;
-    
+
     return sid;
 }
 
 size_t h2_iq_mshift(h2_iqueue *q, int *pint, size_t max)
 {
-    int i;
+    size_t i;
     for (i = 0; i < max; ++i) {
         pint[i] = h2_iq_shift(q);
         if (pint[i] == 0) {
@@ -483,7 +463,7 @@
         int *nq = apr_pcalloc(q->pool, sizeof(int) * nlen);
         if (q->nelts > 0) {
             int l = ((q->head + q->nelts) % q->nalloc) - q->head;
-            
+
             memmove(nq, q->elts + q->head, sizeof(int) * l);
             if (l < q->nelts) {
                 /* elts wrapped, append elts in [0, remain] to nq */
@@ -504,11 +484,11 @@
     q->elts[j] = x;
 }
 
-static int iq_bubble_up(h2_iqueue *q, int i, int top, 
-                        h2_iq_cmp *cmp, void *ctx) 
+static int iq_bubble_up(h2_iqueue *q, int i, int top,
+                        h2_iq_cmp *cmp, void *ctx)
 {
     int prev;
-    while (((prev = (q->nalloc + i - 1) % q->nalloc), i != top) 
+    while (((prev = (q->nalloc + i - 1) % q->nalloc), i != top)
            && (*cmp)(q->elts[i], q->elts[prev], ctx) < 0) {
         iq_swap(q, prev, i);
         i = prev;
@@ -516,11 +496,11 @@
     return i;
 }
 
-static int iq_bubble_down(h2_iqueue *q, int i, int bottom, 
+static int iq_bubble_down(h2_iqueue *q, int i, int bottom,
                           h2_iq_cmp *cmp, void *ctx)
 {
     int next;
-    while (((next = (q->nalloc + i + 1) % q->nalloc), i != bottom) 
+    while (((next = (q->nalloc + i + 1) % q->nalloc), i != bottom)
            && (*cmp)(q->elts[i], q->elts[next], ctx) > 0) {
         iq_swap(q, next, i);
         i = next;
@@ -545,9 +525,10 @@
 
 struct h2_fifo {
     void **elems;
-    int nelems;
+    int capacity;
     int set;
-    int head;
+    int in;
+    int out;
     int count;
     int aborted;
     apr_thread_mutex_t *lock;
@@ -555,12 +536,7 @@
     apr_thread_cond_t  *not_full;
 };
 
-static int nth_index(h2_fifo *fifo, int n) 
-{
-    return (fifo->head + n) % fifo->nelems;
-}
-
-static apr_status_t fifo_destroy(void *data) 
+static apr_status_t fifo_destroy(void *data)
 {
     h2_fifo *fifo = data;
 
@@ -574,21 +550,21 @@
 static int index_of(h2_fifo *fifo, void *elem)
 {
     int i;
-    
-    for (i = 0; i < fifo->count; ++i) {
-        if (elem == fifo->elems[nth_index(fifo, i)]) {
+
+    for (i = fifo->out; i != fifo->in; i = (i + 1) % fifo->capacity) {
+        if (elem == fifo->elems[i]) {
             return i;
         }
     }
     return -1;
 }
 
-static apr_status_t create_int(h2_fifo **pfifo, apr_pool_t *pool, 
+static apr_status_t create_int(h2_fifo **pfifo, apr_pool_t *pool,
                                int capacity, int as_set)
 {
     apr_status_t rv;
     h2_fifo *fifo;
-    
+
     fifo = apr_pcalloc(pool, sizeof(*fifo));
     if (fifo == NULL) {
         return APR_ENOMEM;
@@ -614,9 +590,9 @@
     if (fifo->elems == NULL) {
         return APR_ENOMEM;
     }
-    fifo->nelems = capacity;
+    fifo->capacity = capacity;
     fifo->set = as_set;
-    
+
     *pfifo = fifo;
     apr_pool_cleanup_register(pool, fifo, fifo_destroy, apr_pool_cleanup_null);
 
@@ -647,7 +623,12 @@
 
 int h2_fifo_count(h2_fifo *fifo)
 {
-    return fifo->count;
+    int n;
+
+    apr_thread_mutex_lock(fifo->lock);
+    n = fifo->count;
+    apr_thread_mutex_unlock(fifo->lock);
+    return n;
 }
 
 static apr_status_t check_not_empty(h2_fifo *fifo, int block)
@@ -674,9 +655,9 @@
         /* set mode, elem already member */
         return APR_EEXIST;
     }
-    else if (fifo->count == fifo->nelems) {
+    else if (fifo->count == fifo->capacity) {
         if (block) {
-            while (fifo->count == fifo->nelems) {
+            while (fifo->count == fifo->capacity) {
                 if (fifo->aborted) {
                     return APR_EOF;
                 }
@@ -687,12 +668,14 @@
             return APR_EAGAIN;
         }
     }
-    
-    ap_assert(fifo->count < fifo->nelems);
-    fifo->elems[nth_index(fifo, fifo->count)] = elem;
+
+    fifo->elems[fifo->in++] = elem;
+    if (fifo->in >= fifo->capacity) {
+        fifo->in -= fifo->capacity;
+    }
     ++fifo->count;
     if (fifo->count == 1) {
-        apr_thread_cond_broadcast(fifo->not_empty);
+        apr_thread_cond_signal(fifo->not_empty);
     }
     return APR_SUCCESS;
 }
@@ -700,7 +683,7 @@
 static apr_status_t fifo_push(h2_fifo *fifo, void *elem, int block)
 {
     apr_status_t rv;
-    
+
     if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
         rv = fifo_push_int(fifo, elem, block);
         apr_thread_mutex_unlock(fifo->lock);
@@ -721,18 +704,20 @@
 static apr_status_t pull_head(h2_fifo *fifo, void **pelem, int block)
 {
     apr_status_t rv;
-    
+    int was_full;
+
     if ((rv = check_not_empty(fifo, block)) != APR_SUCCESS) {
         *pelem = NULL;
         return rv;
     }
-    *pelem = fifo->elems[fifo->head];
+    *pelem = fifo->elems[fifo->out++];
+    if (fifo->out >= fifo->capacity) {
+        fifo->out -= fifo->capacity;
+    }
+    was_full = (fifo->count == fifo->capacity);
     --fifo->count;
-    if (fifo->count > 0) {
-        fifo->head = nth_index(fifo, 1);
-        if (fifo->count+1 == fifo->nelems) {
-            apr_thread_cond_broadcast(fifo->not_full);
-        }
+    if (was_full) {
+        apr_thread_cond_broadcast(fifo->not_full);
     }
     return APR_SUCCESS;
 }
@@ -740,7 +725,7 @@
 static apr_status_t fifo_pull(h2_fifo *fifo, void **pelem, int block)
 {
     apr_status_t rv;
-    
+
     if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
         rv = pull_head(fifo, pelem, block);
         apr_thread_mutex_unlock(fifo->lock);
@@ -762,11 +747,11 @@
 {
     apr_status_t rv;
     void *elem;
-    
+
     if (fifo->aborted) {
         return APR_EOF;
     }
-    
+
     if (APR_SUCCESS == (rv = apr_thread_mutex_lock(fifo->lock))) {
         if (APR_SUCCESS == (rv = pull_head(fifo, &elem, block))) {
             switch (fn(elem, ctx)) {
@@ -795,28 +780,58 @@
 apr_status_t h2_fifo_remove(h2_fifo *fifo, void *elem)
 {
     apr_status_t rv;
-    
+
     if (fifo->aborted) {
         return APR_EOF;
     }
 
     if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
-        int i, rc;
-        void *e;
-        
-        rc = 0;
-        for (i = 0; i < fifo->count; ++i) {
-            e = fifo->elems[nth_index(fifo, i)];
-            if (e == elem) {
-                ++rc;
-            }
-            else if (rc) {
-                fifo->elems[nth_index(fifo, i-rc)] = e;
-            }
-        }
-        if (rc) {
-            fifo->count -= rc;
-            if (fifo->count + rc == fifo->nelems) {
+        int i, last_count = fifo->count;
+
+        for (i = fifo->out; i != fifo->in; i = (i + 1) % fifo->capacity) {
+            if (fifo->elems[i] == elem) {
+                --fifo->count;
+                if (fifo->count == 0) {
+                    fifo->out = fifo->in = 0;
+                }
+                else if (i == fifo->out) {
+                    /* first element */
+                    ++fifo->out;
+                    if (fifo->out >= fifo->capacity) {
+                        fifo->out -= fifo->capacity;
+                    }
+                }
+                else if (((i + 1) % fifo->capacity) == fifo->in) {
+                    /* last element */
+                    --fifo->in;
+                    if (fifo->in < 0) {
+                        fifo->in += fifo->capacity;
+                    }
+                }
+                else if (i > fifo->out) {
+                    /* between out and in/capacity, move elements below up */
+                    memmove(&fifo->elems[fifo->out+1], &fifo->elems[fifo->out],
+                            (i - fifo->out) * sizeof(void*));
+                    ++fifo->out;
+                    if (fifo->out >= fifo->capacity) {
+                        fifo->out -= fifo->capacity;
+                    }
+                }
+                else {
+                    /* we wrapped around, move elements above down */
+                    AP_DEBUG_ASSERT((fifo->in - i - 1) > 0);
+                    AP_DEBUG_ASSERT((fifo->in - i - 1) < fifo->capacity);
+                    memmove(&fifo->elems[i], &fifo->elems[i + 1],
+                            (fifo->in - i - 1) * sizeof(void*));
+                    --fifo->in;
+                    if (fifo->in < 0) {
+                        fifo->in += fifo->capacity;
+                    }
+                }
+            }
+        }
+        if (fifo->count != last_count) {
+            if (last_count == fifo->capacity) {
                 apr_thread_cond_broadcast(fifo->not_full);
             }
             rv = APR_SUCCESS;
@@ -824,7 +839,7 @@
         else {
             rv = APR_EAGAIN;
         }
-        
+
         apr_thread_mutex_unlock(fifo->lock);
     }
     return rv;
@@ -836,7 +851,7 @@
 
 struct h2_ififo {
     int *elems;
-    int nelems;
+    int capacity;
     int set;
     int head;
     int count;
@@ -846,12 +861,12 @@
     apr_thread_cond_t  *not_full;
 };
 
-static int inth_index(h2_ififo *fifo, int n) 
+static int inth_index(h2_ififo *fifo, int n)
 {
-    return (fifo->head + n) % fifo->nelems;
+    return (fifo->head + n) % fifo->capacity;
 }
 
-static apr_status_t ififo_destroy(void *data) 
+static apr_status_t ififo_destroy(void *data)
 {
     h2_ififo *fifo = data;
 
@@ -865,7 +880,7 @@
 static int iindex_of(h2_ififo *fifo, int id)
 {
     int i;
-    
+
     for (i = 0; i < fifo->count; ++i) {
         if (id == fifo->elems[inth_index(fifo, i)]) {
             return i;
@@ -874,12 +889,12 @@
     return -1;
 }
 
-static apr_status_t icreate_int(h2_ififo **pfifo, apr_pool_t *pool, 
+static apr_status_t icreate_int(h2_ififo **pfifo, apr_pool_t *pool,
                                 int capacity, int as_set)
 {
     apr_status_t rv;
     h2_ififo *fifo;
-    
+
     fifo = apr_pcalloc(pool, sizeof(*fifo));
     if (fifo == NULL) {
         return APR_ENOMEM;
@@ -905,9 +920,9 @@
     if (fifo->elems == NULL) {
         return APR_ENOMEM;
     }
-    fifo->nelems = capacity;
+    fifo->capacity = capacity;
     fifo->set = as_set;
-    
+
     *pfifo = fifo;
     apr_pool_cleanup_register(pool, fifo, ififo_destroy, apr_pool_cleanup_null);
 
@@ -965,9 +980,9 @@
         /* set mode, elem already member */
         return APR_EEXIST;
     }
-    else if (fifo->count == fifo->nelems) {
+    else if (fifo->count == fifo->capacity) {
         if (block) {
-            while (fifo->count == fifo->nelems) {
+            while (fifo->count == fifo->capacity) {
                 if (fifo->aborted) {
                     return APR_EOF;
                 }
@@ -978,8 +993,8 @@
             return APR_EAGAIN;
         }
     }
-    
-    ap_assert(fifo->count < fifo->nelems);
+
+    ap_assert(fifo->count < fifo->capacity);
     fifo->elems[inth_index(fifo, fifo->count)] = id;
     ++fifo->count;
     if (fifo->count == 1) {
@@ -991,7 +1006,7 @@
 static apr_status_t ififo_push(h2_ififo *fifo, int id, int block)
 {
     apr_status_t rv;
-    
+
     if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
         rv = ififo_push_int(fifo, id, block);
         apr_thread_mutex_unlock(fifo->lock);
@@ -1012,7 +1027,7 @@
 static apr_status_t ipull_head(h2_ififo *fifo, int *pi, int block)
 {
     apr_status_t rv;
-    
+
     if ((rv = icheck_not_empty(fifo, block)) != APR_SUCCESS) {
         *pi = 0;
         return rv;
@@ -1021,7 +1036,7 @@
     --fifo->count;
     if (fifo->count > 0) {
         fifo->head = inth_index(fifo, 1);
-        if (fifo->count+1 == fifo->nelems) {
+        if (fifo->count+1 == fifo->capacity) {
             apr_thread_cond_broadcast(fifo->not_full);
         }
     }
@@ -1031,7 +1046,7 @@
 static apr_status_t ififo_pull(h2_ififo *fifo, int *pi, int block)
 {
     apr_status_t rv;
-    
+
     if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
         rv = ipull_head(fifo, pi, block);
         apr_thread_mutex_unlock(fifo->lock);
@@ -1053,7 +1068,7 @@
 {
     apr_status_t rv;
     int id;
-    
+
     if (APR_SUCCESS == (rv = apr_thread_mutex_lock(fifo->lock))) {
         if (APR_SUCCESS == (rv = ipull_head(fifo, &id, block))) {
             switch (fn(id, ctx)) {
@@ -1082,7 +1097,7 @@
 static apr_status_t ififo_remove(h2_ififo *fifo, int id)
 {
     int rc, i;
-    
+
     if (fifo->aborted) {
         return APR_EOF;
     }
@@ -1101,7 +1116,7 @@
         return APR_EAGAIN;
     }
     fifo->count -= rc;
-    if (fifo->count + rc == fifo->nelems) {
+    if (fifo->count + rc == fifo->capacity) {
         apr_thread_cond_broadcast(fifo->not_full);
     }
     return APR_SUCCESS;
@@ -1110,7 +1125,7 @@
 apr_status_t h2_ififo_remove(h2_ififo *fifo, int id)
 {
     apr_status_t rv;
-    
+
     if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
         rv = ififo_remove(fifo, id);
         apr_thread_mutex_unlock(fifo->lock);
@@ -1121,7 +1136,7 @@
 /*******************************************************************************
  * h2_util for apt_table_t
  ******************************************************************************/
- 
+
 typedef struct {
     apr_size_t bytes;
     apr_size_t pair_extra;
@@ -1143,7 +1158,7 @@
 apr_size_t h2_util_table_bytes(apr_table_t *t, apr_size_t pair_extra)
 {
     table_bytes_ctx ctx;
-    
+
     ctx.bytes = 0;
     ctx.pair_extra = pair_extra;
     apr_table_do(count_bytes, &ctx, t, NULL);
@@ -1155,287 +1170,108 @@
  * h2_util for bucket brigades
  ******************************************************************************/
 
-static apr_status_t last_not_included(apr_bucket_brigade *bb, 
-                                      apr_off_t maxlen, 
-                                      int same_alloc,
-                                      apr_size_t *pfile_buckets_allowed,
-                                      apr_bucket **pend)
+static void fit_bucket_into(apr_bucket *b, apr_off_t *plen)
 {
-    apr_bucket *b;
-    apr_status_t status = APR_SUCCESS;
-    int files_allowed = pfile_buckets_allowed? (int)*pfile_buckets_allowed : 0;
-    
-    if (maxlen >= 0) {
-        /* Find the bucket, up to which we reach maxlen/mem bytes */
-        for (b = APR_BRIGADE_FIRST(bb); 
-             (b != APR_BRIGADE_SENTINEL(bb));
-             b = APR_BUCKET_NEXT(b)) {
-            
-            if (APR_BUCKET_IS_METADATA(b)) {
-                /* included */
-            }
-            else {
-                if (b->length == ((apr_size_t)-1)) {
-                    const char *ign;
-                    apr_size_t ilen;
-                    status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
-                    if (status != APR_SUCCESS) {
-                        return status;
-                    }
-                }
-                
-                if (maxlen == 0 && b->length > 0) {
-                    *pend = b;
-                    return status;
-                }
-                
-                if (same_alloc && APR_BUCKET_IS_FILE(b)) {
-                    /* we like it move it, always */
-                }
-                else if (files_allowed > 0 && APR_BUCKET_IS_FILE(b)) {
-                    /* this has no memory footprint really unless
-                     * it is read, disregard it in length count,
-                     * unless we do not move the file buckets */
-                    --files_allowed;
-                }
-                else if (maxlen < (apr_off_t)b->length) {
-                    apr_bucket_split(b, (apr_size_t)maxlen);
-                    maxlen = 0;
-                }
-                else {
-                    maxlen -= b->length;
-                }
-            }
-        }
+    /* signed apr_off_t is at least as large as unsigned apr_size_t.
+     * Problems may arise when they are both the same size. Then
+     * the bucket length *may* be larger than a value we can hold
+     * in apr_off_t. Before casting b->length to apr_off_t we must
+     * check the limitations.
+     * After we resized the bucket, it is safe to cast and substract.
+     */
+    if ((sizeof(apr_off_t) == sizeof(apr_int64_t)
+         && b->length > APR_INT64_MAX)
+       || (sizeof(apr_off_t) == sizeof(apr_int32_t)
+           && b->length > APR_INT32_MAX)
+       || *plen < (apr_off_t)b->length) {
+        /* bucket is longer the *plen */
+        apr_bucket_split(b, *plen);
     }
-    *pend = APR_BRIGADE_SENTINEL(bb);
-    return status;
+    *plen -= (apr_off_t)b->length;
 }
 
-apr_status_t h2_brigade_concat_length(apr_bucket_brigade *dest, 
+apr_status_t h2_brigade_concat_length(apr_bucket_brigade *dest,
                                       apr_bucket_brigade *src,
                                       apr_off_t length)
 {
     apr_bucket *b;
     apr_off_t remain = length;
     apr_status_t status = APR_SUCCESS;
-    
+
     while (!APR_BRIGADE_EMPTY(src)) {
-        b = APR_BRIGADE_FIRST(src); 
-        
+        b = APR_BRIGADE_FIRST(src);
+
         if (APR_BUCKET_IS_METADATA(b)) {
             APR_BUCKET_REMOVE(b);
             APR_BRIGADE_INSERT_TAIL(dest, b);
         }
         else {
-            if (remain == b->length) {
-                /* fall through */
-            }
-            else if (remain <= 0) {
+            if (remain <= 0) {
                 return status;
             }
-            else {
-                if (b->length == ((apr_size_t)-1)) {
-                    const char *ign;
-                    apr_size_t ilen;
-                    status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
-                    if (status != APR_SUCCESS) {
-                        return status;
-                    }
-                }
-            
-                if (remain < b->length) {
-                    apr_bucket_split(b, remain);
+            if (b->length == ((apr_size_t)-1)) {
+                const char *ign;
+                apr_size_t ilen;
+                status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
+                if (status != APR_SUCCESS) {
+                    return status;
                 }
             }
+            fit_bucket_into(b, &remain);
             APR_BUCKET_REMOVE(b);
             APR_BRIGADE_INSERT_TAIL(dest, b);
-            remain -= b->length;
         }
     }
     return status;
 }
 
-apr_status_t h2_brigade_copy_length(apr_bucket_brigade *dest, 
+apr_status_t h2_brigade_copy_length(apr_bucket_brigade *dest,
                                     apr_bucket_brigade *src,
                                     apr_off_t length)
 {
     apr_bucket *b, *next;
     apr_off_t remain = length;
     apr_status_t status = APR_SUCCESS;
-    
-    for (b = APR_BRIGADE_FIRST(src); 
+
+    for (b = APR_BRIGADE_FIRST(src);
          b != APR_BRIGADE_SENTINEL(src);
          b = next) {
         next = APR_BUCKET_NEXT(b);
-        
+
         if (APR_BUCKET_IS_METADATA(b)) {
             /* fall through */
         }
         else {
-            if (remain == b->length) {
-                /* fall through */
-            }
-            else if (remain <= 0) {
+            if (remain <= 0) {
                 return status;
             }
-            else {
-                if (b->length == ((apr_size_t)-1)) {
-                    const char *ign;
-                    apr_size_t ilen;
-                    status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
-                    if (status != APR_SUCCESS) {
-                        return status;
-                    }
-                }
-            
-                if (remain < b->length) {
-                    apr_bucket_split(b, remain);
+            if (b->length == ((apr_size_t)-1)) {
+                const char *ign;
+                apr_size_t ilen;
+                status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
+                if (status != APR_SUCCESS) {
+                    return status;
                 }
             }
+            fit_bucket_into(b, &remain);
         }
         status = apr_bucket_copy(b, &b);
         if (status != APR_SUCCESS) {
             return status;
         }
         APR_BRIGADE_INSERT_TAIL(dest, b);
-        remain -= b->length;
     }
     return status;
 }
 
-int h2_util_has_eos(apr_bucket_brigade *bb, apr_off_t len)
-{
-    apr_bucket *b, *end;
-    
-    apr_status_t status = last_not_included(bb, len, 0, 0, &end);
-    if (status != APR_SUCCESS) {
-        return status;
-    }
-    
-    for (b = APR_BRIGADE_FIRST(bb);
-         b != APR_BRIGADE_SENTINEL(bb) && b != end;
-         b = APR_BUCKET_NEXT(b))
-    {
-        if (APR_BUCKET_IS_EOS(b)) {
-            return 1;
-        }
-    }
-    return 0;
-}
-
-apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb, 
-                              apr_off_t *plen, int *peos)
-{
-    apr_status_t status;
-    apr_off_t blen = 0;
-
-    /* test read to determine available length */
-    status = apr_brigade_length(bb, 1, &blen);
-    if (status != APR_SUCCESS) {
-        return status;
-    }
-    else if (blen == 0) {
-        /* brigade without data, does it have an EOS bucket somewhere? */
-        *plen = 0;
-        *peos = h2_util_has_eos(bb, -1);
-    }
-    else {
-        /* data in the brigade, limit the length returned. Check for EOS
-         * bucket only if we indicate data. This is required since plen == 0
-         * means "the whole brigade" for h2_util_hash_eos()
-         */
-        if (blen < *plen || *plen < 0) {
-            *plen = blen;
-        }
-        *peos = h2_util_has_eos(bb, *plen);
-    }
-    return APR_SUCCESS;
-}
-
-apr_status_t h2_util_bb_readx(apr_bucket_brigade *bb, 
-                              h2_util_pass_cb *cb, void *ctx, 
-                              apr_off_t *plen, int *peos)
-{
-    apr_status_t status = APR_SUCCESS;
-    int consume = (cb != NULL);
-    apr_off_t written = 0;
-    apr_off_t avail = *plen;
-    apr_bucket *next, *b;
-    
-    /* Pass data in our brigade through the callback until the length
-     * is satisfied or we encounter an EOS.
-     */
-    *peos = 0;
-    for (b = APR_BRIGADE_FIRST(bb);
-         (status == APR_SUCCESS) && (b != APR_BRIGADE_SENTINEL(bb));
-         b = next) {
-        
-        if (APR_BUCKET_IS_METADATA(b)) {
-            if (APR_BUCKET_IS_EOS(b)) {
-                *peos = 1;
-            }
-            else {
-                /* ignore */
-            }
-        }
-        else if (avail <= 0) {
-            break;
-        } 
-        else {
-            const char *data = NULL;
-            apr_size_t data_len;
-            
-            if (b->length == ((apr_size_t)-1)) {
-                /* read to determine length */
-                status = apr_bucket_read(b, &data, &data_len, APR_NONBLOCK_READ);
-            }
-            else {
-                data_len = b->length;
-            }
-            
-            if (data_len > avail) {
-                apr_bucket_split(b, avail);
-                data_len = (apr_size_t)avail;
-            }
-            
-            if (consume) {
-                if (!data) {
-                    status = apr_bucket_read(b, &data, &data_len, 
-                                             APR_NONBLOCK_READ);
-                }
-                if (status == APR_SUCCESS) {
-                    status = cb(ctx, data, data_len);
-                }
-            }
-            else {
-                data_len = b->length;
-            }
-            avail -= data_len;
-            written += data_len;
-        }
-        
-        next = APR_BUCKET_NEXT(b);
-        if (consume) {
-            apr_bucket_delete(b);
-        }
-    }
-    
-    *plen = written;
-    if (status == APR_SUCCESS && !*peos && !*plen) {
-        return APR_EAGAIN;
-    }
-    return status;
-}
-
-apr_size_t h2_util_bucket_print(char *buffer, apr_size_t bmax, 
+apr_size_t h2_util_bucket_print(char *buffer, apr_size_t bmax,
                                 apr_bucket *b, const char *sep)
 {
     apr_size_t off = 0;
     if (sep && *sep) {
         off += apr_snprintf(buffer+off, bmax-off, "%s", sep);
     }
-    
+
     if (bmax <= off) {
         return off;
     }
@@ -1443,30 +1279,30 @@
         off += apr_snprintf(buffer+off, bmax-off, "%s", b->type->name);
     }
     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));
+        off += apr_snprintf(buffer+off, bmax-off, "%s[%ld]",
+                            b->type->name,
+                            (b->length == ((apr_size_t)-1)?
+                                   -1 : (long)b->length));
     }
     return off;
 }
 
-apr_size_t h2_util_bb_print(char *buffer, apr_size_t bmax, 
-                            const char *tag, const char *sep, 
+apr_size_t h2_util_bb_print(char *buffer, apr_size_t bmax,
+                            const char *tag, const char *sep,
                             apr_bucket_brigade *bb)
 {
     apr_size_t off = 0;
     const char *sp = "";
     apr_bucket *b;
-    
+
     if (bmax > 1) {
         if (bb) {
             memset(buffer, 0, bmax--);
             off += apr_snprintf(buffer+off, bmax-off, "%s(", tag);
-            for (b = APR_BRIGADE_FIRST(bb); 
+            for (b = APR_BRIGADE_FIRST(bb);
                  (bmax > off) && (b != APR_BRIGADE_SENTINEL(bb));
                  b = APR_BUCKET_NEXT(b)) {
-                
+
                 off += h2_util_bucket_print(buffer+off, bmax-off, b, sp);
                 sp = " ";
             }
@@ -1482,20 +1318,21 @@
 }
 
 apr_status_t h2_append_brigade(apr_bucket_brigade *to,
-                               apr_bucket_brigade *from, 
+                               apr_bucket_brigade *from,
                                apr_off_t *plen,
                                int *peos,
                                h2_bucket_gate *should_append)
 {
     apr_bucket *e;
-    apr_off_t len = 0, remain = *plen;
+    apr_off_t start, remain;
     apr_status_t rv;
 
     *peos = 0;
-    
+    start = remain = *plen;
+
     while (!APR_BRIGADE_EMPTY(from)) {
         e = APR_BRIGADE_FIRST(from);
-        
+
         if (!should_append(e)) {
             goto leave;
         }
@@ -1506,8 +1343,11 @@
                 continue;
             }
         }
-        else {        
-            if (remain > 0 && e->length == ((apr_size_t)-1)) {
+        else {
+            if (remain <= 0) {
+                goto leave;
+            }
+            if (e->length == ((apr_size_t)-1)) {
                 const char *ign;
                 apr_size_t ilen;
                 rv = apr_bucket_read(e, &ign, &ilen, APR_BLOCK_READ);
@@ -1515,22 +1355,13 @@
                     return rv;
                 }
             }
-            
-            if (remain < e->length) {
-                if (remain <= 0) {
-                    goto leave;
-                }
-                apr_bucket_split(e, (apr_size_t)remain);
-            }
+            fit_bucket_into(e, &remain);
         }
-        
         APR_BUCKET_REMOVE(e);
         APR_BRIGADE_INSERT_TAIL(to, e);
-        len += e->length;
-        remain -= e->length;
     }
 leave:
-    *plen = len;
+    *plen = start - remain;
     return APR_SUCCESS;
 }
 
@@ -1558,20 +1389,10 @@
 /*******************************************************************************
  * h2_ngheader
  ******************************************************************************/
- 
-int h2_util_ignore_header(const char *name) 
-{
-    /* never forward, ch. 8.1.2.2 */
-    return (H2_HD_MATCH_LIT_CS("connection", name)
-            || H2_HD_MATCH_LIT_CS("proxy-connection", name)
-            || H2_HD_MATCH_LIT_CS("upgrade", name)
-            || H2_HD_MATCH_LIT_CS("keep-alive", name)
-            || H2_HD_MATCH_LIT_CS("transfer-encoding", name));
-}
 
 static int count_header(void *ctx, const char *key, const char *value)
 {
-    if (!h2_util_ignore_header(key)) {
+    if (!h2_util_ignore_resp_header(key)) {
         (*((size_t*)ctx))++;
     }
     return 1;
@@ -1592,6 +1413,17 @@
     return (p && *p)? p : NULL;
 }
 
+static void strip_field_value_ws(nghttp2_nv *nv)
+{
+    while(nv->valuelen && (nv->value[0] == ' ' || nv->value[0] == '\t')) {
+        nv->value++; nv->valuelen--;
+    }
+    while(nv->valuelen && (nv->value[nv->valuelen-1] == ' '
+                           || nv->value[nv->valuelen-1] == '\t')) {
+        nv->valuelen--;
+    }
+}
+
 typedef struct ngh_ctx {
     apr_pool_t *p;
     int unsafe;
@@ -1607,14 +1439,14 @@
     if (!ctx->unsafe) {
         if ((p = inv_field_name_chr(key))) {
             ap_log_perror(APLOG_MARK, APLOG_TRACE1, APR_EINVAL, ctx->p,
-                          "h2_request: head field '%s: %s' has invalid char %s", 
+                          "h2_request: head field '%s: %s' has invalid char %s",
                           key, value, p);
             ctx->status = APR_EINVAL;
             return 0;
         }
         if ((p = inv_field_value_chr(value))) {
             ap_log_perror(APLOG_MARK, APLOG_TRACE1, APR_EINVAL, ctx->p,
-                          "h2_request: head field '%s: %s' has invalid char %s", 
+                          "h2_request: head field '%s: %s' has invalid char %s",
                           key, value, p);
             ctx->status = APR_EINVAL;
             return 0;
@@ -1624,69 +1456,100 @@
     nv->namelen = strlen(key);
     nv->value = (uint8_t*)value;
     nv->valuelen = strlen(value);
-    
+    strip_field_value_ws(nv);
+
     return 1;
 }
 
 static int add_table_header(void *ctx, const char *key, const char *value)
 {
-    if (!h2_util_ignore_header(key)) {
+    if (!h2_util_ignore_resp_header(key)) {
         add_header(ctx, key, value);
     }
     return 1;
 }
 
-static apr_status_t ngheader_create(h2_ngheader **ph, apr_pool_t *p, 
-                                    int unsafe, size_t key_count, 
+static apr_status_t ngheader_create(h2_ngheader **ph, apr_pool_t *p,
+                                    int unsafe, size_t key_count,
                                     const char *keys[], const char *values[],
                                     apr_table_t *headers)
 {
     ngh_ctx ctx;
     size_t n, i;
-    
+
     ctx.p = p;
     ctx.unsafe = unsafe;
-    
+
     n = key_count;
     apr_table_do(count_header, &n, headers, NULL);
-    
+
     *ph = ctx.ngh = apr_pcalloc(p, sizeof(h2_ngheader));
     if (!ctx.ngh) {
         return APR_ENOMEM;
     }
-    
-    ctx.ngh->nv =  apr_pcalloc(p, n * sizeof(nghttp2_nv));
+
+    ctx.ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv));
     if (!ctx.ngh->nv) {
         return APR_ENOMEM;
     }
-    
+
     ctx.status = APR_SUCCESS;
     for (i = 0; i < key_count; ++i) {
         if (!add_header(&ctx, keys[i], values[i])) {
             return ctx.status;
         }
     }
-    
+
     apr_table_do(add_table_header, &ctx, headers, NULL);
 
     return ctx.status;
 }
 
+#if AP_HAS_RESPONSE_BUCKETS
+
+static int is_unsafe(ap_bucket_response *h)
+{
+    const char *v = h->notes? apr_table_get(h->notes, H2_HDR_CONFORMANCE) : NULL;
+    return (v && !strcmp(v, H2_HDR_CONFORMANCE_UNSAFE));
+}
+
+apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
+                                    ap_bucket_headers *headers)
+{
+    return ngheader_create(ph, p, 0,
+                           0, NULL, NULL, headers->headers);
+}
+
+apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+                                    ap_bucket_response *response)
+{
+    const char *keys[] = {
+        ":status"
+    };
+    const char *values[] = {
+        apr_psprintf(p, "%d", response->status)
+    };
+    return ngheader_create(ph, p, is_unsafe(response),
+                           H2_ALEN(keys), keys, values, response->headers);
+}
+
+#else /* AP_HAS_RESPONSE_BUCKETS */
+
 static int is_unsafe(h2_headers *h)
 {
-    const char *v = apr_table_get(h->notes, H2_HDR_CONFORMANCE);
+    const char *v = h->notes? apr_table_get(h->notes, H2_HDR_CONFORMANCE) : NULL;
     return (v && !strcmp(v, H2_HDR_CONFORMANCE_UNSAFE));
 }
 
-apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p, 
+apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
                                     h2_headers *headers)
 {
-    return ngheader_create(ph, p, is_unsafe(headers), 
+    return ngheader_create(ph, p, is_unsafe(headers),
                            0, NULL, NULL, headers->headers);
 }
-                                     
+
 apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
-                                    h2_headers *headers) 
+                                    h2_headers *headers)
 {
     const char *keys[] = {
         ":status"
@@ -1694,27 +1557,29 @@
     const char *values[] = {
         apr_psprintf(p, "%d", headers->status)
     };
-    return ngheader_create(ph, p, is_unsafe(headers),  
+    return ngheader_create(ph, p, is_unsafe(headers),
                            H2_ALEN(keys), keys, values, headers->headers);
 }
 
-apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p, 
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+
+apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
                                     const struct h2_request *req)
 {
-    
+
     const char *keys[] = {
-        ":scheme", 
-        ":authority", 
-        ":path", 
-        ":method", 
+        ":scheme",
+        ":authority",
+        ":path",
+        ":method",
     };
     const char *values[] = {
         req->scheme,
-        req->authority, 
-        req->path, 
-        req->method, 
+        req->authority,
+        req->path,
+        req->method,
     };
-    
+
     ap_assert(req->scheme);
     ap_assert(req->authority);
     ap_assert(req->path);
@@ -1726,7 +1591,7 @@
 /*******************************************************************************
  * header HTTP/1 <-> HTTP/2 conversions
  ******************************************************************************/
- 
+
 
 typedef struct {
     const char *name;
@@ -1754,9 +1619,15 @@
     H2_DEF_LITERAL("max-forwards"),
     H2_DEF_LITERAL("cache-control"),
     H2_DEF_LITERAL("authorization"),
-    H2_DEF_LITERAL("content-length"),       
+    H2_DEF_LITERAL("content-length"),
     H2_DEF_LITERAL("proxy-authorization"),
-};    
+};
+static literal IgnoredResponseHeaders[] = {
+    H2_DEF_LITERAL("upgrade"),
+    H2_DEF_LITERAL("connection"),
+    H2_DEF_LITERAL("keep-alive"),
+    H2_DEF_LITERAL("transfer-encoding"),
+};
 static literal IgnoredResponseTrailers[] = {
     H2_DEF_LITERAL("age"),
     H2_DEF_LITERAL("date"),
@@ -1771,108 +1642,124 @@
     H2_DEF_LITERAL("proxy-authenticate"),
 };
 
-static int ignore_header(const literal *lits, size_t llen,
-                         const char *name, size_t nlen)
+static int contains_name(const literal *lits, size_t llen, nghttp2_nv *nv)
 {
     const literal *lit;
     size_t i;
-    
+
     for (i = 0; i < llen; ++i) {
         lit = &lits[i];
-        if (lit->len == nlen && !apr_strnatcasecmp(lit->name, name)) {
+        if (lit->len == nv->namelen
+            && !ap_cstr_casecmp(lit->name, (const char *)nv->name)) {
             return 1;
         }
     }
     return 0;
 }
 
-int h2_req_ignore_header(const char *name, size_t len)
+int h2_util_ignore_resp_header(const char *name)
+{
+    nghttp2_nv nv;
+
+    nv.name = (uint8_t*)name;
+    nv.namelen = strlen(name);
+    return contains_name(H2_LIT_ARGS(IgnoredResponseHeaders), &nv);
+}
+
+
+static int h2_req_ignore_header(nghttp2_nv *nv)
 {
-    return ignore_header(H2_LIT_ARGS(IgnoredRequestHeaders), name, len);
+    return contains_name(H2_LIT_ARGS(IgnoredRequestHeaders), nv);
 }
 
-int h2_req_ignore_trailer(const char *name, size_t len)
+int h2_ignore_req_trailer(const char *name, size_t len)
 {
-    return (h2_req_ignore_header(name, len) 
-            || ignore_header(H2_LIT_ARGS(IgnoredRequestTrailers), name, len));
+    nghttp2_nv nv;
+
+    nv.name = (uint8_t*)name;
+    nv.namelen = strlen(name);
+    return (h2_req_ignore_header(&nv)
+            || contains_name(H2_LIT_ARGS(IgnoredRequestTrailers), &nv));
 }
 
-int h2_res_ignore_trailer(const char *name, size_t len)
+int h2_ignore_resp_trailer(const char *name, size_t len)
 {
-    return ignore_header(H2_LIT_ARGS(IgnoredResponseTrailers), name, len);
+    nghttp2_nv nv;
+
+    nv.name = (uint8_t*)name;
+    nv.namelen = strlen(name);
+    return (contains_name(H2_LIT_ARGS(IgnoredResponseHeaders), &nv)
+            || contains_name(H2_LIT_ARGS(IgnoredResponseTrailers), &nv));
 }
 
-apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool, 
-                              const char *name, size_t nlen,
-                              const char *value, size_t vlen,
-                              size_t max_field_len, int *pwas_added)
+static apr_status_t req_add_header(apr_table_t *headers, apr_pool_t *pool,
+                                   nghttp2_nv *nv, size_t max_field_len,
+                                   int *pwas_added)
 {
     char *hname, *hvalue;
     const char *existing;
-    
+
     *pwas_added = 0;
-    if (h2_req_ignore_header(name, nlen)) {
+    strip_field_value_ws(nv);
+
+    if (h2_req_ignore_header(nv)) {
         return APR_SUCCESS;
     }
-    else if (H2_HD_MATCH_LIT("cookie", name, nlen)) {
+    else if (nv->namelen == sizeof("cookie")-1
+             && !ap_cstr_casecmp("cookie", (const char *)nv->name)) {
         existing = apr_table_get(headers, "cookie");
         if (existing) {
-            char *nval;
-            
             /* Cookie header come separately in HTTP/2, but need
              * to be merged by "; " (instead of default ", ")
              */
-            if (max_field_len && strlen(existing) + vlen + nlen + 4 > max_field_len) {
+            if (max_field_len
+                && strlen(existing) + nv->valuelen + nv->namelen + 4
+                   > max_field_len) {
                 /* "key: oldval, nval" is too long */
                 return APR_EINVAL;
             }
-            hvalue = apr_pstrndup(pool, value, vlen);
-            nval = apr_psprintf(pool, "%s; %s", existing, hvalue);
-            apr_table_setn(headers, "Cookie", nval);
+            hvalue = apr_pstrndup(pool, (const char*)nv->value, nv->valuelen);
+            apr_table_setn(headers, "Cookie",
+                           apr_psprintf(pool, "%s; %s", existing, hvalue));
             return APR_SUCCESS;
         }
     }
-    else if (H2_HD_MATCH_LIT("host", name, nlen)) {
+    else if (nv->namelen == sizeof("host")-1
+             && !ap_cstr_casecmp("host", (const char *)nv->name)) {
         if (apr_table_get(headers, "Host")) {
             return APR_SUCCESS; /* ignore duplicate */
         }
     }
-    
-    hname = apr_pstrndup(pool, name, nlen);
-    h2_util_camel_case_header(hname, nlen);
+
+    hname = apr_pstrndup(pool, (const char*)nv->name, nv->namelen);
+    h2_util_camel_case_header(hname, nv->namelen);
     existing = apr_table_get(headers, hname);
     if (max_field_len) {
-        if ((existing? strlen(existing)+2 : 0) + vlen + nlen + 2 > max_field_len) {
+        if ((existing? strlen(existing)+2 : 0) + nv->valuelen + nv->namelen + 2
+            > max_field_len) {
             /* "key: (oldval, )?nval" is too long */
             return APR_EINVAL;
         }
     }
     if (!existing) *pwas_added = 1;
-    hvalue = apr_pstrndup(pool, value, vlen);
+    hvalue = apr_pstrndup(pool, (const char*)nv->value, nv->valuelen);
     apr_table_mergen(headers, hname, hvalue);
-    
+
     return APR_SUCCESS;
 }
 
-/*******************************************************************************
- * h2 request handling
- ******************************************************************************/
+apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool,
+                              const char *name, size_t nlen,
+                              const char *value, size_t vlen,
+                              size_t max_field_len, int *pwas_added)
+{
+    nghttp2_nv nv;
 
-h2_request *h2_req_create(int id, apr_pool_t *pool, const char *method, 
-                          const char *scheme, const char *authority, 
-                          const char *path, apr_table_t *header, int serialize)
-{
-    h2_request *req = apr_pcalloc(pool, sizeof(h2_request));
-    
-    req->method         = method;
-    req->scheme         = scheme;
-    req->authority      = authority;
-    req->path           = path;
-    req->headers        = header? header : apr_table_make(pool, 10);
-    req->request_time   = apr_time_now();
-    req->serialize      = serialize;
-    
-    return req;
+    nv.name = (uint8_t*)name;
+    nv.namelen = nlen;
+    nv.value = (uint8_t*)value;
+    nv.valuelen = vlen;
+    return req_add_header(headers, pool, &nv, max_field_len, pwas_added);
 }
 
 /*******************************************************************************
@@ -1883,7 +1770,7 @@
 {
     char scratch[128];
     size_t s_len = sizeof(scratch)/sizeof(scratch[0]);
-    
+
     switch (frame->hd.type) {
         case NGHTTP2_DATA: {
             return apr_snprintf(buffer, maxlen,
@@ -1942,13 +1829,13 @@
                 memcpy(scratch, frame->goaway.opaque_data, len);
             scratch[len] = '\0';
             return apr_snprintf(buffer, maxlen, "GOAWAY[error=%d, reason='%s', "
-                                "last_stream=%d]", frame->goaway.error_code, 
+                                "last_stream=%d]", frame->goaway.error_code,
                                 scratch, frame->goaway.last_stream_id);
         }
         case NGHTTP2_WINDOW_UPDATE: {
             return apr_snprintf(buffer, maxlen,
                                 "WINDOW_UPDATE[stream=%d, incr=%d]",
-                                frame->hd.stream_id, 
+                                frame->hd.stream_id,
                                 frame->window_update.window_size_increment);
         }
         default:
@@ -1992,3 +1879,60 @@
     return policy;
 }
 
+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
+         * during wakeup, multiple threads could call the wakeup.
+         * So simply drain out from the input side of the pipe all
+         * the data.
+         */
+        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)
+{
+    char rb[512];
+    apr_size_t nr = sizeof(rb);
+
+    return apr_file_read(pipe, rb, &nr);
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+static int add_header_lengths(void *ctx, const char *name, const char *value)
+{
+    apr_size_t *plen = ctx;
+    *plen += strlen(name) + strlen(value);
+    return 1;
+}
+
+apr_size_t headers_length_estimate(ap_bucket_headers *hdrs)
+{
+    apr_size_t len = 0;
+    apr_table_do(add_header_lengths, &len, hdrs->headers, NULL);
+    return len;
+}
+
+apr_size_t response_length_estimate(ap_bucket_response *resp)
+{
+    apr_size_t len = 3 + 1 + 8 + (resp->reason? strlen(resp->reason) : 10);
+    apr_table_do(add_header_lengths, &len, resp->headers, NULL);
+    return len;
+}
+
+#endif /* AP_HAS_RESPONSE_BUCKETS */
diff -r -N -u a/modules/http2/h2_util.h b/modules/http2/h2_util.h
--- a/modules/http2/h2_util.h	2021-01-17 18:23:37.000000000 +0100
+++ b/modules/http2/h2_util.h	2024-10-30 21:40:01.059958617 +0100
@@ -18,6 +18,10 @@
 #define __mod_h2__h2_util__
 
 #include <nghttp2/nghttp2.h>
+#include <http_protocol.h>
+
+#include "h2.h"
+#include "h2_headers.h"
 
 /*******************************************************************************
  * some debugging/format helpers
@@ -28,10 +32,6 @@
 size_t h2_util_hex_dump(char *buffer, size_t maxlen,
                         const char *data, size_t datalen);
 
-size_t h2_util_header_print(char *buffer, size_t maxlen,
-                            const char *name, size_t namelen,
-                            const char *value, size_t valuelen);
-
 void h2_util_camel_case_header(char *s, size_t len);
 
 int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen);
@@ -49,7 +49,7 @@
  */
 h2_ihash_t *h2_ihash_create(apr_pool_t *pool, size_t offset_of_int);
 
-size_t h2_ihash_count(h2_ihash_t *ih);
+unsigned int h2_ihash_count(h2_ihash_t *ih);
 int h2_ihash_empty(h2_ihash_t *ih);
 void *h2_ihash_get(h2_ihash_t *ih, int id);
 
@@ -102,7 +102,7 @@
 h2_iqueue *h2_iq_create(apr_pool_t *pool, int capacity);
 
 /**
- * Return != 0 iff there are no tasks in the queue.
+ * Return != 0 iff there are no ints in the queue.
  * @param q the queue to check
  */
 int h2_iq_empty(h2_iqueue *q);
@@ -134,11 +134,10 @@
 int h2_iq_append(h2_iqueue *q, int sid);
 
 /**
- * Remove the stream id from the queue. Return != 0 iff task
- * was found in queue.
- * @param q the task queue
+ * Remove the int from the queue. Return != 0 iff it was found.
+ * @param q the queue
  * @param sid the stream id to remove
- * @return != 0 iff task was found in queue
+ * @return != 0 iff int was found in queue
  */
 int h2_iq_remove(h2_iqueue *q, int sid);
 
@@ -148,7 +147,7 @@
 void h2_iq_clear(h2_iqueue *q);
 
 /**
- * Sort the stream idqueue again. Call if the task ordering
+ * Sort the stream idqueue again. Call if the int ordering
  * has changed.
  *
  * @param q the queue to sort
@@ -169,7 +168,7 @@
 /**
  * Get the first max ids from the queue. All these ids will be removed.
  *
- * @param q the queue to get the first task from
+ * @param q the queue to get the first ids from
  * @param pint the int array to receive the values
  * @param max the maximum number of ids to shift
  * @return the actual number of ids shifted
@@ -343,9 +342,8 @@
 /*******************************************************************************
  * HTTP/2 header helpers
  ******************************************************************************/
-int h2_req_ignore_header(const char *name, size_t len);
-int h2_req_ignore_trailer(const char *name, size_t len);
-int h2_res_ignore_trailer(const char *name, size_t len);
+int h2_ignore_req_trailer(const char *name, size_t len);
+int h2_ignore_resp_trailer(const char *name, size_t len);
 
 /**
  * Set the push policy for the given request. Takes request headers into 
@@ -376,39 +374,28 @@
  * nghttp2 helpers
  ******************************************************************************/
 
-#define H2_HD_MATCH_LIT_CS(l, name)  \
-    ((strlen(name) == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
-
-#define H2_CREATE_NV_LIT_CS(nv, NAME, VALUE) nv->name = (uint8_t *)NAME;      \
-                                             nv->namelen = sizeof(NAME) - 1;  \
-                                             nv->value = (uint8_t *)VALUE;    \
-                                             nv->valuelen = strlen(VALUE)
-
-#define H2_CREATE_NV_CS_LIT(nv, NAME, VALUE) nv->name = (uint8_t *)NAME;      \
-                                             nv->namelen = strlen(NAME);      \
-                                             nv->value = (uint8_t *)VALUE;    \
-                                             nv->valuelen = sizeof(VALUE) - 1
-
-#define H2_CREATE_NV_CS_CS(nv, NAME, VALUE) nv->name = (uint8_t *)NAME;       \
-                                            nv->namelen = strlen(NAME);       \
-                                            nv->value = (uint8_t *)VALUE;     \
-                                            nv->valuelen = strlen(VALUE)
-
-int h2_util_ignore_header(const char *name);
-
-struct h2_headers;
+int h2_util_ignore_resp_header(const char *name);
 
 typedef struct h2_ngheader {
     nghttp2_nv *nv;
     apr_size_t nvlen;
 } h2_ngheader;
 
+#if AP_HAS_RESPONSE_BUCKETS
+apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
+                                     ap_bucket_headers *headers);
+apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+                                    ap_bucket_response *response);
+apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+                                    const struct h2_request *req);
+#else
 apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p, 
                                      struct h2_headers *headers); 
 apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p, 
                                     struct h2_headers *headers); 
 apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p, 
                                     const struct h2_request *req);
+#endif
 
 /**
  * Add a HTTP/2 header and return the table key if it really was added
@@ -420,15 +407,6 @@
                                size_t max_field_len, int *pwas_added);
 
 /*******************************************************************************
- * h2_request helpers
- ******************************************************************************/
-
-struct h2_request *h2_req_create(int id, apr_pool_t *pool, const char *method, 
-                                 const char *scheme, const char *authority, 
-                                 const char *path, apr_table_t *header,
-                                 int serialize);
-
-/*******************************************************************************
  * apr brigade helpers
  ******************************************************************************/
 
@@ -448,43 +426,10 @@
                                     apr_bucket_brigade *src,
                                     apr_off_t length);
                                 
-/**
- * Return != 0 iff there is a FLUSH or EOS bucket in the brigade.
- * @param bb the brigade to check on
- * @return != 0 iff brigade holds FLUSH or EOS bucket (or both)
- */
-int h2_util_has_eos(apr_bucket_brigade *bb, apr_off_t len);
-
-/**
- * Check how many bytes of the desired amount are available and if the
- * end of stream is reached by that amount.
- * @param bb the brigade to check
- * @param plen the desired length and, on return, the available length
- * @param on return, if eos has been reached
- */
-apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb, 
-                              apr_off_t *plen, int *peos);
-
-typedef apr_status_t h2_util_pass_cb(void *ctx, 
+typedef apr_status_t h2_util_pass_cb(void *ctx,
                                      const char *data, apr_off_t len);
 
 /**
- * Read at most *plen bytes from the brigade and pass them into the
- * given callback. If cb is NULL, just return the amount of data that
- * could have been read.
- * If an EOS was/would be encountered, set *peos != 0.
- * @param bb the brigade to read from
- * @param cb the callback to invoke for the read data
- * @param ctx optional data passed to callback
- * @param plen inout, as input gives the maximum number of bytes to read,
- *    on return specifies the actual/would be number of bytes
- * @param peos != 0 iff an EOS bucket was/would be encountered.
- */
-apr_status_t h2_util_bb_readx(apr_bucket_brigade *bb, 
-                              h2_util_pass_cb *cb, void *ctx, 
-                              apr_off_t *plen, int *peos);
-
-/**
  * Print a bucket's meta data (type and length) to the buffer.
  * @return number of characters printed
  */
@@ -509,14 +454,16 @@
  * @param bb the brigade to log
  */
 #define h2_util_bb_log(c, sid, level, tag, bb) \
-do { \
-    char buffer[4 * 1024]; \
-    const char *line = "(null)"; \
-    apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
-    len = h2_util_bb_print(buffer, bmax, (tag), "", (bb)); \
-    ap_log_cerror(APLOG_MARK, level, 0, (c), "bb_dump(%ld): %s", \
-        ((c)->master? (c)->master->id : (c)->id), (len? buffer : line)); \
-} while(0)
+if (APLOG_C_IS_LEVEL(c, level)) { \
+    do { \
+        char buffer[4 * 1024]; \
+        const char *line = "(null)"; \
+        apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
+        len = h2_util_bb_print(buffer, bmax, (tag), "", (bb)); \
+        ap_log_cerror(APLOG_MARK, level, 0, (c), "bb_dump(%ld): %s", \
+            ((c)->master? (c)->master->id : (c)->id), (len? buffer : line)); \
+    } while(0); \
+}
 
 
 typedef int h2_bucket_gate(apr_bucket *b);
@@ -544,4 +491,29 @@
  */
 apr_off_t h2_brigade_mem_size(apr_bucket_brigade *bb);
 
+/**
+ * Drain a pipe used for notification.
+ */
+void h2_util_drain_pipe(apr_file_t *pipe);
+
+/**
+ * Wait on data arriving on a pipe.
+ */
+apr_status_t h2_util_wait_on_pipe(apr_file_t *pipe);
+
+
+#if AP_HAS_RESPONSE_BUCKETS
+/**
+ * Give an estimate of the length of the header fields,
+ * without compression or other formatting decorations.
+ */
+apr_size_t headers_length_estimate(ap_bucket_headers *hdrs);
+
+/**
+ * Give an estimate of the length of the response meta data size,
+ * without compression or other formatting decorations.
+ */
+apr_size_t response_length_estimate(ap_bucket_response *resp);
+#endif /* AP_HAS_RESPONSE_BUCKETS */
+
 #endif /* defined(__mod_h2__h2_util__) */
diff -r -N -u a/modules/http2/h2_version.h b/modules/http2/h2_version.h
--- a/modules/http2/h2_version.h	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_version.h	2024-10-30 21:40:01.059958617 +0100
@@ -27,7 +27,7 @@
  * @macro
  * Version number of the http2 module as c string
  */
-#define MOD_HTTP2_VERSION "1.15.24"
+#define MOD_HTTP2_VERSION "2.0.22"
 
 /**
  * @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 0x010f18
+#define MOD_HTTP2_VERSION_NUM 0x020016
 
 
 #endif /* mod_h2_h2_version_h */
diff -r -N -u a/modules/http2/h2_workers.c b/modules/http2/h2_workers.c
--- a/modules/http2/h2_workers.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_workers.c	2024-10-30 21:40:01.059958617 +0100
@@ -15,304 +15,352 @@
  */
 
 #include <assert.h>
-#include <apr_atomic.h>
+#include <apr_ring.h>
 #include <apr_thread_mutex.h>
 #include <apr_thread_cond.h>
 
 #include <mpm_common.h>
 #include <httpd.h>
+#include <http_connection.h>
 #include <http_core.h>
 #include <http_log.h>
+#include <http_protocol.h>
 
 #include "h2.h"
 #include "h2_private.h"
 #include "h2_mplx.h"
-#include "h2_task.h"
+#include "h2_c2.h"
 #include "h2_workers.h"
 #include "h2_util.h"
 
+typedef enum {
+    PROD_IDLE,
+    PROD_ACTIVE,
+    PROD_JOINED,
+} prod_state_t;
+
+struct ap_conn_producer_t {
+    APR_RING_ENTRY(ap_conn_producer_t) link;
+    const char *name;
+    void *baton;
+    ap_conn_producer_next *fn_next;
+    ap_conn_producer_done *fn_done;
+    ap_conn_producer_shutdown *fn_shutdown;
+    volatile prod_state_t state;
+    volatile int conns_active;
+};
+
+
+typedef enum {
+    H2_SLOT_FREE,
+    H2_SLOT_RUN,
+    H2_SLOT_ZOMBIE,
+} h2_slot_state_t;
+
 typedef struct h2_slot h2_slot;
 struct h2_slot {
-    int id;
-    int sticks;
-    h2_slot *next;
+    APR_RING_ENTRY(h2_slot) link;
+    apr_uint32_t id;
+    apr_pool_t *pool;
+    h2_slot_state_t state;
+    volatile int should_shutdown;
+    volatile int is_idle;
     h2_workers *workers;
-    h2_task *task;
+    ap_conn_producer_t *prod;
     apr_thread_t *thread;
-    apr_thread_mutex_t *lock;
-    apr_thread_cond_t *not_idle;
-    volatile apr_uint32_t timed_out;
+    struct apr_thread_cond_t *more_work;
+    int activations;
 };
 
-static h2_slot *pop_slot(h2_slot *volatile *phead) 
-{
-    /* Atomically pop a slot from the list */
-    for (;;) {
-        h2_slot *first = *phead;
-        if (first == NULL) {
-            return NULL;
-        }
-        if (apr_atomic_casptr((void*)phead, first->next, first) == first) {
-            first->next = NULL;
-            return first;
-        }
-    }
-}
+struct h2_workers {
+    server_rec *s;
+    apr_pool_t *pool;
+
+    apr_uint32_t max_slots;
+    apr_uint32_t min_active;
+    volatile apr_time_t idle_limit;
+    volatile int aborted;
+    volatile int shutdown;
+    int dynamic;
+
+    volatile apr_uint32_t active_slots;
+    volatile apr_uint32_t idle_slots;
+
+    apr_threadattr_t *thread_attr;
+    h2_slot *slots;
+
+    APR_RING_HEAD(h2_slots_free, h2_slot) free;
+    APR_RING_HEAD(h2_slots_idle, h2_slot) idle;
+    APR_RING_HEAD(h2_slots_busy, h2_slot) busy;
+    APR_RING_HEAD(h2_slots_zombie, h2_slot) zombie;
+
+    APR_RING_HEAD(ap_conn_producer_active, ap_conn_producer_t) prod_active;
+    APR_RING_HEAD(ap_conn_producer_idle, ap_conn_producer_t) prod_idle;
+
+    struct apr_thread_mutex_t *lock;
+    struct apr_thread_cond_t *prod_done;
+    struct apr_thread_cond_t *all_done;
+};
 
-static void push_slot(h2_slot *volatile *phead, h2_slot *slot)
-{
-    /* Atomically push a slot to the list */
-    ap_assert(!slot->next);
-    for (;;) {
-        h2_slot *next = slot->next = *phead;
-        if (apr_atomic_casptr((void*)phead, slot, next) == next) {
-            return;
-        }
-    }
-}
 
 static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx);
-static void slot_done(h2_slot *slot);
 
-static apr_status_t activate_slot(h2_workers *workers, h2_slot *slot) 
+static apr_status_t activate_slot(h2_workers *workers)
 {
+    h2_slot *slot;
+    apr_pool_t *pool;
     apr_status_t rv;
-    
-    slot->workers = workers;
-    slot->task = NULL;
 
-    apr_thread_mutex_lock(workers->lock);
-    if (!slot->lock) {
-        rv = apr_thread_mutex_create(&slot->lock,
-                                         APR_THREAD_MUTEX_DEFAULT,
-                                         workers->pool);
-        if (rv != APR_SUCCESS) goto cleanup;
+    if (APR_RING_EMPTY(&workers->free, h2_slot, link)) {
+        return APR_EAGAIN;
     }
+    slot = APR_RING_FIRST(&workers->free);
+    ap_assert(slot->state == H2_SLOT_FREE);
+    APR_RING_REMOVE(slot, link);
 
-    if (!slot->not_idle) {
-        rv = apr_thread_cond_create(&slot->not_idle, workers->pool);
-        if (rv != APR_SUCCESS) goto cleanup;
-    }
-    
     ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
-                 "h2_workers: new thread for slot %d", slot->id); 
+                 "h2_workers: activate slot %d", slot->id);
 
-    /* thread will either immediately start work or add itself
-     * to the idle queue */
-    apr_atomic_inc32(&workers->worker_count);
-    slot->timed_out = 0;
-    rv = apr_thread_create(&slot->thread, workers->thread_attr,
-                               slot_run, slot, workers->pool);
-    if (rv != APR_SUCCESS) {
-        apr_atomic_dec32(&workers->worker_count);
-    }
+    slot->state = H2_SLOT_RUN;
+    slot->should_shutdown = 0;
+    slot->is_idle = 0;
+    slot->pool = NULL;
+    ++workers->active_slots;
+    rv = apr_pool_create(&pool, workers->pool);
+    if (APR_SUCCESS != rv) goto cleanup;
+    apr_pool_tag(pool, "h2_worker_slot");
+    slot->pool = pool;
+
+    rv = ap_thread_create(&slot->thread, workers->thread_attr,
+                          slot_run, slot, slot->pool);
 
 cleanup:
-    apr_thread_mutex_unlock(workers->lock);
     if (rv != APR_SUCCESS) {
-        push_slot(&workers->free, slot);
-    }
-    return rv;
-}
-
-static apr_status_t add_worker(h2_workers *workers)
-{
-    h2_slot *slot = pop_slot(&workers->free);
-    if (slot) {
-        return activate_slot(workers, slot);
-    }
-    return APR_EAGAIN;
-}
-
-static void wake_idle_worker(h2_workers *workers) 
-{
-    h2_slot *slot = pop_slot(&workers->idle);
-    if (slot) {
-        int timed_out = 0;
-        apr_thread_mutex_lock(slot->lock);
-        timed_out = slot->timed_out;
-        if (!timed_out) {
-            apr_thread_cond_signal(slot->not_idle);
+        AP_DEBUG_ASSERT(0);
+        slot->state = H2_SLOT_FREE;
+        if (slot->pool) {
+            apr_pool_destroy(slot->pool);
+            slot->pool = NULL;
         }
-        apr_thread_mutex_unlock(slot->lock);
-        if (timed_out) {
-            slot_done(slot);
-            wake_idle_worker(workers);
-        }
-    }
-    else if (workers->dynamic && !workers->shutdown) {
-        add_worker(workers);
+        APR_RING_INSERT_TAIL(&workers->free, slot, h2_slot, link);
+        --workers->active_slots;
     }
+    return rv;
 }
 
 static void join_zombies(h2_workers *workers)
 {
     h2_slot *slot;
-    while ((slot = pop_slot(&workers->zombies))) {
-        apr_status_t status;
+    apr_status_t status;
+
+    while (!APR_RING_EMPTY(&workers->zombie, h2_slot, link)) {
+        slot = APR_RING_FIRST(&workers->zombie);
+        APR_RING_REMOVE(slot, link);
+        ap_assert(slot->state == H2_SLOT_ZOMBIE);
         ap_assert(slot->thread != NULL);
+
+        apr_thread_mutex_unlock(workers->lock);
         apr_thread_join(&status, slot->thread);
-        slot->thread = NULL;
+        apr_thread_mutex_lock(workers->lock);
 
-        push_slot(&workers->free, slot);
+        slot->thread = NULL;
+        slot->state = H2_SLOT_FREE;
+        if (slot->pool) {
+            apr_pool_destroy(slot->pool);
+            slot->pool = NULL;
+        }
+        APR_RING_INSERT_TAIL(&workers->free, slot, h2_slot, link);
     }
 }
 
-static apr_status_t slot_pull_task(h2_slot *slot, h2_mplx *m)
+static void wake_idle_worker(h2_workers *workers, ap_conn_producer_t *prod)
 {
-    apr_status_t rv;
-    
-    rv = h2_mplx_s_pop_task(m, &slot->task);
-    if (slot->task) {
-        /* Ok, we got something to give back to the worker for execution. 
-         * If we still have idle workers, we let the worker be sticky, 
-         * e.g. making it poll the task's h2_mplx instance for more work 
-         * before asking back here. */
-        slot->sticks = slot->workers->max_workers;
-        return rv;            
+    if (!APR_RING_EMPTY(&workers->idle, h2_slot, link)) {
+        h2_slot *slot;
+        for (slot = APR_RING_FIRST(&workers->idle);
+             slot != APR_RING_SENTINEL(&workers->idle, h2_slot, link);
+             slot = APR_RING_NEXT(slot, link)) {
+             if (slot->is_idle && !slot->should_shutdown) {
+                apr_thread_cond_signal(slot->more_work);
+                slot->is_idle = 0;
+                return;
+             }
+        }
+    }
+    if (workers->dynamic && !workers->shutdown
+        && (workers->active_slots < workers->max_slots)) {
+        activate_slot(workers);
     }
-    slot->sticks = 0;
-    return APR_EOF;
-}
-
-static h2_fifo_op_t mplx_peek(void *head, void *ctx)
-{
-    h2_mplx *m = head;
-    h2_slot *slot = ctx;
-    
-    if (slot_pull_task(slot, m) == APR_EAGAIN) {
-        wake_idle_worker(slot->workers);
-        return H2_FIFO_OP_REPUSH;
-    } 
-    return H2_FIFO_OP_PULL;
 }
 
 /**
- * Get the next task for the given worker. Will block until a task arrives
- * or the max_wait timer expires and more than min workers exist.
+ * Get the next connection to work on.
  */
-static int get_next(h2_slot *slot)
+static conn_rec *get_next(h2_slot *slot)
 {
     h2_workers *workers = slot->workers;
-    int non_essential = slot->id >= workers->min_workers;
-    apr_status_t rv;
-
-    while (!workers->aborted && !slot->timed_out) {
-        ap_assert(slot->task == NULL);
-        if (non_essential && workers->shutdown) {
-            /* Terminate non-essential worker on shutdown */
-            break;
+    conn_rec *c = NULL;
+    ap_conn_producer_t *prod;
+    int has_more;
+
+    slot->prod = NULL;
+    if (!APR_RING_EMPTY(&workers->prod_active, ap_conn_producer_t, link)) {
+        slot->prod = prod = APR_RING_FIRST(&workers->prod_active);
+        APR_RING_REMOVE(prod, link);
+        AP_DEBUG_ASSERT(PROD_ACTIVE == prod->state);
+
+        c = prod->fn_next(prod->baton, &has_more);
+        if (c && has_more) {
+            APR_RING_INSERT_TAIL(&workers->prod_active, prod, ap_conn_producer_t, link);
+            wake_idle_worker(workers, slot->prod);
         }
-        if (h2_fifo_try_peek(workers->mplxs, mplx_peek, slot) == APR_EOF) {
-            /* The queue is terminated with the MPM child being cleaned up,
-             * just leave. */
-            break;
-        }
-        if (slot->task) {
-            return 1;
+        else {
+            prod->state = PROD_IDLE;
+            APR_RING_INSERT_TAIL(&workers->prod_idle, prod, ap_conn_producer_t, link);
         }
-        
-        join_zombies(workers);
-
-        apr_thread_mutex_lock(slot->lock);
-        if (!workers->aborted) {
-
-            push_slot(&workers->idle, slot);
-            if (non_essential && workers->max_idle_duration) {
-                rv = apr_thread_cond_timedwait(slot->not_idle, slot->lock,
-                                               workers->max_idle_duration);
-                if (APR_TIMEUP == rv) {
-                    slot->timed_out = 1;
-                }
-            }
-            else {
-                apr_thread_cond_wait(slot->not_idle, slot->lock);
-            }
+        if (c) {
+            ++prod->conns_active;
         }
-        apr_thread_mutex_unlock(slot->lock);
     }
 
-    return 0;
+    return c;
 }
 
-static void slot_done(h2_slot *slot)
+static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx)
 {
+    h2_slot *slot = wctx;
     h2_workers *workers = slot->workers;
+    conn_rec *c;
+    apr_status_t rv;
 
-    push_slot(&workers->zombies, slot);
+    apr_thread_mutex_lock(workers->lock);
+    slot->state = H2_SLOT_RUN;
+    ++slot->activations;
+    APR_RING_ELEM_INIT(slot, link);
+    for(;;) {
+        if (APR_RING_NEXT(slot, link) != slot) {
+            /* slot is part of the idle ring from the last loop */
+            APR_RING_REMOVE(slot, link);
+            --workers->idle_slots;
+        }
+        slot->is_idle = 0;
+
+        if (!workers->aborted && !slot->should_shutdown) {
+            APR_RING_INSERT_TAIL(&workers->busy, slot, h2_slot, link);
+            do {
+                c = get_next(slot);
+                if (!c) {
+                    break;
+                }
+                apr_thread_mutex_unlock(workers->lock);
+                /* See the discussion at <https://github.com/icing/mod_h2/issues/195>
+                 *
+                 * Each conn_rec->id is supposed to be unique at a point in time. Since
+                 * some modules (and maybe external code) uses this id as an identifier
+                 * for the request_rec they handle, it needs to be unique for secondary
+                 * connections also.
+                 *
+                 * The MPM module assigns the connection ids and mod_unique_id is using
+                 * that one to generate identifier for requests. While the implementation
+                 * works for HTTP/1.x, the parallel execution of several requests per
+                 * connection will generate duplicate identifiers on load.
+                 *
+                 * The original implementation for secondary connection identifiers used
+                 * to shift the master connection id up and assign the stream id to the
+                 * lower bits. This was cramped on 32 bit systems, but on 64bit there was
+                 * enough space.
+                 *
+                 * As issue 195 showed, mod_unique_id only uses the lower 32 bit of the
+                 * connection id, even on 64bit systems. Therefore collisions in request ids.
+                 *
+                 * The way master connection ids are generated, there is some space "at the
+                 * top" of the lower 32 bits on allmost all systems. If you have a setup
+                 * with 64k threads per child and 255 child processes, you live on the edge.
+                 *
+                 * The new implementation shifts 8 bits and XORs in the worker
+                 * id. This will experience collisions with > 256 h2 workers and heavy
+                 * load still. There seems to be no way to solve this in all possible
+                 * configurations by mod_h2 alone.
+                 */
+                if (c->master) {
+                    c->id = (c->master->id << 8)^slot->id;
+                }
+                c->current_thread = thread;
+                AP_DEBUG_ASSERT(slot->prod);
 
-    /* If this worker is the last one exiting and the MPM child is stopping,
-     * unblock workers_pool_cleanup().
-     */
-    if (!apr_atomic_dec32(&workers->worker_count) && workers->aborted) {
-        apr_thread_mutex_lock(workers->lock);
-        apr_thread_cond_signal(workers->all_done);
-        apr_thread_mutex_unlock(workers->lock);
-    }
-}
+#if AP_HAS_RESPONSE_BUCKETS
+                ap_process_connection(c, ap_get_conn_socket(c));
+#else
+                h2_c2_process(c, thread, slot->id);
+#endif
+                slot->prod->fn_done(slot->prod->baton, c);
+
+                apr_thread_mutex_lock(workers->lock);
+                if (--slot->prod->conns_active <= 0) {
+                    apr_thread_cond_broadcast(workers->prod_done);
+                }
+                if (slot->prod->state == PROD_IDLE) {
+                    APR_RING_REMOVE(slot->prod, link);
+                    slot->prod->state = PROD_ACTIVE;
+                    APR_RING_INSERT_TAIL(&workers->prod_active, slot->prod, ap_conn_producer_t, link);
+                }
 
+            } while (!workers->aborted && !slot->should_shutdown);
+            APR_RING_REMOVE(slot, link); /* no longer busy */
+        }
 
-static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx)
-{
-    h2_slot *slot = wctx;
-    
-    /* Get the h2_task(s) from the ->mplxs queue. */
-    while (get_next(slot)) {
-        ap_assert(slot->task != NULL);
-        do {
-            h2_task_do(slot->task, thread, slot->id);
-            
-            /* Report the task as done. If stickyness is left, offer the
-             * mplx the opportunity to give us back a new task right away.
-             */
-            if (!slot->workers->aborted && --slot->sticks > 0) {
-                h2_mplx_s_task_done(slot->task->mplx, slot->task, &slot->task);
-            }
-            else {
-                h2_mplx_s_task_done(slot->task->mplx, slot->task, NULL);
-                slot->task = NULL;
+        if (workers->aborted || slot->should_shutdown) {
+            break;
+        }
+
+        join_zombies(workers);
+
+        /* we are idle */
+        APR_RING_INSERT_TAIL(&workers->idle, slot, h2_slot, link);
+        ++workers->idle_slots;
+        slot->is_idle = 1;
+        if (slot->id >= workers->min_active && workers->idle_limit > 0) {
+            rv = apr_thread_cond_timedwait(slot->more_work, workers->lock,
+                                           workers->idle_limit);
+            if (APR_TIMEUP == rv) {
+                APR_RING_REMOVE(slot, link);
+                --workers->idle_slots;
+                ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+                             "h2_workers: idle timeout slot %d in state %d (%d activations)",
+                             slot->id, slot->state, slot->activations);
+                break;
             }
-        } while (slot->task);
+        }
+        else {
+            apr_thread_cond_wait(slot->more_work, workers->lock);
+        }
     }
 
-    if (!slot->timed_out) {
-        slot_done(slot);
+    ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
+                 "h2_workers: terminate slot %d in state %d (%d activations)",
+                 slot->id, slot->state, slot->activations);
+    slot->is_idle = 0;
+    slot->state = H2_SLOT_ZOMBIE;
+    slot->should_shutdown = 0;
+    APR_RING_INSERT_TAIL(&workers->zombie, slot, h2_slot, link);
+    --workers->active_slots;
+    if (workers->active_slots <= 0) {
+        apr_thread_cond_broadcast(workers->all_done);
     }
+    apr_thread_mutex_unlock(workers->lock);
 
     apr_thread_exit(thread, APR_SUCCESS);
     return NULL;
 }
 
-static void wake_non_essential_workers(h2_workers *workers)
-{
-    h2_slot *slot;
-    /* pop all idle, signal the non essentials and add the others again */
-    if ((slot = pop_slot(&workers->idle))) {
-        wake_non_essential_workers(workers);
-        if (slot->id > workers->min_workers) {
-            apr_thread_mutex_lock(slot->lock);
-            apr_thread_cond_signal(slot->not_idle);
-            apr_thread_mutex_unlock(slot->lock);
-        }
-        else {
-            push_slot(&workers->idle, slot);
-        }
-    }
-}
-
-static void workers_abort_idle(h2_workers *workers)
+static void wake_all_idles(h2_workers *workers)
 {
     h2_slot *slot;
-
-    workers->shutdown = 1;
-    workers->aborted = 1;
-    h2_fifo_term(workers->mplxs);
-
-    /* abort all idle slots */
-    while ((slot = pop_slot(&workers->idle))) {
-        apr_thread_mutex_lock(slot->lock);
-        apr_thread_cond_signal(slot->not_idle);
-        apr_thread_mutex_unlock(slot->lock);
+    for (slot = APR_RING_FIRST(&workers->idle);
+         slot != APR_RING_SENTINEL(&workers->idle, h2_slot, link);
+         slot = APR_RING_NEXT(slot, link))
+    {
+        apr_thread_cond_signal(slot->more_work);
     }
 }
 
@@ -321,40 +369,50 @@
     h2_workers *workers = data;
     apr_time_t end, timeout = apr_time_from_sec(1);
     apr_status_t rv;
-    int n, wait_sec = 5;
+    int n = 0, wait_sec = 5;
 
     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
-                 "h2_workers: cleanup %d workers idling",
-                 (int)apr_atomic_read32(&workers->worker_count));
-    workers_abort_idle(workers);
+                 "h2_workers: cleanup %d workers (%d idle)",
+                 workers->active_slots, workers->idle_slots);
+    apr_thread_mutex_lock(workers->lock);
+    workers->shutdown = 1;
+    workers->aborted = 1;
+    wake_all_idles(workers);
+    apr_thread_mutex_unlock(workers->lock);
 
     /* wait for all the workers to become zombies and join them.
      * this gets called after the mpm shuts down and all connections
      * have either been handled (graceful) or we are forced exiting
      * (ungrateful). Either way, we show limited patience. */
-    apr_thread_mutex_lock(workers->lock);
     end = apr_time_now() + apr_time_from_sec(wait_sec);
-    while ((n = apr_atomic_read32(&workers->worker_count)) > 0
-           && apr_time_now() < end) {
+    while (apr_time_now() < end) {
+        apr_thread_mutex_lock(workers->lock);
+        if (!(n = workers->active_slots)) {
+            apr_thread_mutex_unlock(workers->lock);
+            break;
+        }
+        wake_all_idles(workers);
         rv = apr_thread_cond_timedwait(workers->all_done, workers->lock, timeout);
+        apr_thread_mutex_unlock(workers->lock);
+
         if (APR_TIMEUP == rv) {
             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
-                         APLOGNO(10290) "h2_workers: waiting for idle workers to close, "
-                         "still seeing %d workers living",
-                         apr_atomic_read32(&workers->worker_count));
-            continue;
+                         APLOGNO(10290) "h2_workers: waiting for workers to close, "
+                         "still seeing %d workers (%d idle) living",
+                         workers->active_slots, workers->idle_slots);
         }
     }
     if (n) {
         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, workers->s,
-                     APLOGNO(10291) "h2_workers: cleanup, %d idle workers "
+                     APLOGNO(10291) "h2_workers: cleanup, %d workers (%d idle) "
                      "did not exit after %d seconds.",
-                     n, wait_sec);
+                     n, workers->idle_slots, wait_sec);
     }
-    apr_thread_mutex_unlock(workers->lock);
     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
                  "h2_workers: cleanup all workers terminated");
+    apr_thread_mutex_lock(workers->lock);
     join_zombies(workers);
+    apr_thread_mutex_unlock(workers->lock);
     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
                  "h2_workers: cleanup zombie workers joined");
 
@@ -362,23 +420,35 @@
 }
 
 h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pchild,
-                              int min_workers, int max_workers,
-                              int idle_secs)
+                              int max_slots, int min_active,
+                              apr_time_t idle_limit)
 {
     apr_status_t rv;
     h2_workers *workers;
     apr_pool_t *pool;
-    int i, n;
+    apr_allocator_t *allocator;
+    int locked = 0;
+    apr_uint32_t i;
 
     ap_assert(s);
     ap_assert(pchild);
+    ap_assert(idle_limit > 0);
 
     /* let's have our own pool that will be parent to all h2_worker
      * instances we create. This happens in various threads, but always
      * guarded by our lock. Without this pool, all subpool creations would
      * happen on the pool handed to us, which we do not guard.
      */
-    apr_pool_create(&pool, pchild);
+    rv = apr_allocator_create(&allocator);
+    if (rv != APR_SUCCESS) {
+        goto cleanup;
+    }
+    rv = apr_pool_create_ex(&pool, pchild, NULL, allocator);
+    if (rv != APR_SUCCESS) {
+        apr_allocator_destroy(allocator);
+        goto cleanup;
+    }
+    apr_allocator_owner_set(allocator, pool);
     apr_pool_tag(pool, "h2_workers");
     workers = apr_pcalloc(pool, sizeof(h2_workers));
     if (!workers) {
@@ -387,27 +457,23 @@
     
     workers->s = s;
     workers->pool = pool;
-    workers->min_workers = min_workers;
-    workers->max_workers = max_workers;
-    workers->max_idle_duration = apr_time_from_sec((idle_secs > 0)? idle_secs : 10);
+    workers->min_active = min_active;
+    workers->max_slots = max_slots;
+    workers->idle_limit = idle_limit;
+    workers->dynamic = (workers->min_active < workers->max_slots);
+
+    ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
+                 "h2_workers: created with min=%d max=%d idle_ms=%d",
+                 workers->min_active, workers->max_slots,
+                 (int)apr_time_as_msec(idle_limit));
+
+    APR_RING_INIT(&workers->idle, h2_slot, link);
+    APR_RING_INIT(&workers->busy, h2_slot, link);
+    APR_RING_INIT(&workers->free, h2_slot, link);
+    APR_RING_INIT(&workers->zombie, h2_slot, link);
 
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
-                 "h2_workers: created with min=%d max=%d idle_timeout=%d sec",
-                 workers->min_workers, workers->max_workers,
-                 (int)apr_time_sec(workers->max_idle_duration));
-    /* FIXME: the fifo set we use here has limited capacity. Once the
-     * set is full, connections with new requests do a wait. Unfortunately,
-     * we have optimizations in place there that makes such waiting "unfair"
-     * in the sense that it may take connections a looong time to get scheduled.
-     *
-     * Need to rewrite this to use one of our double-linked lists and a mutex
-     * to have unlimited capacity and fair scheduling.
-     *
-     * For now, we just make enough room to have many connections inside one
-     * process.
-     */
-    rv = h2_fifo_set_create(&workers->mplxs, pool, 8 * 1024);
-    if (rv != APR_SUCCESS) goto cleanup;
+    APR_RING_INIT(&workers->prod_active, ap_conn_producer_t, link);
+    APR_RING_INIT(&workers->prod_idle, ap_conn_producer_t, link);
 
     rv = apr_threadattr_create(&workers->thread_attr, workers->pool);
     if (rv != APR_SUCCESS) goto cleanup;
@@ -426,32 +492,35 @@
     if (rv != APR_SUCCESS) goto cleanup;
     rv = apr_thread_cond_create(&workers->all_done, workers->pool);
     if (rv != APR_SUCCESS) goto cleanup;
+    rv = apr_thread_cond_create(&workers->prod_done, workers->pool);
+    if (rv != APR_SUCCESS) goto cleanup;
 
-    n = workers->nslots = workers->max_workers;
-    workers->slots = apr_pcalloc(workers->pool, n * sizeof(h2_slot));
-    if (workers->slots == NULL) {
-        n = workers->nslots = 0;
-        rv = APR_ENOMEM;
-        goto cleanup;
-    }
-    for (i = 0; i < n; ++i) {
+    apr_thread_mutex_lock(workers->lock);
+    locked = 1;
+
+    /* create the slots and put them on the free list */
+    workers->slots = apr_pcalloc(workers->pool, workers->max_slots * sizeof(h2_slot));
+
+    for (i = 0; i < workers->max_slots; ++i) {
         workers->slots[i].id = i;
-    }
-    /* we activate all for now, TODO: support min_workers again.
-     * do this in reverse for vanity reasons so slot 0 will most
-     * likely be at head of idle queue. */
-    n = workers->min_workers;
-    for (i = n-1; i >= 0; --i) {
-        rv = activate_slot(workers, &workers->slots[i]);
+        workers->slots[i].state = H2_SLOT_FREE;
+        workers->slots[i].workers = workers;
+        APR_RING_ELEM_INIT(&workers->slots[i], link);
+        APR_RING_INSERT_TAIL(&workers->free, &workers->slots[i], h2_slot, link);
+        rv = apr_thread_cond_create(&workers->slots[i].more_work, workers->pool);
         if (rv != APR_SUCCESS) goto cleanup;
     }
-    /* the rest of the slots go on the free list */
-    for(i = n; i < workers->nslots; ++i) {
-        push_slot(&workers->free, &workers->slots[i]);
+
+    /* activate the min amount of workers */
+    for (i = 0; i < workers->min_active; ++i) {
+        rv = activate_slot(workers);
+        if (rv != APR_SUCCESS) goto cleanup;
     }
-    workers->dynamic = (workers->worker_count < workers->max_workers);
 
 cleanup:
+    if (locked) {
+        apr_thread_mutex_unlock(workers->lock);
+    }
     if (rv == APR_SUCCESS) {
         /* Stop/join the workers threads when the MPM child exits (pchild is
          * destroyed), and as a pre_cleanup of pchild thus before the threads
@@ -461,26 +530,97 @@
         apr_pool_pre_cleanup_register(pchild, workers, workers_pool_cleanup);    
         return workers;
     }
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s,
+                 "h2_workers: errors initializing");
     return NULL;
 }
 
-apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m)
+apr_uint32_t h2_workers_get_max_workers(h2_workers *workers)
+{
+    return workers->max_slots;
+}
+
+void h2_workers_shutdown(h2_workers *workers, int graceful)
 {
-    apr_status_t status = h2_fifo_push(workers->mplxs, m);
-    wake_idle_worker(workers);
-    return status;
+    ap_conn_producer_t *prod;
+
+    apr_thread_mutex_lock(workers->lock);
+    ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+                 "h2_workers: shutdown graceful=%d", graceful);
+    workers->shutdown = 1;
+    workers->idle_limit = apr_time_from_sec(1);
+    wake_all_idles(workers);
+    for (prod = APR_RING_FIRST(&workers->prod_idle);
+        prod != APR_RING_SENTINEL(&workers->prod_idle, ap_conn_producer_t, link);
+        prod = APR_RING_NEXT(prod, link)) {
+        if (prod->fn_shutdown) {
+            prod->fn_shutdown(prod->baton, graceful);
+        }
+    }
+    apr_thread_mutex_unlock(workers->lock);
 }
 
-apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m)
+ap_conn_producer_t *h2_workers_register(h2_workers *workers,
+                                        apr_pool_t *producer_pool,
+                                        const char *name,
+                                        ap_conn_producer_next *fn_next,
+                                        ap_conn_producer_done *fn_done,
+                                        ap_conn_producer_shutdown *fn_shutdown,
+                                        void *baton)
+{
+    ap_conn_producer_t *prod;
+
+    prod = apr_pcalloc(producer_pool, sizeof(*prod));
+    APR_RING_ELEM_INIT(prod, link);
+    prod->name = name;
+    prod->fn_next = fn_next;
+    prod->fn_done = fn_done;
+    prod->fn_shutdown = fn_shutdown;
+    prod->baton = baton;
+
+    apr_thread_mutex_lock(workers->lock);
+    prod->state = PROD_IDLE;
+    APR_RING_INSERT_TAIL(&workers->prod_idle, prod, ap_conn_producer_t, link);
+    apr_thread_mutex_unlock(workers->lock);
+
+    return prod;
+}
+
+apr_status_t h2_workers_join(h2_workers *workers, ap_conn_producer_t *prod)
 {
-    return h2_fifo_remove(workers->mplxs, m);
+    apr_status_t rv = APR_SUCCESS;
+
+    apr_thread_mutex_lock(workers->lock);
+    if (PROD_JOINED == prod->state) {
+        AP_DEBUG_ASSERT(APR_RING_NEXT(prod, link) == prod); /* should be in no ring */
+        rv = APR_EINVAL;
+    }
+    else {
+        AP_DEBUG_ASSERT(PROD_ACTIVE == prod->state || PROD_IDLE == prod->state);
+        APR_RING_REMOVE(prod, link);
+        prod->state = PROD_JOINED; /* prevent further activations */
+        while (prod->conns_active > 0) {
+            apr_thread_cond_wait(workers->prod_done, workers->lock);
+        }
+        APR_RING_ELEM_INIT(prod, link); /* make it link to itself */
+    }
+    apr_thread_mutex_unlock(workers->lock);
+    return rv;
 }
 
-void h2_workers_graceful_shutdown(h2_workers *workers)
+apr_status_t h2_workers_activate(h2_workers *workers, ap_conn_producer_t *prod)
 {
-    workers->shutdown = 1;
-    workers->min_workers = 1;
-    workers->max_idle_duration = apr_time_from_sec(1);
-    h2_fifo_term(workers->mplxs);
-    wake_non_essential_workers(workers);
+    apr_status_t rv = APR_SUCCESS;
+    apr_thread_mutex_lock(workers->lock);
+    if (PROD_IDLE == prod->state) {
+        APR_RING_REMOVE(prod, link);
+        prod->state = PROD_ACTIVE;
+        APR_RING_INSERT_TAIL(&workers->prod_active, prod, ap_conn_producer_t, link);
+        wake_idle_worker(workers, prod);
+    }
+    else if (PROD_JOINED == prod->state) {
+        rv = APR_EINVAL;
+    }
+    apr_thread_mutex_unlock(workers->lock);
+    return rv;
 }
diff -r -N -u a/modules/http2/h2_workers.h b/modules/http2/h2_workers.h
--- a/modules/http2/h2_workers.h	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/h2_workers.h	2024-10-30 21:40:01.059958617 +0100
@@ -17,73 +17,113 @@
 #ifndef __mod_h2__h2_workers__
 #define __mod_h2__h2_workers__
 
-/* Thread pool specific to executing h2_tasks. Has a minimum and maximum 
- * number of workers it creates. Starts with minimum workers and adds
- * some on load, reduces the number again when idle.
- *
+/* Thread pool specific to executing secondary connections.
+ * Has a minimum and maximum number of workers it creates.
+ * Starts with minimum workers and adds some on load,
+ * reduces the number again when idle.
  */
 struct apr_thread_mutex_t;
 struct apr_thread_cond_t;
 struct h2_mplx;
 struct h2_request;
-struct h2_task;
 struct h2_fifo;
 
-struct h2_slot;
-
 typedef struct h2_workers h2_workers;
 
-struct h2_workers {
-    server_rec *s;
-    apr_pool_t *pool;
-    
-    int next_worker_id;
-    apr_uint32_t max_workers;
-    volatile apr_uint32_t min_workers; /* is changed during graceful shutdown */
-    volatile apr_interval_time_t max_idle_duration; /* is changed during graceful shutdown */
-    
-    volatile int aborted;
-    volatile int shutdown;
-    int dynamic;
-
-    apr_threadattr_t *thread_attr;
-    int nslots;
-    struct h2_slot *slots;
-    
-    volatile apr_uint32_t worker_count;
-    
-    struct h2_slot *free;
-    struct h2_slot *idle;
-    struct h2_slot *zombies;
-    
-    struct h2_fifo *mplxs;
-    
-    struct apr_thread_mutex_t *lock;
-    struct apr_thread_cond_t *all_done;
-};
 
-
-/* Create a worker pool with the given minimum and maximum number of
- * threads.
+/**
+ * Create a worker set with a maximum number of 'slots', e.g. worker
+ * threads to run. Always keep `min_active` workers running. Shutdown
+ * any additional workers after `idle_secs` seconds of doing nothing.
+ *
+ * @oaram s the base server
+ * @param pool for allocations
+ * @param min_active minimum number of workers to run
+ * @param max_slots maximum number of worker slots
+ * @param idle_limit upper duration of idle after a non-minimal slots shuts down
  */
 h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pool,
-                              int min_size, int max_size, int idle_secs);
+                              int max_slots, int min_active, apr_time_t idle_limit);
+
+/**
+ *  Shut down processing.
+ */
+void h2_workers_shutdown(h2_workers *workers, int graceful);
+
+/**
+ * Get the maximum number of workers.
+ */
+apr_uint32_t h2_workers_get_max_workers(h2_workers *workers);
+
+/**
+ * ap_conn_producer_t is the source of connections (conn_rec*) to run.
+ *
+ * Active producers are queried by idle workers for connections.
+ * If they do not hand one back, they become inactive and are not
+ * queried further. `h2_workers_activate()` places them on the active
+ * list again.
+ *
+ * A producer finishing MUST call `h2_workers_join()` which removes
+ * it completely from workers processing and waits for all ongoing
+ * work for this producer to be done.
+ */
+typedef struct ap_conn_producer_t ap_conn_producer_t;
+
+/**
+ * Ask a producer for the next connection to process.
+ * @param baton value from producer registration
+ * @param pconn holds the connection to process on return
+ * @param pmore if the producer has more connections that may be retrieved
+ * @return APR_SUCCESS for a connection to process, APR_EAGAIN for no
+ *         connection being available at the time.
+ */
+typedef conn_rec *ap_conn_producer_next(void *baton, int *pmore);
+
+/**
+ * Tell the producer that processing the connection is done.
+ * @param baton value from producer registration
+ * @param conn the connection that has been processed.
+ */
+typedef void ap_conn_producer_done(void *baton, conn_rec *conn);
+
+/**
+ * Tell the producer that the workers are shutting down.
+ * @param baton value from producer registration
+ * @param graceful != 0 iff shutdown is graceful
+ */
+typedef void ap_conn_producer_shutdown(void *baton, int graceful);
 
 /**
- * Registers a h2_mplx for task scheduling. If this h2_mplx runs
- * out of tasks, it will be automatically be unregistered. Should
- * new tasks arrive, it needs to be registered again.
+ * Register a new producer with the given `baton` and callback functions.
+ * Will allocate internal structures from the given pool (but make no use
+ * of the pool after registration).
+ * Producers are inactive on registration. See `h2_workers_activate()`.
+ * @param producer_pool to allocate the producer from
+ * @param name descriptive name of the producer, must not be unique
+ * @param fn_next callback for retrieving connections to process
+ * @param fn_done callback for processed connections
+ * @param baton provided value passed on in callbacks
+ * @return the producer instance created
  */
-apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m);
+ap_conn_producer_t *h2_workers_register(h2_workers *workers,
+                                        apr_pool_t *producer_pool,
+                                        const char *name,
+                                        ap_conn_producer_next *fn_next,
+                                        ap_conn_producer_done *fn_done,
+                                        ap_conn_producer_shutdown *fn_shutdown,
+                                        void *baton);
 
 /**
- * Remove a h2_mplx from the worker registry.
+ * Stop retrieving more connection from the producer and wait
+ * for all ongoing for from that producer to be done.
  */
-apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m);
+apr_status_t h2_workers_join(h2_workers *workers, ap_conn_producer_t *producer);
 
 /**
- *  Shut down processing gracefully by terminating all idle workers.
+ * Activate a producer. A worker will query the producer for a connection
+ * to process, once a worker is available.
+ * This may be called, irregardless of the producers active/inactive.
  */
-void h2_workers_graceful_shutdown(h2_workers *workers);
+apr_status_t h2_workers_activate(h2_workers *workers, ap_conn_producer_t *producer);
 
 #endif /* defined(__mod_h2__h2_workers__) */
diff -r -N -u a/modules/http2/h2_ws.c b/modules/http2/h2_ws.c
--- a/modules/http2/h2_ws.c	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_ws.c	2024-10-30 21:40:01.059958617 +0100
@@ -0,0 +1,362 @@
+/* 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_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"
+
+#if H2_USE_WEBSOCKETS
+
+#include "apr_encode.h" /* H2_USE_WEBSOCKETS is conditional on APR 1.6+ */
+
+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");
+    apr_table_add(wsreq->headers, "Connection", "Upgrade");
+    /* 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 "
+                              "with '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);
+}
+
+static int ws_post_read(request_rec *r)
+{
+
+    if (r->connection->master) {
+        h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(r->connection);
+        if (conn_ctx && conn_ctx->is_upgrade &&
+            !h2_config_sgeti(r->server, H2_CONF_WEBSOCKETS)) {
+            return HTTP_NOT_IMPLEMENTED;
+        }
+    }
+    return DECLINED;
+}
+
+void h2_ws_register_hooks(void)
+{
+    ap_hook_post_read_request(ws_post_read, NULL, NULL, APR_HOOK_MIDDLE);
+    c2_ws_out_filter_handle =
+        ap_register_output_filter("H2_C2_WS_OUT", h2_c2_ws_filter_out,
+                                  NULL, AP_FTYPE_NETWORK);
+}
+
+#else /* H2_USE_WEBSOCKETS */
+
+const h2_request *h2_ws_rewrite_request(const h2_request *req,
+                                        conn_rec *c2, int no_body)
+{
+    (void)c2;
+    (void)no_body;
+    /* no rewriting */
+    return req;
+}
+
+void h2_ws_register_hooks(void)
+{
+    /*  NOP */
+}
+
+#endif /* H2_USE_WEBSOCKETS (else part) */
diff -r -N -u a/modules/http2/h2_ws.h b/modules/http2/h2_ws.h
--- a/modules/http2/h2_ws.h	1970-01-01 01:00:00.000000000 +0100
+++ b/modules/http2/h2_ws.h	2024-10-30 21:40:01.059958617 +0100
@@ -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 c2 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__) */
diff -r -N -u a/modules/http2/mod_http2.c b/modules/http2/mod_http2.c
--- a/modules/http2/mod_http2.c	2021-09-26 16:30:51.000000000 +0200
+++ b/modules/http2/mod_http2.c	2024-10-30 21:40:01.059958617 +0100
@@ -30,19 +30,19 @@
 
 #include <nghttp2/nghttp2.h>
 #include "h2_stream.h"
-#include "h2_alt_svc.h"
-#include "h2_conn.h"
-#include "h2_filter.h"
-#include "h2_task.h"
+#include "h2_c1.h"
+#include "h2_c2.h"
 #include "h2_session.h"
 #include "h2_config.h"
-#include "h2_ctx.h"
-#include "h2_h2.h"
+#include "h2_conn_ctx.h"
+#include "h2_protocol.h"
 #include "h2_mplx.h"
 #include "h2_push.h"
 #include "h2_request.h"
 #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);
@@ -126,27 +126,6 @@
                  myfeats.dyn_windows? "+DWINS"  : "",
                  ngh2?                ngh2->version_str : "unknown");
     
-    switch (h2_conn_mpm_type()) {
-        case H2_MPM_SIMPLE:
-        case H2_MPM_MOTORZ:
-        case H2_MPM_NETWARE:
-        case H2_MPM_WINNT:
-            /* not sure we need something extra for those. */
-            break;
-        case H2_MPM_EVENT:
-        case H2_MPM_WORKER:
-            /* all fine, we know these ones */
-            break;
-        case H2_MPM_PREFORK:
-            /* ok, we now know how to handle that one */
-            break;
-        case H2_MPM_UNKNOWN:
-            /* ??? */
-            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03091)
-                         "post_config: mpm type unknown");
-            break;
-    }
-    
     if (!h2_mpm_supported() && !mpm_warned) {
         mpm_warned = 1;
         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10034)
@@ -158,14 +137,11 @@
                      h2_conn_mpm_name());
     }
     
-    status = h2_h2_init(p, s);
+    status = h2_protocol_init(p, s);
     if (status == APR_SUCCESS) {
         status = h2_switch_init(p, s);
     }
-    if (status == APR_SUCCESS) {
-        status = h2_task_init(p, s);
-    }
-    
+
     return status;
 }
 
@@ -175,7 +151,9 @@
 
 static void http2_get_num_workers(server_rec *s, int *minw, int *maxw)
 {
-    h2_get_num_workers(s, minw, maxw);
+    apr_time_t tdummy;
+
+    h2_get_workers_config(s, minw, maxw, &tdummy);
 }
 
 /* Runs once per created child process. Perform any process 
@@ -183,29 +161,15 @@
  */
 static void h2_child_init(apr_pool_t *pchild, server_rec *s)
 {
-    apr_allocator_t *allocator;
-    apr_thread_mutex_t *mutex;
-    apr_status_t status;
-
-    /* The allocator of pchild has no mutex with MPM prefork, but we need one
-     * for h2 workers threads synchronization. Even though mod_http2 shouldn't
-     * be used with prefork, better be safe than sorry, so forcibly set the
-     * mutex here. For MPM event/worker, pchild has no allocator so pconf's
-     * is used, with its mutex.
-     */
-    allocator = apr_pool_allocator_get(pchild);
-    if (allocator) {
-        mutex = apr_allocator_mutex_get(allocator);
-        if (!mutex) {
-            apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, pchild);
-            apr_allocator_mutex_set(allocator, mutex);
-        }
-    }
+    apr_status_t rv;
 
     /* Set up our connection processing */
-    status = h2_conn_child_init(pchild, s);
-    if (status != APR_SUCCESS) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, status, s,
+    rv = h2_c1_child_init(pchild, s);
+    if (APR_SUCCESS == rv) {
+        rv = h2_c2_child_init(pchild, s);
+    }
+    if (APR_SUCCESS != rv) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                      APLOGNO(02949) "initializing connection handling");
     }
 }
@@ -230,41 +194,39 @@
      */
     ap_hook_child_init(h2_child_init, NULL, NULL, APR_HOOK_MIDDLE);
 #if AP_MODULE_MAGIC_AT_LEAST(20120211, 110)
-    ap_hook_child_stopping(h2_conn_child_stopping, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_child_stopping(h2_c1_child_stopping, NULL, NULL, APR_HOOK_MIDDLE);
 #endif
-    h2_h2_register_hooks();
+
+    h2_c1_register_hooks();
     h2_switch_register_hooks();
-    h2_task_register_hooks();
+    h2_c2_register_hooks();
+    h2_ws_register_hooks();
 
-    h2_alt_svc_register_hooks();
-    
-    /* Setup subprocess env for certain variables 
+    /* Setup subprocess env for certain variables
      */
     ap_hook_fixups(h2_h2_fixups, NULL,NULL, APR_HOOK_MIDDLE);
-    
-    /* test http2 connection status handler */
-    ap_hook_handler(h2_filter_h2_status_handler, NULL, NULL, APR_HOOK_MIDDLE);
 }
 
 static const char *val_HTTP2(apr_pool_t *p, server_rec *s,
-                             conn_rec *c, request_rec *r, h2_ctx *ctx)
+                             conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx)
 {
     return ctx? "on" : "off";
 }
 
 static const char *val_H2_PUSH(apr_pool_t *p, server_rec *s,
-                               conn_rec *c, request_rec *r, h2_ctx *ctx)
+                               conn_rec *c, request_rec *r,
+                               h2_conn_ctx_t *conn_ctx)
 {
-    if (ctx) {
+    if (conn_ctx) {
         if (r) {
-            if (ctx->task) {
-                h2_stream *stream = h2_mplx_t_stream_get(ctx->task->mplx, ctx->task);
+            if (conn_ctx->stream_id) {
+                const h2_stream *stream = h2_mplx_c2_stream_get(conn_ctx->mplx, conn_ctx->stream_id);
                 if (stream && stream->push_policy != H2_PUSH_NONE) {
                     return "on";
                 }
             }
         }
-        else if (c && h2_session_push_enabled(ctx->session)) {
+        else if (c && h2_session_push_enabled(conn_ctx->session)) {
             return "on";
         }
     }
@@ -277,10 +239,11 @@
 }
 
 static const char *val_H2_PUSHED(apr_pool_t *p, server_rec *s,
-                                 conn_rec *c, request_rec *r, h2_ctx *ctx)
+                                 conn_rec *c, request_rec *r,
+                                 h2_conn_ctx_t *conn_ctx)
 {
-    if (ctx) {
-        if (ctx->task && !H2_STREAM_CLIENT_INITIATED(ctx->task->stream_id)) {
+    if (conn_ctx) {
+        if (conn_ctx->stream_id && !H2_STREAM_CLIENT_INITIATED(conn_ctx->stream_id)) {
             return "PUSHED";
         }
     }
@@ -288,11 +251,12 @@
 }
 
 static const char *val_H2_PUSHED_ON(apr_pool_t *p, server_rec *s,
-                                    conn_rec *c, request_rec *r, h2_ctx *ctx)
+                                    conn_rec *c, request_rec *r,
+                                    h2_conn_ctx_t *conn_ctx)
 {
-    if (ctx) {
-        if (ctx->task && !H2_STREAM_CLIENT_INITIATED(ctx->task->stream_id)) {
-            h2_stream *stream = h2_mplx_t_stream_get(ctx->task->mplx, ctx->task);
+    if (conn_ctx) {
+        if (conn_ctx->stream_id && !H2_STREAM_CLIENT_INITIATED(conn_ctx->stream_id)) {
+            const h2_stream *stream = h2_mplx_c2_stream_get(conn_ctx->mplx, conn_ctx->stream_id);
             if (stream) {
                 return apr_itoa(p, stream->initiated_on);
             }
@@ -302,28 +266,30 @@
 }
 
 static const char *val_H2_STREAM_TAG(apr_pool_t *p, server_rec *s,
-                                     conn_rec *c, request_rec *r, h2_ctx *ctx)
+                                     conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx)
 {
-    if (ctx) {
-        if (ctx->task) {
-            return ctx->task->id;
+    if (c) {
+        h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+        if (conn_ctx) {
+            return conn_ctx->stream_id == 0? conn_ctx->id
+               : apr_psprintf(p, "%s-%d", conn_ctx->id, conn_ctx->stream_id);
         }
     }
     return "";
 }
 
 static const char *val_H2_STREAM_ID(apr_pool_t *p, server_rec *s,
-                                    conn_rec *c, request_rec *r, h2_ctx *ctx)
+                                    conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx)
 {
     const char *cp = val_H2_STREAM_TAG(p, s, c, r, ctx);
-    if (cp && (cp = ap_strchr_c(cp, '-'))) {
+    if (cp && (cp = ap_strrchr_c(cp, '-'))) {
         return ++cp;
     }
     return NULL;
 }
 
 typedef const char *h2_var_lookup(apr_pool_t *p, server_rec *s,
-                                  conn_rec *c, request_rec *r, h2_ctx *ctx);
+                                  conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx);
 typedef struct h2_var_def {
     const char *name;
     h2_var_lookup *lookup;
@@ -347,19 +313,19 @@
 
 static int http2_is_h2(conn_rec *c)
 {
-    return h2_ctx_get(c->master? c->master : c, 0) != NULL;
+    return h2_conn_ctx_get(c->master? c->master : c) != NULL;
 }
 
 static char *http2_var_lookup(apr_pool_t *p, server_rec *s,
                               conn_rec *c, request_rec *r, char *name)
 {
-    int i;
+    unsigned int i;
     /* If the # of vars grow, we need to put definitions in a hash */
     for (i = 0; i < H2_ALEN(H2_VARS); ++i) {
         h2_var_def *vdef = &H2_VARS[i];
         if (!strcmp(vdef->name, name)) {
-            h2_ctx *ctx = (r? h2_ctx_get(c, 0) : 
-                           h2_ctx_get(c->master? c->master : c, 0));
+            h2_conn_ctx_t *ctx = (r? h2_conn_ctx_get(c) :
+                           h2_conn_ctx_get(c->master? c->master : c));
             return (char *)vdef->lookup(p, s, c, r, ctx);
         }
     }
@@ -369,9 +335,9 @@
 static int h2_h2_fixups(request_rec *r)
 {
     if (r->connection->master) {
-        h2_ctx *ctx = h2_ctx_get(r->connection, 0);
-        int i;
-        
+        h2_conn_ctx_t *ctx = h2_conn_ctx_get(r->connection);
+        unsigned int i;
+
         for (i = 0; ctx && i < H2_ALEN(H2_VARS); ++i) {
             h2_var_def *vdef = &H2_VARS[i];
             if (vdef->subprocess) {
diff -r -N -u a/modules/http2/mod_http2.dsp b/modules/http2/mod_http2.dsp
--- a/modules/http2/mod_http2.dsp	2019-03-14 04:46:13.000000000 +0100
+++ b/modules/http2/mod_http2.dsp	2024-10-30 21:40:01.059958617 +0100
@@ -101,10 +101,6 @@
 # Name "mod_http2 - Win32 Debug"
 # Begin Source File
 
-SOURCE=./h2_alt_svc.c
-# End Source File
-# Begin Source File
-
 SOURCE=./h2_bucket_beam.c
 # End Source File
 # Begin Source File
@@ -113,31 +109,31 @@
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_config.c
+SOURCE=./h2_c1.c
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_conn.c
+SOURCE=./h2_c1_io.c
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_conn_io.c
+SOURCE=./h2_c2.c
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_ctx.c
+SOURCE=./h2_c2_filter.c
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_filter.c
+SOURCE=./h2_config.c
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_from_h1.c
+SOURCE=./h2_conn_ctx.c
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_h2.c
+SOURCE=./h2_headers.c
 # End Source File
 # Begin Source File
 
@@ -145,15 +141,15 @@
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_push.c
+SOURCE=./h2_protocol.c
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_request.c
+SOURCE=./h2_push.c
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_headers.c
+SOURCE=./h2_request.c
 # End Source File
 # Begin Source File
 
@@ -169,15 +165,19 @@
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_task.c
+SOURCE=./h2_util.c
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_util.c
+SOURCE=./h2_workers.c
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_workers.c
+SOURCE=./h2_ws.c
+# End Source File
+# Begin Source File
+
+SOURCE=./mod_http2.c
 # End Source File
 # Begin Source File
 
diff -r -N -u a/modules/http2/mod_http2.h b/modules/http2/mod_http2.h
--- a/modules/http2/mod_http2.h	2020-02-21 01:33:40.000000000 +0100
+++ b/modules/http2/mod_http2.h	2024-10-30 21:40:01.059958617 +0100
@@ -28,6 +28,32 @@
 APR_DECLARE_OPTIONAL_FN(int, 
                         http2_is_h2, (conn_rec *));
 
+APR_DECLARE_OPTIONAL_FN(void,
+                        http2_get_num_workers, (server_rec *s,
+                                                int *minw, int *max));
+
+#define AP_HTTP2_HAS_GET_POLLFD
+
+/**
+ * Get a apr_pollfd_t populated for a h2 connection where
+ * (c->master != NULL) is true and pipes are supported.
+ * To be used in Apache modules implementing WebSockets in Apache httpd
+ * versions that do not support the corresponding `ap_get_pollfd_from_conn()`
+ * function.
+ * When available, use `ap_get_pollfd_from_conn()` instead of this function.
+ *
+ * How it works: pass in a `apr_pollfd_t` which gets populated for
+ * monitoring the input of connection `c`. If `c` is not a HTTP/2
+ * stream connection, the function will return `APR_ENOTIMPL`.
+ * `ptimeout` is optional and, if passed, will get the timeout in effect
+ *
+ * On platforms without support for pipes (e.g. Windows), this function
+ * will return `APR_ENOTIMPL`.
+ */
+APR_DECLARE_OPTIONAL_FN(apr_status_t,
+                        http2_get_pollfd_from_conn,
+                        (conn_rec *c, struct apr_pollfd_t *pfd,
+                         apr_interval_time_t *ptimeout));
 
 /*******************************************************************************
  * START HTTP/2 request engines (DEPRECATED)
@@ -68,9 +94,6 @@
                                                 conn_rec *rconn,
                                                 apr_status_t status));
 
-APR_DECLARE_OPTIONAL_FN(void,
-                        http2_get_num_workers, (server_rec *s,
-                                                int *minw, int *max));
 
 /*******************************************************************************
  * END HTTP/2 request engines (DEPRECATED)
diff -r -N -u a/modules/http2/mod_proxy_http2.c b/modules/http2/mod_proxy_http2.c
--- a/modules/http2/mod_proxy_http2.c	2024-10-30 21:39:44.531618894 +0100
+++ b/modules/http2/mod_proxy_http2.c	2024-10-30 21:40:01.059958617 +0100
@@ -50,8 +50,7 @@
 
 typedef struct h2_proxy_ctx {
     const char *id;
-    conn_rec *master;
-    conn_rec *owner;
+    conn_rec *cfront;
     apr_pool_t *pool;
     server_rec *server;
     const char *proxy_func;
@@ -66,10 +65,10 @@
     unsigned is_ssl : 1;
     
     request_rec *r;            /* the request processed in this ctx */
-    apr_status_t r_status;     /* status of request work */
+    int r_status;              /* status of request work */
     int r_done;                /* request was processed, not necessarily successfully */
     int r_may_retry;           /* request may be retried */
-    h2_proxy_session *session; /* current http2 session against backend */
+    int has_reusable_session;  /* http2 session is live and clean */
 } h2_proxy_ctx;
 
 static int h2_proxy_post_config(apr_pool_t *p, apr_pool_t *plog,
@@ -154,29 +153,46 @@
         if (apr_table_get(r->notes, "proxy-nocanon")) {
             path = url;   /* this is the raw path */
         }
+        else if (apr_table_get(r->notes, "proxy-noencode")) {
+            path = url;   /* this is the encoded path already */
+            search = r->args;
+        }
         else {
+#ifdef PROXY_CANONENC_NOENCODEDSLASHENCODING
+            core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
+            int flags = d->allow_encoded_slashes && !d->decode_encoded_slashes ? PROXY_CANONENC_NOENCODEDSLASHENCODING : 0;
+
+            path = ap_proxy_canonenc_ex(r->pool, url, (int)strlen(url),
+                                        enc_path, flags, r->proxyreq);
+#else
             path = ap_proxy_canonenc(r->pool, url, (int)strlen(url),
                                      enc_path, 0, r->proxyreq);
-            search = r->args;
-            if (search && *(ap_scan_vchar_obstext(search))) {
-                /*
-                 * We have a raw control character or a ' ' in r->args.
-                 * Correct encoding was missed.
-                 */
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
-                              "To be forwarded query string contains control "
-                              "characters or spaces");
-                return HTTP_FORBIDDEN;
+#endif
+            if (!path) {
+                return HTTP_BAD_REQUEST;
             }
+            search = r->args;
         }
         break;
     case PROXYREQ_PROXY:
         path = url;
         break;
     }
-
-    if (path == NULL) {
-        return HTTP_BAD_REQUEST;
+    /*
+     * If we have a raw control character or a ' ' in nocanon path or
+     * r->args, correct encoding was missed.
+     */
+    if (path == url && *ap_scan_vchar_obstext(path)) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10420)
+                      "To be forwarded path contains control "
+                      "characters or spaces");
+        return HTTP_FORBIDDEN;
+    }
+    if (search && *ap_scan_vchar_obstext(search)) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10412)
+                      "To be forwarded query string contains control "
+                      "characters or spaces");
+        return HTTP_FORBIDDEN;
     }
 
     if (port != def_port) {
@@ -215,82 +231,81 @@
 }
 
 static void request_done(h2_proxy_ctx *ctx, request_rec *r,
-                         apr_status_t status, int touched)
+                         apr_status_t status, int touched, int error_code)
 {   
     if (r == ctx->r) {
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, r->connection, 
-                      "h2_proxy_session(%s): request done, touched=%d",
-                      ctx->id, touched);
+                      "h2_proxy_session(%s): request done, touched=%d, error=%d",
+                      ctx->id, touched, error_code);
         ctx->r_done = 1;
         if (touched) ctx->r_may_retry = 0;
-        ctx->r_status = ((status == APR_SUCCESS)? APR_SUCCESS
-                         : HTTP_SERVICE_UNAVAILABLE);
+        ctx->r_status = error_code? HTTP_BAD_GATEWAY :
+            ((status == APR_SUCCESS)? OK :
+             ap_map_http_request_error(status, HTTP_SERVICE_UNAVAILABLE));
     }
 }    
 
 static void session_req_done(h2_proxy_session *session, request_rec *r,
-                             apr_status_t status, int touched)
+                             apr_status_t status, int touched, int error_code)
 {
-    request_done(session->user_data, r, status, touched);
+    request_done(session->user_data, r, status, touched, error_code);
 }
 
 static apr_status_t ctx_run(h2_proxy_ctx *ctx) {
     apr_status_t status = OK;
+    h2_proxy_session *session;
     int h2_front;
     
     /* Step Four: Send the Request in a new HTTP/2 stream and
      * loop until we got the response or encounter errors.
      */
-    h2_front = is_h2? is_h2(ctx->owner) : 0;
-    ctx->session = h2_proxy_session_setup(ctx->id, ctx->p_conn, ctx->conf,
-                                          h2_front, 30, 
-                                          h2_proxy_log2((int)ctx->req_buffer_size), 
-                                          session_req_done);
-    if (!ctx->session) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, 
+    ctx->has_reusable_session = 0; /* don't know yet */
+    h2_front = is_h2? is_h2(ctx->cfront) : 0;
+    session = h2_proxy_session_setup(ctx->id, ctx->p_conn, ctx->conf,
+                                     h2_front, 30,
+                                     h2_proxy_log2((int)ctx->req_buffer_size),
+                                     session_req_done);
+    if (!session) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->cfront,
                       APLOGNO(03372) "session unavailable");
         return HTTP_SERVICE_UNAVAILABLE;
     }
     
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(03373)
-                  "eng(%s): run session %s", ctx->id, ctx->session->id);
-    ctx->session->user_data = ctx;
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->cfront, APLOGNO(03373)
+                  "eng(%s): run session %s", ctx->id, session->id);
+    session->user_data = ctx;
     
     ctx->r_done = 0;
-    add_request(ctx->session, ctx->r);
+    add_request(session, ctx->r);
     
-    while (!ctx->master->aborted && !ctx->r_done) {
+    while (!ctx->cfront->aborted && !ctx->r_done) {
     
-        status = h2_proxy_session_process(ctx->session);
+        status = h2_proxy_session_process(session);
         if (status != APR_SUCCESS) {
             /* Encountered an error during session processing */
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, 
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->cfront,
                           APLOGNO(03375) "eng(%s): end of session %s", 
-                          ctx->id, ctx->session->id);
+                          ctx->id, session->id);
             /* Any open stream of that session needs to
              * a) be reopened on the new session iff safe to do so
              * b) reported as done (failed) otherwise
              */
-            h2_proxy_session_cleanup(ctx->session, session_req_done);
+            h2_proxy_session_cleanup(session, session_req_done);
             goto out;
         }
     }
     
 out:
-    if (ctx->master->aborted) {
+    if (ctx->cfront->aborted) {
         /* master connection gone */
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, 
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->cfront,
                       APLOGNO(03374) "eng(%s): master connection gone", ctx->id);
         /* cancel all ongoing requests */
-        h2_proxy_session_cancel_all(ctx->session);
-        h2_proxy_session_process(ctx->session);
-        if (!ctx->master->aborted) {
-            status = ctx->r_status = APR_SUCCESS;
-        }
+        h2_proxy_session_cancel_all(session);
+        h2_proxy_session_process(session);
     }
-    
-    ctx->session->user_data = NULL;
-    ctx->session = NULL;
+    ctx->has_reusable_session = h2_proxy_session_is_reusable(session);
+    session->user_data = NULL;
     return status;
 }
 
@@ -301,7 +316,7 @@
                                const char *proxyname,
                                apr_port_t proxyport)
 {
-    const char *proxy_func, *task_id;
+    const char *proxy_func;
     char *locurl = url, *u;
     apr_size_t slen;
     int is_ssl = 0;
@@ -334,12 +349,9 @@
             return DECLINED;
     }
 
-    task_id = apr_table_get(r->connection->notes, H2_TASK_ID_NOTE);
-    
     ctx = apr_pcalloc(r->pool, sizeof(*ctx));
-    ctx->master = r->connection->master? r->connection->master : r->connection;
-    ctx->id = task_id? task_id : apr_psprintf(r->pool, "%ld", (long)ctx->master->id);
-    ctx->owner = r->connection;
+    ctx->id = apr_psprintf(r->pool, "%ld", (long)r->connection->id);
+    ctx->cfront = r->connection;
     ctx->pool = r->pool;
     ctx->server = r->server;
     ctx->proxy_func = proxy_func;
@@ -352,7 +364,7 @@
     ctx->r_done = 0;
     ctx->r_may_retry =  1;
     
-    ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, ctx);
+    ap_set_module_config(ctx->cfront->conn_config, &proxy_http2_module, ctx);
 
     /* scheme says, this is for us. */
     apr_table_setn(ctx->r->notes, H2_PROXY_REQ_URL_NOTE, url);
@@ -360,7 +372,7 @@
                   "H2: serving URL %s", url);
     
 run_connect:    
-    if (ctx->master->aborted) goto cleanup;
+    if (ctx->cfront->aborted) goto cleanup;
 
     /* Get a proxy_conn_rec from the worker, might be a new one, might
      * be one still open from another request, or it might fail if the
@@ -388,7 +400,7 @@
      * backend->hostname. */
     if (ap_proxy_connect_backend(ctx->proxy_func, ctx->p_conn, ctx->worker, 
                                  ctx->server)) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(03352)
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->cfront, APLOGNO(03352)
                       "H2: failed to make connection to backend: %s",
                       ctx->p_conn->hostname);
         goto cleanup;
@@ -397,11 +409,11 @@
     /* Step Three: Create conn_rec for the socket we have open now. */
     status = ap_proxy_connection_create_ex(ctx->proxy_func, ctx->p_conn, ctx->r);
     if (status != OK) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, APLOGNO(03353)
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->cfront, APLOGNO(03353)
                       "setup new connection: is_ssl=%d %s %s %s", 
                       ctx->p_conn->is_ssl, ctx->p_conn->ssl_hostname, 
                       locurl, ctx->p_conn->hostname);
-        ctx->r_status = status;
+        ctx->r_status = ap_map_http_request_error(status, HTTP_SERVICE_UNAVAILABLE);
         goto cleanup;
     }
     
@@ -412,10 +424,10 @@
                        "proxy-request-alpn-protos", "h2");
     }
 
-    if (ctx->master->aborted) goto cleanup;
+    if (ctx->cfront->aborted) goto cleanup;
     status = ctx_run(ctx);
 
-    if (ctx->r_status != APR_SUCCESS && ctx->r_may_retry && !ctx->master->aborted) {
+    if (ctx->r_status != OK && ctx->r_may_retry && !ctx->cfront->aborted) {
         /* Not successfully processed, but may retry, tear down old conn and start over */
         if (ctx->p_conn) {
             ctx->p_conn->close = 1;
@@ -429,15 +441,16 @@
         if (reconnects < 2) {
             goto run_connect;
         } 
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(10023)
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->cfront, APLOGNO(10023)
                       "giving up after %d reconnects, request-done=%d",
                       reconnects, ctx->r_done);
     }
     
 cleanup:
     if (ctx->p_conn) {
-        if (status != APR_SUCCESS) {
-            /* close socket when errors happened or session shut down (EOF) */
+        if (status != APR_SUCCESS || !ctx->has_reusable_session) {
+            /* close socket when errors happened or session is not "clean",
+             * meaning in a working condition with no open streams */
             ctx->p_conn->close = 1;
         }
 #if AP_MODULE_MAGIC_AT_LEAST(20140207, 2)
@@ -447,9 +460,15 @@
         ctx->p_conn = NULL;
     }
 
-    ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, NULL);
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, 
+    ap_set_module_config(ctx->cfront->conn_config, &proxy_http2_module, NULL);
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->cfront,
                   APLOGNO(03377) "leaving handler");
+    if (ctx->r_status != OK) {
+        ap_die(ctx->r_status, r);
+    }
+    else if (status != APR_SUCCESS) {
+        ap_die(ap_map_http_request_error(status, HTTP_SERVICE_UNAVAILABLE), r);
+    }
     return ctx->r_status;
 }
 
