]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
*) mod_http2: new directive "H2HeaderStrictness" to control the compliance
authorStefan Eissing <icing@apache.org>
Mon, 26 Sep 2022 12:29:47 +0000 (12:29 +0000)
committerStefan Eissing <icing@apache.org>
Mon, 26 Sep 2022 12:29:47 +0000 (12:29 +0000)
     level of header checks as defined in the HTTP/2 RFCs. Default is 7540.
     9113 activates the checks for forbidden leading/trailing whitespace in
     field values (available from nghttp2 v1.50.0 on).

   - source sync with github version
   - fix for keepalive idle wait in mpm_worker setup
   - ensuring EOS when secondary connection has been handled
   - fixed race in late input EOS arrival when stream was
     already scheduled for execution.

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

21 files changed:
changes-entries/h2_header_strictness.txt [new file with mode: 0644]
docs/manual/mod/mod_http2.xml
modules/http2/config2.m4
modules/http2/h2.h
modules/http2/h2_bucket_beam.c
modules/http2/h2_bucket_beam.h
modules/http2/h2_c1.c
modules/http2/h2_c1_io.c
modules/http2/h2_c2.c
modules/http2/h2_config.c
modules/http2/h2_config.h
modules/http2/h2_mplx.c
modules/http2/h2_mplx.h
modules/http2/h2_request.c
modules/http2/h2_session.c
modules/http2/h2_stream.c
modules/http2/h2_util.c
modules/http2/h2_version.h
modules/http2/h2_workers.c
modules/http2/h2_workers.h
test/modules/http2/test_105_timeout.py

diff --git a/changes-entries/h2_header_strictness.txt b/changes-entries/h2_header_strictness.txt
new file mode 100644 (file)
index 0000000..ab39479
--- /dev/null
@@ -0,0 +1,4 @@
+  *) mod_http2: new directive "H2HeaderStrictness" to control the compliance
+     level of header checks as defined in the HTTP/2 RFCs. Default is 7540.
+     9113 activates the checks for forbidden leading/trailing whitespace in
+     field values (available from nghttp2 v1.50.0 on).
index 876d6fe7c4733d2122b31875fd5d73ce98b4c1ae..0eba28f415e695578bea3a2f5fe1f7212fc61ee8 100644 (file)
@@ -1024,4 +1024,25 @@ H2TLSCoolDownSecs 0
         </usage>
     </directivesynopsis>
 
+    <directivesynopsis>
+        <name>H2HeaderStrictness</name>
+        <description>Strictness applied to head checks, via RFC number.</description>
+        <syntax>H2HeaderStrictness <var>rfc-number</var></syntax>
+        <default>7540</default>
+        <contextlist>
+            <context>server config</context>
+            <context>virtual host</context>
+        </contextlist>
+        <compatibility>Available in version 2.5.1 and later.</compatibility>
+
+        <usage>
+            <p>
+                <directive>H2HeaderStrictness</directive> specifies the compliance
+                checks for header values, as specified in the IETF RFC number.
+                '7540' is the original HTTP/2 RFC, 9113 is the updated version that
+                disallows leading and trialing spaces in fields.
+            </p>
+        </usage>
+    </directivesynopsis>
+
 </modulesynopsis>
index bec019b77bb50e88a88042dc5d4e0d5f59061735..87d4cc2ae2dca97da51c29ac3afbab226470ce9c 100644 (file)
@@ -163,6 +163,9 @@ dnl # nghttp2 >= 1.15.0: get/set stream window sizes
 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
index 179c55db23a2ce025c2e2711c4470be057d2d5c1..f1017480ed072b22a9e373c9668c311e5200f3d0 100644 (file)
@@ -26,39 +26,6 @@ struct h2_stream;
  * When apr pollsets can poll file descriptors (e.g. pipes),
  * we use it for polling stream input/output.
  */
-/* Disabel for now. Measurements on a macOS dev machine
- * show up to 25% performance loss with pollsets. See
- * 12 connection case with 2 requests in flight:
- * 28124 req/s with pollsets vs. 38895 without.
- *
- * trunk (pollsets):
- *  1k files, 1k size, *conn, 100k req, h2 (req/s)
- *  max requests      1c      2c      6c     12c
- * h2   1 100000    6045   11501   28090   29320
- * h2   2 100000   10040   17425   28307   28124
- * h2   6 100000   14107   19354   25256   23752
- * h2  20 100000   16073   21376   22334   20671
- * h1   1 100000    8009   15691   37003   44808
- *
- * trunk (no pollsets):
- *  1k files, 1k size, *conn, 100k req, h2 (req/s)
- *  max requests      1c      2c      6c     12c
- * h2   1 100000    6330   12197   30259   37462
- * h2   2 100000   10548   18694   35870   38895
- * h2   6 100000   15988   23974   32073   27346
- * h2  20 100000   17630   26481   30788   28301
- * h1   1 100000    7996   15789   37108   45358
- *
- * My gut feeling is that there is just too much
- * administrative overhead with removing/adding files
- * to pollsets because secondary connection are
- * used for only a single request in the current
- * implementation.
- *
- * This needs to be revisisted when c2 connections
- * are used for many consecutive requests where
- * pollsets stay unchanged much longer.
- */
 #ifdef H2_NO_PIPES
 #define H2_USE_PIPES            0
 #else
@@ -188,6 +155,7 @@ struct h2_request {
     apr_table_t *headers;
 
     apr_time_t request_time;
+    unsigned int chunked : 1;   /* iff request body needs to be forwarded as chunked */
     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
index 657d62f825a4daffa9aebb70224aabc8857093a7..524d93bc937abb2c92e969e04047dbbe78c8bc63 100644 (file)
@@ -53,7 +53,7 @@
     } while (0)
 
 
