diff options
author | Stefan Eissing <icing@apache.org> | 2022-09-26 14:29:47 +0200 |
---|---|---|
committer | Stefan Eissing <icing@apache.org> | 2022-09-26 14:29:47 +0200 |
commit | 8476af1eb6c7618e561fbb790dddde885ac56b5e (patch) | |
tree | a819c3431375c7666ac43da04adb3881ed5a42e3 /modules | |
parent | *) mod_http2: removing bucket splitting into an extra recv brigade. (diff) | |
download | apache2-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.m4 | 3 | ||||
-rw-r--r-- | modules/http2/h2.h | 34 | ||||
-rw-r--r-- | modules/http2/h2_bucket_beam.c | 37 | ||||
-rw-r--r-- | modules/http2/h2_bucket_beam.h | 9 | ||||
-rw-r--r-- | modules/http2/h2_c1.c | 4 | ||||
-rw-r--r-- | modules/http2/h2_c1_io.c | 12 | ||||
-rw-r--r-- | modules/http2/h2_c2.c | 3 | ||||
-rw-r--r-- | modules/http2/h2_config.c | 29 | ||||
-rw-r--r-- | modules/http2/h2_config.h | 1 | ||||
-rw-r--r-- | modules/http2/h2_mplx.c | 83 | ||||
-rw-r--r-- | modules/http2/h2_mplx.h | 12 | ||||
-rw-r--r-- | modules/http2/h2_request.c | 31 | ||||
-rw-r--r-- | modules/http2/h2_session.c | 64 | ||||
-rw-r--r-- | modules/http2/h2_stream.c | 25 | ||||
-rw-r--r-- | modules/http2/h2_util.c | 254 | ||||
-rw-r--r-- | modules/http2/h2_version.h | 4 | ||||
-rw-r--r-- | modules/http2/h2_workers.c | 20 | ||||
-rw-r--r-- | modules/http2/h2_workers.h | 12 |
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); /** |