summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorStefan Eissing <icing@apache.org>2022-09-26 14:29:47 +0200
committerStefan Eissing <icing@apache.org>2022-09-26 14:29:47 +0200
commit8476af1eb6c7618e561fbb790dddde885ac56b5e (patch)
treea819c3431375c7666ac43da04adb3881ed5a42e3 /modules
parent *) mod_http2: removing bucket splitting into an extra recv brigade. (diff)
downloadapache2-8476af1eb6c7618e561fbb790dddde885ac56b5e.tar.xz
apache2-8476af1eb6c7618e561fbb790dddde885ac56b5e.zip
*) 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). - 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
Diffstat (limited to 'modules')
-rw-r--r--modules/http2/config2.m43
-rw-r--r--modules/http2/h2.h34
-rw-r--r--modules/http2/h2_bucket_beam.c37
-rw-r--r--modules/http2/h2_bucket_beam.h9
-rw-r--r--modules/http2/h2_c1.c4
-rw-r--r--modules/http2/h2_c1_io.c12
-rw-r--r--modules/http2/h2_c2.c3
-rw-r--r--modules/http2/h2_config.c29
-rw-r--r--modules/http2/h2_config.h1
-rw-r--r--modules/http2/h2_mplx.c83
-rw-r--r--modules/http2/h2_mplx.h12
-rw-r--r--modules/http2/h2_request.c31
-rw-r--r--modules/http2/h2_session.c64
-rw-r--r--modules/http2/h2_stream.c25
-rw-r--r--modules/http2/h2_util.c254
-rw-r--r--modules/http2/h2_version.h4
-rw-r--r--modules/http2/h2_workers.c20
-rw-r--r--modules/http2/h2_workers.h12
18 files changed, 359 insertions, 278 deletions
diff --git a/modules/http2/config2.m4 b/modules/http2/config2.m4
index bec019b77b..87d4cc2ae2 100644
--- a/modules/http2/config2.m4
+++ b/modules/http2/config2.m4
@@ -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
diff --git a/modules/http2/h2.h b/modules/http2/h2.h
index 179c55db23..f1017480ed 100644
--- a/modules/http2/h2.h
+++ b/modules/http2/h2.h
@@ -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
diff --git a/modules/http2/h2_bucket_beam.c b/modules/http2/h2_bucket_beam.c
index 657d62f825..524d93bc93 100644
--- a/modules/http2/h2_bucket_beam.c
+++ b/modules/http2/h2_bucket_beam.c
@@ -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;
}
diff --git a/modules/http2/h2_bucket_beam.h b/modules/http2/h2_bucket_beam.h
index 934a893d99..2a9d5f0f01 100644
--- a/modules/http2/h2_bucket_beam.h
+++ b/modules/http2/h2_bucket_beam.h
@@ -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;
@@ -157,6 +158,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.
*/
void h2_beam_timeout_set(h2_bucket_beam *beam,
diff --git a/modules/http2/h2_c1.c b/modules/http2/h2_c1.c
index 7662a0e4fe..afb26fc073 100644
--- a/modules/http2/h2_c1.c
+++ b/modules/http2/h2_c1.c
@@ -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);
}
}
diff --git a/modules/http2/h2_c1_io.c b/modules/http2/h2_c1_io.c
index 2300c61201..ade8836635 100644
--- a/modules/http2/h2_c1_io.c
+++ b/modules/http2/h2_c1_io.c
@@ -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;
}
diff --git a/modules/http2/h2_c2.c b/modules/http2/h2_c2.c
index f11a53cf25..ec5d3a99fd 100644
--- a/modules/http2/h2_c2.c
+++ b/modules/http2/h2_c2.c
@@ -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) {
diff --git a/modules/http2/h2_config.c b/modules/http2/h2_config.c
index da1cf79a07..026e255fb5 100644
--- a/modules/http2/h2_config.c
+++ b/modules/http2/h2_config.c
@@ -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
};
diff --git a/modules/http2/h2_config.h b/modules/http2/h2_config.h
index 6d2e65f926..d3d47386a8 100644
--- a/modules/http2/h2_config.h
+++ b/modules/http2/h2_config.h
@@ -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;
diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c
index b83c338123..ffc17ffb4b 100644
--- a/modules/http2/h2_mplx.c
+++ b/modules/http2/h2_mplx.c
@@ -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 */
diff --git a/modules/http2/h2_mplx.h b/modules/http2/h2_mplx.h
index e056acacdd..2382e46cf4 100644
--- a/modules/http2/h2_mplx.h
+++ b/modules/http2/h2_mplx.h
@@ -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 */
@@ -200,15 +201,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.
*/
const struct h2_stream *h2_mplx_c2_stream_get(h2_mplx *m, int stream_id);
diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c
index aa54351969..0a181b86a6 100644
--- a/modules/http2/h2_request.c
+++ b/modules/http2/h2_request.c
@@ -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);
+ }
}
}
diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c
index 20c35beef1..1056d4b356 100644
--- a/modules/http2/h2_session.c
+++ b/modules/http2/h2_session.c
@@ -32,6 +32,10 @@
#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;
@@ -942,6 +954,15 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *
* 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);
nghttp2_session_callbacks_del(callbacks);
@@ -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);
diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c
index 2c16290078..a884af4a40 100644
--- a/modules/http2/h2_stream.c
+++ b/modules/http2/h2_stream.c
@@ -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);
diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c
index 8ac0b1a11e..90d4e7e176 100644
--- a/modules/http2/h2_util.c
+++ b/modules/http2/h2_util.c
@@ -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:
diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h
index cbc6d5e38a..c488758ce7 100644
--- a/modules/http2/h2_version.h
+++ b/modules/http2/h2_version.h
@@ -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 */
diff --git a/modules/http2/h2_workers.c b/modules/http2/h2_workers.c
index 4a71760d33..1732f945a5 100644
--- a/modules/http2/h2_workers.c
+++ b/modules/http2/h2_workers.c
@@ -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);
diff --git a/modules/http2/h2_workers.h b/modules/http2/h2_workers.h
index 20169a0d50..5cbf16e400 100644
--- a/modules/http2/h2_workers.h
+++ b/modules/http2/h2_workers.h
@@ -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.
@@ -87,6 +87,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
* of the pool after registration).
@@ -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);
/**