-static int is_empty(h2_bucket_beam *beam);
+static int buffer_is_empty(h2_bucket_beam *beam);
 static apr_off_t get_buffered_data_len(h2_bucket_beam *beam);
 
 static int h2_blist_count(h2_blist *blist)
@@ -78,7 +78,7 @@ static int h2_blist_count(h2_blist *blist)
                           "BEAM[%s,%s%sdata=%ld,buckets(send/consumed)=%d/%d]: %s %s", \
                           (beam)->name, \
                           (beam)->aborted? "aborted," : "", \
-                          is_empty(beam)? "empty," : "", \
+                          buffer_is_empty(beam)? "empty," : "", \
                           (long)get_buffered_data_len(beam), \
                           h2_blist_count(&(beam)->buckets_to_send), \
                           h2_blist_count(&(beam)->buckets_consumed), \
@@ -181,6 +181,9 @@ static apr_status_t wait_not_empty(h2_bucket_beam *beam, conn_rec *c, apr_read_t
         if (beam->aborted) {
             rv = APR_ECONNABORTED;
         }
+        else if (beam->closed) {
+            rv = APR_EOF;
+        }
         else if (APR_BLOCK_READ != block) {
             rv = APR_EAGAIN;
         }
@@ -374,6 +377,24 @@ void h2_beam_abort(h2_bucket_beam *beam, conn_rec *c)
     apr_thread_mutex_unlock(beam->lock);
 }
 
+void h2_beam_close(h2_bucket_beam *beam, conn_rec *c)
+{
+    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_brigade *bb,
                                   apr_read_type_e block,
@@ -584,6 +605,8 @@ transfer:
         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);
             }
             else if (APR_BUCKET_IS_FLUSH(bsender)) {
@@ -677,6 +700,9 @@ transfer:
     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) {
@@ -767,17 +793,12 @@ apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam)
     return l;
 }
 
-static int is_empty(h2_bucket_beam *beam)
-{
-    return H2_BLIST_EMPTY(&beam->buckets_to_send);
-}
-
 int h2_beam_empty(h2_bucket_beam *beam)
 {
     int empty = 1;
 
     apr_thread_mutex_lock(beam->lock);
-    empty = is_empty(beam);
+    empty = buffer_is_empty(beam);
     apr_thread_mutex_unlock(beam->lock);
     return empty;
 }
index 934a893d9915f8e681f6b4626fc9f4d3eee9f0e7..2a9d5f0f01856cf7c14bd1881865c464febbebbe 100644 (file)
@@ -53,6 +53,7 @@ struct h2_bucket_beam {
     apr_interval_time_t timeout;
 
     int aborted;
+    int closed;
     int tx_mem_limits; /* only memory size counts on transfers */
     int copy_files;
 
@@ -156,6 +157,14 @@ int h2_beam_empty(h2_bucket_beam *beam);
  */
 void h2_beam_abort(h2_bucket_beam *beam, conn_rec *c);
 
+/**
+ * Close the beam. Make certain an EOS is sent.
+ *
+ * @param beam the beam to abort
+ * @param c the connection the caller is working with
+ */
+void h2_beam_close(h2_bucket_beam *beam, conn_rec *c);
+
 /**
  * Set/get the timeout for blocking sebd/receive operations.
  */
index 7662a0e4fec7ebca12e9d9ea17de825428092f56..afb26fc0737a48a977ca4456dac56e64ea300f84 100644 (file)
@@ -78,8 +78,8 @@ apr_status_t h2_c1_child_init(apr_pool_t *pool, server_rec *s)
 
 void h2_c1_child_stopping(apr_pool_t *pool, int graceful)
 {
-    if (workers && graceful) {
-        h2_workers_graceful_shutdown(workers);
+    if (workers) {
+        h2_workers_shutdown(workers, graceful);
     }
 }
 
index 2300c61201884fbf0d26b7f053b4111093ddef11..ade8836635887b4d477ed0ed1b515600ba1df611 100644 (file)
@@ -488,9 +488,15 @@ static apr_status_t read_and_feed(h2_session *session)
                         APR_NONBLOCK_READ, bytes_requested);
 
     if (APR_SUCCESS == rv) {
-        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;
+        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;
 }
index f11a53cf256e77ecaf38ccb5f95867075ae8a485..ec5d3a99fdb52e789694b1262634495553ce31e2 100644 (file)
@@ -183,7 +183,8 @@ static apr_status_t h2_c2_filter_in(ap_filter_t* f,
     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);
+                      conn_ctx->id, conn_ctx->stream_id, mode, block,
+                      (long)readbytes);
     }
 
     if (!fctx) {
index da1cf79a0714e0afea693e0adea1fb6e4a1780cb..026e255fb5ff69f4ab4f6d79c6bce3963b5e1c21 100644 (file)
@@ -75,6 +75,7 @@ typedef struct h2_config {
     int padding_always;
     int output_buffered;
     apr_interval_time_t stream_timeout;/* beam timeout */
+    int header_strictness;           /* which rfc to follow when verifying header */
 } h2_config;
 
 typedef struct h2_dir_config {
@@ -110,6 +111,7 @@ static h2_config defconf = {
     1,                      /* padding always */
     1,                      /* stream output buffered */
     -1,                     /* beam timeout */
+    7540,                   /* header strictness */
 };
 
 static h2_dir_config defdconf = {
@@ -153,6 +155,7 @@ void *h2_config_create_svr(apr_pool_t *pool, server_rec *s)
     conf->padding_always       = DEF_VAL;
     conf->output_buffered      = DEF_VAL;
     conf->stream_timeout       = DEF_VAL;
+    conf->header_strictness    = DEF_VAL;
     return conf;
 }
 
@@ -195,6 +198,7 @@ static void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
     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->header_strictness    = H2_CONFIG_GET(add, base, header_strictness);
     return n;
 }
 
@@ -278,6 +282,8 @@ static apr_int64_t h2_srv_config_geti64(const h2_config *conf, h2_config_var_t v
             return H2_CONFIG_GET(conf, &defconf, output_buffered);
         case H2_CONF_STREAM_TIMEOUT:
             return H2_CONFIG_GET(conf, &defconf, stream_timeout);
+        case H2_CONF_HEADER_STRICTNESS:
+            return H2_CONFIG_GET(conf, &defconf, header_strictness);
         default:
             return DEF_VAL;
     }
@@ -337,6 +343,9 @@ static void h2_srv_config_seti(h2_config *conf, h2_config_var_t var, int val)
         case H2_CONF_OUTPUT_BUFFER:
             H2_CONFIG_SET(conf, output_buffered, val);
             break;
+        case H2_CONF_HEADER_STRICTNESS:
+            H2_CONFIG_SET(conf, header_strictness, val);
+            break;
         default:
             break;
     }
@@ -700,6 +709,24 @@ static const char *h2_conf_set_modern_tls_only(cmd_parms *cmd,
     return "value must be On or Off";
 }
 
+static const char *h2_conf_set_header_strictness(
+    cmd_parms *cmd, void *dirconf, const char *value)
+{
+    if (!strcasecmp(value, "highest")) {
+        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_HEADER_STRICTNESS, 1000000);
+        return NULL;
+    }
+    else if (!strcasecmp(value, "rfc7540")) {
+        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_HEADER_STRICTNESS, 7540);
+        return NULL;
+    }
+    else if (!strcasecmp(value, "rfc9113")) {
+        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_HEADER_STRICTNESS, 9113);
+        return NULL;
+    }
+    return "value must be one of highest|rfc7540|rfc9113";
+}
+
 static const char *h2_conf_set_upgrade(cmd_parms *cmd,
                                        void *dirconf, const char *value)
 {
@@ -934,6 +961,8 @@ const command_rec h2_cmds[] = {
                   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("H2HeaderStrictness", h2_conf_set_header_strictness, NULL,
+                  RSRC_CONF, "set strictness of header value checks"),
     AP_END_CMD
 };
 
index 6d2e65f926a5683b6e61c3011f9e2f056192bb18..d3d47386a8dc383480c96bcf9cedf6b4acf7921b 100644 (file)
@@ -43,6 +43,7 @@ typedef enum {
     H2_CONF_PADDING_ALWAYS,
     H2_CONF_OUTPUT_BUFFER,
     H2_CONF_STREAM_TIMEOUT,
+    H2_CONF_HEADER_STRICTNESS
 } h2_config_var_t;
 
 struct apr_hash_t;
index b83c338123dae57429fe9c249da2f564da0fffb9..ffc17ffb4bc2400d953aca428f242b468b8d2503 100644 (file)
@@ -60,6 +60,7 @@ typedef struct {
 
 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);
@@ -306,7 +307,7 @@ h2_mplx *h2_mplx_c1_create(int child_num, apr_uint32_t id, h2_stream *stream0,
     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_max = H2MIN((int)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);
@@ -333,11 +334,13 @@ h2_mplx *h2_mplx_c1_create(int child_num, apr_uint32_t id, h2_stream *stream0,
 
     m->scratch_r = apr_pcalloc(m->pool, sizeof(*m->scratch_r));
     m->max_spare_transits = 3;
-    m->c2_transits = apr_array_make(m->pool, m->max_spare_transits, sizeof(h2_c2_transit*));
+    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-%d", (int)m->id),
-                                      c2_prod_next, c2_prod_done, m);
+                                      c2_prod_next, c2_prod_done,
+                                      workers_shutdown, m);
     return m;
 
 failure:
@@ -445,7 +448,7 @@ void h2_mplx_c1_destroy(h2_mplx *m)
                   H2_MPLX_MSG(m, "start release"));
     /* How to shut down a h2 connection:
      * 0. abort and tell the workers that no more work will come from us */
-    m->aborted = 1;
+    m->shutdown = m->aborted = 1;
 
     H2_MPLX_ENTER_ALWAYS(m);
 
@@ -633,7 +636,7 @@ static apr_status_t c1_process_stream(h2_mplx *m,
                                       h2_stream_pri_cmp_fn *cmp,
                                       h2_session *session)
 {
-    apr_status_t rv;
+    apr_status_t rv = APR_SUCCESS;
 
     if (m->aborted) {
         rv = APR_ECONNABORTED;
@@ -650,9 +653,6 @@ static apr_status_t c1_process_stream(h2_mplx *m,
                       r->method, r->scheme, r->authority, r->path);
     }
 
-    rv = h2_stream_setup_input(stream);
-    if (APR_SUCCESS != rv) goto cleanup;
-
     stream->scheduled = 1;
     h2_ihash_add(m->streams, stream);
     if (h2_stream_is_ready(stream)) {
@@ -787,23 +787,19 @@ static apr_status_t c2_setup_io(h2_mplx *m, conn_rec *c2, h2_stream *stream, h2_
         h2_beam_on_was_empty(conn_ctx->beam_out, c2_beam_output_write_notify, c2);
     }
 
-    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);
-    }
+    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
-    if (stream->input) {
-        if (!conn_ctx->pipe_in[H2_PIPE_OUT]) {
-            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;
-        }
+    if (!conn_ctx->pipe_in[H2_PIPE_OUT]) {
+        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;
     }
 #else
     memset(&conn_ctx->pipe_in, 0, sizeof(conn_ctx->pipe_in));
@@ -962,6 +958,22 @@ static void c2_prod_done(void *baton, conn_rec *c2)
     H2_MPLX_LEAVE(m);
 }
 
+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);
+}
+
 /*******************************************************************************
  * h2_mplx DoS protection
  ******************************************************************************/
@@ -1056,31 +1068,6 @@ apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id)
     return status;
 }
 
-apr_status_t h2_mplx_c1_input_closed(h2_mplx *m, int stream_id)
-{
-    h2_stream *stream;
-    h2_conn_ctx_t *c2_ctx;
-    apr_status_t status = APR_EAGAIN;
-
-    H2_MPLX_ENTER_ALWAYS(m);
-    stream = h2_ihash_get(m->streams, stream_id);
-    if (stream && (c2_ctx = h2_conn_ctx_get(stream->c2))) {
-        if (c2_ctx->beam_in) {
-            apr_bucket_brigade *tmp =apr_brigade_create(
-                stream->pool, m->c1->bucket_alloc);
-            apr_bucket *eos = apr_bucket_eos_create(m->c1->bucket_alloc);
-            apr_off_t written;
-
-            APR_BRIGADE_INSERT_TAIL(tmp, eos);
-            status = h2_beam_send(c2_ctx->beam_in, m->c1,
-                      tmp, APR_BLOCK_READ, &written);
-            apr_brigade_destroy(tmp);
-        }
-    }
-    H2_MPLX_LEAVE(m);
-    return status;
-}
-
 static apr_status_t mplx_pollset_create(h2_mplx *m)
 {
     /* stream0 output only */
index e056acacdd10adf56013cc99e9c1462ad1ee8cc2..2382e46cf4d1c3d9adab07dc8363f39acb2f9c2f 100644 (file)
@@ -63,7 +63,8 @@ struct h2_mplx {
     struct h2_stream *stream0;      /* HTTP/2's stream 0 */
     server_rec *s;                  /* server for master conn */
 
-    int aborted;
+    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 */
 
@@ -199,15 +200,6 @@ apr_status_t h2_mplx_c1_streams_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx)
  */
 apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id);
 
-/**
- * Input for stream has been closed. Notify a possibly started
- * and waiting stream by sending an EOS.
- * @param m the mplx
- * @param stream_id the closed stream
- * @return APR_SUCCESS iff EOS was sent, APR_EAGAIN if not necessary
- */
-apr_status_t h2_mplx_c1_input_closed(h2_mplx *m, int stream_id);
-
 /**
  * Get readonly access to a stream for a secondary connection.
  */
index aa54351969000431dca0bee08d3f0e46f15f4590..0a181b86a6e16f8e44a0deb25ed92b92ab5b7a1b 100644 (file)
@@ -94,22 +94,21 @@ apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool,
      * the URL for the request. r->hostname has stripped any port info that
      * might have been present. Do we need to add it?
      */
-    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) {
-        /* client sent an absolute URI, with no port in the authority.
-         * Use that also in the h2 request. */
-    }
-    else {
-        /* request came in as relative uri, meaning the client did not specify
-         * a port number and we have to guess which one to use. */
-        apr_port_t defport = apr_uri_port_of_scheme(scheme);
-        apr_port_t port = ap_get_server_port(r);
-
-        if (defport != port) {
-            authority = apr_psprintf(pool, "%s:%d", authority, (int)port);
+    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);
+            }
         }
     }
 
index 20c35beef1d76d4ee407ed8e388554ba6dfb9765..1056d4b356a315a7b9284906a6c2d4f376eb64f6 100644 (file)
 
 #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"
@@ -546,6 +550,9 @@ static int on_send_data_cb(nghttp2_session *ngh2,
     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 {
@@ -875,7 +882,12 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *
      * 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;
@@ -941,6 +953,15 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *
      * 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. */
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                  "nghttp2_session_server_new: header strictness is %d",
+                  h2_config_sgeti(s, H2_CONF_HEADER_STRICTNESS));
+    nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(options,
+        h2_config_sgeti(s, H2_CONF_HEADER_STRICTNESS) < 9113);
 #endif
     rv = nghttp2_session_server_new2(&session->ngh2, callbacks,
                                      session, options);
@@ -1843,10 +1864,35 @@ apr_status_t h2_session_process(h2_session *session, int async)
                  * connection handling when nothing really happened. */
                 h2_c1_read(session);
                 if (H2_SESSION_ST_IDLE == session->state) {
-                    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
-                                  H2_SSSN_LOG(APLOGNO(10306), session,
-                                  "returning to mpm c1 monitoring"));
-                    goto leaving;
+                    if (async) {
+                        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
+                                      H2_SSSN_LOG(APLOGNO(10306), session,
+                                      "returning to mpm c1 monitoring"));
+                        goto leaving;
+                    }
+                    else {
+                        /* 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;
+                        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 {
@@ -1872,14 +1918,20 @@ apr_status_t h2_session_process(h2_session *session, int async)
                 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");
+            }
             /* 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 */
             status = h2_mplx_c1_poll(session->mplx, session->s->timeout,
                                      on_stream_input, on_stream_output, session);
             if (APR_STATUS_IS_TIMEUP(status)) {
-                h2_session_dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, status, NULL);
-                break;
+                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);
index 2c16290078e2446713180771ee6b268bad2b2df1..a884af4a40a676ccc9f4d964050257ce7cc899d9 100644 (file)
@@ -199,8 +199,6 @@ apr_status_t h2_stream_setup_input(h2_stream *stream)
 {
     /* already done? */
     if (stream->input != NULL) goto cleanup;
-    /* if already closed and nothing was every sent, leave it */
-    if (stream->input_closed && !stream->in_buffer) goto cleanup;
 
     ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
                   H2_STRM_MSG(stream, "setup input beam"));
@@ -220,9 +218,6 @@ static apr_status_t input_flush(h2_stream *stream)
 
     ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
                   H2_STRM_MSG(stream, "flush input"));
-    if (!stream->input) {
-        h2_stream_setup_input(stream);
-    }
     status = h2_beam_send(stream->input, stream->session->c1,
                           stream->in_buffer, APR_BLOCK_READ, &written);
     stream->in_last_write = apr_time_now();
@@ -276,18 +271,13 @@ static apr_status_t close_input(h2_stream *stream)
     }
 
     stream->input_closed = 1;
-    if (stream->in_buffer) {
-        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);
-    }
-    else {
-        rv = h2_mplx_c1_input_closed(stream->session->mplx, stream->id);
-        if (APR_SUCCESS == rv) {
-            h2_stream_dispatch(stream, H2_SEV_IN_DATA_PENDING);
-        }
-    }
+    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;
 }
@@ -549,6 +539,7 @@ h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session,
                 stream->session->ngh2, stream->id);
     }
 #endif
+    h2_stream_setup_input(stream);
     ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
                   H2_STRM_LOG(APLOGNO(03082), stream, "created"));
     on_state_enter(stream);
index 8ac0b1a11e76e85f69463618c364efd2059f68c5..90d4e7e176af78395ad229450a90636939401165 100644 (file)
@@ -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>
@@ -56,7 +56,7 @@ unsigned char h2_log2(int n)
     if (!(n & 0x80000000u)) {
         lz += 1;
     }
-    
+
     return 31 - lz;
 }
 
@@ -85,7 +85,7 @@ void h2_util_camel_case_header(char *s, size_t len)
             if (s[i] >= 'a' && s[i] <= 'z') {
                 s[i] -= 'a' - 'A';
             }
-            
+
             start = 0;
         }
         else if (s[i] == '-') {
@@ -101,9 +101,9 @@ void h2_util_camel_case_header(char *s, size_t len)
 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 */
@@ -129,7 +129,7 @@ static const unsigned char BASE64URL_CHARS[] = {
 
 #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;
@@ -137,14 +137,14 @@ apr_size_t h2_util_base64url_decode(const char **decoded, const char *encoded,
     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) {
@@ -178,14 +178,14 @@ apr_size_t h2_util_base64url_decode(const char **decoded, const char *encoded,
     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) );
@@ -193,7 +193,7 @@ const char *h2_util_base64url_encode(const char *data,
         *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)) {
@@ -249,7 +249,7 @@ typedef struct {
     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;
@@ -307,7 +307,7 @@ size_t h2_ihash_shift(h2_ihash_t *ih, void **buffer, size_t max)
 {
     collect_ctx ctx;
     size_t i;
-    
+
     ctx.ih = ih;
     ctx.buffer = buffer;
     ctx.max = max;
@@ -325,9 +325,9 @@ size_t h2_ihash_shift(h2_ihash_t *ih, void **buffer, size_t max)
 
 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)
@@ -353,7 +353,7 @@ int h2_iq_count(h2_iqueue *q)
 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;
     }
@@ -363,7 +363,7 @@ int h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx)
     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);
@@ -384,7 +384,7 @@ int h2_iq_remove(h2_iqueue *q, int sid)
             break;
         }
     }
-    
+
     if (i < q->nelts) {
         ++i;
         for (; i < q->nelts; ++i) {
@@ -409,14 +409,14 @@ void h2_iq_sort(h2_iqueue *q, h2_iq_cmp *cmp, void *ctx)
      */
     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
@@ -432,15 +432,15 @@ void h2_iq_sort(h2_iqueue *q, h2_iq_cmp *cmp, void *ctx)
 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;
 }
 
@@ -462,7 +462,7 @@ static void iq_grow(h2_iqueue *q, int nlen)
         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 */
@@ -483,11 +483,11 @@ static void iq_swap(h2_iqueue *q, int i, int j)
     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;
@@ -495,11 +495,11 @@ static int iq_bubble_up(h2_iqueue *q, int i, int top,
     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;
@@ -549,7 +549,7 @@ static apr_status_t fifo_destroy(void *data)
 static int index_of(h2_fifo *fifo, void *elem)
 {
     int i;
-    
+
     for (i = fifo->out; i != fifo->in; i = (i + 1) % fifo->capacity) {
         if (elem == fifo->elems[i]) {
             return i;
@@ -558,12 +558,12 @@ static int index_of(h2_fifo *fifo, void *elem)
     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;
@@ -591,7 +591,7 @@ static apr_status_t create_int(h2_fifo **pfifo, apr_pool_t *pool,
     }
     fifo->capacity = capacity;
     fifo->set = as_set;
-    
+
     *pfifo = fifo;
     apr_pool_cleanup_register(pool, fifo, fifo_destroy, apr_pool_cleanup_null);
 
@@ -667,7 +667,7 @@ static apr_status_t fifo_push_int(h2_fifo *fifo, void *elem, int block)
             return APR_EAGAIN;
         }
     }
-    
+
     fifo->elems[fifo->in++] = elem;
     if (fifo->in >= fifo->capacity) {
         fifo->in -= fifo->capacity;
@@ -682,7 +682,7 @@ static apr_status_t fifo_push_int(h2_fifo *fifo, void *elem, int block)
 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);
@@ -704,7 +704,7 @@ 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;
@@ -724,7 +724,7 @@ static apr_status_t pull_head(h2_fifo *fifo, void **pelem, int block)
 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);
@@ -746,11 +746,11 @@ static apr_status_t fifo_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx, int
 {
     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)) {
@@ -779,7 +779,7 @@ apr_status_t h2_fifo_try_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx)
 apr_status_t h2_fifo_remove(h2_fifo *fifo, void *elem)
 {
     apr_status_t rv;
-    
+
     if (fifo->aborted) {
         return APR_EOF;
     }
@@ -838,7 +838,7 @@ apr_status_t h2_fifo_remove(h2_fifo *fifo, void *elem)
         else {
             rv = APR_EAGAIN;
         }
-        
+
         apr_thread_mutex_unlock(fifo->lock);
     }
     return rv;
@@ -860,12 +860,12 @@ struct h2_ififo {
     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->capacity;
 }
 
-static apr_status_t ififo_destroy(void *data) 
+static apr_status_t ififo_destroy(void *data)
 {
     h2_ififo *fifo = data;
 
@@ -879,7 +879,7 @@ static apr_status_t ififo_destroy(void *data)
 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;
@@ -888,12 +888,12 @@ static int iindex_of(h2_ififo *fifo, int id)
     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;
@@ -921,7 +921,7 @@ static apr_status_t icreate_int(h2_ififo **pfifo, apr_pool_t *pool,
     }
     fifo->capacity = capacity;
     fifo->set = as_set;
-    
+
     *pfifo = fifo;
     apr_pool_cleanup_register(pool, fifo, ififo_destroy, apr_pool_cleanup_null);
 
@@ -992,7 +992,7 @@ static apr_status_t ififo_push_int(h2_ififo *fifo, int id, int block)
             return APR_EAGAIN;
         }
     }
-    
+
     ap_assert(fifo->count < fifo->capacity);
     fifo->elems[inth_index(fifo, fifo->count)] = id;
     ++fifo->count;
@@ -1005,7 +1005,7 @@ static apr_status_t ififo_push_int(h2_ififo *fifo, int id, int block)
 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);
@@ -1026,7 +1026,7 @@ apr_status_t h2_ififo_try_push(h2_ififo *fifo, int id)
 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;
@@ -1045,7 +1045,7 @@ static apr_status_t ipull_head(h2_ififo *fifo, int *pi, int block)
 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);
@@ -1067,7 +1067,7 @@ static apr_status_t ififo_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx,
 {
     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)) {
@@ -1096,7 +1096,7 @@ apr_status_t h2_ififo_try_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx)
 static apr_status_t ififo_remove(h2_ififo *fifo, int id)
 {
     int rc, i;
-    
+
     if (fifo->aborted) {
         return APR_EOF;
     }
@@ -1124,7 +1124,7 @@ static apr_status_t ififo_remove(h2_ififo *fifo, int id)
 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);
@@ -1135,7 +1135,7 @@ apr_status_t h2_ififo_remove(h2_ififo *fifo, int id)
 /*******************************************************************************
  * h2_util for apt_table_t
  ******************************************************************************/
+
 typedef struct {
     apr_size_t bytes;
     apr_size_t pair_extra;
@@ -1157,7 +1157,7 @@ static int count_bytes(void *x, const char *key, const char *value)
 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);
@@ -1169,8 +1169,8 @@ apr_size_t h2_util_table_bytes(apr_table_t *t, apr_size_t pair_extra)
  * h2_util for bucket brigades
  ******************************************************************************/
 
-static apr_status_t last_not_included(apr_bucket_brigade *bb, 
-                                      apr_off_t maxlen, 
+static apr_status_t last_not_included(apr_bucket_brigade *bb,
+                                      apr_off_t maxlen,
                                       apr_bucket **pend)
 {
     apr_bucket *b;
@@ -1178,10 +1178,10 @@ static apr_status_t last_not_included(apr_bucket_brigade *bb,
 
     if (maxlen >= 0) {
         /* Find the bucket, up to which we reach maxlen/mem bytes */
-        for (b = APR_BRIGADE_FIRST(bb); 
+        for (b = APR_BRIGADE_FIRST(bb);
              (b != APR_BRIGADE_SENTINEL(bb));
              b = APR_BUCKET_NEXT(b)) {
-            
+
             if (APR_BUCKET_IS_METADATA(b)) {
                 /* included */
             }
@@ -1194,12 +1194,12 @@ static apr_status_t last_not_included(apr_bucket_brigade *bb,
                         return status;
                     }
                 }
-                
+
                 if (maxlen == 0 && b->length > 0) {
                     *pend = b;
                     return status;
                 }
-                
+
                 if (APR_BUCKET_IS_FILE(b)
 #if APR_HAS_MMAP
                  || APR_BUCKET_IS_MMAP(b)
@@ -1221,17 +1221,17 @@ static apr_status_t last_not_included(apr_bucket_brigade *bb,
     return status;
 }
 
-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);
@@ -1252,7 +1252,7 @@ apr_status_t h2_brigade_concat_length(apr_bucket_brigade *dest,
                         return status;
                     }
                 }
-            
+
                 if (remain < b->length) {
                     apr_bucket_split(b, remain);
                 }
@@ -1265,19 +1265,19 @@ apr_status_t h2_brigade_concat_length(apr_bucket_brigade *dest,
     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 */
         }
@@ -1297,7 +1297,7 @@ apr_status_t h2_brigade_copy_length(apr_bucket_brigade *dest,
                         return status;
                     }
                 }
-            
+
                 if (remain < b->length) {
                     apr_bucket_split(b, remain);
                 }
@@ -1316,12 +1316,12 @@ apr_status_t h2_brigade_copy_length(apr_bucket_brigade *dest,
 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, &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))
@@ -1333,7 +1333,7 @@ int h2_util_has_eos(apr_bucket_brigade *bb, apr_off_t len)
     return 0;
 }
 
-apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb, 
+apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb,
                               apr_off_t *plen, int *peos)
 {
     apr_status_t status;
@@ -1369,7 +1369,7 @@ apr_size_t h2_util_bucket_print(char *buffer, apr_size_t bmax,
     if (sep && *sep) {
         off += apr_snprintf(buffer+off, bmax-off, "%s", sep);
     }
-    
+
     if (bmax <= off) {
         return off;
     }
@@ -1377,30 +1377,30 @@ apr_size_t h2_util_bucket_print(char *buffer, apr_size_t bmax,
         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)? 
+        off += apr_snprintf(buffer+off, bmax-off, "%s[%ld]",
+                            b->type->name,
+                            (long)(b->length == ((apr_size_t)-1)?
                                    -1 : 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 = " ";
             }
@@ -1416,7 +1416,7 @@ apr_size_t h2_util_bb_print(char *buffer, apr_size_t bmax,
 }
 
 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)
@@ -1426,10 +1426,10 @@ apr_status_t h2_append_brigade(apr_bucket_brigade *to,
     apr_status_t rv;
 
     *peos = 0;
-    
+
     while (!APR_BRIGADE_EMPTY(from)) {
         e = APR_BRIGADE_FIRST(from);
-        
+
         if (!should_append(e)) {
             goto leave;
         }
@@ -1440,7 +1440,7 @@ apr_status_t h2_append_brigade(apr_bucket_brigade *to,
                 continue;
             }
         }
-        else {        
+        else {
             if (remain > 0 && e->length == ((apr_size_t)-1)) {
                 const char *ign;
                 apr_size_t ilen;
@@ -1449,7 +1449,7 @@ apr_status_t h2_append_brigade(apr_bucket_brigade *to,
                     return rv;
                 }
             }
-            
+
             if (remain < e->length) {
                 if (remain <= 0) {
                     goto leave;
@@ -1457,7 +1457,7 @@ apr_status_t h2_append_brigade(apr_bucket_brigade *to,
                 apr_bucket_split(e, (apr_size_t)remain);
             }
         }
-        
+
         APR_BUCKET_REMOVE(e);
         APR_BRIGADE_INSERT_TAIL(to, e);
         len += e->length;
@@ -1492,8 +1492,8 @@ apr_off_t h2_brigade_mem_size(apr_bucket_brigade *bb)
 /*******************************************************************************
  * h2_ngheader
  ******************************************************************************/
-int h2_util_ignore_header(const char *name) 
+
+int h2_util_ignore_header(const char *name)
 {
     /* never forward, ch. 8.1.2.2 */
     return (H2_HD_MATCH_LIT_CS("connection", name)
@@ -1541,14 +1541,14 @@ static int add_header(ngh_ctx *ctx, const char *key, const char *value)
     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;
@@ -1558,7 +1558,7 @@ static int add_header(ngh_ctx *ctx, const char *key, const char *value)
     nv->namelen = strlen(key);
     nv->value = (uint8_t*)value;
     nv->valuelen = strlen(value);
-    
+
     return 1;
 }
 
@@ -1570,37 +1570,37 @@ static int add_table_header(void *ctx, const char *key, const char *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));
     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;
@@ -1618,7 +1618,7 @@ apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
     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)
 {
@@ -1632,23 +1632,23 @@ apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
                            H2_ALEN(keys), keys, values, response->headers);
 }
 
-apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p, 
+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);
@@ -1660,7 +1660,7 @@ apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
 /*******************************************************************************
  * header HTTP/1 <-> HTTP/2 conversions
  ******************************************************************************/
+
 
 typedef struct {
     const char *name;
@@ -1688,9 +1688,9 @@ static literal IgnoredRequestTrailers[] = { /* Ignore, see rfc7230, ch. 4.1.2 */
     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 IgnoredResponseTrailers[] = {
     H2_DEF_LITERAL("age"),
     H2_DEF_LITERAL("date"),
@@ -1710,7 +1710,7 @@ static int ignore_header(const literal *lits, size_t llen,
 {
     const literal *lit;
     size_t i;
-    
+
     for (i = 0; i < llen; ++i) {
         lit = &lits[i];
         if (lit->len == nlen && !apr_strnatcasecmp(lit->name, name)) {
@@ -1727,7 +1727,7 @@ int h2_req_ignore_header(const char *name, size_t len)
 
 int h2_req_ignore_trailer(const char *name, size_t len)
 {
-    return (h2_req_ignore_header(name, len) 
+    return (h2_req_ignore_header(name, len)
             || ignore_header(H2_LIT_ARGS(IgnoredRequestTrailers), name, len));
 }
 
@@ -1736,14 +1736,14 @@ int h2_res_ignore_trailer(const char *name, size_t len)
     return ignore_header(H2_LIT_ARGS(IgnoredResponseTrailers), name, len);
 }
 
-apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool, 
+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)
 {
     char *hname, *hvalue;
     const char *existing;
-    
+
     *pwas_added = 0;
     if (h2_req_ignore_header(name, nlen)) {
         return APR_SUCCESS;
@@ -1752,7 +1752,7 @@ apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool,
         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 ", ")
              */
@@ -1771,7 +1771,7 @@ apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool,
             return APR_SUCCESS; /* ignore duplicate */
         }
     }
-    
+
     hname = apr_pstrndup(pool, name, nlen);
     h2_util_camel_case_header(hname, nlen);
     existing = apr_table_get(headers, hname);
@@ -1784,7 +1784,7 @@ apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool,
     if (!existing) *pwas_added = 1;
     hvalue = apr_pstrndup(pool, value, vlen);
     apr_table_mergen(headers, hname, hvalue);
-    
+
     return APR_SUCCESS;
 }
 
@@ -1796,7 +1796,7 @@ int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
 {
     char scratch[128];
     size_t s_len = sizeof(scratch)/sizeof(scratch[0]);
-    
+
     switch (frame->hd.type) {
         case NGHTTP2_DATA: {
             return apr_snprintf(buffer, maxlen,
@@ -1855,13 +1855,13 @@ int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
                 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:
index cbc6d5e38a057ab3d22b81ccd69ff660b56a2bbf..c488758ce70a465297289e74e08530c314c57600 100644 (file)
@@ -27,7 +27,7 @@
  * @macro
  * Version number of the http2 module as c string
  */
-#define MOD_HTTP2_VERSION "2.0.2"
+#define MOD_HTTP2_VERSION "2.0.7"
 
 /**
  * @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 0x020002
+#define MOD_HTTP2_VERSION_NUM 0x020007
 
 
 #endif /* mod_h2_h2_version_h */
index 4a71760d33982f9baf7273decf4222f1a6eec55e..1732f945a5491135f9f13497d028697bcdcdd958 100644 (file)
@@ -45,6 +45,7 @@ struct ap_conn_producer_t {
     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;
 };
@@ -320,7 +321,7 @@ static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx)
             if (APR_TIMEUP == rv) {
                 APR_RING_REMOVE(slot, link);
                 --workers->idle_slots;
-                ap_log_error(APLOG_MARK, APLOG_ERR, 0, workers->s,
+                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;
@@ -451,7 +452,7 @@ h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pchild,
     workers->pool = pool;
     workers->min_active = min_active;
     workers->max_slots = max_slots;
-    workers->idle_limit = (idle_limit > 0)? idle_limit : apr_time_from_sec(10);
+    workers->idle_limit = (int)((idle_limit > 0)? idle_limit : apr_time_from_sec(10));
     workers->dynamic = (workers->min_active < workers->max_slots);
 
     ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
@@ -532,12 +533,23 @@ apr_size_t h2_workers_get_max_workers(h2_workers *workers)
     return workers->max_slots;
 }
 
-void h2_workers_graceful_shutdown(h2_workers *workers)
+void h2_workers_shutdown(h2_workers *workers, int graceful)
 {
+    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);
 }
 
@@ -546,6 +558,7 @@ ap_conn_producer_t *h2_workers_register(h2_workers *workers,
                                         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;
@@ -555,6 +568,7 @@ ap_conn_producer_t *h2_workers_register(h2_workers *workers,
     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);
index 20169a0d50dd375f20a9283de32cac9e4b9059c1..5cbf16e40086397b89d1863005c99c533f465a93 100644 (file)
@@ -46,9 +46,9 @@ h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pool,
                               int max_slots, int min_active, apr_time_t idle_limit);
 
 /**
- *  Shut down processing gracefully by terminating all idle workers.
+ *  Shut down processing.
  */
-void h2_workers_graceful_shutdown(h2_workers *workers);
+void h2_workers_shutdown(h2_workers *workers, int graceful);
 
 /**
  * Get the maximum number of workers.
@@ -86,6 +86,13 @@ typedef conn_rec *ap_conn_producer_next(void *baton, int *pmore);
  */
 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);
+
 /**
  * Register a new producer with the given `baton` and callback functions.
  * Will allocate internal structures from the given pool (but make no use
@@ -103,6 +110,7 @@ ap_conn_producer_t *h2_workers_register(h2_workers *workers,
                                         const char *name,
                                         ap_conn_producer_next *fn_next,
                                         ap_conn_producer_done *fn_done,
+                                        ap_conn_producer_shutdown *fn_shutdown,
                                         void *baton);
 
 /**
index e107a78fca8e56958d7df046d9a8a4e23f24f015..dfa5f2dbc7741c7e5d41a954c5cc360aeb9186bb 100644 (file)
@@ -113,17 +113,17 @@ class TestTimeout:
 
     def test_h2_105_11(self, env):
         # short connection timeout, longer stream delay
-        # receiving the first response chunk, then timeout
+        # connection timeout must not abort ongoing streams
         conf = H2Conf(env)
         conf.add_vhost_cgi()
         conf.add("Timeout 1")
         conf.install()
         assert env.apache_restart() == 0
-        url = env.mkurl("https", "cgi", "/h2test/delay?5")
+        url = env.mkurl("https", "cgi", "/h2test/delay?1200ms")
         piper = CurlPiper(env=env, url=url)
         piper.start()
         stdout, stderr = piper.close()
-        assert len("".join(stdout)) == 8192
+        assert len("".join(stdout)) == 3 * 8192
 
     def test_h2_105_12(self, env):
         # long connection timeout, short stream timeout