From: Stefan Eissing Date: Thu, 19 Nov 2015 17:14:03 +0000 (+0000) Subject: latest changes from trunk re mod_http2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d059ba7d93ea75d7e389850ac25c4875b55da73a;p=thirdparty%2Fapache%2Fhttpd.git latest changes from trunk re mod_http2 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4-http2-alpha@1715218 13f79535-47bb-0310-9956-ffa450edef68 --- d059ba7d93ea75d7e389850ac25c4875b55da73a diff --cc modules/http2/config.m4 index 35b25e11a3a,9c5eb86740e,9c5eb86740e..f383b3cd838 --- a/modules/http2/config.m4 +++ b/modules/http2/config.m4 @@@@ -31,6 -29,6 -29,6 +31,7 @@@@ h2_h2.lo dn h2_io.lo dnl h2_io_set.lo dnl h2_mplx.lo dnl +++h2_push.lo dnl h2_request.lo dnl h2_response.lo dnl h2_session.lo dnl @@@@ -41,7 -39,7 -39,7 +42,6 @@@@ h2_task.lo dn h2_task_input.lo dnl h2_task_output.lo dnl h2_task_queue.lo dnl ---h2_to_h1.lo dnl h2_util.lo dnl h2_worker.lo dnl h2_workers.lo dnl diff --cc modules/http2/h2_bucket_eoc.c index eccc0678053,00000000000,00000000000..8b145cf29ed mode 100644,000000,000000..100644 --- a/modules/http2/h2_bucket_eoc.c +++ b/modules/http2/h2_bucket_eoc.c @@@@ -1,106 -1,0 -1,0 +1,108 @@@@ ++/* Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You under the Apache License, Version 2.0 ++ * (the "License"); you may not use this file except in compliance with ++ * the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include "h2_private.h" ++#include "h2_mplx.h" ++#include "h2_session.h" ++#include "h2_bucket_eoc.h" ++ ++typedef struct { ++ apr_bucket_refcount refcount; ++ h2_session *session; ++} h2_bucket_eoc; ++ ++static apr_status_t bucket_cleanup(void *data) ++{ ++ h2_session **psession = data; ++ ++ if (*psession) { ++ /* ++ * If bucket_destroy is called after us, this prevents ++ * bucket_destroy from trying to destroy the pool again. ++ */ ++ *psession = NULL; ++ } ++ return APR_SUCCESS; ++} ++ ++static apr_status_t bucket_read(apr_bucket *b, const char **str, ++ apr_size_t *len, apr_read_type_e block) ++{ +++ (void)b; +++ (void)block; ++ *str = NULL; ++ *len = 0; ++ return APR_SUCCESS; ++} ++ ++apr_bucket * h2_bucket_eoc_make(apr_bucket *b, h2_session *session) ++{ ++ h2_bucket_eoc *h; ++ ++ h = apr_bucket_alloc(sizeof(*h), b->list); ++ h->session = session; ++ ++ b = apr_bucket_shared_make(b, h, 0, 0); ++ b->type = &h2_bucket_type_eoc; ++ ++ return b; ++} ++ ++apr_bucket * h2_bucket_eoc_create(apr_bucket_alloc_t *list, h2_session *session) ++{ ++ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); ++ ++ APR_BUCKET_INIT(b); ++ b->free = apr_bucket_free; ++ b->list = list; ++ b = h2_bucket_eoc_make(b, session); ++ if (session) { ++ h2_bucket_eoc *h = b->data; ++ apr_pool_pre_cleanup_register(session->pool, &h->session, bucket_cleanup); ++ } ++ return b; ++} ++ ++static void bucket_destroy(void *data) ++{ ++ h2_bucket_eoc *h = data; ++ ++ if (apr_bucket_shared_destroy(h)) { ++ h2_session *session = h->session; ++ if (session) { - h2_session_cleanup(session); +++ h2_session_eoc_callback(session); ++ } ++ apr_bucket_free(h); ++ } ++} ++ ++const apr_bucket_type_t h2_bucket_type_eoc = { ++ "H2EOC", 5, APR_BUCKET_METADATA, ++ bucket_destroy, ++ bucket_read, ++ apr_bucket_setaside_noop, ++ apr_bucket_split_notimpl, ++ apr_bucket_shared_copy ++}; ++ diff --cc modules/http2/h2_bucket_eos.c index 029cd1034e3,00000000000,00000000000..98a0b365c68 mode 100644,000000,000000..100644 --- a/modules/http2/h2_bucket_eos.c +++ b/modules/http2/h2_bucket_eos.c @@@@ -1,107 -1,0 -1,0 +1,109 @@@@ ++/* Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed with ++ * this work for additional information regarding copyright ownership. ++ * The ASF licenses this file to You under the Apache License, Version 2.0 ++ * (the "License"); you may not use this file except in compliance with ++ * the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include "h2_private.h" ++#include "h2_mplx.h" ++#include "h2_stream.h" ++#include "h2_bucket_eos.h" ++ ++typedef struct { ++ apr_bucket_refcount refcount; ++ h2_stream *stream; ++} h2_bucket_eos; ++ ++static apr_status_t bucket_cleanup(void *data) ++{ ++ h2_stream **pstream = data; ++ ++ if (*pstream) { ++ /* ++ * If bucket_destroy is called after us, this prevents ++ * bucket_destroy from trying to destroy the pool again. ++ */ ++ *pstream = NULL; ++ } ++ return APR_SUCCESS; ++} ++ ++static apr_status_t bucket_read(apr_bucket *b, const char **str, ++ apr_size_t *len, apr_read_type_e block) ++{ +++ (void)b; +++ (void)block; ++ *str = NULL; ++ *len = 0; ++ return APR_SUCCESS; ++} ++ ++apr_bucket *h2_bucket_eos_make(apr_bucket *b, h2_stream *stream) ++{ ++ h2_bucket_eos *h; ++ ++ h = apr_bucket_alloc(sizeof(*h), b->list); ++ h->stream = stream; ++ ++ b = apr_bucket_shared_make(b, h, 0, 0); ++ b->type = &h2_bucket_type_eos; ++ ++ return b; ++} ++ ++apr_bucket *h2_bucket_eos_create(apr_bucket_alloc_t *list, ++ h2_stream *stream) ++{ ++ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); ++ ++ APR_BUCKET_INIT(b); ++ b->free = apr_bucket_free; ++ b->list = list; ++ b = h2_bucket_eos_make(b, stream); ++ if (stream) { ++ h2_bucket_eos *h = b->data; ++ apr_pool_pre_cleanup_register(stream->pool, &h->stream, bucket_cleanup); ++ } ++ return b; ++} ++ ++static void bucket_destroy(void *data) ++{ ++ h2_bucket_eos *h = data; ++ ++ if (apr_bucket_shared_destroy(h)) { ++ h2_stream *stream = h->stream; ++ if (stream) { ++ h2_stream_cleanup(stream); ++ } ++ apr_bucket_free(h); ++ } ++} ++ ++const apr_bucket_type_t h2_bucket_type_eos = { ++ "H2EOS", 5, APR_BUCKET_METADATA, ++ bucket_destroy, ++ bucket_read, ++ apr_bucket_setaside_noop, ++ apr_bucket_split_notimpl, ++ apr_bucket_shared_copy ++}; ++ diff --cc modules/http2/h2_config.c index 25fdd29908f,6db702eca80,6db702eca80..7ac4297b328 --- a/modules/http2/h2_config.c +++ b/modules/http2/h2_config.c @@@@ -29,6 -29,6 -29,6 +29,7 @@@@ #include "h2_ctx.h" #include "h2_conn.h" #include "h2_config.h" +++#include "h2_h2.h" #include "h2_private.h" #define DEF_VAL (-1) @@@@ -38,21 -38,17 -38,17 +39,22 @@@@ static h2_config defconf = { "default", --- 100, /* max_streams */ --- 64 * 1024, /* window_size */ --- -1, /* min workers */ --- -1, /* max workers */ --- 10 * 60, /* max workers idle secs */ --- 64 * 1024, /* stream max mem size */ --- NULL, /* no alt-svcs */ --- -1, /* alt-svc max age */ --- 0, /* serialize headers */ --- -1, /* h2 direct mode */ --- -1, /* # session extra files */ - 1, /* modern TLS only */ - -1, /* HTTP/1 Upgrade support */ - 1024*1024, /* TLS warmup size */ - 1, /* TLS cooldown secs */ +++ 100, /* max_streams */ +++ H2_INITIAL_WINDOW_SIZE, /* window_size */ +++ -1, /* min workers */ +++ -1, /* max workers */ +++ 10 * 60, /* max workers idle secs */ +++ 64 * 1024, /* stream max mem size */ +++ NULL, /* no alt-svcs */ +++ -1, /* alt-svc max age */ +++ 0, /* serialize headers */ +++ -1, /* h2 direct mode */ +++ -1, /* # session extra files */ +++ 1, /* modern TLS only */ +++ -1, /* HTTP/1 Upgrade support */ +++ 1024*1024, /* TLS warmup size */ +++ 1, /* TLS cooldown secs */ +++ 1, /* HTTP/2 server push enabled */ }; static int files_per_session = 0; @@@@ -104,11 -100,6 -100,6 +106,12 @@@@ static void *h2_config_create(apr_pool_ conf->serialize_headers = DEF_VAL; conf->h2_direct = DEF_VAL; conf->session_extra_files = DEF_VAL; ++ conf->modern_tls_only = DEF_VAL; ++ conf->h2_upgrade = DEF_VAL; ++ conf->tls_warmup_size = DEF_VAL; ++ conf->tls_cooldown_secs = DEF_VAL; +++ conf->h2_push = DEF_VAL; ++ return conf; } @@@@ -136,21 -127,17 -127,17 +139,22 @@@@ void *h2_config_merge(apr_pool_t *pool strcat(name, "]"); n->name = name; -- n->h2_max_streams = H2_CONFIG_GET(add, base, h2_max_streams); -- n->h2_window_size = H2_CONFIG_GET(add, base, h2_window_size); -- n->min_workers = H2_CONFIG_GET(add, base, min_workers); -- n->max_workers = H2_CONFIG_GET(add, base, max_workers); ++ n->h2_max_streams = H2_CONFIG_GET(add, base, h2_max_streams); ++ n->h2_window_size = H2_CONFIG_GET(add, base, h2_window_size); ++ n->min_workers = H2_CONFIG_GET(add, base, min_workers); ++ n->max_workers = H2_CONFIG_GET(add, base, max_workers); n->max_worker_idle_secs = H2_CONFIG_GET(add, base, max_worker_idle_secs); -- n->stream_max_mem_size = H2_CONFIG_GET(add, base, stream_max_mem_size); -- n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs; -- n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age); -- n->serialize_headers = H2_CONFIG_GET(add, base, serialize_headers); -- n->h2_direct = H2_CONFIG_GET(add, base, h2_direct); -- n->session_extra_files = H2_CONFIG_GET(add, base, session_extra_files); ++ n->stream_max_mem_size = H2_CONFIG_GET(add, base, stream_max_mem_size); ++ n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs; ++ n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age); ++ n->serialize_headers = H2_CONFIG_GET(add, base, serialize_headers); ++ n->h2_direct = H2_CONFIG_GET(add, base, h2_direct); ++ n->session_extra_files = H2_CONFIG_GET(add, base, session_extra_files); ++ n->modern_tls_only = H2_CONFIG_GET(add, base, modern_tls_only); ++ n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade); ++ n->tls_warmup_size = H2_CONFIG_GET(add, base, tls_warmup_size); ++ n->tls_cooldown_secs = H2_CONFIG_GET(add, base, tls_cooldown_secs); +++ n->h2_push = H2_CONFIG_GET(add, base, h2_push); return n; } @@@@ -192,10 -170,6 -170,6 +196,12 @@@@ apr_int64_t h2_config_geti64(h2_config n = files_per_session; } return n; ++ case H2_CONF_TLS_WARMUP_SIZE: ++ return H2_CONFIG_GET(conf, &defconf, tls_warmup_size); ++ case H2_CONF_TLS_COOLDOWN_SECS: ++ return H2_CONFIG_GET(conf, &defconf, tls_cooldown_secs); +++ case H2_CONF_PUSH: +++ return H2_CONFIG_GET(conf, &defconf, h2_push); default: return DEF_VAL; } @@@@ -358,60 -332,8 -332,8 +364,77 @@@@ static const char *h2_conf_set_direct(c return "value must be On or Off"; } --#define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL) +++static const char *h2_conf_set_push(cmd_parms *parms, +++ void *arg, const char *value) +++{ +++ h2_config *cfg = h2_config_sget(parms->server); +++ if (!strcasecmp(value, "On")) { +++ cfg->h2_push = 1; +++ return NULL; +++ } +++ else if (!strcasecmp(value, "Off")) { +++ cfg->h2_push = 0; +++ return NULL; +++ } +++ +++ (void)arg; +++ return "value must be On or Off"; +++} +++ ++static const char *h2_conf_set_modern_tls_only(cmd_parms *parms, ++ void *arg, const char *value) ++{ ++ h2_config *cfg = h2_config_sget(parms->server); ++ if (!strcasecmp(value, "On")) { ++ cfg->modern_tls_only = 1; ++ return NULL; ++ } ++ else if (!strcasecmp(value, "Off")) { ++ cfg->modern_tls_only = 0; ++ return NULL; ++ } ++ ++ (void)arg; ++ return "value must be On or Off"; ++} ++ ++static const char *h2_conf_set_upgrade(cmd_parms *parms, ++ void *arg, const char *value) ++{ ++ h2_config *cfg = h2_config_sget(parms->server); ++ if (!strcasecmp(value, "On")) { ++ cfg->h2_upgrade = 1; ++ return NULL; ++ } ++ else if (!strcasecmp(value, "Off")) { ++ cfg->h2_upgrade = 0; ++ return NULL; ++ } ++ ++ (void)arg; ++ return "value must be On or Off"; ++} ++ ++static const char *h2_conf_set_tls_warmup_size(cmd_parms *parms, ++ void *arg, const char *value) ++{ ++ h2_config *cfg = h2_config_sget(parms->server); ++ cfg->tls_warmup_size = apr_atoi64(value); ++ (void)arg; ++ return NULL; ++} ++static const char *h2_conf_set_tls_cooldown_secs(cmd_parms *parms, ++ void *arg, const char *value) ++{ ++ h2_config *cfg = h2_config_sget(parms->server); ++ cfg->tls_cooldown_secs = (int)apr_atoi64(value); ++ (void)arg; ++ return NULL; ++} ++ ++ ++#define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL) const command_rec h2_cmds[] = { AP_INIT_TAKE1("H2MaxSessionStreams", h2_conf_set_max_streams, NULL, @@@@ -440,10 -358,6 -358,6 +463,12 @@@@ RSRC_CONF, "on to enable direct HTTP/2 mode"), AP_INIT_TAKE1("H2SessionExtraFiles", h2_conf_set_session_extra_files, NULL, RSRC_CONF, "number of extra file a session might keep open"), ++ AP_INIT_TAKE1("H2TLSWarmUpSize", h2_conf_set_tls_warmup_size, NULL, ++ RSRC_CONF, "number of bytes on TLS connection before doing max writes"), ++ AP_INIT_TAKE1("H2TLSCoolDownSecs", h2_conf_set_tls_cooldown_secs, NULL, ++ RSRC_CONF, "seconds of idle time on TLS before shrinking writes"), +++ AP_INIT_TAKE1("H2Push", h2_conf_set_push, NULL, +++ RSRC_CONF, "off to disable HTTP/2 server push"), AP_END_CMD }; diff --cc modules/http2/h2_config.h index 9c59817a992,931af59d302,931af59d302..7d2ed0fa28e --- a/modules/http2/h2_config.h +++ b/modules/http2/h2_config.h @@@@ -34,10 -34,6 -34,6 +34,11 @@@@ typedef enum H2_CONF_SER_HEADERS, H2_CONF_DIRECT, H2_CONF_SESSION_FILES, ++ H2_CONF_MODERN_TLS_ONLY, ++ H2_CONF_UPGRADE, ++ H2_CONF_TLS_WARMUP_SIZE, ++ H2_CONF_TLS_COOLDOWN_SECS, +++ H2_CONF_PUSH, } h2_config_var_t; /* Apache httpd module configuration for h2. */ @@@@ -55,10 -51,6 -51,6 +56,11 @@@@ typedef struct h2_config processing, better compatibility */ int h2_direct; /* if mod_h2 is active directly */ int session_extra_files; /* # of extra files a session may keep open */ ++ int modern_tls_only; /* Accept only modern TLS in HTTP/2 connections */ ++ int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */ ++ apr_int64_t tls_warmup_size; /* Amount of TLS data to send before going full write size */ ++ int tls_cooldown_secs; /* Seconds of idle time before going back to small TLS records */ +++ int h2_push; /* if HTTP/2 server push is enabled */ } h2_config; diff --cc modules/http2/h2_conn.c index 877447e2ffe,8bffbc42693,8bffbc42693..d4f56c66306 --- a/modules/http2/h2_conn.c +++ b/modules/http2/h2_conn.c @@@@ -40,10 -39,11 -39,11 +40,8 @@@@ static struct h2_workers *workers; - static apr_status_t h2_conn_loop(h2_session *session); --static apr_status_t h2_session_process(h2_session *session); --- static h2_mpm_type_t mpm_type = H2_MPM_UNKNOWN; static module *mpm_module; --static module *ssl_module; static int checked; static void check_modules(void) @@@@ -133,246 -139,228 -139,228 +131,70 @@@@ static module *h2_conn_mpm_module(void return mpm_module; } ---apr_status_t h2_conn_rprocess(request_rec *r) +++apr_status_t h2_conn_process(conn_rec *c, request_rec *r) { --- h2_config *config = h2_config_rget(r); +++ apr_status_t status; h2_session *session; +++ h2_config *config; +++ int rv; --- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "h2_conn_process start"); if (!workers) { --- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02911) +++ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911) "workers not initialized"); return APR_EGENERAL; } --- session = h2_session_rcreate(r, config, workers); --- if (!session) { --- return APR_EGENERAL; --- } --- - return h2_conn_loop(session); -- return h2_session_process(session); ---} --- ---apr_status_t h2_conn_main(conn_rec *c) ---{ --- h2_config *config = h2_config_get(c); --- h2_session *session; --- apr_status_t status; +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "h2_conn_process start"); --- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "h2_conn_main start"); --- if (!workers) { --- ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02912) --- "workers not initialized"); --- return APR_EGENERAL; +++ if (r) { +++ config = h2_config_rget(r); +++ session = h2_session_rcreate(r, config, workers); } --- --- session = h2_session_create(c, config, workers); --- if (!session) { --- return APR_EGENERAL; +++ else { +++ config = h2_config_get(c); +++ session = h2_session_create(c, config, workers); } -- status = h2_session_process(session); -- -- /* Make sure this connection gets closed properly. */ -- c->keepalive = AP_CONN_CLOSE; -- if (c->cs) { -- c->cs->state = CONN_STATE_WRITE_COMPLETION; -- } ++ if (!h2_is_acceptable_connection(c, 1)) { ++ nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 0, ++ NGHTTP2_INADEQUATE_SECURITY, NULL, 0); ++ } - status = h2_conn_loop(session); - - /* Make sure this connection gets closed properly. */ - c->keepalive = AP_CONN_CLOSE; - if (c->cs) { - c->cs->state = CONN_STATE_WRITE_COMPLETION; - } - --- return status; ---} --- - static apr_status_t h2_conn_loop(h2_session *session) --apr_status_t h2_session_process(h2_session *session) ---{ --- apr_status_t status = APR_SUCCESS; --- int rv = 0; --- apr_interval_time_t wait_micros = 0; --- static const int MAX_WAIT_MICROS = 200 * 1000; --- --- /* Start talking to the client. Apart from protocol meta data, --- * we mainly will see new http/2 streams opened by the client, which --- * basically are http requests we need to dispatch. --- * --- * There will be bursts of new streams, to be served concurrently, --- * followed by long pauses of no activity. --- * --- * Since the purpose of http/2 is to allow siumultaneous streams, we --- * need to dispatch the handling of each stream into a separate worker --- * thread, keeping this thread open for sending responses back as --- * soon as they arrive. --- * At the same time, we need to continue reading new frames from --- * our client, which may be meta (WINDOWS_UPDATEs, PING, SETTINGS) or --- * new streams. --- * --- * As long as we have streams open in this session, we cannot really rest --- * since there are two conditions to wait on: 1. new data from the client, --- * 2. new data from the open streams to send back. --- * --- * Only when we have no more streams open, can we do a blocking read --- * on our connection. --- * --- * TODO: implement graceful GO_AWAY after configurable idle time --- */ --- --- ap_update_child_status_from_conn(session->c->sbh, SERVER_BUSY_READ, --- session->c); --- --- if (APLOGctrace2(session->c)) { --- ap_filter_t *filter = session->c->input_filters; --- while (filter) { --- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, --- "h2_conn(%ld), has connection filter %s", --- session->id, filter->frec->name); --- filter = filter->next; --- } --- } --- +++ ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c); status = h2_session_start(session, &rv); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, "h2_session(%ld): starting on %s:%d", session->id, --- session->c->base_server->defn_name, +++ session->c->base_server->server_hostname, session->c->local_addr->port); if (status != APR_SUCCESS) { h2_session_abort(session, status, rv); - h2_session_cleanup(session); -- h2_session_destroy(session); +++ h2_session_eoc_callback(session); return status; } --- while (!h2_session_is_done(session)) { --- int have_written = 0; --- int have_read = 0; --- int got_streams; --- --- status = h2_session_write(session, wait_micros); --- if (status == APR_SUCCESS) { --- have_written = 1; --- wait_micros = 0; --- } - else if (APR_STATUS_IS_EAGAIN(status)) { -- else if (status == APR_EAGAIN) { --- /* nop */ --- } --- else if (status == APR_TIMEUP) { --- wait_micros *= 2; --- if (wait_micros > MAX_WAIT_MICROS) { --- wait_micros = MAX_WAIT_MICROS; --- } --- } --- else { --- ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c, --- "h2_session(%ld): writing, terminating", --- session->id); --- h2_session_abort(session, status, 0); --- break; --- } --- --- /* We would like to do blocking reads as often as possible as they --- * are more efficient in regard to server resources. --- * We can do them under the following circumstances: --- * - we have no open streams and therefore have nothing to write --- * - we have just started the session and are waiting for the first --- * two frames to come in. There will always be at least 2 frames as --- * * h2 will send SETTINGS and SETTINGS-ACK --- * * h2c will count the header settings as one frame and we --- * submit our settings and need the ACK. --- */ --- got_streams = !h2_stream_set_is_empty(session->streams); - if (!got_streams || session->frames_received <= 1) { - if (session->c->cs) { - session->c->cs->state = CONN_STATE_WRITE_COMPLETION; - } - status = h2_session_read(session, APR_BLOCK_READ); - } - else { - if (session->c->cs) { - session->c->cs->state = CONN_STATE_HANDLER; - } - status = h2_session_read(session, APR_NONBLOCK_READ); - } - -- status = h2_session_read(session, -- (!got_streams -- || session->frames_received <= 1)? -- APR_BLOCK_READ : APR_NONBLOCK_READ); --- switch (status) { --- case APR_SUCCESS: /* successful read, reset our idle timers */ --- have_read = 1; --- wait_micros = 0; --- break; --- case APR_EAGAIN: /* non-blocking read, nothing there */ -- break; -- case APR_EBADF: /* connection is not there any more */ -- case APR_EOF: -- case APR_ECONNABORTED: -- case APR_ECONNRESET: -- case APR_TIMEUP: /* blocked read, timed out */ -- ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c, -- "h2_session(%ld): reading", -- session->id); -- h2_session_abort(session, status, 0); --- break; --- default: - if (APR_STATUS_IS_ETIMEDOUT(status) - || APR_STATUS_IS_ECONNABORTED(status) - || APR_STATUS_IS_ECONNRESET(status) - || APR_STATUS_IS_EOF(status) - || APR_STATUS_IS_EBADF(status)) { - /* common status for a client that has left */ - ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c, - "h2_session(%ld): terminating", - session->id); - /* Stolen from mod_reqtimeout to speed up lingering when - * a read timeout happened. - */ - apr_table_setn(session->c->notes, "short-lingering-close", "1"); - } - else { - /* uncommon status, log on INFO so that we see this */ - ap_log_cerror( APLOG_MARK, APLOG_INFO, status, session->c, - APLOGNO(02950) - "h2_session(%ld): error reading, terminating", - session->id); - } -- ap_log_cerror( APLOG_MARK, APLOG_INFO, status, session->c, -- APLOGNO(02950) -- "h2_session(%ld): error reading, terminating", -- session->id); --- h2_session_abort(session, status, 0); --- break; --- } --- --- if (!have_read && !have_written --- && !h2_stream_set_is_empty(session->streams)) { --- /* Nothing to read or write, we have streams, but --- * the have no data yet ready to be delivered. Slowly --- * back off to give others a chance to do their work. --- */ --- if (wait_micros == 0) { --- wait_micros = 10; --- } --- } --- } --- +++ status = h2_session_process(session); +++ ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c, "h2_session(%ld): done", session->id); --- -- ap_update_child_status_from_conn(session->c->sbh, SERVER_CLOSING, -- session->c); -- h2_session_close(session); - ap_update_child_status_from_conn(session->c->sbh, SERVER_CLOSING, - session->c); - return DONE; -- h2_session_destroy(session); +++ h2_session_flush(session); +++ /* hereafter session might be gone */ + -- return DONE; +++ /* Make sure this connection gets closed properly. */ +++ ap_update_child_status_from_conn(c->sbh, SERVER_CLOSING, c); +++ c->keepalive = AP_CONN_CLOSE; +++ if (c->cs) { +++ c->cs->state = CONN_STATE_WRITE_COMPLETION; +++ } +++ +++ return status; } static void fix_event_conn(conn_rec *c, conn_rec *master); ---/* --- * We would like to create the connection more lightweight like --- * slave connections in 2.5-DEV. But we get 500 responses on long --- * cgi tests in modules/h2.t as the script parsing seems to see an --- * EOF from the cgi before anything is sent. --- * ---conn_rec *h2_conn_create(conn_rec *master, apr_pool_t *pool) ---{ --- conn_rec *c = (conn_rec *) apr_palloc(pool, sizeof(conn_rec)); --- --- memcpy(c, master, sizeof(conn_rec)); --- c->id = (master->id & (long)pool); --- c->slaves = NULL; --- c->master = master; --- c->input_filters = NULL; --- c->output_filters = NULL; --- c->pool = pool; --- --- return c; ---} ---*/ +++static int SLAVE_CONN_25DEV_STYLE = 1; conn_rec *h2_conn_create(conn_rec *master, apr_pool_t *pool) { @@@@ -380,39 -368,39 -368,39 +202,56 @@@@ conn_rec *c; AP_DEBUG_ASSERT(master); --- --- /* CAVEAT: it seems necessary to setup the conn_rec in the master --- * connection thread. Other attempts crashed. --- * HOWEVER: we setup the connection using the pools and other items --- * from the master connection, since we do not want to allocate --- * lots of resources here. --- * Lets allocated pools and everything else when we actually start --- * working on this new connection. --- */ --- /* Not sure about the scoreboard handle. Reusing the one from the main --- * connection could make sense, is not really correct, but we cannot --- * easily create new handles for our worker threads either. --- * TODO --- */ --- socket = ap_get_module_config(master->conn_config, &core_module); --- c = ap_run_create_connection(pool, master->base_server, --- socket, --- master->id^((long)pool), --- master->sbh, --- master->bucket_alloc); +++ +++ if (SLAVE_CONN_25DEV_STYLE) { +++ /* This is like the slave connection creation from 2.5-DEV. A +++ * very efficient way - not sure how compatible this is, since +++ * the core hooks are no longer run. +++ * But maybe it's is better this way, not sure yet. +++ */ +++ c = (conn_rec *) apr_palloc(pool, sizeof(conn_rec)); +++ +++ memcpy(c, master, sizeof(conn_rec)); +++ c->id = (master->id & (long)pool); +++ c->master = master; +++ c->input_filters = NULL; +++ c->output_filters = NULL; +++ c->pool = pool; +++ } +++ else { +++ /* CAVEAT: it seems necessary to setup the conn_rec in the master +++ * connection thread. Other attempts crashed. +++ * HOWEVER: we setup the connection using the pools and other items +++ * from the master connection, since we do not want to allocate +++ * lots of resources here. +++ * Lets allocated pools and everything else when we actually start +++ * working on this new connection. +++ */ +++ /* Not sure about the scoreboard handle. Reusing the one from the main +++ * connection could make sense, is not really correct, but we cannot +++ * easily create new handles for our worker threads either. +++ * TODO +++ */ +++ socket = ap_get_module_config(master->conn_config, &core_module); +++ c = ap_run_create_connection(pool, master->base_server, +++ socket, +++ master->id^((long)pool), +++ master->sbh, +++ master->bucket_alloc); +++ } if (c == NULL) { ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, pool, APLOGNO(02913) "h2_task: creating conn"); --- return NULL; } return c; } - apr_status_t h2_conn_setup(h2_task *task, struct h2_worker *worker) --apr_status_t h2_conn_setup(h2_task_env *env, struct h2_worker *worker) +++apr_status_t h2_conn_setup(h2_task *task, apr_bucket_alloc_t *bucket_alloc, +++ apr_thread_t *thread, apr_socket_t *socket) { -- conn_rec *master = env->mplx->c; ++ conn_rec *master = task->mplx->c; -- ap_log_perror(APLOG_MARK, APLOG_TRACE3, 0, env->pool, ++ ap_log_perror(APLOG_MARK, APLOG_TRACE3, 0, task->pool, "h2_conn(%ld): created from master", master->id); /* Ok, we are just about to start processing the connection and @@@@ -421,18 -409,28 -409,28 +260,17 @@@@ * sub-resources from it, so that we get a nice reuse of * pools. */ -- env->c.pool = env->pool; -- env->c.bucket_alloc = h2_worker_get_bucket_alloc(worker); -- env->c.current_thread = h2_worker_get_thread(worker); ++ task->c->pool = task->pool; - task->c->bucket_alloc = h2_worker_get_bucket_alloc(worker); - task->c->current_thread = h2_worker_get_thread(worker); +++ task->c->current_thread = thread; +++ task->c->bucket_alloc = bucket_alloc; -- env->c.conn_config = ap_create_conn_config(env->pool); -- env->c.notes = apr_table_make(env->pool, 5); ++ task->c->conn_config = ap_create_conn_config(task->pool); ++ task->c->notes = apr_table_make(task->pool, 5); -- ap_set_module_config(env->c.conn_config, &core_module, -- h2_worker_get_socket(worker)); ++ /* In order to do this in 2.4.x, we need to add a member to conn_rec */ ++ task->c->master = master; - ap_set_module_config(task->c->conn_config, &core_module, - h2_worker_get_socket(worker)); -- /* If we serve http:// requests over a TLS connection, we do -- * not want any mod_ssl vars to be visible. -- */ -- if (ssl_module && (!env->scheme || strcmp("http", env->scheme))) { -- /* See #19, there is a range of SSL variables to be gotten from -- * the main connection that should be available in request handlers -- */ -- void *sslcfg = ap_get_module_config(master->conn_config, ssl_module); -- if (sslcfg) { -- ap_set_module_config(env->c.conn_config, ssl_module, sslcfg); -- } -- } +++ ap_set_module_config(task->c->conn_config, &core_module, socket); /* This works for mpm_worker so far. Other mpm modules have * different needs, unfortunately. The most interesting one diff --cc modules/http2/h2_conn.h index 752f28a74cb,49a70db8507,49a70db8507..84cf8d83c40 --- a/modules/http2/h2_conn.h +++ b/modules/http2/h2_conn.h @@@@ -17,19 -17,20 -17,20 +17,16 @@@@ #define __mod_h2__h2_conn__ struct h2_task; --struct h2_task_env; ---struct h2_worker; ---/* Process the connection that is now starting the HTTP/2 --- * conversation. Return when the HTTP/2 session is done --- * and the connection will close. --- */ ---apr_status_t h2_conn_main(conn_rec *c); --- ---/* Process the request that has been upgraded to a HTTP/2 +++/** +++ * Process the connection that is now starting the HTTP/2 * conversation. Return when the HTTP/2 session is done * and the connection will close. +++ * +++ * @param c the connection HTTP/2 is starting on +++ * @param r the upgrad requestion that still awaits an answer, optional */ ---apr_status_t h2_conn_rprocess(request_rec *r); +++apr_status_t h2_conn_process(conn_rec *c, request_rec *r); /* Initialize this child process for h2 connection work, * to be called once during child init before multi processing @@@@ -51,9 -52,9 -52,9 +48,7 @@@@ h2_mpm_type_t h2_conn_mpm_type(void) conn_rec *h2_conn_create(conn_rec *master, apr_pool_t *stream_pool); - apr_status_t h2_conn_setup(struct h2_task *task, struct h2_worker *worker); --apr_status_t h2_conn_setup(struct h2_task_env *env, struct h2_worker *worker); ---apr_status_t h2_conn_post(conn_rec *c, struct h2_worker *worker); --- ---apr_status_t h2_conn_process(conn_rec *c, apr_socket_t *socket); +++apr_status_t h2_conn_setup(struct h2_task *task, apr_bucket_alloc_t *bucket_alloc, +++ apr_thread_t *thread, apr_socket_t *socket); #endif /* defined(__mod_h2__h2_conn__) */ diff --cc modules/http2/h2_conn_io.c index 0299343063c,08d00a4f3a6,08d00a4f3a6..aa8d4d58028 --- a/modules/http2/h2_conn_io.c +++ b/modules/http2/h2_conn_io.c @@@@ -271,17 -238,11 -238,11 +271,17 @@@@ apr_status_t h2_conn_io_write(h2_conn_i const char *buf, size_t length) { apr_status_t status = APR_SUCCESS; -- io->unflushed = 1; -- if (io->buffer_output) { ++ io->unflushed = 1; ++ if (io->bufsize > 0) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection, "h2_conn_io: buffering %ld bytes", (long)length); ++ ++ if (!APR_BRIGADE_EMPTY(io->output)) { - status = h2_conn_io_flush(io); +++ status = h2_conn_io_pass(io); ++ io->unflushed = 1; ++ } ++ while (length > 0 && (status == APR_SUCCESS)) { apr_size_t avail = io->bufsize - io->buflen; if (avail <= 0) { @@@@ -313,65 -276,37 -276,37 +313,70 @@@@ return status; } ++apr_status_t h2_conn_io_writeb(h2_conn_io *io, apr_bucket *b) ++{ ++ APR_BRIGADE_INSERT_TAIL(io->output, b); ++ io->unflushed = 1; ++ return APR_SUCCESS; ++} --apr_status_t h2_conn_io_flush(h2_conn_io *io) ++apr_status_t h2_conn_io_consider_flush(h2_conn_io *io) { ++ apr_status_t status = APR_SUCCESS; - int flush_now = 0; ++ ++ /* The HTTP/1.1 network output buffer/flush behaviour does not ++ * give optimal performance in the HTTP/2 case, as the pattern of ++ * buckets (data/eor/eos) is different. - * As long as we do not have found out the "best" way to deal with +++ * As long as we have not found out the "best" way to deal with ++ * this, force a flush at least every WRITE_BUFFER_SIZE amount - * of data which seems to work nicely. +++ * of data. ++ */ if (io->unflushed) { -- apr_status_t status; ++ apr_off_t len = 0; ++ if (!APR_BRIGADE_EMPTY(io->output)) { ++ apr_brigade_length(io->output, 0, &len); ++ } ++ len += io->buflen; - flush_now = (len >= WRITE_BUFFER_SIZE); - } - - if (flush_now) { - return h2_conn_io_flush(io); +++ if (len >= WRITE_BUFFER_SIZE) { +++ return h2_conn_io_pass(io); +++ } ++ } ++ return status; ++} ++ - apr_status_t h2_conn_io_flush(h2_conn_io *io) +++static apr_status_t h2_conn_io_flush_int(h2_conn_io *io, int force) ++{ - if (io->unflushed) { - apr_status_t status; +++ if (io->unflushed || force) { if (io->buflen > 0) { ++ /* something in the buffer, put it in the output brigade */ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection, "h2_conn_io: flush, flushing %ld bytes", (long)io->buflen); bucketeer_buffer(io); io->buflen = 0; } -- /* Append flush. -- */ -- APR_BRIGADE_INSERT_TAIL(io->output, -- apr_bucket_flush_create(io->output->bucket_alloc)); -- -- /* Send it out through installed filters (TLS) to the client */ -- status = flush_out(io->output, io); - APR_BRIGADE_INSERT_TAIL(io->output, - apr_bucket_flush_create(io->output->bucket_alloc)); - /* Send it out */ - status = pass_out(io->output, io); - - if (status != APR_SUCCESS) { -- if (status == APR_SUCCESS) { -- /* These are all fine and no reason for concern. Everything else -- * is interesting. */ -- io->unflushed = 0; -- } -- else { --- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, io->connection, - "h2_conn_io: flush"); - return status; -- "h2_conn_io: flush error"); +++ if (force) { +++ APR_BRIGADE_INSERT_TAIL(io->output, +++ apr_bucket_flush_create(io->output->bucket_alloc)); } - + -- return status; +++ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->connection, +++ "h2_conn_io: flush"); +++ /* Send it out */ ++ io->unflushed = 0; +++ return pass_out(io->output, io); +++ /* no more access after this, as we might have flushed an EOC bucket +++ * that de-allocated us all. */ } return APR_SUCCESS; } +++apr_status_t h2_conn_io_flush(h2_conn_io *io) +++{ +++ return h2_conn_io_flush_int(io, 1); +++} +++ +++apr_status_t h2_conn_io_pass(h2_conn_io *io) +++{ +++ return h2_conn_io_flush_int(io, 0); +++} diff --cc modules/http2/h2_conn_io.h index c5a861605c6,084445ef2b0,084445ef2b0..4406261a33b --- a/modules/http2/h2_conn_io.h +++ b/modules/http2/h2_conn_io.h @@@@ -26,16 -26,11 -26,11 +26,16 @@@@ typedef struct conn_rec *connection; apr_bucket_brigade *input; apr_bucket_brigade *output; -- int buffer_output; -- int write_size; ++ ++ int is_tls; ++ apr_time_t cooldown_usecs; ++ apr_int64_t warmup_size; ++ - int write_size; +++ apr_size_t write_size; apr_time_t last_write; -- apr_size_t bytes_written; ++ apr_int64_t bytes_written; ++ int buffer_output; char *buffer; apr_size_t buflen; apr_size_t bufsize; @@@@ -58,11 -52,7 -52,7 +58,12 @@@@ apr_status_t h2_conn_io_read(h2_conn_i apr_status_t h2_conn_io_write(h2_conn_io *io, const char *buf, size_t length); ++ ++apr_status_t h2_conn_io_writeb(h2_conn_io *io, apr_bucket *b); ++ ++apr_status_t h2_conn_io_consider_flush(h2_conn_io *io); +++apr_status_t h2_conn_io_pass(h2_conn_io *io); apr_status_t h2_conn_io_flush(h2_conn_io *io); #endif /* defined(__mod_h2__h2_conn_io__) */ diff --cc modules/http2/h2_from_h1.c index c763ca56ea2,be11f5c317e,be11f5c317e..43a4f0822b3 --- a/modules/http2/h2_from_h1.c +++ b/modules/http2/h2_from_h1.c @@@@ -59,11 -59,11 -59,11 +59,6 @@@@ apr_status_t h2_from_h1_destroy(h2_from return APR_SUCCESS; } ---h2_from_h1_state_t h2_from_h1_get_state(h2_from_h1 *from_h1) ---{ --- return from_h1->state; ---} --- static void set_state(h2_from_h1 *from_h1, h2_from_h1_state_t state) { if (from_h1->state != state) { @@@@ -78,16 -78,16 -78,16 +73,9 @@@@ h2_response *h2_from_h1_get_response(h2 static apr_status_t make_h2_headers(h2_from_h1 *from_h1, request_rec *r) { -- from_h1->response = h2_response_create(from_h1->stream_id, -- from_h1->status, from_h1->hlines, -- from_h1->pool); -- if (from_h1->response == NULL) { -- ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, r->connection, -- APLOGNO(02915) -- "h2_from_h1(%d): unable to create resp_head", -- from_h1->stream_id); -- return APR_EINVAL; -- } ++ from_h1->response = h2_response_create(from_h1->stream_id, 0, - from_h1->status, from_h1->hlines, +++ from_h1->http_status, from_h1->hlines, ++ from_h1->pool); - if (from_h1->response == NULL) { - ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, r->connection, - APLOGNO(02915) - "h2_from_h1(%d): unable to create resp_head", - from_h1->stream_id); - return APR_EINVAL; - } from_h1->content_length = from_h1->response->content_length; from_h1->chunked = r->chunked; @@@@ -202,8 -202,8 -202,8 +190,7 @@@@ apr_status_t h2_from_h1_read_response(h } if (from_h1->state == H2_RESP_ST_STATUS_LINE) { /* instead of parsing, just take it directly */ --- from_h1->status = apr_psprintf(from_h1->pool, --- "%d", f->r->status); +++ from_h1->http_status = f->r->status; from_h1->state = H2_RESP_ST_HEADERS; } else if (line[0] == '\0') { @@@@ -417,7 -417,7 -417,7 +404,7 @@@@ static h2_response *create_response(h2_ } if (!apr_is_empty_array(r->content_languages)) { --- int i; +++ unsigned int i; char *token; char **languages = (char **)(r->content_languages->elts); const char *field = apr_table_get(r->headers_out, "Content-Language"); diff --cc modules/http2/h2_from_h1.h index 115a3142668,115a3142668,115a3142668..4f5ebad618b --- a/modules/http2/h2_from_h1.h +++ b/modules/http2/h2_from_h1.h @@@@ -48,35 -48,35 -48,35 +48,25 @@@@ struct h2_from_h1 apr_pool_t *pool; apr_bucket_brigade *bb; --- apr_size_t content_length; +++ apr_off_t content_length; int chunked; --- const char *status; +++ int http_status; apr_array_header_t *hlines; struct h2_response *response; }; ---typedef void h2_from_h1_state_change_cb(struct h2_from_h1 *resp, --- h2_from_h1_state_t prevstate, --- void *cb_ctx); --- h2_from_h1 *h2_from_h1_create(int stream_id, apr_pool_t *pool); apr_status_t h2_from_h1_destroy(h2_from_h1 *response); ---void h2_from_h1_set_state_change_cb(h2_from_h1 *from_h1, --- h2_from_h1_state_change_cb *callback, --- void *cb_ctx); --- apr_status_t h2_from_h1_read_response(h2_from_h1 *from_h1, ap_filter_t* f, apr_bucket_brigade* bb); struct h2_response *h2_from_h1_get_response(h2_from_h1 *from_h1); ---h2_from_h1_state_t h2_from_h1_get_state(h2_from_h1 *from_h1); --- apr_status_t h2_response_output_filter(ap_filter_t *f, apr_bucket_brigade *bb); #endif /* defined(__mod_h2__h2_from_h1__) */ diff --cc modules/http2/h2_h2.c index 64ac321db19,221f5118df9,221f5118df9..54fe9e0fa0a --- a/modules/http2/h2_h2.c +++ b/modules/http2/h2_h2.c @@@@ -53,384 -53,6 -53,6 +53,383 @@@@ APR_DECLARE_OPTIONAL_FN(int, ssl_is_htt static int (*opt_ssl_engine_disable)(conn_rec*); static int (*opt_ssl_is_https)(conn_rec*); ++/******************************************************************************* ++ * SSL var lookup ++ */ ++APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup, ++ (apr_pool_t *, server_rec *, ++ conn_rec *, request_rec *, ++ char *)); ++static char *(*opt_ssl_var_lookup)(apr_pool_t *, server_rec *, ++ conn_rec *, request_rec *, ++ char *); ++ ++ ++/******************************************************************************* ++ * HTTP/2 error stuff ++ */ ++static const char *h2_err_descr[] = { ++ "no error", /* 0x0 */ ++ "protocol error", ++ "internal error", ++ "flow control error", ++ "settings timeout", ++ "stream closed", /* 0x5 */ ++ "frame size error", ++ "refused stream", ++ "cancel", ++ "compression error", ++ "connect error", /* 0xa */ ++ "enhance your calm", ++ "inadequate security", ++ "http/1.1 required", ++}; ++ - const char *h2_h2_err_description(int h2_error) +++const char *h2_h2_err_description(unsigned int h2_error) ++{ - if (h2_error >= 0 - && h2_error < (sizeof(h2_err_descr)/sizeof(h2_err_descr[0]))) { +++ if (h2_error < (sizeof(h2_err_descr)/sizeof(h2_err_descr[0]))) { ++ return h2_err_descr[h2_error]; ++ } ++ return "unknown http/2 errotr code"; ++} ++ ++/******************************************************************************* ++ * Check connection security requirements of RFC 7540 ++ */ ++ ++/* ++ * Black Listed Ciphers from RFC 7549 Appendix A ++ * ++ */ ++static const char *RFC7540_names[] = { ++ /* ciphers with NULL encrpytion */ ++ "NULL-MD5", /* TLS_NULL_WITH_NULL_NULL */ ++ /* same */ /* TLS_RSA_WITH_NULL_MD5 */ ++ "NULL-SHA", /* TLS_RSA_WITH_NULL_SHA */ ++ "NULL-SHA256", /* TLS_RSA_WITH_NULL_SHA256 */ ++ "PSK-NULL-SHA", /* TLS_PSK_WITH_NULL_SHA */ ++ "DHE-PSK-NULL-SHA", /* TLS_DHE_PSK_WITH_NULL_SHA */ ++ "RSA-PSK-NULL-SHA", /* TLS_RSA_PSK_WITH_NULL_SHA */ ++ "PSK-NULL-SHA256", /* TLS_PSK_WITH_NULL_SHA256 */ ++ "PSK-NULL-SHA384", /* TLS_PSK_WITH_NULL_SHA384 */ ++ "DHE-PSK-NULL-SHA256", /* TLS_DHE_PSK_WITH_NULL_SHA256 */ ++ "DHE-PSK-NULL-SHA384", /* TLS_DHE_PSK_WITH_NULL_SHA384 */ ++ "RSA-PSK-NULL-SHA256", /* TLS_RSA_PSK_WITH_NULL_SHA256 */ ++ "RSA-PSK-NULL-SHA384", /* TLS_RSA_PSK_WITH_NULL_SHA384 */ ++ "ECDH-ECDSA-NULL-SHA", /* TLS_ECDH_ECDSA_WITH_NULL_SHA */ ++ "ECDHE-ECDSA-NULL-SHA", /* TLS_ECDHE_ECDSA_WITH_NULL_SHA */ ++ "ECDH-RSA-NULL-SHA", /* TLS_ECDH_RSA_WITH_NULL_SHA */ ++ "ECDHE-RSA-NULL-SHA", /* TLS_ECDHE_RSA_WITH_NULL_SHA */ ++ "AECDH-NULL-SHA", /* TLS_ECDH_anon_WITH_NULL_SHA */ ++ "ECDHE-PSK-NULL-SHA", /* TLS_ECDHE_PSK_WITH_NULL_SHA */ ++ "ECDHE-PSK-NULL-SHA256", /* TLS_ECDHE_PSK_WITH_NULL_SHA256 */ ++ "ECDHE-PSK-NULL-SHA384", /* TLS_ECDHE_PSK_WITH_NULL_SHA384 */ ++ ++ /* DES/3DES ciphers */ ++ "PSK-3DES-EDE-CBC-SHA", /* TLS_PSK_WITH_3DES_EDE_CBC_SHA */ ++ "DHE-PSK-3DES-EDE-CBC-SHA", /* TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA */ ++ "RSA-PSK-3DES-EDE-CBC-SHA", /* TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA */ ++ "ECDH-ECDSA-DES-CBC3-SHA", /* TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA */ ++ "ECDHE-ECDSA-DES-CBC3-SHA", /* TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA */ ++ "ECDH-RSA-DES-CBC3-SHA", /* TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA */ ++ "ECDHE-RSA-DES-CBC3-SHA", /* TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA */ ++ "AECDH-DES-CBC3-SHA", /* TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA */ ++ "SRP-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA */ ++ "SRP-RSA-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA */ ++ "SRP-DSS-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA */ ++ "ECDHE-PSK-3DES-EDE-CBC-SHA", /* TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA */ ++ "DES-CBC-SHA", /* TLS_RSA_WITH_DES_CBC_SHA */ ++ "DES-CBC3-SHA", /* TLS_RSA_WITH_3DES_EDE_CBC_SHA */ ++ "DHE-DSS-DES-CBC3-SHA", /* TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA */ ++ "DHE-RSA-DES-CBC-SHA", /* TLS_DHE_RSA_WITH_DES_CBC_SHA */ ++ "DHE-RSA-DES-CBC3-SHA", /* TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA */ ++ "ADH-DES-CBC-SHA", /* TLS_DH_anon_WITH_DES_CBC_SHA */ ++ "ADH-DES-CBC3-SHA", /* TLS_DH_anon_WITH_3DES_EDE_CBC_SHA */ ++ "EXP-DH-DSS-DES-CBC-SHA", /* TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA */ ++ "DH-DSS-DES-CBC-SHA", /* TLS_DH_DSS_WITH_DES_CBC_SHA */ ++ "DH-DSS-DES-CBC3-SHA", /* TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA */ ++ "EXP-DH-RSA-DES-CBC-SHA", /* TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA */ ++ "DH-RSA-DES-CBC-SHA", /* TLS_DH_RSA_WITH_DES_CBC_SHA */ ++ "DH-RSA-DES-CBC3-SHA", /* TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA */ ++ ++ /* blacklisted EXPORT ciphers */ ++ "EXP-RC4-MD5", /* TLS_RSA_EXPORT_WITH_RC4_40_MD5 */ ++ "EXP-RC2-CBC-MD5", /* TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 */ ++ "EXP-DES-CBC-SHA", /* TLS_RSA_EXPORT_WITH_DES40_CBC_SHA */ ++ "EXP-DHE-DSS-DES-CBC-SHA", /* TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA */ ++ "EXP-DHE-RSA-DES-CBC-SHA", /* TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA */ ++ "EXP-ADH-DES-CBC-SHA", /* TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA */ ++ "EXP-ADH-RC4-MD5", /* TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 */ ++ ++ /* blacklisted RC4 encryption */ ++ "RC4-MD5", /* TLS_RSA_WITH_RC4_128_MD5 */ ++ "RC4-SHA", /* TLS_RSA_WITH_RC4_128_SHA */ ++ "ADH-RC4-MD5", /* TLS_DH_anon_WITH_RC4_128_MD5 */ ++ "KRB5-RC4-SHA", /* TLS_KRB5_WITH_RC4_128_SHA */ ++ "KRB5-RC4-MD5", /* TLS_KRB5_WITH_RC4_128_MD5 */ ++ "EXP-KRB5-RC4-SHA", /* TLS_KRB5_EXPORT_WITH_RC4_40_SHA */ ++ "EXP-KRB5-RC4-MD5", /* TLS_KRB5_EXPORT_WITH_RC4_40_MD5 */ ++ "PSK-RC4-SHA", /* TLS_PSK_WITH_RC4_128_SHA */ ++ "DHE-PSK-RC4-SHA", /* TLS_DHE_PSK_WITH_RC4_128_SHA */ ++ "RSA-PSK-RC4-SHA", /* TLS_RSA_PSK_WITH_RC4_128_SHA */ ++ "ECDH-ECDSA-RC4-SHA", /* TLS_ECDH_ECDSA_WITH_RC4_128_SHA */ ++ "ECDHE-ECDSA-RC4-SHA", /* TLS_ECDHE_ECDSA_WITH_RC4_128_SHA */ ++ "ECDH-RSA-RC4-SHA", /* TLS_ECDH_RSA_WITH_RC4_128_SHA */ ++ "ECDHE-RSA-RC4-SHA", /* TLS_ECDHE_RSA_WITH_RC4_128_SHA */ ++ "AECDH-RC4-SHA", /* TLS_ECDH_anon_WITH_RC4_128_SHA */ ++ "ECDHE-PSK-RC4-SHA", /* TLS_ECDHE_PSK_WITH_RC4_128_SHA */ ++ ++ /* blacklisted AES128 encrpytion ciphers */ ++ "AES128-SHA256", /* TLS_RSA_WITH_AES_128_CBC_SHA */ ++ "DH-DSS-AES128-SHA", /* TLS_DH_DSS_WITH_AES_128_CBC_SHA */ ++ "DH-RSA-AES128-SHA", /* TLS_DH_RSA_WITH_AES_128_CBC_SHA */ ++ "DHE-DSS-AES128-SHA", /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA */ ++ "DHE-RSA-AES128-SHA", /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA */ ++ "ADH-AES128-SHA", /* TLS_DH_anon_WITH_AES_128_CBC_SHA */ ++ "AES128-SHA256", /* TLS_RSA_WITH_AES_128_CBC_SHA256 */ ++ "DH-DSS-AES128-SHA256", /* TLS_DH_DSS_WITH_AES_128_CBC_SHA256 */ ++ "DH-RSA-AES128-SHA256", /* TLS_DH_RSA_WITH_AES_128_CBC_SHA256 */ ++ "DHE-DSS-AES128-SHA256", /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 */ ++ "DHE-RSA-AES128-SHA256", /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 */ ++ "ECDH-ECDSA-AES128-SHA", /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA */ ++ "ECDHE-ECDSA-AES128-SHA", /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA */ ++ "ECDH-RSA-AES128-SHA", /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA */ ++ "ECDHE-RSA-AES128-SHA", /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA */ ++ "AECDH-AES128-SHA", /* TLS_ECDH_anon_WITH_AES_128_CBC_SHA */ ++ "ECDHE-ECDSA-AES128-SHA256", /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 */ ++ "ECDH-ECDSA-AES128-SHA256", /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 */ ++ "ECDHE-RSA-AES128-SHA256", /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 */ ++ "ECDH-RSA-AES128-SHA256", /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 */ ++ "ADH-AES128-SHA256", /* TLS_DH_anon_WITH_AES_128_CBC_SHA256 */ ++ "PSK-AES128-CBC-SHA", /* TLS_PSK_WITH_AES_128_CBC_SHA */ ++ "DHE-PSK-AES128-CBC-SHA", /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA */ ++ "RSA-PSK-AES128-CBC-SHA", /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA */ ++ "PSK-AES128-CBC-SHA256", /* TLS_PSK_WITH_AES_128_CBC_SHA256 */ ++ "DHE-PSK-AES128-CBC-SHA256", /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 */ ++ "RSA-PSK-AES128-CBC-SHA256", /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 */ ++ "ECDHE-PSK-AES128-CBC-SHA", /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA */ ++ "ECDHE-PSK-AES128-CBC-SHA256", /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 */ ++ "AES128-CCM", /* TLS_RSA_WITH_AES_128_CCM */ ++ "AES128-CCM8", /* TLS_RSA_WITH_AES_128_CCM_8 */ ++ "PSK-AES128-CCM", /* TLS_PSK_WITH_AES_128_CCM */ ++ "PSK-AES128-CCM8", /* TLS_PSK_WITH_AES_128_CCM_8 */ ++ "AES128-GCM-SHA256", /* TLS_RSA_WITH_AES_128_GCM_SHA256 */ ++ "DH-RSA-AES128-GCM-SHA256", /* TLS_DH_RSA_WITH_AES_128_GCM_SHA256 */ ++ "DH-DSS-AES128-GCM-SHA256", /* TLS_DH_DSS_WITH_AES_128_GCM_SHA256 */ ++ "ADH-AES128-GCM-SHA256", /* TLS_DH_anon_WITH_AES_128_GCM_SHA256 */ ++ "PSK-AES128-GCM-SHA256", /* TLS_PSK_WITH_AES_128_GCM_SHA256 */ ++ "RSA-PSK-AES128-GCM-SHA256", /* TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 */ ++ "ECDH-ECDSA-AES128-GCM-SHA256", /* TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 */ ++ "ECDH-RSA-AES128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 */ ++ "SRP-AES-128-CBC-SHA", /* TLS_SRP_SHA_WITH_AES_128_CBC_SHA */ ++ "SRP-RSA-AES-128-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA */ ++ "SRP-DSS-AES-128-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA */ ++ ++ /* blacklisted AES256 encrpytion ciphers */ ++ "AES256-SHA", /* TLS_RSA_WITH_AES_256_CBC_SHA */ ++ "DH-DSS-AES256-SHA", /* TLS_DH_DSS_WITH_AES_256_CBC_SHA */ ++ "DH-RSA-AES256-SHA", /* TLS_DH_RSA_WITH_AES_256_CBC_SHA */ ++ "DHE-DSS-AES256-SHA", /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA */ ++ "DHE-RSA-AES256-SHA", /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA */ ++ "ADH-AES256-SHA", /* TLS_DH_anon_WITH_AES_256_CBC_SHA */ ++ "AES256-SHA256", /* TLS_RSA_WITH_AES_256_CBC_SHA256 */ ++ "DH-DSS-AES256-SHA256", /* TLS_DH_DSS_WITH_AES_256_CBC_SHA256 */ ++ "DH-RSA-AES256-SHA256", /* TLS_DH_RSA_WITH_AES_256_CBC_SHA256 */ ++ "DHE-DSS-AES256-SHA256", /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 */ ++ "DHE-RSA-AES256-SHA256", /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 */ ++ "ADH-AES256-SHA256", /* TLS_DH_anon_WITH_AES_256_CBC_SHA256 */ ++ "ECDH-ECDSA-AES256-SHA", /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA */ ++ "ECDHE-ECDSA-AES256-SHA", /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA */ ++ "ECDH-RSA-AES256-SHA", /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA */ ++ "ECDHE-RSA-AES256-SHA", /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA */ ++ "AECDH-AES256-SHA", /* TLS_ECDH_anon_WITH_AES_256_CBC_SHA */ ++ "ECDHE-ECDSA-AES256-SHA384", /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 */ ++ "ECDH-ECDSA-AES256-SHA384", /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 */ ++ "ECDHE-RSA-AES256-SHA384", /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 */ ++ "ECDH-RSA-AES256-SHA384", /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 */ ++ "PSK-AES256-CBC-SHA", /* TLS_PSK_WITH_AES_256_CBC_SHA */ ++ "DHE-PSK-AES256-CBC-SHA", /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA */ ++ "RSA-PSK-AES256-CBC-SHA", /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA */ ++ "PSK-AES256-CBC-SHA384", /* TLS_PSK_WITH_AES_256_CBC_SHA384 */ ++ "DHE-PSK-AES256-CBC-SHA384", /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 */ ++ "RSA-PSK-AES256-CBC-SHA384", /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 */ ++ "ECDHE-PSK-AES256-CBC-SHA", /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA */ ++ "ECDHE-PSK-AES256-CBC-SHA384", /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 */ ++ "SRP-AES-256-CBC-SHA", /* TLS_SRP_SHA_WITH_AES_256_CBC_SHA */ ++ "SRP-RSA-AES-256-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA */ ++ "SRP-DSS-AES-256-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA */ ++ "AES256-CCM", /* TLS_RSA_WITH_AES_256_CCM */ ++ "AES256-CCM8", /* TLS_RSA_WITH_AES_256_CCM_8 */ ++ "PSK-AES256-CCM", /* TLS_PSK_WITH_AES_256_CCM */ ++ "PSK-AES256-CCM8", /* TLS_PSK_WITH_AES_256_CCM_8 */ ++ "AES256-GCM-SHA384", /* TLS_RSA_WITH_AES_256_GCM_SHA384 */ ++ "DH-RSA-AES256-GCM-SHA384", /* TLS_DH_RSA_WITH_AES_256_GCM_SHA384 */ ++ "DH-DSS-AES256-GCM-SHA384", /* TLS_DH_DSS_WITH_AES_256_GCM_SHA384 */ ++ "ADH-AES256-GCM-SHA384", /* TLS_DH_anon_WITH_AES_256_GCM_SHA384 */ ++ "PSK-AES256-GCM-SHA384", /* TLS_PSK_WITH_AES_256_GCM_SHA384 */ ++ "RSA-PSK-AES256-GCM-SHA384", /* TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 */ ++ "ECDH-ECDSA-AES256-GCM-SHA384", /* TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 */ ++ "ECDH-RSA-AES256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 */ ++ ++ /* blacklisted CAMELLIA128 encrpytion ciphers */ ++ "CAMELLIA128-SHA", /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA */ ++ "DH-DSS-CAMELLIA128-SHA", /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA */ ++ "DH-RSA-CAMELLIA128-SHA", /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA */ ++ "DHE-DSS-CAMELLIA128-SHA", /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA */ ++ "DHE-RSA-CAMELLIA128-SHA", /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA */ ++ "ADH-CAMELLIA128-SHA", /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA */ ++ "ECDHE-ECDSA-CAMELLIA128-SHA256", /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */ ++ "ECDH-ECDSA-CAMELLIA128-SHA256", /* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */ ++ "ECDHE-RSA-CAMELLIA128-SHA256", /* TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */ ++ "ECDH-RSA-CAMELLIA128-SHA256", /* TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */ ++ "PSK-CAMELLIA128-SHA256", /* TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 */ ++ "DHE-PSK-CAMELLIA128-SHA256", /* TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */ ++ "RSA-PSK-CAMELLIA128-SHA256", /* TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 */ ++ "ECDHE-PSK-CAMELLIA128-SHA256", /* TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */ ++ "CAMELLIA128-GCM-SHA256", /* TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 */ ++ "DH-RSA-CAMELLIA128-GCM-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */ ++ "DH-DSS-CAMELLIA128-GCM-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 */ ++ "ADH-CAMELLIA128-GCM-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 */ ++ "ECDH-ECDSA-CAMELLIA128-GCM-SHA256",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 */ ++ "ECDH-RSA-CAMELLIA128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */ ++ "PSK-CAMELLIA128-GCM-SHA256", /* TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 */ ++ "RSA-PSK-CAMELLIA128-GCM-SHA256", /* TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 */ ++ "CAMELLIA128-SHA256", /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 */ ++ "DH-DSS-CAMELLIA128-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 */ ++ "DH-RSA-CAMELLIA128-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */ ++ "DHE-DSS-CAMELLIA128-SHA256", /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 */ ++ "DHE-RSA-CAMELLIA128-SHA256", /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */ ++ "ADH-CAMELLIA128-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 */ ++ ++ /* blacklisted CAMELLIA256 encrpytion ciphers */ ++ "CAMELLIA256-SHA", /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA */ ++ "DH-RSA-CAMELLIA256-SHA", /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA */ ++ "DH-DSS-CAMELLIA256-SHA", /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA */ ++ "DHE-DSS-CAMELLIA256-SHA", /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA */ ++ "DHE-RSA-CAMELLIA256-SHA", /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA */ ++ "ADH-CAMELLIA256-SHA", /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA */ ++ "ECDHE-ECDSA-CAMELLIA256-SHA384", /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */ ++ "ECDH-ECDSA-CAMELLIA256-SHA384", /* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */ ++ "ECDHE-RSA-CAMELLIA256-SHA384", /* TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 */ ++ "ECDH-RSA-CAMELLIA256-SHA384", /* TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 */ ++ "PSK-CAMELLIA256-SHA384", /* TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 */ ++ "DHE-PSK-CAMELLIA256-SHA384", /* TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */ ++ "RSA-PSK-CAMELLIA256-SHA384", /* TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 */ ++ "ECDHE-PSK-CAMELLIA256-SHA384", /* TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */ ++ "CAMELLIA256-SHA256", /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 */ ++ "DH-DSS-CAMELLIA256-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 */ ++ "DH-RSA-CAMELLIA256-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 */ ++ "DHE-DSS-CAMELLIA256-SHA256", /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 */ ++ "DHE-RSA-CAMELLIA256-SHA256", /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 */ ++ "ADH-CAMELLIA256-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 */ ++ "CAMELLIA256-GCM-SHA384", /* TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 */ ++ "DH-RSA-CAMELLIA256-GCM-SHA384", /* TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */ ++ "DH-DSS-CAMELLIA256-GCM-SHA384", /* TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 */ ++ "ADH-CAMELLIA256-GCM-SHA384", /* TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 */ ++ "ECDH-ECDSA-CAMELLIA256-GCM-SHA384",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 */ ++ "ECDH-RSA-CAMELLIA256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */ ++ "PSK-CAMELLIA256-GCM-SHA384", /* TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 */ ++ "RSA-PSK-CAMELLIA256-GCM-SHA384", /* TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 */ ++ ++ /* The blacklisted ARIA encrpytion ciphers */ ++ "ARIA128-SHA256", /* TLS_RSA_WITH_ARIA_128_CBC_SHA256 */ ++ "ARIA256-SHA384", /* TLS_RSA_WITH_ARIA_256_CBC_SHA384 */ ++ "DH-DSS-ARIA128-SHA256", /* TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 */ ++ "DH-DSS-ARIA256-SHA384", /* TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 */ ++ "DH-RSA-ARIA128-SHA256", /* TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 */ ++ "DH-RSA-ARIA256-SHA384", /* TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 */ ++ "DHE-DSS-ARIA128-SHA256", /* TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 */ ++ "DHE-DSS-ARIA256-SHA384", /* TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 */ ++ "DHE-RSA-ARIA128-SHA256", /* TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 */ ++ "DHE-RSA-ARIA256-SHA384", /* TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 */ ++ "ADH-ARIA128-SHA256", /* TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 */ ++ "ADH-ARIA256-SHA384", /* TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 */ ++ "ECDHE-ECDSA-ARIA128-SHA256", /* TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 */ ++ "ECDHE-ECDSA-ARIA256-SHA384", /* TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 */ ++ "ECDH-ECDSA-ARIA128-SHA256", /* TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 */ ++ "ECDH-ECDSA-ARIA256-SHA384", /* TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 */ ++ "ECDHE-RSA-ARIA128-SHA256", /* TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 */ ++ "ECDHE-RSA-ARIA256-SHA384", /* TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 */ ++ "ECDH-RSA-ARIA128-SHA256", /* TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 */ ++ "ECDH-RSA-ARIA256-SHA384", /* TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 */ ++ "ARIA128-GCM-SHA256", /* TLS_RSA_WITH_ARIA_128_GCM_SHA256 */ ++ "ARIA256-GCM-SHA384", /* TLS_RSA_WITH_ARIA_256_GCM_SHA384 */ ++ "DH-DSS-ARIA128-GCM-SHA256", /* TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 */ ++ "DH-DSS-ARIA256-GCM-SHA384", /* TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 */ ++ "DH-RSA-ARIA128-GCM-SHA256", /* TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 */ ++ "DH-RSA-ARIA256-GCM-SHA384", /* TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 */ ++ "ADH-ARIA128-GCM-SHA256", /* TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 */ ++ "ADH-ARIA256-GCM-SHA384", /* TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 */ ++ "ECDH-ECDSA-ARIA128-GCM-SHA256", /* TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 */ ++ "ECDH-ECDSA-ARIA256-GCM-SHA384", /* TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 */ ++ "ECDH-RSA-ARIA128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 */ ++ "ECDH-RSA-ARIA256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 */ ++ "PSK-ARIA128-SHA256", /* TLS_PSK_WITH_ARIA_128_CBC_SHA256 */ ++ "PSK-ARIA256-SHA384", /* TLS_PSK_WITH_ARIA_256_CBC_SHA384 */ ++ "DHE-PSK-ARIA128-SHA256", /* TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 */ ++ "DHE-PSK-ARIA256-SHA384", /* TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 */ ++ "RSA-PSK-ARIA128-SHA256", /* TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 */ ++ "RSA-PSK-ARIA256-SHA384", /* TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 */ ++ "ARIA128-GCM-SHA256", /* TLS_PSK_WITH_ARIA_128_GCM_SHA256 */ ++ "ARIA256-GCM-SHA384", /* TLS_PSK_WITH_ARIA_256_GCM_SHA384 */ ++ "RSA-PSK-ARIA128-GCM-SHA256", /* TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 */ ++ "RSA-PSK-ARIA256-GCM-SHA384", /* TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 */ ++ "ECDHE-PSK-ARIA128-SHA256", /* TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 */ ++ "ECDHE-PSK-ARIA256-SHA384", /* TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 */ ++ ++ /* blacklisted SEED encryptions */ ++ "SEED-SHA", /*TLS_RSA_WITH_SEED_CBC_SHA */ ++ "DH-DSS-SEED-SHA", /* TLS_DH_DSS_WITH_SEED_CBC_SHA */ ++ "DH-RSA-SEED-SHA", /* TLS_DH_RSA_WITH_SEED_CBC_SHA */ ++ "DHE-DSS-SEED-SHA", /* TLS_DHE_DSS_WITH_SEED_CBC_SHA */ ++ "DHE-RSA-SEED-SHA", /* TLS_DHE_RSA_WITH_SEED_CBC_SHA */ ++ "ADH-SEED-SHA", /* TLS_DH_anon_WITH_SEED_CBC_SHA */ ++ ++ /* blacklisted KRB5 ciphers */ ++ "KRB5-DES-CBC-SHA", /* TLS_KRB5_WITH_DES_CBC_SHA */ ++ "KRB5-DES-CBC3-SHA", /* TLS_KRB5_WITH_3DES_EDE_CBC_SHA */ ++ "KRB5-IDEA-CBC-SHA", /* TLS_KRB5_WITH_IDEA_CBC_SHA */ ++ "KRB5-DES-CBC-MD5", /* TLS_KRB5_WITH_DES_CBC_MD5 */ ++ "KRB5-DES-CBC3-MD5", /* TLS_KRB5_WITH_3DES_EDE_CBC_MD5 */ ++ "KRB5-IDEA-CBC-MD5", /* TLS_KRB5_WITH_IDEA_CBC_MD5 */ ++ "EXP-KRB5-DES-CBC-SHA", /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA */ ++ "EXP-KRB5-DES-CBC-MD5", /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 */ ++ "EXP-KRB5-RC2-CBC-SHA", /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA */ ++ "EXP-KRB5-RC2-CBC-MD5", /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 */ ++ ++ /* blacklisted exoticas */ ++ "DHE-DSS-CBC-SHA", /* TLS_DHE_DSS_WITH_DES_CBC_SHA */ ++ "IDEA-CBC-SHA", /* TLS_RSA_WITH_IDEA_CBC_SHA */ ++ ++ /* not really sure if the following names are correct */ ++ "SSL3_CK_SCSV", /* TLS_EMPTY_RENEGOTIATION_INFO_SCSV */ ++ "SSL3_CK_FALLBACK_SCSV" ++}; ++static size_t RFC7540_names_LEN = sizeof(RFC7540_names)/sizeof(RFC7540_names[0]); ++ ++ ++static apr_hash_t *BLCNames; ++ ++static void cipher_init(apr_pool_t *pool) ++{ ++ apr_hash_t *hash = apr_hash_make(pool); ++ const char *source; - int i; +++ unsigned int i; ++ ++ source = "rfc7540"; ++ for (i = 0; i < RFC7540_names_LEN; ++i) { ++ apr_hash_set(hash, RFC7540_names[i], APR_HASH_KEY_STRING, source); ++ } ++ ++ BLCNames = hash; ++} ++ ++static int cipher_is_blacklisted(const char *cipher, const char **psource) ++{ ++ *psource = apr_hash_get(BLCNames, cipher, APR_HASH_KEY_STRING); ++ return !!*psource; ++} ++ /******************************************************************************* * Hooks for processing incoming connections: * - pre_conn_before_tls switches SSL off for stream connections @@@@ -467,88 -86,12 -86,12 +466,88 @@@@ int h2_h2_is_tls(conn_rec *c return opt_ssl_is_https && opt_ssl_is_https(c); } --int h2_tls_disable(conn_rec *c) ++int h2_is_acceptable_connection(conn_rec *c, int require_all) { -- if (opt_ssl_engine_disable) { -- return opt_ssl_engine_disable(c); ++ int is_tls = h2_h2_is_tls(c); ++ h2_config *cfg = h2_config_get(c); ++ ++ if (is_tls && h2_config_geti(cfg, H2_CONF_MODERN_TLS_ONLY) > 0) { ++ /* Check TLS connection for modern TLS parameters, as defined in ++ * RFC 7540 and https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility ++ */ ++ apr_pool_t *pool = c->pool; ++ server_rec *s = c->base_server; ++ char *val; ++ ++ if (!opt_ssl_var_lookup) { ++ /* unable to check */ ++ return 0; ++ } ++ ++ /* Need Tlsv1.2 or higher, rfc 7540, ch. 9.2 ++ */ - val = opt_ssl_var_lookup(pool, s, c, NULL, "SSL_PROTOCOL"); +++ val = opt_ssl_var_lookup(pool, s, c, NULL, (char*)"SSL_PROTOCOL"); ++ if (val && *val) { ++ if (strncmp("TLS", val, 3) ++ || !strcmp("TLSv1", val) ++ || !strcmp("TLSv1.1", val)) { ++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, ++ "h2_h2(%ld): tls protocol not suitable: %s", ++ (long)c->id, val); ++ return 0; ++ } ++ } ++ else if (require_all) { ++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, ++ "h2_h2(%ld): tls protocol is indetermined", (long)c->id); ++ return 0; ++ } ++ ++ /* Check TLS cipher blacklist ++ */ - val = opt_ssl_var_lookup(pool, s, c, NULL, "SSL_CIPHER"); +++ val = opt_ssl_var_lookup(pool, s, c, NULL, (char*)"SSL_CIPHER"); ++ if (val && *val) { ++ const char *source; ++ if (cipher_is_blacklisted(val, &source)) { ++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, ++ "h2_h2(%ld): tls cipher %s blacklisted by %s", ++ (long)c->id, val, source); ++ return 0; ++ } ++ } ++ else if (require_all) { ++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, ++ "h2_h2(%ld): tls cipher is indetermined", (long)c->id); ++ return 0; ++ } } -- return 0; ++ return 1; ++} ++ ++int h2_allows_h2_direct(conn_rec *c) ++{ ++ h2_config *cfg = h2_config_get(c); ++ int h2_direct = h2_config_geti(cfg, H2_CONF_DIRECT); ++ ++ if (h2_direct < 0) { ++ if (h2_h2_is_tls(c)) { ++ /* disabled by default on TLS */ ++ h2_direct = 0; ++ } ++ else { ++ /* enabled if "Protocols h2c" is configured */ ++ h2_direct = ap_is_allowed_protocol(c, NULL, NULL, "h2c"); ++ } ++ } ++ return !!h2_direct; ++} ++ ++int h2_allows_h2_upgrade(conn_rec *c) ++{ ++ h2_config *cfg = h2_config_get(c); ++ int h2_upgrade = h2_config_geti(cfg, H2_CONF_UPGRADE); ++ ++ return h2_upgrade > 0 || (h2_upgrade < 0 && !h2_h2_is_tls(c)); } /******************************************************************************* @@@@ -652,7 -199,7 -199,7 +651,7 @@@@ int h2_h2_process_conn(conn_rec* c ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, connection, h2 active"); --- return h2_conn_main(c); +++ return h2_conn_process(c, NULL); } ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined"); @@@@ -662,14 -209,14 -209,14 +661,14 @@@@ static int h2_h2_post_read_req(request_rec *r) { h2_ctx *ctx = h2_ctx_rget(r); -- struct h2_task_env *env = h2_ctx_get_task(ctx); -- if (env) { ++ struct h2_task *task = h2_ctx_get_task(ctx); ++ if (task) { /* h2_task connection for a stream, not for h2c */ --- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, +++ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "adding h1_to_h2_resp output filter"); -- if (env->serialize_headers) { ++ if (task->serialize_headers) { ap_remove_output_filter_byhandle(r->output_filters, "H1_TO_H2_RESP"); -- ap_add_output_filter("H1_TO_H2_RESP", env, r, r->connection); ++ ap_add_output_filter("H1_TO_H2_RESP", task, r, r->connection); } else { /* replace the core http filter that formats response headers diff --cc modules/http2/h2_h2.h index 4cf2785929d,9a1184d8b69,9a1184d8b69..4974d866115 --- a/modules/http2/h2_h2.h +++ b/modules/http2/h2_h2.h @@@@ -34,31 -34,6 -34,6 +34,37 @@@@ extern const char *h2_tls_protos[] */ extern const char *H2_MAGIC_TOKEN; ++#define H2_ERR_NO_ERROR (0x00) ++#define H2_ERR_PROTOCOL_ERROR (0x01) ++#define H2_ERR_INTERNAL_ERROR (0x02) ++#define H2_ERR_FLOW_CONTROL_ERROR (0x03) ++#define H2_ERR_SETTINGS_TIMEOUT (0x04) ++#define H2_ERR_STREAM_CLOSED (0x05) ++#define H2_ERR_FRAME_SIZE_ERROR (0x06) ++#define H2_ERR_REFUSED_STREAM (0x07) ++#define H2_ERR_CANCEL (0x08) ++#define H2_ERR_COMPRESSION_ERROR (0x09) ++#define H2_ERR_CONNECT_ERROR (0x0a) ++#define H2_ERR_ENHANCE_YOUR_CALM (0x0b) ++#define H2_ERR_INADEQUATE_SECURITY (0x0c) ++#define H2_ERR_HTTP_1_1_REQUIRED (0x0d) ++ ++/* Maximum number of padding bytes in a frame, rfc7540 */ ++#define H2_MAX_PADLEN 256 +++/* Initial default window size, RFC 7540 ch. 6.5.2 */ +++#define H2_INITIAL_WINDOW_SIZE ((64*1024)-1) +++ +++#define H2_HTTP_2XX(a) ((a) >= 200 && (a) < 300) +++ +++#define H2_STREAM_CLIENT_INITIATED(id) (id&0x01) ++ ++/** ++ * Provide a user readable description of the HTTP/2 error code- ++ * @param h2_error http/2 error code, as in rfc 7540, ch. 7 ++ * @return textual description of code or that it is unknown. ++ */ - const char *h2_h2_err_description(int h2_error); +++const char *h2_h2_err_description(unsigned int h2_error); ++ /* * One time, post config intialization. */ diff --cc modules/http2/h2_io.c index e952ad0f6b4,42734430fa4,42734430fa4..b33faee1f3d --- a/modules/http2/h2_io.c +++ b/modules/http2/h2_io.c @@@@ -32,53 -31,33 -31,33 +32,50 @@@@ h2_io *h2_io_create(int id, apr_pool_t if (io) { io->id = id; io->pool = pool; +++ io->bucket_alloc = bucket_alloc; io->bbin = NULL; --- io->bbout = apr_brigade_create(pool, bucket_alloc); +++ io->bbout = NULL; } return io; } ---static void h2_io_cleanup(h2_io *io) - { - if (io->task) { - h2_task_destroy(io->task); - io->task = NULL; - } - } - ++void h2_io_destroy(h2_io *io) { - h2_io_cleanup(io); -- (void)io; +++ if (io->pool) { +++ apr_pool_destroy(io->pool); +++ /* gone */ +++ } } --void h2_io_destroy(h2_io *io) ++void h2_io_set_response(h2_io *io, h2_response *response) ++{ +++ AP_DEBUG_ASSERT(io->pool); ++ AP_DEBUG_ASSERT(response); ++ AP_DEBUG_ASSERT(!io->response); ++ io->response = h2_response_copy(io->pool, response); ++ if (response->rst_error) { ++ h2_io_rst(io, response->rst_error); ++ } ++} ++ ++ ++void h2_io_rst(h2_io *io, int error) { -- h2_io_cleanup(io); ++ io->rst_error = error; ++ io->eos_in = 1; } int h2_io_in_has_eos_for(h2_io *io) { --- return io->eos_in || (io->bbin && h2_util_has_eos(io->bbin, 0)); +++ return io->eos_in || (io->bbin && h2_util_has_eos(io->bbin, -1)); } int h2_io_out_has_data(h2_io *io) { --- return h2_util_bb_has_data_or_eos(io->bbout); +++ return io->bbout && h2_util_bb_has_data_or_eos(io->bbout); } ---apr_size_t h2_io_out_length(h2_io *io) +++apr_off_t h2_io_out_length(h2_io *io) { if (io->bbout) { apr_off_t len = 0; @@@@ -127,13 -99,13 -99,13 +124,12 @@@@ apr_status_t h2_io_in_write(h2_io *io, if (io->eos_in) { return APR_EOF; } --- io->eos_in = h2_util_has_eos(bb, 0); +++ io->eos_in = h2_util_has_eos(bb, -1); if (!APR_BRIGADE_EMPTY(bb)) { if (!io->bbin) { --- io->bbin = apr_brigade_create(io->bbout->p, --- io->bbout->bucket_alloc); +++ io->bbin = apr_brigade_create(io->pool, io->bucket_alloc); } - return h2_util_move(io->bbin, bb, 0, NULL, "h2_io_in_write"); -- return h2_util_move(io->bbin, bb, 0, 0, "h2_io_in_write"); +++ return h2_util_move(io->bbin, bb, -1, NULL, "h2_io_in_write"); } return APR_SUCCESS; } @@@@ -154,80 -122,18 -122,18 +150,93 @@@@ apr_status_t h2_io_in_close(h2_io *io apr_status_t h2_io_out_readx(h2_io *io, h2_io_data_cb *cb, void *ctx, --- apr_size_t *plen, int *peos) +++ apr_off_t *plen, int *peos) { ++ apr_status_t status; ++ ++ if (io->rst_error) { ++ return APR_ECONNABORTED; ++ } ++ ++ if (io->eos_out) { ++ *plen = 0; ++ *peos = 1; ++ return APR_SUCCESS; ++ } +++ else if (!io->bbout) { +++ *plen = 0; +++ *peos = 0; +++ return APR_EAGAIN; +++ } ++ if (cb == NULL) { /* just checking length available */ -- return h2_util_bb_avail(io->bbout, plen, peos); ++ status = h2_util_bb_avail(io->bbout, plen, peos); ++ } ++ else { ++ status = h2_util_bb_readx(io->bbout, cb, ctx, plen, peos); ++ if (status == APR_SUCCESS) { ++ io->eos_out = *peos; ++ } } -- return h2_util_bb_readx(io->bbout, cb, ctx, plen, peos); ++ ++ return status; ++} ++ ++apr_status_t h2_io_out_read_to(h2_io *io, apr_bucket_brigade *bb, - apr_size_t *plen, int *peos) +++ apr_off_t *plen, int *peos) ++{ ++ if (io->rst_error) { ++ return APR_ECONNABORTED; ++ } ++ ++ if (io->eos_out) { ++ *plen = 0; ++ *peos = 1; ++ return APR_SUCCESS; ++ } - +++ else if (!io->bbout) { +++ *plen = 0; +++ *peos = 0; +++ return APR_EAGAIN; +++ } ++ ++ io->eos_out = *peos = h2_util_has_eos(io->bbout, *plen); ++ return h2_util_move(bb, io->bbout, *plen, NULL, "h2_io_read_to"); } apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb, apr_size_t maxlen, int *pfile_handles_allowed) { ++ apr_status_t status; ++ int start_allowed; ++ ++ if (io->rst_error) { ++ return APR_ECONNABORTED; ++ } ++ ++ if (io->eos_out) { ++ apr_off_t len; ++ /* We have already delivered an EOS bucket to a reader, no ++ * sense in storing anything more here. ++ */ ++ status = apr_brigade_length(bb, 1, &len); ++ if (status == APR_SUCCESS) { ++ if (len > 0) { ++ /* someone tries to write real data after EOS, that ++ * does not look right. */ ++ status = APR_EOF; ++ } ++ /* cleanup, as if we had moved the data */ ++ apr_brigade_cleanup(bb); ++ } ++ return status; ++ } +++ +++ if (!io->bbout) { +++ io->bbout = apr_brigade_create(io->pool, io->bucket_alloc); +++ } ++ /* Let's move the buckets from the request processing in here, so * that the main thread can read them when it has time/capacity. * @@@@ -238,8 -144,8 -144,8 +247,7 @@@@ * many open files already buffered. Otherwise we will run out of * file handles. */ -- int start_allowed = *pfile_handles_allowed; -- apr_status_t status; ++ start_allowed = *pfile_handles_allowed; - status = h2_util_move(io->bbout, bb, maxlen, pfile_handles_allowed, "h2_io_out_write"); /* track # file buckets moved into our pool */ @@@@ -252,12 -158,7 -158,7 +260,15 @@@@ apr_status_t h2_io_out_close(h2_io *io) { -- APR_BRIGADE_INSERT_TAIL(io->bbout, -- apr_bucket_eos_create(io->bbout->bucket_alloc)); ++ if (io->rst_error) { ++ return APR_ECONNABORTED; ++ } - if (!io->eos_out && !h2_util_has_eos(io->bbout, 0)) { +++ if (!io->bbout) { +++ io->bbout = apr_brigade_create(io->pool, io->bucket_alloc); +++ } +++ if (!io->eos_out && !h2_util_has_eos(io->bbout, -1)) { ++ APR_BRIGADE_INSERT_TAIL(io->bbout, ++ apr_bucket_eos_create(io->bbout->bucket_alloc)); ++ } return APR_SUCCESS; } diff --cc modules/http2/h2_io.h index d8769dd1182,946ee44334e,946ee44334e..1fd1167747a --- a/modules/http2/h2_io.h +++ b/modules/http2/h2_io.h @@@@ -18,12 -18,11 -18,11 +18,12 @@@@ struct h2_response; struct apr_thread_cond_t; ---struct h2_task; +++struct h2_request; - typedef apr_status_t h2_io_data_cb(void *ctx, const char *data, apr_size_t len); --typedef apr_status_t h2_io_data_cb(void *ctx, -- const char *data, apr_size_t len); +++typedef apr_status_t h2_io_data_cb(void *ctx, const char *data, apr_off_t len); ++ ++typedef int h2_stream_pri_cmp(int stream_id1, int stream_id2, void *ctx); typedef struct h2_io h2_io; @@@@ -31,22 -30,17 -30,17 +31,24 @@@@ struct h2_io { int id; /* stream identifier */ apr_pool_t *pool; /* stream pool */ --- apr_bucket_brigade *bbin; /* input data for stream */ --- int eos_in; -- int task_done; +++ int orphaned; /* h2_stream is gone for this io */ + -- apr_size_t input_consumed; /* how many bytes have been read */ ++ int task_done; +++ const struct h2_request *request; /* request on this io */ +++ int request_body; /* == 0 iff request has no body */ +++ struct h2_response *response;/* response for submit, once created */ ++ int rst_error; - int zombie; - - struct h2_task *task; /* task created for this io */ ++ - apr_size_t input_consumed; /* how many bytes have been read */ +++ int eos_in; +++ apr_bucket_brigade *bbin; /* input data for stream */ struct apr_thread_cond_t *input_arrived; /* block on reading */ +++ apr_size_t input_consumed; /* how many bytes have been read */ - apr_bucket_brigade *bbout; /* output data from stream */ ++ int eos_out; + apr_bucket_brigade *bbout; /* output data from stream */ +++ apr_bucket_alloc_t *bucket_alloc; struct apr_thread_cond_t *output_drained; /* block on writing */ --- struct h2_response *response;/* submittable response created */ int files_handles_owned; }; @@@@ -121,11 -105,7 -105,7 +123,11 @@@@ apr_status_t h2_io_in_close(h2_io *io) */ apr_status_t h2_io_out_readx(h2_io *io, h2_io_data_cb *cb, void *ctx, --- apr_size_t *plen, int *peos); +++ apr_off_t *plen, int *peos); ++ ++apr_status_t h2_io_out_read_to(h2_io *io, ++ apr_bucket_brigade *bb, - apr_size_t *plen, int *peos); +++ apr_off_t *plen, int *peos); apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb, apr_size_t maxlen, int *pfile_buckets_allowed); @@@@ -140,7 -120,7 -120,7 +142,7 @@@@ apr_status_t h2_io_out_close(h2_io *io) * Gives the overall length of the data that is currently queued for * output. */ ---apr_size_t h2_io_out_length(h2_io *io); +++apr_off_t h2_io_out_length(h2_io *io); #endif /* defined(__mod_h2__h2_io__) */ diff --cc modules/http2/h2_mplx.c index ad6bd30e32d,2d07b1eb6c3,2d07b1eb6c3..3908590985a --- a/modules/http2/h2_mplx.c +++ b/modules/http2/h2_mplx.c @@@@ -41,10 -40,9 -40,9 +41,24 @@@@ #include "h2_task_input.h" #include "h2_task_output.h" #include "h2_task_queue.h" +++#include "h2_worker.h" #include "h2_workers.h" ++#include "h2_util.h" ++ ++ +++#define H2_MPLX_IO_OUT(lvl,m,io,msg) \ +++ do { \ +++ if (APLOG_C_IS_LEVEL((m)->c,lvl)) \ +++ h2_util_bb_log((m)->c,(io)->id,lvl,msg,(io)->bbout); \ +++ } while(0) +++ +++#define H2_MPLX_IO_IN(lvl,m,io,msg) \ +++ do { \ +++ if (APLOG_C_IS_LEVEL((m)->c,lvl)) \ +++ h2_util_bb_log((m)->c,(io)->id,lvl,msg,(io)->bbin); \ +++ } while(0) + + static int is_aborted(h2_mplx *m, apr_status_t *pstatus) { AP_DEBUG_ASSERT(m); if (m->aborted) { @@@@ -74,6 -76,6 -76,6 +88,10 @@@@ static void h2_mplx_destroy(h2_mplx *m m->lock = NULL; } +++ if (m->spare_pool) { +++ apr_pool_destroy(m->spare_pool); +++ m->spare_pool = NULL; +++ } if (m->pool) { apr_pool_destroy(m->pool); } @@@@ -107,7 -109,7 -109,7 +125,7 @@@@ h2_mplx *h2_mplx_create(conn_rec *c, ap if (m) { m->id = c->id; APR_RING_ELEM_INIT(m, link); --- apr_atomic_set32(&m->refs, 1); +++ m->refs = 1; m->c = c; apr_pool_create_ex(&m->pool, parent, NULL, allocator); if (!m->pool) { @@@@ -135,33 -138,27 -138,27 +153,31 @@@@ return m; } ---static void reference(h2_mplx *m) - { - apr_atomic_inc32(&m->refs); - } - ++static void release(h2_mplx *m, int lock) { -- apr_atomic_inc32(&m->refs); --} -- --static void release(h2_mplx *m) --{ --- if (!apr_atomic_dec32(&m->refs)) { - if (lock) { - apr_thread_mutex_lock(m->lock); - } +++ if (lock) { +++ apr_thread_mutex_lock(m->lock); +++ --m->refs; if (m->join_wait) { apr_thread_cond_signal(m->join_wait); } - if (lock) { - apr_thread_mutex_unlock(m->lock); - } +++ apr_thread_mutex_unlock(m->lock); +++ } +++ else { +++ --m->refs; } } void h2_mplx_reference(h2_mplx *m) { --- reference(m); +++ apr_thread_mutex_lock(m->lock); +++ ++m->refs; +++ apr_thread_mutex_unlock(m->lock); } +++ void h2_mplx_release(h2_mplx *m) { -- release(m); ++ release(m, 1); } static void workers_register(h2_mplx *m) { @@@@ -190,17 -187,29 -187,29 +206,17 @@@@ apr_status_t h2_mplx_release_and_join(h status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { -- int attempts = 0; -- -- release(m); -- while (apr_atomic_read32(&m->refs) > 0) { ++ release(m, 0); - while (apr_atomic_read32(&m->refs) > 0) { +++ while (m->refs > 0) { m->join_wait = wait; -- ap_log_cerror(APLOG_MARK, (attempts? APLOG_INFO : APLOG_DEBUG), -- 0, m->c, ++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, "h2_mplx(%ld): release_join, refs=%d, waiting...", m->id, m->refs); -- apr_thread_cond_timedwait(wait, m->lock, apr_time_from_sec(10)); -- if (++attempts >= 6) { -- ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, -- APLOGNO(02952) -- "h2_mplx(%ld): join attempts exhausted, refs=%d", -- m->id, m->refs); -- break; -- } ++ apr_thread_cond_wait(wait, m->lock); } -- if (m->join_wait) { -- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, -- "h2_mplx(%ld): release_join -> destroy", m->id); -- } --- m->join_wait = NULL; ++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, ++ "h2_mplx(%ld): release_join -> destroy", m->id); +++ m->pool = NULL; apr_thread_mutex_unlock(m->lock); h2_mplx_destroy(m); } @@@@ -221,58 -230,77 -230,77 +237,60 @@@@ void h2_mplx_abort(h2_mplx *m } --h2_stream *h2_mplx_open_io(h2_mplx *m, int stream_id) ++static void io_destroy(h2_mplx *m, h2_io *io) { - if (io) { - apr_pool_t *pool = io->pool; - if (pool) { - io->pool = NULL; - apr_pool_clear(pool); - if (m->spare_pool) { - apr_pool_destroy(m->spare_pool); - } - m->spare_pool = pool; -- h2_stream *stream = NULL; -- apr_status_t status; -- h2_io *io; -- -- if (m->aborted) { -- return NULL; -- } -- status = apr_thread_mutex_lock(m->lock); -- if (APR_SUCCESS == status) { -- apr_pool_t *stream_pool = m->spare_pool; -- -- if (!stream_pool) { -- apr_pool_create(&stream_pool, m->pool); -- } -- else { -- m->spare_pool = NULL; -- } -- -- stream = h2_stream_create(stream_id, stream_pool, m); -- stream->state = H2_STREAM_ST_OPEN; -- -- io = h2_io_set_get(m->stream_ios, stream_id); -- if (!io) { -- io = h2_io_create(stream_id, stream_pool, m->bucket_alloc); -- h2_io_set_add(m->stream_ios, io); -- } -- status = io? APR_SUCCESS : APR_ENOMEM; -- apr_thread_mutex_unlock(m->lock); -- } -- return stream; --} -- --static void stream_destroy(h2_mplx *m, h2_stream *stream, h2_io *io) --{ -- apr_pool_t *pool = h2_stream_detach_pool(stream); +++ apr_pool_t *pool = io->pool; +++ +++ io->pool = NULL; +++ /* The pool is cleared/destroyed which also closes all +++ * allocated file handles. Give this count back to our +++ * file handle pool. */ +++ m->file_handles_allowed += io->files_handles_owned; +++ h2_io_set_remove(m->stream_ios, io); +++ h2_io_set_remove(m->ready_ios, io); +++ h2_io_destroy(io); +++ + if (pool) { + apr_pool_clear(pool); + if (m->spare_pool) { + apr_pool_destroy(m->spare_pool); } - /* The pool is cleared/destroyed which also closes all - * allocated file handles. Give this count back to our - * file handle pool. */ - m->file_handles_allowed += io->files_handles_owned; - h2_io_set_remove(m->stream_ios, io); - h2_io_set_remove(m->ready_ios, io); - h2_io_destroy(io); + m->spare_pool = pool; } -- h2_stream_destroy(stream); -- if (io) { -- /* The pool is cleared/destroyed which also closes all -- * allocated file handles. Give this count back to our -- * file handle pool. */ -- m->file_handles_allowed += io->files_handles_owned; -- h2_io_set_remove(m->stream_ios, io); -- h2_io_set_remove(m->ready_ios, io); -- h2_io_destroy(io); -- } } --apr_status_t h2_mplx_cleanup_stream(h2_mplx *m, h2_stream *stream) ++apr_status_t h2_mplx_stream_done(h2_mplx *m, int stream_id, int rst_error) { apr_status_t status; ++ AP_DEBUG_ASSERT(m); ++ if (m->aborted) { ++ return APR_ECONNABORTED; ++ } status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { -- h2_io *io = h2_io_set_get(m->stream_ios, stream->id); -- if (!io || io->task_done) { -- /* No more io or task already done -> cleanup immediately */ -- stream_destroy(m, stream, io); -- } -- else { -- /* Add stream to closed set for cleanup when task is done */ -- h2_stream_set_add(m->closed, stream); ++ h2_io *io = h2_io_set_get(m->stream_ios, stream_id); - +++ +++ /* there should be an h2_io, once the stream has been scheduled +++ * for processing, e.g. when we received all HEADERs. But when +++ * a stream is cancelled very early, it will not exist. */ ++ if (io) { ++ /* Remove io from ready set, we will never submit it */ ++ h2_io_set_remove(m->ready_ios, io); - - if (io->task_done) { +++ if (io->task_done || h2_tq_remove(m->q, io->id)) { +++ /* already finished or not even started yet */ ++ io_destroy(m, io); ++ } ++ else { ++ /* cleanup once task is done */ - io->zombie = 1; +++ io->orphaned = 1; ++ if (rst_error) { - /* Forward error code to fail any further attempt to - * write to io */ ++ h2_io_rst(io, rst_error); ++ } ++ } +++ } ++ apr_thread_mutex_unlock(m->lock); } return status; @@@@ -285,14 -313,18 -313,18 +303,14 @@@@ void h2_mplx_task_done(h2_mplx *m, int h2_io *io = h2_io_set_get(m->stream_ios, stream_id); ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c, "h2_mplx(%ld): task(%d) done", m->id, stream_id); -- if (stream) { -- /* stream was already closed by main connection and is in -- * zombie state. Now that the task is done with it, we -- * can free its resources. */ -- h2_stream_set_remove(m->closed, stream); -- stream_destroy(m, stream, io); -- } -- else if (io) { -- /* main connection has not finished stream. Mark task as done -- * so that eventual cleanup can start immediately. */ ++ if (io) { io->task_done = 1; - if (io->zombie) { +++ if (io->orphaned) { ++ io_destroy(m, io); ++ } ++ else { ++ /* hang around until the stream deregisteres */ ++ } } apr_thread_mutex_unlock(m->lock); } @@@@ -310,15 -342,15 -342,15 +328,17 @@@@ apr_status_t h2_mplx_in_read(h2_mplx *m status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); --- if (io) { +++ if (io && !io->orphaned) { io->input_arrived = iowait; --- status = h2_io_in_read(io, bb, 0); -- while (status == APR_EAGAIN +++ H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_read_pre"); +++ status = h2_io_in_read(io, bb, -1); ++ while (APR_STATUS_IS_EAGAIN(status) && !is_aborted(m, &status) && block == APR_BLOCK_READ) { apr_thread_cond_wait(io->input_arrived, m->lock); --- status = h2_io_in_read(io, bb, 0); +++ status = h2_io_in_read(io, bb, -1); } +++ H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_read_post"); io->input_arrived = NULL; } else { @@@@ -340,8 -372,8 -372,8 +360,10 @@@@ apr_status_t h2_mplx_in_write(h2_mplx * status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); --- if (io) { +++ if (io && !io->orphaned) { +++ H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_write_pre"); status = h2_io_in_write(io, bb); +++ H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_write_post"); if (io->input_arrived) { apr_thread_cond_signal(io->input_arrived); } @@@@ -364,8 -396,8 -396,8 +386,9 @@@@ apr_status_t h2_mplx_in_close(h2_mplx * status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); --- if (io) { +++ if (io && !io->orphaned) { status = h2_io_in_close(io); +++ H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_close"); if (io->input_arrived) { apr_thread_cond_signal(io->input_arrived); } @@@@ -422,16 -454,9 -454,9 +445,9 @@@@ apr_status_t h2_mplx_in_update_windows( return status; } - #define H2_MPLX_IO_OUT(lvl,m,io,msg) \ - do { \ - if (APLOG_C_IS_LEVEL((m)->c,lvl)) \ - h2_util_bb_log((m)->c,(io)->id,lvl,msg,(io)->bbout); \ - } while(0) - - apr_status_t h2_mplx_out_readx(h2_mplx *m, int stream_id, h2_io_data_cb *cb, void *ctx, --- apr_size_t *plen, int *peos) +++ apr_off_t *plen, int *peos) { apr_status_t status; AP_DEBUG_ASSERT(m); @@@@ -441,42 -466,8 -466,8 +457,42 @@@@ status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); --- if (io) { +++ if (io && !io->orphaned) { ++ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_readx_pre"); ++ status = h2_io_out_readx(io, cb, ctx, plen, peos); ++ ++ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_readx_post"); ++ if (status == APR_SUCCESS && cb && io->output_drained) { ++ apr_thread_cond_signal(io->output_drained); ++ } ++ } ++ else { ++ status = APR_ECONNABORTED; ++ } ++ apr_thread_mutex_unlock(m->lock); ++ } ++ return status; ++} ++ ++apr_status_t h2_mplx_out_read_to(h2_mplx *m, int stream_id, ++ apr_bucket_brigade *bb, - apr_size_t *plen, int *peos) +++ apr_off_t *plen, int *peos) ++{ ++ apr_status_t status; ++ AP_DEBUG_ASSERT(m); ++ if (m->aborted) { ++ return APR_ECONNABORTED; ++ } ++ status = apr_thread_mutex_lock(m->lock); ++ if (APR_SUCCESS == status) { ++ h2_io *io = h2_io_set_get(m->stream_ios, stream_id); - if (io) { +++ if (io && !io->orphaned) { ++ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_read_to_pre"); ++ ++ status = h2_io_out_read_to(io, bb, plen, peos); ++ ++ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_read_to_post"); if (status == APR_SUCCESS && io->output_drained) { apr_thread_cond_signal(io->output_drained); } @@@@ -499,33 -490,24 -490,24 +515,42 @@@@ h2_stream *h2_mplx_next_submit(h2_mplx } status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { -- h2_io *io = h2_io_set_get_highest_prio(m->ready_ios); ++ h2_io *io = h2_io_set_pop_highest_prio(m->ready_ios); if (io) { -- h2_response *response = io->response; -- -- AP_DEBUG_ASSERT(response); -- h2_io_set_remove(m->ready_ios, io); -- -- stream = h2_stream_set_get(streams, response->stream_id); ++ stream = h2_stream_set_get(streams, io->id); if (stream) { -- h2_stream_set_response(stream, response, io->bbout); -- if (io->output_drained) { -- apr_thread_cond_signal(io->output_drained); ++ if (io->rst_error) { ++ h2_stream_rst(stream, io->rst_error); ++ } ++ else { ++ AP_DEBUG_ASSERT(io->response); +++ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_next_submit_pre"); ++ h2_stream_set_response(stream, io->response, io->bbout); +++ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_next_submit_post"); } ++ } else { -- ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_NOTFOUND, m->c, -- APLOGNO(02953) "h2_mplx(%ld): stream for response %d", -- m->id, response->stream_id); ++ /* We have the io ready, but the stream has gone away, maybe ++ * reset by the client. Should no longer happen since such ++ * streams should clear io's from the ready queue. ++ */ ++ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, m->c, APLOGNO(02953) ++ "h2_mplx(%ld): stream for response %d closed, " ++ "resetting io to close request processing", ++ m->id, io->id); - h2_io_rst(io, H2_ERR_STREAM_CLOSED); +++ io->orphaned = 1; +++ if (io->task_done) { +++ io_destroy(m, io); +++ } +++ else { +++ /* hang around until the h2_task is done */ +++ h2_io_rst(io, H2_ERR_STREAM_CLOSED); +++ } ++ } ++ ++ if (io->output_drained) { ++ apr_thread_cond_signal(io->output_drained); } } apr_thread_mutex_unlock(m->lock); @@@@ -577,15 -559,15 -559,15 +602,15 @@@@ static apr_status_t out_open(h2_mplx *m apr_status_t status = APR_SUCCESS; h2_io *io = h2_io_set_get(m->stream_ios, stream_id); --- if (io) { +++ if (io && !io->orphaned) { if (f) { --- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, - "h2_mplx(%ld-%d): open response: %s, rst=%d", - m->id, stream_id, response->status, -- "h2_mplx(%ld-%d): open response: %s", -- m->id, stream_id, response->status); +++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, +++ "h2_mplx(%ld-%d): open response: %d, rst=%d", +++ m->id, stream_id, response->http_status, ++ response->rst_error); } -- io->response = h2_response_copy(io->pool, response); -- AP_DEBUG_ASSERT(io->response); ++ h2_io_set_response(io, response); h2_io_set_add(m->ready_ios, io); if (bb) { status = out_write(m, io, f, bb, iowait); @@@@ -635,10 -614,8 -614,8 +660,10 @@@@ apr_status_t h2_mplx_out_write(h2_mplx if (APR_SUCCESS == status) { if (!m->aborted) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); --- if (io) { +++ if (io && !io->orphaned) { status = out_write(m, io, f, bb, iowait); ++ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_write"); ++ have_out_data_for(m, stream_id); if (m->aborted) { return APR_ECONNABORTED; @@@@ -667,22 -644,17 -644,17 +692,22 @@@@ apr_status_t h2_mplx_out_close(h2_mplx if (APR_SUCCESS == status) { if (!m->aborted) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); --- if (io) { -- if (!io->response || !io->response->ngheader) { +++ if (io && !io->orphaned) { ++ if (!io->response && !io->rst_error) { /* In case a close comes before a response was created, * insert an error one so that our streams can properly * reset. */ -- h2_response *r = h2_response_create(stream_id, -- "500", NULL, m->pool); ++ h2_response *r = h2_response_create(stream_id, 0, - "500", NULL, m->pool); +++ 500, NULL, m->pool); status = out_open(m, stream_id, r, NULL, NULL, NULL); ++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, m->c, ++ "h2_mplx(%ld-%d): close, no response, no rst", ++ m->id, io->id); } status = h2_io_out_close(io); ++ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_close"); ++ have_out_data_for(m, stream_id); if (m->aborted) { /* if we were the last output, the whole session might @@@@ -700,38 -672,6 -672,6 +725,38 @@@@ return status; } ++apr_status_t h2_mplx_out_rst(h2_mplx *m, int stream_id, int error) ++{ ++ apr_status_t status; ++ AP_DEBUG_ASSERT(m); ++ if (m->aborted) { ++ return APR_ECONNABORTED; ++ } ++ status = apr_thread_mutex_lock(m->lock); ++ if (APR_SUCCESS == status) { ++ if (!m->aborted) { ++ h2_io *io = h2_io_set_get(m->stream_ios, stream_id); - if (io && !io->rst_error) { +++ if (io && !io->rst_error && !io->orphaned) { ++ h2_io_rst(io, error); ++ if (!io->response) { ++ h2_io_set_add(m->ready_ios, io); ++ } ++ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_rst"); ++ ++ have_out_data_for(m, stream_id); ++ if (io->output_drained) { ++ apr_thread_cond_signal(io->output_drained); ++ } ++ } ++ else { ++ status = APR_ECONNABORTED; ++ } ++ } ++ apr_thread_mutex_unlock(m->lock); ++ } ++ return status; ++} ++ int h2_mplx_in_has_eos_for(h2_mplx *m, int stream_id) { int has_eos = 0; @@@@ -744,7 -684,7 -684,7 +769,7 @@@@ if (APR_SUCCESS == status) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io) { --- has_eos = h2_io_in_has_eos_for(io); +++ has_eos = io->orphaned || h2_io_in_has_eos_for(io); } apr_thread_mutex_unlock(m->lock); } @@@@ -802,116 -742,61 -742,61 +827,104 @@@@ static void have_out_data_for(h2_mplx * } } - typedef struct { - h2_stream_pri_cmp *cmp; - void *ctx; - } cmp_ctx; - - static int task_cmp(h2_task *t1, h2_task *t2, void *ctx) - { - cmp_ctx *x = ctx; - return x->cmp(t1->stream_id, t2->stream_id, x->ctx); - } - --apr_status_t h2_mplx_do_task(h2_mplx *m, struct h2_task *task) ++apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx) { apr_status_t status; ++ AP_DEBUG_ASSERT(m); if (m->aborted) { return APR_ECONNABORTED; } status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { - cmp_ctx x; - - x.cmp = cmp; - x.ctx = ctx; - h2_tq_sort(m->q, task_cmp, &x); -- /* TODO: needs to sort queue by priority */ +++ h2_tq_sort(m->q, cmp, ctx); ++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c, -- "h2_mplx: do task(%s)", task->id); -- h2_tq_append(m->q, task); ++ "h2_mplx(%ld): reprioritize tasks", m->id); apr_thread_mutex_unlock(m->lock); } workers_register(m); return status; } --h2_task *h2_mplx_pop_task(h2_mplx *m, int *has_more) ++static h2_io *open_io(h2_mplx *m, int stream_id) ++{ ++ apr_pool_t *io_pool = m->spare_pool; ++ h2_io *io; ++ ++ if (!io_pool) { ++ apr_pool_create(&io_pool, m->pool); ++ } ++ else { ++ m->spare_pool = NULL; ++ } ++ ++ io = h2_io_create(stream_id, io_pool, m->bucket_alloc); ++ h2_io_set_add(m->stream_ios, io); ++ ++ return io; ++} ++ ++ ++apr_status_t h2_mplx_process(h2_mplx *m, int stream_id, - struct h2_request *r, int eos, +++ const h2_request *req, int eos, ++ h2_stream_pri_cmp *cmp, void *ctx) { -- h2_task *task = NULL; apr_status_t status; ++ AP_DEBUG_ASSERT(m); if (m->aborted) { -- *has_more = 0; -- return NULL; ++ return APR_ECONNABORTED; } status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { - conn_rec *c; - h2_io *io; - cmp_ctx x; - - io = open_io(m, stream_id); - c = h2_conn_create(m->c, io->pool); - io->task = h2_task_create(m->id, stream_id, io->pool, m, c); - - status = h2_request_end_headers(r, m, io->task, eos); - if (status == APR_SUCCESS && eos) { -- task = h2_tq_pop_first(m->q); -- if (task) { -- h2_task_set_started(task); +++ h2_io *io = open_io(m, stream_id); +++ io->request = req; +++ io->request_body = !eos; +++ +++ if (eos) { ++ status = h2_io_in_close(io); } -- *has_more = !h2_tq_empty(m->q); ++ - if (status == APR_SUCCESS) { - x.cmp = cmp; - x.ctx = ctx; - h2_tq_add(m->q, io->task, task_cmp, &x); - } +++ h2_tq_add(m->q, io->id, cmp, ctx); +++ ++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, m->c, ++ "h2_mplx(%ld-%d): process", m->c->id, stream_id); +++ H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_process"); apr_thread_mutex_unlock(m->lock); } -- return task; ++ ++ if (status == APR_SUCCESS) { ++ workers_register(m); ++ } ++ return status; } - h2_task *h2_mplx_pop_task(h2_mplx *m, int *has_more) --apr_status_t h2_mplx_create_task(h2_mplx *m, struct h2_stream *stream) +++h2_task *h2_mplx_pop_task(h2_mplx *m, h2_worker *w, int *has_more) { ++ h2_task *task = NULL; apr_status_t status; +++ AP_DEBUG_ASSERT(m); if (m->aborted) { -- return APR_ECONNABORTED; ++ *has_more = 0; ++ return NULL; } status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { - task = h2_tq_shift(m->q); -- conn_rec *c = h2_conn_create(m->c, stream->pool); -- stream->task = h2_task_create(m->id, stream->id, -- stream->pool, m, c); -- +++ int sid; +++ while (!task && (sid = h2_tq_shift(m->q)) > 0) { +++ /* Anything not already setup correctly in the task +++ * needs to be so now, as task will be executed right about +++ * when this method returns. */ +++ h2_io *io = h2_io_set_get(m->stream_ios, sid); +++ if (io) { +++ task = h2_worker_create_task(w, m, io->request, !io->request_body); +++ } +++ } ++ *has_more = !h2_tq_empty(m->q); apr_thread_mutex_unlock(m->lock); } -- return status; ++ return task; } diff --cc modules/http2/h2_mplx.h index 2bb650535ef,62977d6157c,62977d6157c..5c950b9c271 --- a/modules/http2/h2_mplx.h +++ b/modules/http2/h2_mplx.h @@@@ -41,9 -41,8 -41,8 +41,10 @@@@ struct h2_config struct h2_response; struct h2_task; struct h2_stream; ++struct h2_request; struct h2_io_set; struct apr_thread_cond_t; +++struct h2_worker; struct h2_workers; struct h2_stream_set; struct h2_task_queue; @@@@ -55,7 -54,7 -54,7 +56,7 @@@@ typedef struct h2_mplx h2_mplx struct h2_mplx { long id; APR_RING_ENTRY(h2_mplx) link; --- volatile apr_uint32_t refs; +++ volatile int refs; conn_rec *c; apr_pool_t *pool; apr_bucket_alloc_t *bucket_alloc; @@@@ -147,29 -143,13 -143,13 +148,29 @@@@ apr_status_t h2_mplx_out_trywait(h2_mpl ******************************************************************************/ /** -- * Perform the task on the given stream. ++ * Process a stream request. ++ * ++ * @param m the multiplexer ++ * @param stream_id the identifier of the stream ++ * @param r the request to be processed ++ * @param eos if input is complete ++ * @param cmp the stream priority compare function ++ * @param ctx context data for the compare function */ --apr_status_t h2_mplx_do_task(h2_mplx *mplx, struct h2_task *task); ++apr_status_t h2_mplx_process(h2_mplx *m, int stream_id, - struct h2_request *r, int eos, +++ const struct h2_request *r, int eos, ++ h2_stream_pri_cmp *cmp, void *ctx); --struct h2_task *h2_mplx_pop_task(h2_mplx *mplx, int *has_more); ++/** ++ * Stream priorities have changed, reschedule pending tasks. ++ * ++ * @param m the multiplexer ++ * @param cmp the stream priority compare function ++ * @param ctx context data for the compare function ++ */ ++apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx); - struct h2_task *h2_mplx_pop_task(h2_mplx *mplx, int *has_more); --apr_status_t h2_mplx_create_task(h2_mplx *mplx, struct h2_stream *stream); +++struct h2_task *h2_mplx_pop_task(h2_mplx *mplx, struct h2_worker *w, int *has_more); /******************************************************************************* * Input handling of streams. @@@@ -208,7 -188,7 -188,7 +209,7 @@@@ int h2_mplx_in_has_eos_for(h2_mplx *m, * Callback invoked for every stream that had input data read since * the last invocation. */ ---typedef void h2_mplx_consumed_cb(void *ctx, int stream_id, apr_size_t consumed); +++typedef void h2_mplx_consumed_cb(void *ctx, int stream_id, apr_off_t consumed); /** * Invoke the callback for all streams that had bytes read since the last @@@@ -239,15 -219,7 -219,7 +240,15 @@@@ struct h2_stream *h2_mplx_next_submit(h */ apr_status_t h2_mplx_out_readx(h2_mplx *mplx, int stream_id, h2_io_data_cb *cb, void *ctx, --- apr_size_t *plen, int *peos); +++ apr_off_t *plen, int *peos); ++ ++/** ++ * Reads output data into the given brigade. Will never block, but ++ * return APR_EAGAIN until data arrives or the stream is closed. ++ */ ++apr_status_t h2_mplx_out_read_to(h2_mplx *mplx, int stream_id, ++ apr_bucket_brigade *bb, - apr_size_t *plen, int *peos); +++ apr_off_t *plen, int *peos); /** * Opens the output for the given stream with the specified response. diff --cc modules/http2/h2_push.c index 00000000000,00000000000,00000000000..703ea761a04 new file mode 100644 --- /dev/null +++ b/modules/http2/h2_push.c @@@@ -1,0 -1,0 -1,0 +1,400 @@@@ +++/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de) +++ * +++ * Licensed under the Apache License, Version 2.0 (the "License"); +++ * you may not use this file except in compliance with the License. +++ * You may obtain a copy of the License at +++ * +++ * http://www.apache.org/licenses/LICENSE-2.0 +++ +++ * Unless required by applicable law or agreed to in writing, software +++ * distributed under the License is distributed on an "AS IS" BASIS, +++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +++ * See the License for the specific language governing permissions and +++ * limitations under the License. +++ */ +++ +++#include +++#include +++ +++#include +++#include +++ +++#include +++#include +++#include +++ +++#include "h2_private.h" +++#include "h2_h2.h" +++#include "h2_util.h" +++#include "h2_push.h" +++#include "h2_request.h" +++#include "h2_response.h" +++ +++ +++typedef struct { +++ const h2_request *req; +++ apr_pool_t *pool; +++ apr_array_header_t *pushes; +++ const char *s; +++ size_t slen; +++ size_t i; +++ +++ const char *link; +++ apr_table_t *params; +++ char b[4096]; +++} link_ctx; +++ +++static int attr_char(char c) +++{ +++ switch (c) { +++ case '!': +++ case '#': +++ case '$': +++ case '&': +++ case '+': +++ case '-': +++ case '.': +++ case '^': +++ case '_': +++ case '`': +++ case '|': +++ case '~': +++ return 1; +++ default: +++ return apr_isalnum(c); +++ } +++} +++ +++static int ptoken_char(char c) +++{ +++ switch (c) { +++ case '!': +++ case '#': +++ case '$': +++ case '&': +++ case '\'': +++ case '(': +++ case ')': +++ case '*': +++ case '+': +++ case '-': +++ case '.': +++ case '/': +++ case ':': +++ case '<': +++ case '=': +++ case '>': +++ case '?': +++ case '@': +++ case '[': +++ case ']': +++ case '^': +++ case '_': +++ case '`': +++ case '{': +++ case '|': +++ case '}': +++ case '~': +++ return 1; +++ default: +++ return apr_isalnum(c); +++ } +++} +++ +++static int skip_ws(link_ctx *ctx) +++{ +++ char c; +++ while (ctx->i < ctx->slen +++ && (((c = ctx->s[ctx->i]) == ' ') || (c == '\t'))) { +++ ++ctx->i; +++ } +++ return (ctx->i < ctx->slen); +++} +++ +++static int find_chr(link_ctx *ctx, char c, size_t *pidx) +++{ +++ size_t j; +++ for (j = ctx->i; j < ctx->slen; ++j) { +++ if (ctx->s[j] == c) { +++ *pidx = j; +++ return 1; +++ } +++ } +++ return 0; +++} +++ +++static int read_chr(link_ctx *ctx, char c) +++{ +++ if (ctx->i < ctx->slen && ctx->s[ctx->i] == c) { +++ ++ctx->i; +++ return 1; +++ } +++ return 0; +++} +++ +++static char *mk_str(link_ctx *ctx, size_t end) +++{ +++ if (ctx->i < end) { +++ return apr_pstrndup(ctx->pool, ctx->s + ctx->i, end - ctx->i); +++ } +++ return ""; +++} +++ +++static int read_qstring(link_ctx *ctx, char **ps) +++{ +++ if (skip_ws(ctx) && read_chr(ctx, '\"')) { +++ size_t end; +++ if (find_chr(ctx, '\"', &end)) { +++ *ps = mk_str(ctx, end); +++ ctx->i = end + 1; +++ return 1; +++ } +++ } +++ return 0; +++} +++ +++static int read_ptoken(link_ctx *ctx, char **ps) +++{ +++ if (skip_ws(ctx)) { +++ size_t i; +++ for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) { +++ /* nop */ +++ } +++ if (i > ctx->i) { +++ *ps = mk_str(ctx, i); +++ ctx->i = i; +++ return 1; +++ } +++ } +++ return 0; +++} +++ +++ +++static int read_link(link_ctx *ctx) +++{ +++ if (skip_ws(ctx) && read_chr(ctx, '<')) { +++ size_t end; +++ if (find_chr(ctx, '>', &end)) { +++ ctx->link = mk_str(ctx, end); +++ ctx->i = end + 1; +++ return 1; +++ } +++ } +++ return 0; +++} +++ +++static int read_pname(link_ctx *ctx, char **pname) +++{ +++ if (skip_ws(ctx)) { +++ size_t i; +++ for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) { +++ /* nop */ +++ } +++ if (i > ctx->i) { +++ *pname = mk_str(ctx, i); +++ ctx->i = i; +++ return 1; +++ } +++ } +++ return 0; +++} +++ +++static int read_pvalue(link_ctx *ctx, char **pvalue) +++{ +++ if (skip_ws(ctx) && read_chr(ctx, '=')) { +++ if (read_qstring(ctx, pvalue) || read_ptoken(ctx, pvalue)) { +++ return 1; +++ } +++ } +++ return 0; +++} +++ +++static int read_param(link_ctx *ctx) +++{ +++ if (skip_ws(ctx) && read_chr(ctx, ';')) { +++ char *name, *value = ""; +++ if (read_pname(ctx, &name)) { +++ read_pvalue(ctx, &value); /* value is optional */ +++ apr_table_setn(ctx->params, name, value); +++ return 1; +++ } +++ } +++ return 0; +++} +++ +++static int read_sep(link_ctx *ctx) +++{ +++ if (skip_ws(ctx) && read_chr(ctx, ',')) { +++ return 1; +++ } +++ return 0; +++} +++ +++static void init_params(link_ctx *ctx) +++{ +++ if (!ctx->params) { +++ ctx->params = apr_table_make(ctx->pool, 5); +++ } +++ else { +++ apr_table_clear(ctx->params); +++ } +++} +++ +++static int same_authority(const h2_request *req, const apr_uri_t *uri) +++{ +++ if (uri->scheme != NULL && strcmp(uri->scheme, req->scheme)) { +++ return 0; +++ } +++ if (uri->hostinfo != NULL && strcmp(uri->hostinfo, req->authority)) { +++ return 0; +++ } +++ return 1; +++} +++ +++static int set_header(void *ctx, const char *key, const char *value) +++{ +++ apr_table_setn(ctx, key, value); +++ return 1; +++} +++ +++ +++static int add_push(link_ctx *ctx) +++{ +++ /* so, we have read a Link header and need to decide +++ * if we transform it into a push. +++ */ +++ const char *rel = apr_table_get(ctx->params, "rel"); +++ if (rel && !strcmp("preload", rel)) { +++ apr_uri_t uri; +++ if (apr_uri_parse(ctx->pool, ctx->link, &uri) == APR_SUCCESS) { +++ if (uri.path && same_authority(ctx->req, &uri)) { +++ char *path; +++ apr_table_t *headers; +++ h2_request *req; +++ h2_push *push; +++ +++ /* We only want to generate pushes for resources in the +++ * same authority than the original request. +++ * icing: i think that is wise, otherwise we really need to +++ * check that the vhost/server is available and uses the same +++ * TLS (if any) parameters. +++ */ +++ path = apr_uri_unparse(ctx->pool, &uri, APR_URI_UNP_OMITSITEPART); +++ +++ push = apr_pcalloc(ctx->pool, sizeof(*push)); +++ push->initiating_id = ctx->req->id; +++ +++ headers = apr_table_make(ctx->pool, 5); +++ apr_table_do(set_header, headers, ctx->req->headers, +++ "User-Agent", +++ "Cache-Control", +++ "Accept-Language", +++ NULL); +++ /* TODO: which headers do we add here? +++ */ +++ +++ req = h2_request_createn(0, ctx->pool, +++ ctx->req->method, +++ ctx->req->scheme, +++ ctx->req->authority, +++ path, headers); +++ h2_request_end_headers(req, ctx->pool, 1); +++ push->req = req; +++ +++ if (!ctx->pushes) { +++ ctx->pushes = apr_array_make(ctx->pool, 5, sizeof(h2_push*)); +++ } +++ APR_ARRAY_PUSH(ctx->pushes, h2_push*) = push; +++ } +++ } +++ } +++ return 0; +++} +++ +++static void inspect_link(link_ctx *ctx, const char *s, size_t slen) +++{ +++ /* RFC 5988 +++ Link = "Link" ":" #link-value +++ link-value = "<" URI-Reference ">" *( ";" link-param ) +++ link-param = ( ( "rel" "=" relation-types ) +++ | ( "anchor" "=" <"> URI-Reference <"> ) +++ | ( "rev" "=" relation-types ) +++ | ( "hreflang" "=" Language-Tag ) +++ | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) ) +++ | ( "title" "=" quoted-string ) +++ | ( "title*" "=" ext-value ) +++ | ( "type" "=" ( media-type | quoted-mt ) ) +++ | ( link-extension ) ) +++ link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] ) +++ | ( ext-name-star "=" ext-value ) +++ ext-name-star = parmname "*" ; reserved for RFC2231-profiled +++ ; extensions. Whitespace NOT +++ ; allowed in between. +++ ptoken = 1*ptokenchar +++ ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "(" +++ | ")" | "*" | "+" | "-" | "." | "/" | DIGIT +++ | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA +++ | "[" | "]" | "^" | "_" | "`" | "{" | "|" +++ | "}" | "~" +++ media-type = type-name "/" subtype-name +++ quoted-mt = <"> media-type <"> +++ relation-types = relation-type +++ | <"> relation-type *( 1*SP relation-type ) <"> +++ relation-type = reg-rel-type | ext-rel-type +++ reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" ) +++ ext-rel-type = URI +++ +++ and from +++ parmname = 1*attr-char +++ attr-char = ALPHA / DIGIT +++ / "!" / "#" / "$" / "&" / "+" / "-" / "." +++ / "^" / "_" / "`" / "|" / "~" +++ */ +++ +++ ctx->s = s; +++ ctx->slen = slen; +++ ctx->i = 0; +++ +++ while (read_link(ctx)) { +++ init_params(ctx); +++ while (read_param(ctx)) { +++ /* nop */ +++ } +++ add_push(ctx); +++ if (!read_sep(ctx)) { +++ break; +++ } +++ } +++} +++ +++static int head_iter(void *ctx, const char *key, const char *value) +++{ +++ if (!apr_strnatcasecmp("link", key)) { +++ inspect_link(ctx, value, strlen(value)); +++ } +++ return 1; +++} +++ +++apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req, +++ const h2_response *res) +++{ +++ /* Collect push candidates from the request/response pair. +++ * +++ * One source for pushes are "rel=preload" link headers +++ * in the response. +++ * +++ * TODO: This may be extended in the future by hooks or callbacks +++ * where other modules can provide push information directly. +++ */ +++ if (res->header) { +++ link_ctx ctx; +++ +++ memset(&ctx, 0, sizeof(ctx)); +++ ctx.req = req; +++ ctx.pool = p; +++ +++ apr_table_do(head_iter, &ctx, res->header, NULL); +++ return ctx.pushes; +++ } +++ return NULL; +++} diff --cc modules/http2/h2_push.h index 00000000000,00000000000,00000000000..64edee65d19 new file mode 100644 --- /dev/null +++ b/modules/http2/h2_push.h @@@@ -1,0 -1,0 -1,0 +1,33 @@@@ +++/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de) +++ * +++ * Licensed under the Apache License, Version 2.0 (the "License"); +++ * you may not use this file except in compliance with the License. +++ * You may obtain a copy of the License at +++ * +++ * http://www.apache.org/licenses/LICENSE-2.0 +++ +++ * Unless required by applicable law or agreed to in writing, software +++ * distributed under the License is distributed on an "AS IS" BASIS, +++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +++ * See the License for the specific language governing permissions and +++ * limitations under the License. +++ */ +++#ifndef __mod_h2__h2_push__ +++#define __mod_h2__h2_push__ +++ +++struct h2_request; +++struct h2_response; +++struct h2_ngheader; +++ +++typedef struct h2_push { +++ int initiating_id; +++ const struct h2_request *req; +++ const char *as; +++} h2_push; +++ +++ +++apr_array_header_t *h2_push_collect(apr_pool_t *p, +++ const struct h2_request *req, +++ const struct h2_response *res); +++ +++#endif /* defined(__mod_h2__h2_push__) */ diff --cc modules/http2/h2_request.c index 4a1e19058a6,ca9a362cb56,ca9a362cb56..2a697a0eda8 --- a/modules/http2/h2_request.c +++ b/modules/http2/h2_request.c @@@@ -19,68 -19,68 -19,68 +19,150 @@@@ #include #include +++#include #include #include #include "h2_private.h" #include "h2_mplx.h" ---#include "h2_to_h1.h" #include "h2_request.h" #include "h2_task.h" #include "h2_util.h" ---h2_request *h2_request_create(int id, apr_pool_t *pool, --- apr_bucket_alloc_t *bucket_alloc) +++h2_request *h2_request_create(int id, apr_pool_t *pool) +++{ +++ return h2_request_createn(id, pool, NULL, NULL, NULL, NULL, NULL); +++} +++ +++h2_request *h2_request_createn(int id, apr_pool_t *pool, +++ const char *method, const char *scheme, +++ const char *authority, const char *path, +++ apr_table_t *header) { h2_request *req = apr_pcalloc(pool, sizeof(h2_request)); --- if (req) { --- req->id = id; --- req->pool = pool; --- req->bucket_alloc = bucket_alloc; --- } +++ +++ req->id = id; +++ req->method = method; +++ req->scheme = scheme; +++ req->authority = authority; +++ req->path = path; +++ req->headers = header? header : apr_table_make(pool, 10); +++ return req; } void h2_request_destroy(h2_request *req) { --- if (req->to_h1) { --- h2_to_h1_destroy(req->to_h1); --- req->to_h1 = NULL; +++} +++ +++static apr_status_t inspect_clen(h2_request *req, const char *s) +++{ +++ char *end; +++ req->content_length = apr_strtoi64(s, &end, 10); +++ return (s == end)? APR_EINVAL : APR_SUCCESS; +++} +++ +++static apr_status_t add_h1_header(h2_request *req, apr_pool_t *pool, +++ const char *name, size_t nlen, +++ const char *value, size_t vlen) +++{ +++ char *hname, *hvalue; +++ +++ if (H2_HD_MATCH_LIT("expect", name, nlen) +++ || H2_HD_MATCH_LIT("upgrade", name, nlen) +++ || H2_HD_MATCH_LIT("connection", name, nlen) +++ || H2_HD_MATCH_LIT("proxy-connection", name, nlen) +++ || H2_HD_MATCH_LIT("transfer-encoding", name, nlen) +++ || H2_HD_MATCH_LIT("keep-alive", name, nlen) +++ || H2_HD_MATCH_LIT("http2-settings", name, nlen)) { +++ /* ignore these. */ +++ return APR_SUCCESS; +++ } +++ else if (H2_HD_MATCH_LIT("cookie", name, nlen)) { +++ const char *existing = apr_table_get(req->headers, "cookie"); +++ if (existing) { +++ char *nval; +++ +++ /* Cookie header come separately in HTTP/2, but need +++ * to be merged by "; " (instead of default ", ") +++ */ +++ hvalue = apr_pstrndup(pool, value, vlen); +++ nval = apr_psprintf(pool, "%s; %s", existing, hvalue); +++ apr_table_setn(req->headers, "Cookie", nval); +++ return APR_SUCCESS; +++ } } +++ else if (H2_HD_MATCH_LIT("host", name, nlen)) { +++ if (apr_table_get(req->headers, "Host")) { +++ return APR_SUCCESS; /* ignore duplicate */ +++ } +++ } +++ +++ hname = apr_pstrndup(pool, name, nlen); +++ hvalue = apr_pstrndup(pool, value, vlen); +++ h2_util_camel_case_header(hname, nlen); +++ apr_table_mergen(req->headers, hname, hvalue); +++ +++ return APR_SUCCESS; } ---static apr_status_t insert_request_line(h2_request *req, h2_mplx *m); +++typedef struct { +++ h2_request *req; +++ apr_pool_t *pool; +++} h1_ctx; +++ +++static int set_h1_header(void *ctx, const char *key, const char *value) +++{ +++ h1_ctx *x = ctx; +++ add_h1_header(x->req, x->pool, key, strlen(key), value, strlen(value)); +++ return 1; +++} ---apr_status_t h2_request_rwrite(h2_request *req, request_rec *r, h2_mplx *m) +++static apr_status_t add_all_h1_header(h2_request *req, apr_pool_t *pool, +++ apr_table_t *header) +++{ +++ h1_ctx x; +++ x.req = req; +++ x.pool = pool; +++ apr_table_do(set_h1_header, &x, header, NULL); +++ return APR_SUCCESS; +++} +++ +++ +++apr_status_t h2_request_rwrite(h2_request *req, request_rec *r) { apr_status_t status; --- req->method = r->method; +++ +++ req->method = r->method; +++ req->scheme = (r->parsed_uri.scheme? r->parsed_uri.scheme +++ : ap_http_scheme(r)); req->authority = r->hostname; --- req->path = r->uri; --- if (!ap_strchr_c(req->authority, ':') && r->parsed_uri.port_str) { --- req->authority = apr_psprintf(req->pool, "%s:%s", req->authority, --- r->parsed_uri.port_str); +++ req->path = apr_uri_unparse(r->pool, &r->parsed_uri, +++ APR_URI_UNP_OMITSITEPART); +++ +++ if (!ap_strchr_c(req->authority, ':') && r->server) { +++ req->authority = apr_psprintf(r->pool, "%s:%d", req->authority, +++ (int)r->server->port); } --- req->scheme = NULL; --- --- status = insert_request_line(req, m); --- if (status == APR_SUCCESS) { --- status = h2_to_h1_add_headers(req->to_h1, r->headers_in); --- } +++ AP_DEBUG_ASSERT(req->scheme); +++ AP_DEBUG_ASSERT(req->authority); +++ AP_DEBUG_ASSERT(req->path); +++ AP_DEBUG_ASSERT(req->method); +++ +++ status = add_all_h1_header(req, r->pool, r->headers_in); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, --- "h2_request(%d): written request %s %s, host=%s", --- req->id, req->method, req->path, req->authority); --- +++ "h2_request(%d): rwrite %s host=%s://%s%s", +++ req->id, req->method, req->scheme, req->authority, req->path); +++ return status; } ---apr_status_t h2_request_write_header(h2_request *req, --- const char *name, size_t nlen, --- const char *value, size_t vlen, --- h2_mplx *m) +++apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, +++ const char *name, size_t nlen, +++ const char *value, size_t vlen) { apr_status_t status = APR_SUCCESS; @@@@ -90,8 -90,8 -90,8 +172,8 @@@@ if (name[0] == ':') { /* pseudo header, see ch. 8.1.2.3, always should come first */ --- if (req->to_h1) { --- ap_log_perror(APLOG_MARK, APLOG_ERR, 0, req->pool, +++ if (!apr_is_empty_table(req->headers)) { +++ ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, APLOGNO(02917) "h2_request(%d): pseudo header after request start", req->id); @@@@ -100,25 -100,25 -100,25 +182,25 @@@@ if (H2_HEADER_METHOD_LEN == nlen && !strncmp(H2_HEADER_METHOD, name, nlen)) { --- req->method = apr_pstrndup(req->pool, value, vlen); +++ req->method = apr_pstrndup(pool, value, vlen); } else if (H2_HEADER_SCHEME_LEN == nlen && !strncmp(H2_HEADER_SCHEME, name, nlen)) { --- req->scheme = apr_pstrndup(req->pool, value, vlen); +++ req->scheme = apr_pstrndup(pool, value, vlen); } else if (H2_HEADER_PATH_LEN == nlen && !strncmp(H2_HEADER_PATH, name, nlen)) { --- req->path = apr_pstrndup(req->pool, value, vlen); +++ req->path = apr_pstrndup(pool, value, vlen); } else if (H2_HEADER_AUTH_LEN == nlen && !strncmp(H2_HEADER_AUTH, name, nlen)) { --- req->authority = apr_pstrndup(req->pool, value, vlen); +++ req->authority = apr_pstrndup(pool, value, vlen); } else { char buffer[32]; memset(buffer, 0, 32); strncpy(buffer, name, (nlen > 31)? 31 : nlen); --- ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, req->pool, +++ ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, pool, APLOGNO(02954) "h2_request(%d): ignoring unknown pseudo header %s", req->id, buffer); @@@@ -126,65 -126,57 -126,57 +208,141 @@@@ } else { /* non-pseudo header, append to work bucket of stream */ --- if (!req->to_h1) { --- status = insert_request_line(req, m); --- if (status != APR_SUCCESS) { --- return status; --- } --- } --- --- if (status == APR_SUCCESS) { --- status = h2_to_h1_add_header(req->to_h1, --- name, nlen, value, vlen); --- } +++ status = add_h1_header(req, pool, name, nlen, value, vlen); } return status; } ---apr_status_t h2_request_write_data(h2_request *req, --- const char *data, size_t len) +++apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos) { --- return h2_to_h1_add_data(req->to_h1, data, len); ---} +++ const char *s; +++ +++ if (req->eoh) { +++ return APR_EINVAL; +++ } ---apr_status_t h2_request_end_headers(h2_request *req, struct h2_mplx *m, --- h2_task *task, int eos) ---{ - apr_status_t status; -- if (!req->to_h1) { -- apr_status_t status = insert_request_line(req, m); -- if (status != APR_SUCCESS) { -- return status; +++ /* be safe, some header we do not accept on h2(c) */ +++ apr_table_unset(req->headers, "expect"); +++ apr_table_unset(req->headers, "upgrade"); +++ apr_table_unset(req->headers, "connection"); +++ apr_table_unset(req->headers, "proxy-connection"); +++ apr_table_unset(req->headers, "transfer-encoding"); +++ apr_table_unset(req->headers, "keep-alive"); +++ apr_table_unset(req->headers, "http2-settings"); +++ +++ if (!apr_table_get(req->headers, "Host")) { +++ /* Need to add a "Host" header if not already there to +++ * make virtual hosts work correctly. */ +++ if (!req->authority) { +++ return APR_BADARG; +++ } +++ apr_table_set(req->headers, "Host", req->authority); +++ } +++ +++ s = apr_table_get(req->headers, "Content-Length"); +++ if (s) { +++ if (inspect_clen(req, s) != APR_SUCCESS) { +++ ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool, +++ APLOGNO(02959) +++ "h2_request(%d): content-length value not parsed: %s", +++ req->id, s); +++ return APR_EINVAL; +++ } +++ } +++ else { +++ /* no content-length given */ +++ req->content_length = -1; +++ if (!eos) { +++ /* We have not seen a content-length and have no eos, +++ * simulate a chunked encoding for our HTTP/1.1 infrastructure, +++ * in case we have "H2SerializeHeaders on" here +++ */ +++ req->chunked = 1; +++ apr_table_mergen(req->headers, "Transfer-Encoding", "chunked"); +++ } +++ else if (apr_table_get(req->headers, "Content-Type")) { +++ /* If we have a content-type, but already see eos, no more +++ * data will come. Signal a zero content length explicitly. +++ */ +++ apr_table_setn(req->headers, "Content-Length", "0"); +++ } +++ } +++ +++ req->eoh = 1; ++ - if (!req->to_h1) { - status = insert_request_line(req, m); - if (status != APR_SUCCESS) { - return status; +++ /* In the presence of trailers, force behaviour of chunked encoding */ +++ s = apr_table_get(req->headers, "Trailer"); +++ if (s && s[0]) { +++ req->trailers = apr_table_make(pool, 5); +++ if (!req->chunked) { +++ req->chunked = 1; +++ apr_table_mergen(req->headers, "Transfer-Encoding", "chunked"); } } - status = h2_to_h1_end_headers(req->to_h1, eos); - h2_task_set_request(task, req->to_h1->method, - req->to_h1->scheme, - req->to_h1->authority, - req->to_h1->path, - req->to_h1->headers, eos); - return status; -- return h2_to_h1_end_headers(req->to_h1, task, eos); +++ +++ return APR_SUCCESS; } ---apr_status_t h2_request_close(h2_request *req) +++static apr_status_t add_h1_trailer(h2_request *req, apr_pool_t *pool, +++ const char *name, size_t nlen, +++ const char *value, size_t vlen) { --- return h2_to_h1_close(req->to_h1); +++ char *hname, *hvalue; +++ +++ if (H2_HD_MATCH_LIT("expect", name, nlen) +++ || H2_HD_MATCH_LIT("upgrade", name, nlen) +++ || H2_HD_MATCH_LIT("connection", name, nlen) +++ || H2_HD_MATCH_LIT("host", name, nlen) +++ || H2_HD_MATCH_LIT("proxy-connection", name, nlen) +++ || H2_HD_MATCH_LIT("transfer-encoding", name, nlen) +++ || H2_HD_MATCH_LIT("keep-alive", name, nlen) +++ || H2_HD_MATCH_LIT("http2-settings", name, nlen)) { +++ /* ignore these. */ +++ return APR_SUCCESS; +++ } +++ +++ hname = apr_pstrndup(pool, name, nlen); +++ hvalue = apr_pstrndup(pool, value, vlen); +++ h2_util_camel_case_header(hname, nlen); +++ +++ apr_table_mergen(req->trailers, hname, hvalue); +++ +++ return APR_SUCCESS; } ---static apr_status_t insert_request_line(h2_request *req, h2_mplx *m) +++ +++apr_status_t h2_request_add_trailer(h2_request *req, apr_pool_t *pool, +++ const char *name, size_t nlen, +++ const char *value, size_t vlen) { --- req->to_h1 = h2_to_h1_create(req->id, req->pool, req->bucket_alloc, --- req->method, --- req->scheme, --- req->authority, --- req->path, m); --- return req->to_h1? APR_SUCCESS : APR_ENOMEM; +++ if (!req->trailers) { +++ ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, pool, +++ "h2_request(%d): unanounced trailers", +++ req->id); +++ return APR_EINVAL; +++ } +++ if (nlen == 0 || name[0] == ':') { +++ ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, pool, +++ "h2_request(%d): pseudo header in trailer", +++ req->id); +++ return APR_EINVAL; +++ } +++ return add_h1_trailer(req, pool, name, nlen, value, vlen); } ---apr_status_t h2_request_flush(h2_request *req) +++#define OPT_COPY(p, s) ((s)? apr_pstrdup(p, s) : NULL) +++ +++void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src) { --- return h2_to_h1_flush(req->to_h1); +++ /* keep the dst id */ +++ dst->method = OPT_COPY(p, src->method); +++ dst->scheme = OPT_COPY(p, src->scheme); +++ dst->authority = OPT_COPY(p, src->authority); +++ dst->path = OPT_COPY(p, src->path); +++ dst->headers = apr_table_clone(p, src->headers); +++ dst->content_length = src->content_length; +++ dst->chunked = src->chunked; +++ dst->eoh = src->eoh; } diff --cc modules/http2/h2_request.h index aa5e0bc3c09,aa5e0bc3c09,aa5e0bc3c09..19005a88e6d --- a/modules/http2/h2_request.h +++ b/modules/http2/h2_request.h @@@@ -19,9 -19,9 -19,9 +19,6 @@@@ /* h2_request is the transformer of HTTP2 streams into HTTP/1.1 internal * format that will be fed to various httpd input filters to finally * become a request_rec to be handled by soemone. --- * --- * Ideally, we would make a request_rec without serializing the headers --- * we have only to make someone else parse them back. */ struct h2_to_h1; struct h2_mplx; @@@@ -30,38 -30,38 -30,38 +27,45 @@@@ struct h2_task typedef struct h2_request h2_request; struct h2_request { --- int id; /* http2 stream id */ --- apr_pool_t *pool; --- apr_bucket_alloc_t *bucket_alloc; --- struct h2_to_h1 *to_h1; /* Converter to HTTP/1.1 format*/ --- +++ int id; /* stream id */ +++ /* pseudo header values, see ch. 8.1.2.3 */ const char *method; const char *scheme; const char *authority; const char *path; +++ +++ apr_table_t *headers; +++ apr_table_t *trailers; +++ +++ apr_off_t content_length; +++ int chunked; +++ int eoh; }; ---h2_request *h2_request_create(int id, apr_pool_t *pool, --- apr_bucket_alloc_t *bucket_alloc); +++h2_request *h2_request_create(int id, apr_pool_t *pool); +++ +++h2_request *h2_request_createn(int id, apr_pool_t *pool, +++ const char *method, const char *scheme, +++ const char *authority, const char *path, +++ apr_table_t *headers); +++ void h2_request_destroy(h2_request *req); ---apr_status_t h2_request_flush(h2_request *req); +++apr_status_t h2_request_rwrite(h2_request *req, request_rec *r); +++ +++apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, +++ const char *name, size_t nlen, +++ const char *value, size_t vlen); ---apr_status_t h2_request_write_header(h2_request *req, --- const char *name, size_t nlen, --- const char *value, size_t vlen, --- struct h2_mplx *m); +++apr_status_t h2_request_add_trailer(h2_request *req, apr_pool_t *pool, +++ const char *name, size_t nlen, +++ const char *value, size_t vlen); ---apr_status_t h2_request_write_data(h2_request *request, --- const char *data, size_t len); +++apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos); ---apr_status_t h2_request_end_headers(h2_request *req, struct h2_mplx *m, --- struct h2_task *task, int eos); +++void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src); ---apr_status_t h2_request_close(h2_request *req); ---apr_status_t h2_request_rwrite(h2_request *req, request_rec *r, --- struct h2_mplx *m); #endif /* defined(__mod_h2__h2_request__) */ diff --cc modules/http2/h2_response.c index bd0a5fba979,9cedd855615,9cedd855615..2751f2d377c --- a/modules/http2/h2_response.c +++ b/modules/http2/h2_response.c @@@@ -29,21 -28,20 -28,20 +29,10 @@@@ #include "h2_util.h" #include "h2_response.h" ---static h2_ngheader *make_ngheader(apr_pool_t *pool, const char *status, --- apr_table_t *header); --- ---static int ignore_header(const char *name) ---{ --- return (H2_HD_MATCH_LIT_CS("connection", name) --- || H2_HD_MATCH_LIT_CS("proxy-connection", name) --- || H2_HD_MATCH_LIT_CS("upgrade", name) --- || H2_HD_MATCH_LIT_CS("keep-alive", name) --- || H2_HD_MATCH_LIT_CS("transfer-encoding", name)); ---} h2_response *h2_response_create(int stream_id, -- const char *http_status, ++ int rst_error, - const char *http_status, +++ int http_status, apr_array_header_t *hlines, apr_pool_t *pool) { @@@@ -55,8 -53,7 -53,7 +44,8 @@@@ } response->stream_id = stream_id; -- response->status = http_status; ++ response->rst_error = rst_error; - response->status = http_status? http_status : "500"; +++ response->http_status = http_status? http_status : 500; response->content_length = -1; if (hlines) { @@@@ -75,10 -72,10 -72,10 +64,8 @@@@ while (*sep == ' ' || *sep == '\t') { ++sep; } --- if (ignore_header(hline)) { --- /* never forward, ch. 8.1.2.2 */ --- } --- else { +++ +++ if (!h2_util_ignore_header(hline)) { apr_table_merge(header, hline, sep); if (*sep && H2_HD_MATCH_LIT_CS("content-length", hline)) { char *end; @@@@ -99,7 -96,7 -96,7 +86,7 @@@@ header = apr_table_make(pool, 0); } --- response->rheader = header; +++ response->header = header; return response; } @@@@ -112,22 -109,9 -109,9 +99,22 @@@@ h2_response *h2_response_rcreate(int st } response->stream_id = stream_id; --- response->status = apr_psprintf(pool, "%d", r->status); +++ response->http_status = r->status; response->content_length = -1; --- response->rheader = header; +++ response->header = header; ++ - if (r->status == HTTP_FORBIDDEN) { +++ if (response->http_status == HTTP_FORBIDDEN) { ++ const char *cause = apr_table_get(r->notes, "ssl-renegotiate-forbidden"); ++ if (cause) { ++ /* This request triggered a TLS renegotiation that is now allowed ++ * in HTTP/2. Tell the client that it should use HTTP/1.1 for this. ++ */ - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, r->status, r, +++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, response->http_status, r, ++ "h2_response(%ld-%d): renegotiate forbidden, cause: %s", ++ (long)r->connection->id, stream_id, cause); ++ response->rst_error = H2_ERR_HTTP_1_1_REQUIRED; ++ } ++ } return response; } @@@@ -141,108 -125,108 -125,108 +128,12 @@@@ h2_response *h2_response_copy(apr_pool_ { h2_response *to = apr_pcalloc(pool, sizeof(h2_response)); to->stream_id = from->stream_id; --- to->status = apr_pstrdup(pool, from->status); +++ to->http_status = from->http_status; to->content_length = from->content_length; --- if (from->rheader) { --- to->ngheader = make_ngheader(pool, to->status, from->rheader); +++ if (from->header) { +++ to->header = apr_table_clone(pool, from->header); } return to; } ---typedef struct { --- nghttp2_nv *nv; --- size_t nvlen; --- size_t nvstrlen; --- size_t offset; --- char *strbuf; --- apr_pool_t *pool; ---} nvctx_t; --- ---static int count_header(void *ctx, const char *key, const char *value) ---{ --- if (!ignore_header(key)) { --- nvctx_t *nvctx = (nvctx_t*)ctx; --- nvctx->nvlen++; --- nvctx->nvstrlen += strlen(key) + strlen(value) + 2; --- } --- return 1; ---} --- ---#define NV_ADD_LIT_CS(nv, k, v) addnv_lit_cs(nv, k, sizeof(k) - 1, v, strlen(v)) ---#define NV_ADD_CS_CS(nv, k, v) addnv_cs_cs(nv, k, strlen(k), v, strlen(v)) ---#define NV_BUF_ADD(nv, s, len) memcpy(nv->strbuf, s, len); \ ---s = nv->strbuf; \ ---nv->strbuf += len + 1 --- ---static void addnv_cs_cs(nvctx_t *ctx, const char *key, size_t key_len, --- const char *value, size_t val_len) ---{ --- nghttp2_nv *nv = &ctx->nv[ctx->offset]; --- --- NV_BUF_ADD(ctx, key, key_len); --- NV_BUF_ADD(ctx, value, val_len); --- --- nv->name = (uint8_t*)key; --- nv->namelen = key_len; --- nv->value = (uint8_t*)value; --- nv->valuelen = val_len; --- --- ctx->offset++; ---} --- ---static void addnv_lit_cs(nvctx_t *ctx, const char *key, size_t key_len, --- const char *value, size_t val_len) ---{ --- nghttp2_nv *nv = &ctx->nv[ctx->offset]; --- --- NV_BUF_ADD(ctx, value, val_len); --- --- nv->name = (uint8_t*)key; --- nv->namelen = key_len; --- nv->value = (uint8_t*)value; --- nv->valuelen = val_len; --- --- ctx->offset++; ---} --- ---static int add_header(void *ctx, const char *key, const char *value) ---{ --- if (!ignore_header(key)) { --- nvctx_t *nvctx = (nvctx_t*)ctx; --- NV_ADD_CS_CS(nvctx, key, value); --- } --- return 1; ---} --- ---static h2_ngheader *make_ngheader(apr_pool_t *pool, const char *status, --- apr_table_t *header) ---{ --- size_t n; --- h2_ngheader *h; --- nvctx_t ctx; --- --- ctx.nv = NULL; --- ctx.nvlen = 1; --- ctx.nvstrlen = strlen(status) + 1; --- ctx.offset = 0; --- ctx.strbuf = NULL; --- ctx.pool = pool; --- --- apr_table_do(count_header, &ctx, header, NULL); --- --- n = (sizeof(h2_ngheader) --- + (ctx.nvlen * sizeof(nghttp2_nv)) + ctx.nvstrlen); --- h = apr_pcalloc(pool, n); --- if (h) { --- ctx.nv = (nghttp2_nv*)(h + 1); --- ctx.strbuf = (char*)&ctx.nv[ctx.nvlen]; --- --- NV_ADD_LIT_CS(&ctx, ":status", status); --- apr_table_do(add_header, &ctx, header, NULL); --- --- h->nv = ctx.nv; --- h->nvlen = ctx.nvlen; --- } --- return h; ---} diff --cc modules/http2/h2_response.h index 64d68cc9c22,456d2226ed2,456d2226ed2..59f7b035aa5 --- a/modules/http2/h2_response.h +++ b/modules/http2/h2_response.h @@@@ -16,28 -16,26 -16,26 +16,21 @@@@ #ifndef __mod_h2__h2_response__ #define __mod_h2__h2_response__ ---/* h2_response is just the data belonging the the head of a HTTP response, --- * suitable prepared to be fed to nghttp2 for response submit. --- */ ---typedef struct h2_ngheader { --- nghttp2_nv *nv; --- apr_size_t nvlen; ---} h2_ngheader; +++struct h2_push; typedef struct h2_response { int stream_id; -- const char *status; ++ int rst_error; - const char *status; +++ int http_status; apr_off_t content_length; --- apr_table_t *rheader; --- h2_ngheader *ngheader; +++ apr_table_t *header; } h2_response; h2_response *h2_response_create(int stream_id, -- const char *http_status, -- apr_array_header_t *hlines, -- apr_pool_t *pool); ++ int rst_error, - const char *http_status, +++ int http_status, ++ apr_array_header_t *hlines, ++ apr_pool_t *pool); h2_response *h2_response_rcreate(int stream_id, request_rec *r, apr_table_t *header, apr_pool_t *pool); diff --cc modules/http2/h2_session.c index fc0287042bb,c3456a0654d,c3456a0654d..46898c43160 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@@@ -29,6 -27,6 -27,6 +29,8 @@@@ #include "h2_config.h" #include "h2_h2.h" #include "h2_mplx.h" +++#include "h2_push.h" +++#include "h2_request.h" #include "h2_response.h" #include "h2_stream.h" #include "h2_stream_set.h" @@@@ -43,109 -41,57 -41,57 +45,110 @@@@ static int frame_print(const nghttp2_fr static int h2_session_status_from_apr_status(apr_status_t rv) { -- switch (rv) { -- case APR_SUCCESS: -- return NGHTTP2_NO_ERROR; -- case APR_EAGAIN: -- case APR_TIMEUP: -- return NGHTTP2_ERR_WOULDBLOCK; -- case APR_EOF: ++ if (rv == APR_SUCCESS) { ++ return NGHTTP2_NO_ERROR; ++ } ++ else if (APR_STATUS_IS_EAGAIN(rv)) { ++ return NGHTTP2_ERR_WOULDBLOCK; ++ } ++ else if (APR_STATUS_IS_EOF(rv)) { return NGHTTP2_ERR_EOF; -- default: -- return NGHTTP2_ERR_PROTO; } ++ return NGHTTP2_ERR_PROTO; } ---static int stream_open(h2_session *session, int stream_id) +++h2_stream *h2_session_open_stream(h2_session *session, int stream_id) { h2_stream * stream; ++ apr_pool_t *stream_pool; if (session->aborted) { --- return NGHTTP2_ERR_CALLBACK_FAILURE; +++ return NULL; } -- stream = h2_mplx_open_io(session->mplx, stream_id); -- if (stream) { -- h2_stream_set_add(session->streams, stream); -- if (stream->id > session->max_stream_received) { -- session->max_stream_received = stream->id; -- } -- -- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, -- "h2_session: stream(%ld-%d): opened", -- session->id, stream_id); ++ if (session->spare) { ++ stream_pool = session->spare; ++ session->spare = NULL; ++ } ++ else { ++ apr_pool_create(&stream_pool, session->pool); ++ } ++ - stream = h2_stream_create(stream_id, stream_pool, session); - stream->state = H2_STREAM_ST_OPEN; +++ stream = h2_stream_open(stream_id, stream_pool, session); ++ ++ h2_stream_set_add(session->streams, stream); - if (stream->id > session->max_stream_received) { +++ if (H2_STREAM_CLIENT_INITIATED(stream_id) +++ && stream_id > session->max_stream_received) { ++ session->max_stream_received = stream->id; ++ } ++ - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - "h2_session: stream(%ld-%d): opened", - session->id, stream_id); - - return 0; +++ return stream; +++} +++ +++apr_status_t h2_session_flush(h2_session *session) +++{ +++ return h2_conn_io_flush(&session->io); ++} ++ ++/** ++ * Determine the importance of streams when scheduling tasks. ++ * - if both stream depend on the same one, compare weights ++ * - if one stream is closer to the root, prioritize that one ++ * - if both are on the same level, use the weight of their root ++ * level ancestors ++ */ ++static int spri_cmp(int sid1, nghttp2_stream *s1, ++ int sid2, nghttp2_stream *s2, h2_session *session) ++{ ++ nghttp2_stream *p1, *p2; ++ ++ p1 = nghttp2_stream_get_parent(s1); ++ p2 = nghttp2_stream_get_parent(s2); ++ ++ if (p1 == p2) { ++ int32_t w1, w2; -- return 0; ++ w1 = nghttp2_stream_get_weight(s1); ++ w2 = nghttp2_stream_get_weight(s2); ++ return w2 - w1; ++ } ++ else if (!p1) { ++ /* stream 1 closer to root */ ++ return -1; } ++ else if (!p2) { ++ /* stream 2 closer to root */ ++ return 1; ++ } ++ return spri_cmp(sid1, p1, sid2, p2, session); ++} ++ ++static int stream_pri_cmp(int sid1, int sid2, void *ctx) ++{ ++ h2_session *session = ctx; ++ nghttp2_stream *s1, *s2; -- ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, session->c, -- APLOGNO(02918) -- "h2_session: stream(%ld-%d): unable to create", -- session->id, stream_id); -- return NGHTTP2_ERR_INVALID_STREAM_ID; ++ s1 = nghttp2_session_find_stream(session->ngh2, sid1); ++ s2 = nghttp2_session_find_stream(session->ngh2, sid2); ++ ++ if (s1 == s2) { ++ return 0; ++ } ++ else if (!s1) { ++ return 1; ++ } ++ else if (!s2) { ++ return -1; ++ } ++ return spri_cmp(sid1, s1, sid2, s2, session); } ---static apr_status_t stream_end_headers(h2_session *session, --- h2_stream *stream, int eos) +++static apr_status_t stream_schedule(h2_session *session, +++ h2_stream *stream, int eos) { (void)session; -- return h2_stream_write_eoh(stream, eos); ++ return h2_stream_schedule(stream, eos, stream_pri_cmp, session); } --static apr_status_t send_data(h2_session *session, const char *data, -- apr_size_t length); -- /* * Callback when nghttp2 wants to send bytes back to the client. */ @@@@ -195,19 -140,19 -140,19 +198,19 @@@@ static int on_data_chunk_recv_cb(nghttp int32_t stream_id, const uint8_t *data, size_t len, void *userp) { --- int rv; h2_session *session = (h2_session *)userp; +++ apr_status_t status = APR_SUCCESS; h2_stream * stream; --- apr_status_t status; +++ int rv; (void)flags; if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } --- stream = h2_stream_set_get(session->streams, stream_id); +++ +++ stream = h2_session_get_stream(session, stream_id); if (!stream) { --- ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, --- APLOGNO(02919) +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, "h2_session: stream(%ld-%d): on_data_chunk for unknown stream", session->id, (int)stream_id); rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id, @@@@ -220,11 -165,11 -165,11 +223,11 @@@@ status = h2_stream_write_data(stream, (const char *)data, len); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c, --- "h2_stream(%ld-%d): written DATA, length %d", --- session->id, stream_id, (int)len); +++ "h2_stream(%ld-%d): data_chunk_recv, written %ld bytes", +++ session->id, stream_id, (long)len); if (status != APR_SUCCESS) { rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id, -- NGHTTP2_INTERNAL_ERROR); ++ H2_STREAM_RST(stream, H2_ERR_INTERNAL_ERROR)); if (nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } @@@@ -232,75 -177,56 -177,56 +235,7 @@@@ return 0; } ---static int before_frame_send_cb(nghttp2_session *ngh2, --- const nghttp2_frame *frame, --- void *userp) ---{ --- h2_session *session = (h2_session *)userp; --- (void)ngh2; --- --- if (session->aborted) { --- return NGHTTP2_ERR_CALLBACK_FAILURE; - } - /* Set the need to flush output when we have added one of the - * following frame types */ - switch (frame->hd.type) { - case NGHTTP2_RST_STREAM: - case NGHTTP2_WINDOW_UPDATE: - case NGHTTP2_PUSH_PROMISE: - case NGHTTP2_PING: - case NGHTTP2_GOAWAY: - session->flush = 1; - break; - default: - break; - --- } --- if (APLOGctrace2(session->c)) { --- char buffer[256]; --- frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); --- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, --- "h2_session(%ld): before_frame_send %s", --- session->id, buffer); --- } --- return 0; ---} --- ---static int on_frame_send_cb(nghttp2_session *ngh2, --- const nghttp2_frame *frame, --- void *userp) ---{ --- h2_session *session = (h2_session *)userp; - (void)ngh2; - if (APLOGctrace2(session->c)) { - char buffer[256]; - frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - "h2_session(%ld): on_frame_send %s", - session->id, buffer); - } -- (void)ngh2; (void)frame; -- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, -- "h2_session(%ld): on_frame_send", session->id); --- return 0; ---} --- ---static int on_frame_not_send_cb(nghttp2_session *ngh2, --- const nghttp2_frame *frame, --- int lib_error_code, void *userp) ---{ --- h2_session *session = (h2_session *)userp; --- (void)ngh2; --- --- if (APLOGctrace2(session->c)) { --- char buffer[256]; --- --- frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); --- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, --- "h2_session: callback on_frame_not_send error=%d %s", --- lib_error_code, buffer); --- } --- return 0; ---} --- ---static apr_status_t stream_destroy(h2_session *session, +++static apr_status_t stream_release(h2_session *session, h2_stream *stream, uint32_t error_code) { @@@@ -335,33 -259,33 -259,33 +270,30 @@@@ static int on_stream_close_cb(nghttp2_s if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } --- stream = h2_stream_set_get(session->streams, stream_id); +++ stream = h2_session_get_stream(session, stream_id); if (stream) { --- stream_destroy(session, stream, error_code); - } - - if (error_code) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - "h2_stream(%ld-%d): close error %d", - session->id, (int)stream_id, error_code); +++ stream_release(session, stream, error_code); } -- -- if (error_code) { -- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, -- "h2_stream(%ld-%d): close error %d", -- session->id, (int)stream_id, error_code); -- } --- return 0; } static int on_begin_headers_cb(nghttp2_session *ngh2, const nghttp2_frame *frame, void *userp) { --- /* This starts a new stream. */ --- int rv; +++ h2_session *session = (h2_session *)userp; +++ h2_stream *s; +++ +++ /* We may see HEADERs at the start of a stream or after all DATA +++ * streams to carry trailers. */ (void)ngh2; --- rv = stream_open((h2_session *)userp, frame->hd.stream_id); --- if (rv != NGHTTP2_ERR_CALLBACK_FAILURE) { --- /* on_header_cb or on_frame_recv_cb will dectect that stream --- does not exist and submit RST_STREAM. */ --- return 0; +++ s = h2_session_get_stream(session, frame->hd.stream_id); +++ if (s) { +++ /* nop */ ++ } - return NGHTTP2_ERR_CALLBACK_FAILURE; +++ else { +++ s = h2_session_open_stream((h2_session *)userp, frame->hd.stream_id); + } -- return NGHTTP2_ERR_CALLBACK_FAILURE; +++ return s? 0 : NGHTTP2_ERR_CALLBACK_FAILURE; } static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame, @@@@ -379,8 -303,8 -303,8 +311,8 @@@@ if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } --- stream = h2_stream_set_get(session->streams, --- frame->hd.stream_id); +++ +++ stream = h2_session_get_stream(session, frame->hd.stream_id); if (!stream) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, APLOGNO(02920) @@@@ -389,9 -313,9 -313,9 +321,9 @@@@ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } --- status = h2_stream_write_header(stream, --- (const char *)name, namelen, --- (const char *)value, valuelen); +++ status = h2_stream_add_header(stream, (const char *)name, namelen, +++ (const char *)value, valuelen); +++ if (status != APR_SUCCESS) { return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } @@@@ -407,64 -331,63 -331,63 +339,67 @@@@ static int on_frame_recv_cb(nghttp2_ses const nghttp2_frame *frame, void *userp) { --- int rv; h2_session *session = (h2_session *)userp; apr_status_t status = APR_SUCCESS; +++ h2_stream *stream; +++ if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } --- ++session->frames_received; --- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, --- "h2_session(%ld): on_frame_rcv #%ld, type=%d", session->id, +++ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, +++ "h2_stream(%ld-%d): on_frame_rcv #%ld, type=%d", +++ session->id, frame->hd.stream_id, (long)session->frames_received, frame->hd.type); +++ +++ ++session->frames_received; switch (frame->hd.type) { --- case NGHTTP2_HEADERS: { --- int eos; --- h2_stream * stream = h2_stream_set_get(session->streams, --- frame->hd.stream_id); --- if (stream == NULL) { --- ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, --- APLOGNO(02921) --- "h2_session: stream(%ld-%d): HEADERS frame " --- "for unknown stream", session->id, --- (int)frame->hd.stream_id); --- rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE, --- frame->hd.stream_id, --- NGHTTP2_INTERNAL_ERROR); --- if (nghttp2_is_fatal(rv)) { --- return NGHTTP2_ERR_CALLBACK_FAILURE; +++ case NGHTTP2_HEADERS: +++ /* This can be HEADERS for a new stream, defining the request, +++ * or HEADER may come after DATA at the end of a stream as in +++ * trailers */ +++ stream = h2_session_get_stream(session, frame->hd.stream_id); +++ if (stream) { +++ int eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM); +++ +++ if (h2_stream_is_scheduled(stream)) { +++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, +++ "h2_stream(%ld-%d): TRAILER, eos=%d", +++ session->id, frame->hd.stream_id, eos); +++ if (eos) { +++ status = h2_stream_close_input(stream); +++ } +++ } +++ else { +++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, +++ "h2_stream(%ld-%d): HEADER, eos=%d", +++ session->id, frame->hd.stream_id, eos); +++ status = stream_schedule(session, stream, eos); } --- return 0; } --- --- eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM); --- status = stream_end_headers(session, stream, eos); --- +++ else { +++ status = APR_EINVAL; +++ } break; --- } --- case NGHTTP2_DATA: { --- h2_stream * stream = h2_stream_set_get(session->streams, --- frame->hd.stream_id); --- if (stream == NULL) { --- ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, --- APLOGNO(02922) --- "h2_session: stream(%ld-%d): DATA frame " --- "for unknown stream", session->id, --- (int)frame->hd.stream_id); --- rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE, --- frame->hd.stream_id, --- NGHTTP2_INTERNAL_ERROR); --- if (nghttp2_is_fatal(rv)) { --- return NGHTTP2_ERR_CALLBACK_FAILURE; +++ case NGHTTP2_DATA: +++ stream = h2_session_get_stream(session, frame->hd.stream_id); +++ if (stream) { +++ int eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM); +++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, +++ "h2_stream(%ld-%d): DATA, len=%ld, eos=%d", +++ session->id, frame->hd.stream_id, +++ (long)frame->hd.length, eos); +++ if (eos) { +++ status = h2_stream_close_input(stream); } --- return 0; +++ } +++ else { +++ status = APR_EINVAL; } break; --- } --- case NGHTTP2_PRIORITY: { -- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, +++ case NGHTTP2_PRIORITY: ++ session->reprioritize = 1; - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, +++ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, "h2_session: stream(%ld-%d): PRIORITY frame " " weight=%d, dependsOn=%d, exclusive=%d", session->id, (int)frame->hd.stream_id, @@@@ -472,7 -395,7 -395,7 +407,13 @@@@ frame->priority.pri_spec.stream_id, frame->priority.pri_spec.exclusive); break; --- } +++ case NGHTTP2_WINDOW_UPDATE: +++ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, +++ "h2_session: stream(%ld-%d): WINDOW_UPDATE " +++ "incr=%d", +++ session->id, (int)frame->hd.stream_id, +++ frame->window_update.window_size_increment); +++ break; default: if (APLOGctrace2(session->c)) { char buffer[256]; @@@@ -485,23 -408,23 -408,23 +426,10 @@@@ break; } --- /* only DATA and HEADERS frame can bear END_STREAM flag. Other --- frame types may have other flag which has the same value, so we --- have to check the frame type first. */ --- if ((frame->hd.type == NGHTTP2_DATA || frame->hd.type == NGHTTP2_HEADERS) && --- frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { --- h2_stream * stream = h2_stream_set_get(session->streams, --- frame->hd.stream_id); --- if (stream != NULL) { --- status = h2_stream_write_eos(stream); --- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, --- "h2_stream(%ld-%d): input closed", --- session->id, (int)frame->hd.stream_id); --- } --- } --- if (status != APR_SUCCESS) { --- ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, +++ int rv; +++ +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO(02923) "h2_session: stream(%ld-%d): error handling frame", session->id, (int)frame->hd.stream_id); @@@@ -511,21 -434,24 -434,24 +439,20 @@@@ if (nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } --- return 0; } return 0; } --static apr_status_t send_data(h2_session *session, const char *data, -- apr_size_t length) --{ -- return h2_conn_io_write(&session->io, data, length); --} -- static apr_status_t pass_data(void *ctx, --- const char *data, apr_size_t length) +++ const char *data, apr_off_t length) { -- return send_data((h2_session*)ctx, data, length); ++ return h2_conn_io_write(&((h2_session*)ctx)->io, data, length); } ++ ++static char immortal_zeros[H2_MAX_PADLEN]; ++ static int on_send_data_cb(nghttp2_session *ngh2, nghttp2_frame *frame, const uint8_t *framehd, @@@@ -536,7 -462,7 -462,7 +463,7 @@@@ apr_status_t status = APR_SUCCESS; h2_session *session = (h2_session *)userp; int stream_id = (int)frame->hd.stream_id; --- const unsigned char padlen = frame->data.padlen; +++ unsigned char padlen; int eos; h2_stream *stream; @@@@ -546,7 -472,7 -472,7 +473,12 @@@@ return NGHTTP2_ERR_CALLBACK_FAILURE; } --- stream = h2_stream_set_get(session->streams, stream_id); +++ if (frame->data.padlen > H2_MAX_PADLEN) { +++ return NGHTTP2_ERR_PROTO; +++ } +++ padlen = (unsigned char)frame->data.padlen; +++ +++ stream = h2_session_get_stream(session, stream_id); if (!stream) { ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c, APLOGNO(02924) @@@@ -555,48 -481,16 -481,16 +487,46 @@@@ return NGHTTP2_ERR_CALLBACK_FAILURE; } -- status = send_data(session, (const char *)framehd, 9); -- if (status == APR_SUCCESS) { ++ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, ++ "h2_stream(%ld-%d): send_data_cb for %ld bytes", ++ session->id, (int)stream_id, (long)length); ++ ++ if (h2_conn_io_is_buffered(&session->io)) { ++ status = h2_conn_io_write(&session->io, (const char *)framehd, 9); ++ if (status == APR_SUCCESS) { ++ if (padlen) { ++ status = h2_conn_io_write(&session->io, (const char *)&padlen, 1); ++ } ++ ++ if (status == APR_SUCCESS) { - apr_size_t len = length; - status = h2_stream_readx(stream, pass_data, session, - &len, &eos); +++ apr_off_t len = length; +++ status = h2_stream_readx(stream, pass_data, session, &len, &eos); ++ if (status == APR_SUCCESS && len != length) { ++ status = APR_EINVAL; ++ } ++ } ++ ++ if (status == APR_SUCCESS && padlen) { ++ if (padlen) { ++ status = h2_conn_io_write(&session->io, immortal_zeros, padlen); ++ } ++ } ++ } ++ } ++ else { ++ apr_bucket *b; ++ char *header = apr_pcalloc(stream->pool, 10); ++ memcpy(header, (const char *)framehd, 9); if (padlen) { -- status = send_data(session, (const char *)&padlen, 1); ++ header[9] = (char)padlen; } -- ++ b = apr_bucket_pool_create(header, padlen? 10 : 9, ++ stream->pool, session->c->bucket_alloc); ++ status = h2_conn_io_writeb(&session->io, b); ++ if (status == APR_SUCCESS) { --- apr_size_t len = length; -- status = h2_stream_readx(stream, pass_data, session, -- &len, &eos); +++ apr_off_t len = length; ++ status = h2_stream_read_to(stream, session->io.output, &len, &eos); - session->io.unflushed = 1; if (status == APR_SUCCESS && len != length) { status = APR_EINVAL; } @@@@ -625,20 -519,7 -519,7 +555,6 @@@@ return h2_session_status_from_apr_status(status); } - static ssize_t on_data_source_read_length_cb(nghttp2_session *session, - uint8_t frame_type, int32_t stream_id, - int32_t session_remote_window_size, - int32_t stream_remote_window_size, - uint32_t remote_max_frame_size, - void *user_data) - { - /* DATA frames add 9 bytes header plus 1 byte for padlen and additional - * padlen bytes. Keep below TLS maximum record size. - * TODO: respect pad bytes when we have that feature. - */ - return (16*1024 - 10); - } --- #define NGH2_SET_CALLBACK(callbacks, name, fn)\ nghttp2_session_callbacks_set_##name##_callback(callbacks, fn) @@@@ -656,9 -537,9 -537,9 +572,6 @@@@ static apr_status_t init_callbacks(conn NGH2_SET_CALLBACK(*pcb, on_frame_recv, on_frame_recv_cb); NGH2_SET_CALLBACK(*pcb, on_invalid_frame_recv, on_invalid_frame_recv_cb); NGH2_SET_CALLBACK(*pcb, on_data_chunk_recv, on_data_chunk_recv_cb); --- NGH2_SET_CALLBACK(*pcb, before_frame_send, before_frame_send_cb); --- NGH2_SET_CALLBACK(*pcb, on_frame_send, on_frame_send_cb); --- NGH2_SET_CALLBACK(*pcb, on_frame_not_send, on_frame_not_send_cb); NGH2_SET_CALLBACK(*pcb, on_stream_close, on_stream_close_cb); NGH2_SET_CALLBACK(*pcb, on_begin_headers, on_begin_headers_cb); NGH2_SET_CALLBACK(*pcb, on_header, on_header_cb); @@@@ -668,6 -548,6 -548,6 +580,16 @@@@ return APR_SUCCESS; } +++static apr_status_t session_pool_cleanup(void *data) +++{ +++ h2_session *session = data; +++ +++ /* keep us from destroying the pool, since that is already ongoing. */ +++ session->pool = NULL; +++ h2_session_destroy(session); +++ return APR_SUCCESS; +++} +++ static h2_session *h2_session_create_int(conn_rec *c, request_rec *r, h2_config *config, @@@@ -690,6 -570,6 -570,6 +612,8 @@@@ session->c = c; session->r = r; +++ apr_pool_pre_cleanup_register(pool, session, session_pool_cleanup); +++ session->max_stream_count = h2_config_geti(config, H2_CONF_MAX_STREAMS); session->max_stream_mem = h2_config_geti(config, H2_CONF_STREAM_MAX_MEM); @@@@ -700,7 -580,7 -580,7 +624,7 @@@@ return NULL; } --- session->streams = h2_stream_set_create(session->pool); +++ session->streams = h2_stream_set_create(session->pool, session->max_stream_count); session->workers = workers; session->mplx = h2_mplx_create(c, session->pool, workers); @@@@ -761,15 -641,15 -641,15 +685,37 @@@@ h2_session *h2_session_rcreate(request_ return h2_session_create_int(r->connection, r, config, workers); } ---void h2_session_destroy(h2_session *session) +++static void h2_session_cleanup(h2_session *session) { AP_DEBUG_ASSERT(session); +++ /* This is an early cleanup of the session that may +++ * discard what is no longer necessary for *new* streams +++ * and general HTTP/2 processing. +++ * At this point, all frames are in transit or somehwere in +++ * our buffers or passed down output filters. +++ * h2 streams might still being written out. +++ */ +++ if (session->ngh2) { +++ nghttp2_session_del(session->ngh2); +++ session->ngh2 = NULL; +++ } +++ if (session->spare) { +++ apr_pool_destroy(session->spare); +++ session->spare = NULL; +++ } if (session->mplx) { h2_mplx_release_and_join(session->mplx, session->iowait); session->mplx = NULL; } +++} +++ +++void h2_session_destroy(h2_session *session) +++{ +++ AP_DEBUG_ASSERT(session); +++ h2_session_cleanup(session); +++ if (session->streams) { --- if (h2_stream_set_size(session->streams)) { +++ if (!h2_stream_set_is_empty(session->streams)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, "h2_session(%ld): destroy, %d streams open", session->id, (int)h2_stream_set_size(session->streams)); @@@@ -777,20 -657,22 -657,22 +723,20 @@@@ h2_stream_set_destroy(session->streams); session->streams = NULL; } --- if (session->ngh2) { --- nghttp2_session_del(session->ngh2); --- session->ngh2 = NULL; -- } -- h2_conn_io_destroy(&session->io); -- -- if (session->iowait) { -- apr_thread_cond_destroy(session->iowait); -- session->iowait = NULL; -- } -- + if (session->pool) { + apr_pool_destroy(session->pool); } } - void h2_session_cleanup(h2_session *session) +++ +++void h2_session_eoc_callback(h2_session *session) ++{ +++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, +++ "session(%ld): cleanup and destroy", session->id); +++ apr_pool_cleanup_kill(session->pool, session, session_pool_cleanup); ++ h2_session_destroy(session); - if (session->pool) { - apr_pool_destroy(session->pool); - } ++} ++ static apr_status_t h2_session_abort_int(h2_session *session, int reason) { AP_DEBUG_ASSERT(session); @@@@ -816,15 -694,22 -694,22 +762,14 @@@@ "session(%ld): aborting session, reason=%d %s", session->id, reason, err); -- if (NGHTTP2_ERR_EOF == reason) { -- /* This is our way of indication that the connection is -- * gone. No use to send any GOAWAY frames. */ -- nghttp2_session_terminate_session(session->ngh2, reason); -- } -- else { -- /* The connection might still be there and we shut down -- * with GOAWAY and reason information. */ -- nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, -- session->max_stream_received, -- reason, (const uint8_t *)err, -- strlen(err)); -- nghttp2_session_send(session->ngh2); -- h2_conn_io_flush(&session->io); -- } ++ /* The connection might still be there and we shut down ++ * with GOAWAY and reason information. */ ++ nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, ++ session->max_stream_received, ++ reason, (const uint8_t *)err, ++ strlen(err)); ++ nghttp2_session_send(session->ngh2); } - h2_conn_io_flush(&session->io); } h2_mplx_abort(session->mplx); } @@@@ -863,6 -746,6 -746,6 +808,8 @@@@ apr_status_t h2_session_start(h2_sessio apr_status_t status = APR_SUCCESS; h2_config *config; nghttp2_settings_entry settings[3]; +++ size_t slen; +++ int i; AP_DEBUG_ASSERT(session); /* Start the conversation by submitting our SETTINGS frame */ @@@@ -906,8 -789,8 -789,8 +853,8 @@@@ } /* Now we need to auto-open stream 1 for the request we got. */ --- *rv = stream_open(session, 1); --- if (*rv != 0) { +++ stream = h2_session_open_stream(session, 1); +++ if (!stream) { status = APR_EGENERAL; ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, APLOGNO(02933) "open stream 1: %s", @@@@ -915,34 -798,34 -798,34 +862,29 @@@@ return status; } --- stream = h2_stream_set_get(session->streams, 1); --- if (stream == NULL) { --- status = APR_EGENERAL; --- ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, --- APLOGNO(02934) "lookup of stream 1"); --- return status; --- } --- --- status = h2_stream_rwrite(stream, session->r); +++ status = h2_stream_set_request(stream, session->r); if (status != APR_SUCCESS) { return status; } --- status = stream_end_headers(session, stream, 1); +++ status = stream_schedule(session, stream, 1); if (status != APR_SUCCESS) { return status; } } --- settings[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; --- settings[0].value = (uint32_t)session->max_stream_count; --- settings[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; --- settings[1].value = h2_config_geti(config, H2_CONF_WIN_SIZE); --- settings[2].settings_id = NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE; --- settings[2].value = 64*1024; +++ slen = 0; +++ settings[slen].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; +++ settings[slen].value = (uint32_t)session->max_stream_count; +++ ++slen; +++ i = h2_config_geti(config, H2_CONF_WIN_SIZE); +++ if (i != H2_INITIAL_WINDOW_SIZE) { +++ settings[slen].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; +++ settings[slen].value = i; +++ ++slen; +++ } *rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE, --- settings, --- sizeof(settings)/sizeof(settings[0])); +++ settings, slen); if (*rv != 0) { status = APR_EGENERAL; ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, @@@@ -953,11 -836,11 -836,11 +895,6 @@@@ return status; } ---static int h2_session_want_write(h2_session *session) ---{ --- return nghttp2_session_want_write(session->ngh2); ---} --- typedef struct { h2_session *session; int resume_count; @@@@ -1003,102 -886,98 -886,98 +940,18 @@@@ static int h2_session_resume_streams_wi return 0; } ---static void update_window(void *ctx, int stream_id, apr_size_t bytes_read) +++static void update_window(void *ctx, int stream_id, apr_off_t bytes_read) { h2_session *session = (h2_session*)ctx; nghttp2_session_consume(session->ngh2, stream_id, bytes_read); } - static apr_status_t h2_session_flush(h2_session *session) - { - session->flush = 0; - return h2_conn_io_flush(&session->io); - } - ---static apr_status_t h2_session_update_windows(h2_session *session) ---{ --- return h2_mplx_in_update_windows(session->mplx, update_window, session); ---} --- ---apr_status_t h2_session_write(h2_session *session, apr_interval_time_t timeout) ---{ --- apr_status_t status = APR_EAGAIN; --- h2_stream *stream = NULL; -- int flush_output = 0; --- --- AP_DEBUG_ASSERT(session); - - if (session->reprioritize) { - h2_mplx_reprioritize(session->mplx, stream_pri_cmp, session); - session->reprioritize = 0; - } --- --- /* Check that any pending window updates are sent. */ --- status = h2_session_update_windows(session); - if (status != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(status)) { -- if (status == APR_SUCCESS) { -- flush_output = 1; -- } -- else if (status != APR_EAGAIN) { --- return status; --- } --- --- if (h2_session_want_write(session)) { --- int rv; --- status = APR_SUCCESS; --- rv = nghttp2_session_send(session->ngh2); --- if (rv != 0) { --- ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c, --- "h2_session: send: %s", nghttp2_strerror(rv)); --- if (nghttp2_is_fatal(rv)) { --- h2_session_abort_int(session, rv); --- status = APR_ECONNABORTED; --- } --- } -- flush_output = 1; --- } --- --- /* If we have responses ready, submit them now. */ - while (!session->aborted - && (stream = h2_mplx_next_submit(session->mplx, session->streams)) != NULL) { -- while ((stream = h2_mplx_next_submit(session->mplx, -- session->streams)) != NULL) { --- status = h2_session_handle_response(session, stream); -- flush_output = 1; --- } --- - if (!session->aborted && h2_session_resume_streams_with_data(session) > 0) { -- if (h2_session_resume_streams_with_data(session) > 0) { -- flush_output = 1; --- } --- - if (!session->aborted && !session->flush && timeout > 0 - && !h2_session_want_write(session)) { - h2_session_flush(session); -- if (!flush_output && timeout > 0 && !h2_session_want_write(session)) { --- status = h2_mplx_out_trywait(session->mplx, timeout, session->iowait); --- --- if (status != APR_TIMEUP --- && h2_session_resume_streams_with_data(session) > 0) { -- flush_output = 1; --- } --- else { --- /* nothing happened to ongoing streams, do some house-keeping */ --- } --- } --- --- if (h2_session_want_write(session)) { --- int rv; --- status = APR_SUCCESS; --- rv = nghttp2_session_send(session->ngh2); --- if (rv != 0) { --- ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c, --- "h2_session: send2: %s", nghttp2_strerror(rv)); --- if (nghttp2_is_fatal(rv)) { --- h2_session_abort_int(session, rv); --- status = APR_ECONNABORTED; --- } --- } -- flush_output = 1; --- } --- - if (session->flush) { - h2_session_flush(session); -- if (flush_output) { -- h2_conn_io_flush(&session->io); --- } --- --- return status; ---} --- h2_stream *h2_session_get_stream(h2_session *session, int stream_id) { --- AP_DEBUG_ASSERT(session); --- return h2_stream_set_get(session->streams, stream_id); +++ if (!session->last_stream || stream_id != session->last_stream->id) { +++ session->last_stream = h2_stream_set_get(session->streams, stream_id); +++ } +++ return session->last_stream; } /* h2_io_on_read_cb implementation that offers the data read @@@@ -1131,31 -1010,20 -1010,20 +984,21 @@@@ static apr_status_t session_receive(con return APR_SUCCESS; } ---apr_status_t h2_session_read(h2_session *session, apr_read_type_e block) ---{ --- AP_DEBUG_ASSERT(session); - if (block == APR_BLOCK_READ) { - /* before we do a blocking read, make sure that all our output - * is send out. Otherwise we might deadlock. */ - h2_session_flush(session); - } --- return h2_conn_io_read(&session->io, block, session_receive, session); ---} --- apr_status_t h2_session_close(h2_session *session) { AP_DEBUG_ASSERT(session); -- return session->aborted? APR_SUCCESS : h2_conn_io_flush(&session->io); ++ if (!session->aborted) { ++ h2_session_abort_int(session, 0); ++ } ++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0,session->c, ++ "h2_session: closing, writing eoc"); - h2_conn_io_writeb(&session->io, - h2_bucket_eoc_create(session->c->bucket_alloc, - session)); - return h2_conn_io_flush(&session->io); +++ +++ h2_session_cleanup(session); +++ return h2_conn_io_writeb(&session->io, +++ h2_bucket_eoc_create(session->c->bucket_alloc, +++ session)); } --/* The session wants to send more DATA for the given stream. -- */ static ssize_t stream_data_cb(nghttp2_session *ng2s, int32_t stream_id, uint8_t *buf, @@@@ -1165,7 -1033,7 -1033,7 +1008,7 @@@@ void *puser) { h2_session *session = (h2_session *)puser; --- apr_size_t nread = length; +++ apr_off_t nread = length; int eos = 0; apr_status_t status; h2_stream *stream; @@@@ -1183,9 -1042,9 -1042,9 +1026,9 @@@@ (void)ng2s; (void)buf; (void)source; --- stream = h2_stream_set_get(session->streams, stream_id); +++ stream = h2_session_get_stream(session, stream_id); if (!stream) { -- ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c, ++ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, APLOGNO(02937) "h2_stream(%ld-%d): data requested but stream not found", session->id, (int)stream_id); @@@@ -1245,58 -1100,57 -1100,57 +1088,75 @@@@ typedef struct size_t offset; } nvctx_t; ---static int submit_response(h2_session *session, h2_response *response) ---{ --- nghttp2_data_provider provider; --- int rv; --- --- memset(&provider, 0, sizeof(provider)); --- provider.source.fd = response->stream_id; --- provider.read_callback = stream_data_cb; --- --- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, --- "h2_stream(%ld-%d): submitting response %s", --- session->id, response->stream_id, response->status); --- --- rv = nghttp2_submit_response(session->ngh2, response->stream_id, --- response->ngheader->nv, --- response->ngheader->nvlen, &provider); --- --- if (rv != 0) { --- ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, --- APLOGNO(02939) "h2_stream(%ld-%d): submit_response: %s", --- session->id, response->stream_id, nghttp2_strerror(rv)); --- } --- else { --- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, --- "h2_stream(%ld-%d): submitted response %s, rv=%d", --- session->id, response->stream_id, --- response->status, rv); --- } --- return rv; ---} --- ---/* Start submitting the response to a stream request. This is possible +++/** +++ * Start submitting the response to a stream request. This is possible * once we have all the response headers. The response body will be * read by the session using the callback we supply. */ ---apr_status_t h2_session_handle_response(h2_session *session, h2_stream *stream) +++static apr_status_t submit_response(h2_session *session, h2_stream *stream) { apr_status_t status = APR_SUCCESS; int rv = 0; AP_DEBUG_ASSERT(session); AP_DEBUG_ASSERT(stream); -- AP_DEBUG_ASSERT(stream->response); ++ AP_DEBUG_ASSERT(stream->response || stream->rst_error); - if (stream->response && stream->response->ngheader) { -- if (stream->response->ngheader) { --- rv = submit_response(session, stream->response); +++ if (stream->submitted) { +++ rv = NGHTTP2_PROTOCOL_ERROR; +++ } +++ else if (stream->response && stream->response->header) { +++ nghttp2_data_provider provider; +++ h2_response *response = stream->response; +++ h2_ngheader *ngh; +++ +++ memset(&provider, 0, sizeof(provider)); +++ provider.source.fd = stream->id; +++ provider.read_callback = stream_data_cb; +++ +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, +++ "h2_stream(%ld-%d): submit response %d", +++ session->id, stream->id, response->http_status); +++ +++ ngh = h2_util_ngheader_make_res(stream->pool, response->http_status, +++ response->header); +++ rv = nghttp2_submit_response(session->ngh2, response->stream_id, +++ ngh->nv, ngh->nvlen, &provider); +++ +++ /* If the submit worked, +++ * and this stream is not a pushed one itself, +++ * and HTTP/2 server push is enabled here, +++ * and the response is in the range 200-299 *), +++ * and the remote side has pushing enabled, +++ * -> find and perform any pushes on this stream +++ * +++ * *) the response code is relevant, as we do not want to +++ * make pushes on 401 or 403 codes, neiterh on 301/302 +++ * and friends. And if we see a 304, we do not push either +++ * as the client, having this resource in its cache, might +++ * also have the pushed ones as well. +++ */ +++ if (!rv +++ && !stream->initiated_on +++ && h2_config_geti(h2_config_get(session->c), H2_CONF_PUSH) +++ && H2_HTTP_2XX(response->http_status) +++ && h2_session_push_enabled(session)) { +++ +++ h2_stream_submit_pushes(stream); +++ } } else { +++ int err = H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR); +++ +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, +++ "h2_stream(%ld-%d): RST_STREAM, err=%d", +++ session->id, stream->id, err); +++ rv = nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE, - stream->id, - H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR)); -- stream->id, NGHTTP2_PROTOCOL_ERROR); +++ stream->id, err); } +++ stream->submitted = 1; +++ if (nghttp2_is_fatal(rv)) { status = APR_EGENERAL; h2_session_abort_int(session, rv); @@@@ -1304,36 -1158,39 -1158,39 +1164,90 @@@@ APLOGNO(02940) "submit_response: %s", nghttp2_strerror(rv)); } +++ return status; } --int h2_session_is_done(h2_session *session) +++struct h2_stream *h2_session_push(h2_session *session, h2_stream *is, +++ h2_push *push) + { -- AP_DEBUG_ASSERT(session); -- return (session->aborted -- || !session->ngh2 -- || (!nghttp2_session_want_read(session->ngh2) -- && !nghttp2_session_want_write(session->ngh2))); --} +++ apr_status_t status; +++ h2_stream *stream; +++ h2_ngheader *ngh; +++ int nid; +++ +++ ngh = h2_util_ngheader_make_req(is->pool, push->req); +++ nid = nghttp2_submit_push_promise(session->ngh2, 0, push->initiating_id, +++ ngh->nv, ngh->nvlen, NULL); +++ +++ if (nid <= 0) { +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, +++ "h2_stream(%ld-%d): submitting push promise fail: %s", +++ session->id, push->initiating_id, +++ nghttp2_strerror(nid)); +++ return NULL; +++ } +++ +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, +++ "h2_stream(%ld-%d): promised new stream %d for %s %s", +++ session->id, push->initiating_id, nid, +++ push->req->method, push->req->path); +++ +++ stream = h2_session_open_stream(session, nid); +++ if (stream) { +++ h2_stream_set_h2_request(stream, is->id, push->req); +++ status = stream_schedule(session, stream, 1); +++ if (status != APR_SUCCESS) { +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, +++ "h2_stream(%ld-%d): scheduling push stream", +++ session->id, stream->id); +++ h2_stream_cleanup(stream); +++ stream = NULL; +++ } +++ } +++ else { +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, +++ "h2_stream(%ld-%d): failed to create stream obj %d", +++ session->id, push->initiating_id, nid); +++ } + --static int log_stream(void *ctx, h2_stream *stream) --{ -- h2_session *session = (h2_session *)ctx; -- AP_DEBUG_ASSERT(session); -- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, -- "h2_stream(%ld-%d): in set, suspended=%d, aborted=%d, " -- "has_data=%d", -- session->id, stream->id, stream->suspended, stream->aborted, -- h2_mplx_out_has_data_for(session->mplx, stream->id)); -- return 1; +++ if (!stream) { +++ /* try to tell the client that it should not wait. */ +++ nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE, nid, +++ NGHTTP2_INTERNAL_ERROR); +++ } +++ +++ return stream; + } + --void h2_session_log_stats(h2_session *session) ++apr_status_t h2_session_stream_destroy(h2_session *session, h2_stream *stream) { -- AP_DEBUG_ASSERT(session); -- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, -- "h2_session(%ld): %d open streams", -- session->id, (int)h2_stream_set_size(session->streams)); -- h2_stream_set_iter(session->streams, log_stream, session); ++ apr_pool_t *pool = h2_stream_detach_pool(stream); ++ - h2_mplx_stream_done(session->mplx, stream->id, stream->rst_error); - h2_stream_set_remove(session->streams, stream->id); +++ /* this may be called while the session has already freed +++ * some internal structures. */ +++ if (session->mplx) { +++ h2_mplx_stream_done(session->mplx, stream->id, stream->rst_error); +++ if (session->last_stream == stream) { +++ session->last_stream = NULL; +++ } +++ } +++ +++ if (session->streams) { +++ h2_stream_set_remove(session->streams, stream->id); +++ } ++ h2_stream_destroy(stream); ++ ++ if (pool) { ++ apr_pool_clear(pool); ++ if (session->spare) { ++ apr_pool_destroy(session->spare); ++ } ++ session->spare = pool; ++ } ++ return APR_SUCCESS; } - int h2_session_is_done(h2_session *session) - { - AP_DEBUG_ASSERT(session); - return (session->aborted - || !session->ngh2 - || (!nghttp2_session_want_read(session->ngh2) - && !nghttp2_session_want_write(session->ngh2))); - } - static int frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen) { char scratch[128]; @@@@ -1411,3 -1268,3 -1268,3 +1325,184 @@@@ } } +++int h2_session_push_enabled(h2_session *session) +++{ +++ return nghttp2_session_get_remote_settings(session->ngh2, +++ NGHTTP2_SETTINGS_ENABLE_PUSH); +++} +++ +++ +++apr_status_t h2_session_process(h2_session *session) +++{ +++ apr_status_t status = APR_SUCCESS; +++ apr_interval_time_t wait_micros = 0; +++ static const int MAX_WAIT_MICROS = 200 * 1000; +++ int got_streams = 0; +++ +++ while (!session->aborted && (nghttp2_session_want_read(session->ngh2) +++ || nghttp2_session_want_write(session->ngh2))) { +++ int have_written = 0; +++ int have_read = 0; +++ +++ /* Send data as long as we have it and window sizes allow. We are +++ * a server after all. +++ */ +++ if (nghttp2_session_want_write(session->ngh2)) { +++ int rv; +++ +++ rv = nghttp2_session_send(session->ngh2); +++ if (rv != 0) { +++ ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c, +++ "h2_session: send: %s", nghttp2_strerror(rv)); +++ if (nghttp2_is_fatal(rv)) { +++ h2_session_abort(session, status, rv); +++ goto end_process; +++ } +++ } +++ else { +++ have_written = 1; +++ wait_micros = 0; +++ } +++ } +++ +++ if (wait_micros > 0) { +++ ap_log_cerror( APLOG_MARK, APLOG_TRACE3, 0, session->c, +++ "h2_session: wait for data, %ld micros", (long)(wait_micros)); +++ h2_conn_io_pass(&session->io); +++ status = h2_mplx_out_trywait(session->mplx, wait_micros, session->iowait); +++ +++ if (status == APR_TIMEUP) { +++ if (wait_micros < MAX_WAIT_MICROS) { +++ wait_micros *= 2; +++ } +++ } +++ } +++ +++ if (nghttp2_session_want_read(session->ngh2)) +++ { +++ /* When we +++ * - and have no streams at all +++ * - or have streams, but none is suspended or needs submit and +++ * have nothing written on the last try +++ * +++ * or, the other way around +++ * - have only streams where data can be sent, but could +++ * not send anything +++ * +++ * then we are waiting on frames from the client (for +++ * example WINDOW_UPDATE or HEADER) and without new frames +++ * from the client, we cannot make any progress, +++ * +++ * and *then* we can safely do a blocking read. +++ */ +++ int may_block = (session->frames_received <= 1); +++ if (!may_block) { +++ if (got_streams) { +++ may_block = (!have_written +++ && !h2_stream_set_has_unsubmitted(session->streams) +++ && !h2_stream_set_has_suspended(session->streams)); +++ } +++ else { +++ may_block = 1; +++ } +++ } +++ +++ if (may_block) { +++ h2_conn_io_flush(&session->io); +++ if (session->c->cs) { +++ session->c->cs->state = (got_streams? CONN_STATE_HANDLER +++ : CONN_STATE_WRITE_COMPLETION); +++ } +++ status = h2_conn_io_read(&session->io, APR_BLOCK_READ, +++ session_receive, session); +++ } +++ else { +++ if (session->c->cs) { +++ session->c->cs->state = CONN_STATE_HANDLER; +++ } +++ status = h2_conn_io_read(&session->io, APR_NONBLOCK_READ, +++ session_receive, session); +++ } +++ +++ switch (status) { +++ case APR_SUCCESS: /* successful read, reset our idle timers */ +++ have_read = 1; +++ wait_micros = 0; +++ break; +++ case APR_EAGAIN: /* non-blocking read, nothing there */ +++ break; +++ default: +++ if (APR_STATUS_IS_ETIMEDOUT(status) +++ || APR_STATUS_IS_ECONNABORTED(status) +++ || APR_STATUS_IS_ECONNRESET(status) +++ || APR_STATUS_IS_EOF(status) +++ || APR_STATUS_IS_EBADF(status)) { +++ /* common status for a client that has left */ +++ ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c, +++ "h2_session(%ld): terminating", +++ session->id); +++ /* Stolen from mod_reqtimeout to speed up lingering when +++ * a read timeout happened. +++ */ +++ apr_table_setn(session->c->notes, "short-lingering-close", "1"); +++ } +++ else { +++ /* uncommon status, log on INFO so that we see this */ +++ ap_log_cerror( APLOG_MARK, APLOG_INFO, status, session->c, +++ APLOGNO(02950) +++ "h2_session(%ld): error reading, terminating", +++ session->id); +++ } +++ h2_session_abort(session, status, 0); +++ goto end_process; +++ } +++ } +++ +++ got_streams = !h2_stream_set_is_empty(session->streams); +++ if (got_streams) { +++ h2_stream *stream; +++ +++ if (session->reprioritize) { +++ h2_mplx_reprioritize(session->mplx, stream_pri_cmp, session); +++ session->reprioritize = 0; +++ } +++ +++ if (!have_read && !have_written) { +++ /* Nothing read or written. That means no data yet ready to +++ * be send out. Slowly back off... +++ */ +++ if (wait_micros == 0) { +++ wait_micros = 10; +++ } +++ } +++ +++ if (h2_stream_set_has_open_input(session->streams)) { +++ /* Check that any pending window updates are sent. */ +++ status = h2_mplx_in_update_windows(session->mplx, update_window, session); +++ if (APR_STATUS_IS_EAGAIN(status)) { +++ status = APR_SUCCESS; +++ } +++ else if (status == APR_SUCCESS) { +++ /* need to flush window updates onto the connection asap */ +++ h2_conn_io_flush(&session->io); +++ } +++ } +++ +++ h2_session_resume_streams_with_data(session); +++ +++ if (h2_stream_set_has_unsubmitted(session->streams)) { +++ /* If we have responses ready, submit them now. */ +++ while ((stream = h2_mplx_next_submit(session->mplx, session->streams))) { +++ status = submit_response(session, stream); +++ } +++ } +++ } +++ +++ if (have_written) { +++ h2_conn_io_flush(&session->io); +++ } +++ } +++ +++end_process: +++ return status; +++} diff --cc modules/http2/h2_session.h index a1d718a9ca6,12768a90fb3,12768a90fb3..90052fc9e7f --- a/modules/http2/h2_session.h +++ b/modules/http2/h2_session.h @@@@ -41,6 -41,6 -41,6 +41,7 @@@@ struct apr_thread_mutext_t struct apr_thread_cond_t; struct h2_config; struct h2_mplx; +++struct h2_push; struct h2_response; struct h2_session; struct h2_stream; @@@@ -58,9 -58,6 -58,6 +59,8 @@@@ struct h2_session request_rec *r; /* the request that started this in case * of 'h2c', NULL otherwise */ int aborted; /* this session is being aborted */ - int flush; /* if != 0, flush output on next occasion */ ++ int reprioritize; /* scheduled streams priority needs to ++ * be re-evaluated */ apr_size_t frames_received; /* number of http/2 frames received */ apr_size_t max_stream_count; /* max number of open streams */ apr_size_t max_stream_mem; /* max buffer memory for a single stream */ @@@@ -72,6 -69,6 -69,6 +72,7 @@@@ h2_conn_io io; /* io on httpd conn filters */ struct h2_mplx *mplx; /* multiplexer for stream data */ +++ struct h2_stream *last_stream; /* last stream worked with */ struct h2_stream_set *streams; /* streams handled by this session */ int max_stream_received; /* highest stream id created */ @@@@ -106,6 -101,6 -101,6 +107,14 @@@@ h2_session *h2_session_create(conn_rec h2_session *h2_session_rcreate(request_rec *r, struct h2_config *cfg, struct h2_workers *workers); +++/** +++ * Process the given HTTP/2 session until it is ended or a fatal +++ * error occured. +++ * +++ * @param session the sessionm to process +++ */ +++apr_status_t h2_session_process(h2_session *session); +++ /** * Destroy the session and all objects it still contains. This will not * destroy h2_task instances that have not finished yet. @@@@ -113,13 -108,6 -108,6 +122,13 @@@@ */ void h2_session_destroy(h2_session *session); ++/** ++ * Cleanup the session and all objects it still contains. This will not ++ * destroy h2_task instances that have not finished yet. ++ * @param session the session to destroy ++ */ - void h2_session_cleanup(h2_session *session); +++void h2_session_eoc_callback(h2_session *session); ++ /** * Called once at start of session. * Sets up the session and sends the initial SETTINGS frame. @@@@ -129,12 -117,12 -117,12 +138,6 @@@@ */ apr_status_t h2_session_start(h2_session *session, int *rv); ---/** --- * Determine if session is finished. --- * @return != 0 iff session is finished and connection can be closed. --- */ ---int h2_session_is_done(h2_session *session); --- /** * Called when an error occured and the session needs to shut down. * @param session the session to shut down @@@@ -145,21 -133,22 -133,22 +148,15 @@@@ apr_status_t h2_session_abort(h2_session *session, apr_status_t reason, int rv); /** --- * Called before a session gets destroyed, might flush output etc. +++ * Pass any buffered output data through the connection filters. +++ * @param session the session to flush */ ---apr_status_t h2_session_close(h2_session *session); -- --/* Read more data from the client connection. Used normally with blocking -- * APR_NONBLOCK_READ, which will return APR_EAGAIN when no data is available. -- * Use with APR_BLOCK_READ only when certain that no data needs to be written -- * while waiting. */ --apr_status_t h2_session_read(h2_session *session, apr_read_type_e block); +++apr_status_t h2_session_flush(h2_session *session); - /* Read more data from the client connection. Used normally with blocking - * APR_NONBLOCK_READ, which will return APR_EAGAIN when no data is available. - * Use with APR_BLOCK_READ only when certain that no data needs to be written - * while waiting. */ - apr_status_t h2_session_read(h2_session *session, apr_read_type_e block); - ---/* Write data out to the client, if there is any. Otherwise, wait for --- * a maximum of timeout micro-seconds and return to the caller. If timeout --- * occurred, APR_TIMEUP will be returned. +++/** +++ * Called before a session gets destroyed, might flush output etc. */ - apr_status_t h2_session_write(h2_session *session, apr_interval_time_t timeout); --apr_status_t h2_session_write(h2_session *session, -- apr_interval_time_t timeout); +++apr_status_t h2_session_close(h2_session *session); /* Start submitting the response to a stream request. This is possible * once we have all the response headers. */ @@@@ -169,12 -158,6 -158,6 +166,39 @@@@ apr_status_t h2_session_handle_response /* Get the h2_stream for the given stream idenrtifier. */ struct h2_stream *h2_session_get_stream(h2_session *session, int stream_id); --void h2_session_log_stats(h2_session *session); +++/** +++ * Create and register a new stream under the given id. +++ * +++ * @param session the session to register in +++ * @param stream_id the new stream identifier +++ * @return the new stream +++ */ +++struct h2_stream *h2_session_open_stream(h2_session *session, int stream_id); +++ +++/** +++ * Returns if client settings have push enabled. +++ * @param != 0 iff push is enabled in client settings +++ */ +++int h2_session_push_enabled(h2_session *session); +++ ++/** ++ * Destroy the stream and release it everywhere. Reclaim all resources. ++ * @param session the session to which the stream belongs ++ * @param stream the stream to destroy ++ */ ++apr_status_t h2_session_stream_destroy(h2_session *session, ++ struct h2_stream *stream); ++ +++/** +++ * Submit a push promise on the stream and schedule the new steam for +++ * processing.. +++ * +++ * @param session the session to work in +++ * @param is the stream initiating the push +++ * @param push the push to promise +++ * @return the new promised stream or NULL +++ */ +++struct h2_stream *h2_session_push(h2_session *session, +++ struct h2_stream *is, struct h2_push *push); + #endif /* defined(__mod_h2__h2_session__) */ diff --cc modules/http2/h2_stream.c index 5a5af6372b7,52781d8474f,52781d8474f..594175a7854 --- a/modules/http2/h2_stream.c +++ b/modules/http2/h2_stream.c @@@@ -25,10 -27,9 -27,9 +25,12 @@@@ #include "h2_private.h" #include "h2_conn.h" +++#include "h2_h2.h" #include "h2_mplx.h" +++#include "h2_push.h" #include "h2_request.h" #include "h2_response.h" ++#include "h2_session.h" #include "h2_stream.h" #include "h2_task.h" #include "h2_ctx.h" @@@@ -37,43 -38,48 -38,48 +39,143 @@@@ #include "h2_util.h" ---static void set_state(h2_stream *stream, h2_stream_state_t state) +++#define H2_STREAM_OUT(lvl,s,msg) \ +++ do { \ +++ if (APLOG_C_IS_LEVEL((s)->session->c,lvl)) \ +++ h2_util_bb_log((s)->session->c,(s)->id,lvl,msg,(s)->bbout); \ +++ } while(0) +++#define H2_STREAM_IN(lvl,s,msg) \ +++ do { \ +++ if (APLOG_C_IS_LEVEL((s)->session->c,lvl)) \ +++ h2_util_bb_log((s)->session->c,(s)->id,lvl,msg,(s)->bbin); \ +++ } while(0) +++ +++ +++static int state_transition[][7] = { +++ /* ID OP RL RR CI CO CL */ +++/*ID*/{ 1, 0, 0, 0, 0, 0, 0 }, +++/*OP*/{ 1, 1, 0, 0, 0, 0, 0 }, +++/*RL*/{ 0, 0, 1, 0, 0, 0, 0 }, +++/*RR*/{ 0, 0, 0, 1, 0, 0, 0 }, +++/*CI*/{ 1, 1, 0, 0, 1, 0, 0 }, +++/*CO*/{ 1, 1, 0, 0, 0, 1, 0 }, +++/*CL*/{ 1, 1, 0, 0, 1, 1, 1 }, +++}; +++ +++static int set_state(h2_stream *stream, h2_stream_state_t state) { --- AP_DEBUG_ASSERT(stream); --- if (stream->state != state) { +++ int allowed = state_transition[state][stream->state]; +++ if (allowed) { stream->state = state; +++ return 1; + } +++ +++ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c, +++ "h2_stream(%ld-%d): invalid state transition from %d to %d", +++ stream->session->id, stream->id, stream->state, state); +++ return 0; + } + --h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_mplx *m) +++static int close_input(h2_stream *stream) + { -- h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream)); -- if (stream != NULL) { -- stream->id = id; -- stream->state = H2_STREAM_ST_IDLE; -- stream->pool = pool; -- stream->m = m; -- stream->request = h2_request_create(id, pool, m->c->bucket_alloc); -- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, -- "h2_stream(%ld-%d): created", m->id, stream->id); +++ switch (stream->state) { +++ case H2_STREAM_ST_CLOSED_INPUT: +++ case H2_STREAM_ST_CLOSED: +++ return 0; /* ignore, idempotent */ +++ case H2_STREAM_ST_CLOSED_OUTPUT: +++ /* both closed now */ +++ set_state(stream, H2_STREAM_ST_CLOSED); +++ break; +++ default: +++ /* everything else we jump to here */ +++ set_state(stream, H2_STREAM_ST_CLOSED_INPUT); +++ break; + } -- return stream; +++ return 1; + } + --static void h2_stream_cleanup(h2_stream *stream) +++static int input_closed(h2_stream *stream) + { -- if (stream->request) { -- h2_request_destroy(stream->request); -- stream->request = NULL; +++ switch (stream->state) { +++ case H2_STREAM_ST_OPEN: +++ case H2_STREAM_ST_CLOSED_OUTPUT: +++ return 0; +++ default: +++ return 1; +++ } +++} +++ +++static int close_output(h2_stream *stream) +++{ +++ switch (stream->state) { +++ case H2_STREAM_ST_CLOSED_OUTPUT: +++ case H2_STREAM_ST_CLOSED: +++ return 0; /* ignore, idempotent */ +++ case H2_STREAM_ST_CLOSED_INPUT: +++ /* both closed now */ +++ set_state(stream, H2_STREAM_ST_CLOSED); +++ break; +++ default: +++ /* everything else we jump to here */ +++ set_state(stream, H2_STREAM_ST_CLOSED_OUTPUT); +++ break; +++ } +++ return 1; +++} +++ +++static int input_open(h2_stream *stream) +++{ +++ switch (stream->state) { +++ case H2_STREAM_ST_OPEN: +++ case H2_STREAM_ST_CLOSED_OUTPUT: +++ return 1; +++ default: +++ return 0; +++ } +++} +++ +++static int output_open(h2_stream *stream) +++{ +++ switch (stream->state) { +++ case H2_STREAM_ST_OPEN: +++ case H2_STREAM_ST_CLOSED_INPUT: +++ return 1; +++ default: +++ return 0; } } ++h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session) ++{ ++ h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream)); - if (stream != NULL) { - stream->id = id; - stream->state = H2_STREAM_ST_IDLE; - stream->pool = pool; - stream->session = session; - stream->bbout = apr_brigade_create(stream->pool, +++ stream->id = id; +++ stream->state = H2_STREAM_ST_IDLE; +++ stream->pool = pool; +++ stream->session = session; +++ return stream; +++} +++ +++h2_stream *h2_stream_open(int id, apr_pool_t *pool, h2_session *session) +++{ +++ h2_stream *stream = h2_stream_create(id, pool, session); +++ set_state(stream, H2_STREAM_ST_OPEN); +++ stream->request = h2_request_create(id, pool); +++ stream->bbout = apr_brigade_create(stream->pool, ++ stream->session->c->bucket_alloc); - stream->request = h2_request_create(id, pool, session->c->bucket_alloc); - - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - "h2_stream(%ld-%d): created", session->id, stream->id); - } +++ +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, +++ "h2_stream(%ld-%d): opened", session->id, stream->id); ++ return stream; ++} ++ apr_status_t h2_stream_destroy(h2_stream *stream) { AP_DEBUG_ASSERT(stream); -- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->m->c, -- "h2_stream(%ld-%d): destroy", stream->m->id, stream->id); -- h2_stream_cleanup(stream); --- - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c, - "h2_stream(%ld-%d): destroy", stream->session->id, stream->id); -- if (stream->task) { -- h2_task_destroy(stream->task); -- stream->task = NULL; ++ if (stream->request) { ++ h2_request_destroy(stream->request); ++ stream->request = NULL; } ++ if (stream->pool) { apr_pool_destroy(stream->pool); } @@@@ -93,256 -98,171 -98,171 +195,373 @@@@ apr_pool_t *h2_stream_detach_pool(h2_st return pool; } --void h2_stream_abort(h2_stream *stream) ++void h2_stream_rst(h2_stream *stream, int error_code) { -- AP_DEBUG_ASSERT(stream); -- stream->aborted = 1; ++ stream->rst_error = error_code; +++ close_input(stream); +++ close_output(stream); ++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c, ++ "h2_stream(%ld-%d): reset, error=%d", ++ stream->session->id, stream->id, error_code); } apr_status_t h2_stream_set_response(h2_stream *stream, h2_response *response, apr_bucket_brigade *bb) { ++ apr_status_t status = APR_SUCCESS; +++ if (!output_open(stream)) { +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c, +++ "h2_stream(%ld-%d): output closed", +++ stream->session->id, stream->id); +++ return APR_ECONNRESET; +++ } ++ stream->response = response; if (bb && !APR_BRIGADE_EMPTY(bb)) { -- if (!stream->bbout) { -- stream->bbout = apr_brigade_create(stream->pool, -- stream->m->c->bucket_alloc); -- } -- return h2_util_move(stream->bbout, bb, 16 * 1024, NULL, -- "h2_stream_set_response"); ++ int move_all = INT_MAX; ++ /* we can move file handles from h2_mplx into this h2_stream as many ++ * as we want, since the lifetimes are the same and we are not freeing ++ * the ones in h2_mplx->io before this stream is done. */ - status = h2_util_move(stream->bbout, bb, 16 * 1024, &move_all, +++ H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream set_response_pre"); +++ status = h2_util_move(stream->bbout, bb, -1, &move_all, ++ "h2_stream_set_response"); +++ H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream set_response_post"); } - if (APLOGctrace1(stream->session->c)) { - apr_size_t len = 0; - int eos = 0; - h2_util_bb_avail(stream->bbout, &len, &eos); - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c, - "h2_stream(%ld-%d): set_response(%s), len=%ld, eos=%d", - stream->session->id, stream->id, response->status, - (long)len, (int)eos); - } -- return APR_SUCCESS; +++ +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c, +++ "h2_stream(%ld-%d): set_response(%d)", +++ stream->session->id, stream->id, response->http_status); ++ return status; } ---static int set_closed(h2_stream *stream) - { - switch (stream->state) { - case H2_STREAM_ST_CLOSED_INPUT: - case H2_STREAM_ST_CLOSED: - return 0; /* ignore, idempotent */ - case H2_STREAM_ST_CLOSED_OUTPUT: - /* both closed now */ - set_state(stream, H2_STREAM_ST_CLOSED); - break; - default: - /* everything else we jump to here */ - set_state(stream, H2_STREAM_ST_CLOSED_INPUT); - break; - } - return 1; - } - - apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r) +++apr_status_t h2_stream_set_request(h2_stream *stream, request_rec *r) { -- switch (stream->state) { -- case H2_STREAM_ST_CLOSED_INPUT: -- case H2_STREAM_ST_CLOSED: -- return 0; /* ignore, idempotent */ -- case H2_STREAM_ST_CLOSED_OUTPUT: -- /* both closed now */ -- set_state(stream, H2_STREAM_ST_CLOSED); -- break; -- default: -- /* everything else we jump to here */ -- set_state(stream, H2_STREAM_ST_CLOSED_INPUT); -- break; ++ apr_status_t status; ++ AP_DEBUG_ASSERT(stream); ++ if (stream->rst_error) { ++ return APR_ECONNRESET; } -- return 1; ++ set_state(stream, H2_STREAM_ST_OPEN); - status = h2_request_rwrite(stream->request, r, stream->session->mplx); +++ status = h2_request_rwrite(stream->request, r); ++ return status; } --apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r) +++void h2_stream_set_h2_request(h2_stream *stream, int initiated_on, +++ const h2_request *req) +++{ +++ h2_request_copy(stream->pool, stream->request, req); +++ stream->initiated_on = initiated_on; +++ stream->request->eoh = 0; +++} +++ +++apr_status_t h2_stream_add_header(h2_stream *stream, +++ const char *name, size_t nlen, +++ const char *value, size_t vlen) + { -- apr_status_t status; + AP_DEBUG_ASSERT(stream); -- set_state(stream, H2_STREAM_ST_OPEN); -- status = h2_request_rwrite(stream->request, r, stream->m); -- return status; +++ if (h2_stream_is_scheduled(stream)) { +++ return h2_request_add_trailer(stream->request, stream->pool, +++ name, nlen, value, vlen); +++ } +++ else { +++ if (!input_open(stream)) { +++ return APR_ECONNRESET; +++ } +++ return h2_request_add_header(stream->request, stream->pool, +++ name, nlen, value, vlen); +++ } + } + --apr_status_t h2_stream_write_eoh(h2_stream *stream, int eos) ++apr_status_t h2_stream_schedule(h2_stream *stream, int eos, ++ h2_stream_pri_cmp *cmp, void *ctx) { apr_status_t status; AP_DEBUG_ASSERT(stream); +++ AP_DEBUG_ASSERT(stream->session); +++ AP_DEBUG_ASSERT(stream->session->mplx); ++ - if (stream->rst_error) { +++ if (!output_open(stream)) { ++ return APR_ECONNRESET; ++ } +++ if (stream->scheduled) { +++ return APR_EINVAL; +++ } +++ if (eos) { +++ close_input(stream); +++ } + /* Seeing the end-of-headers, we have everything we need to * start processing it. */ - status = h2_mplx_process(stream->session->mplx, stream->id, - stream->request, eos, cmp, ctx); - if (eos) { - set_closed(stream); -- status = h2_mplx_create_task(stream->m, stream); +++ status = h2_request_end_headers(stream->request, stream->pool, eos); + if (status == APR_SUCCESS) { -- status = h2_request_end_headers(stream->request, -- stream->m, stream->task, eos); -- if (status == APR_SUCCESS) { -- status = h2_mplx_do_task(stream->m, stream->task); +++ if (!eos) { +++ stream->bbin = apr_brigade_create(stream->pool, +++ stream->session->c->bucket_alloc); + } -- if (eos) { -- status = h2_stream_write_eos(stream); -- } -- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->m->c, -- "h2_mplx(%ld-%d): start stream, task %s %s (%s)", -- stream->m->id, stream->id, -- stream->request->method, stream->request->path, -- stream->request->authority); +++ stream->input_remaining = stream->request->content_length; + +++ status = h2_mplx_process(stream->session->mplx, stream->id, +++ stream->request, eos, cmp, ctx); +++ stream->scheduled = 1; +++ } +++ else { +++ h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR); } +++ ++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c, - "h2_mplx(%ld-%d): start stream, task %s %s (%s)", +++ "h2_stream(%ld-%d): scheduled %s %s://%s%s", ++ stream->session->id, stream->id, - stream->request->method, stream->request->path, - stream->request->authority); +++ stream->request->method, stream->request->scheme, +++ stream->request->authority, stream->request->path); ++ return status; } ---apr_status_t h2_stream_write_eos(h2_stream *stream) +++int h2_stream_is_scheduled(h2_stream *stream) { --- AP_DEBUG_ASSERT(stream); - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c, -- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->m->c, --- "h2_stream(%ld-%d): closing input", - stream->session->id, stream->id); - if (stream->rst_error) { - return APR_ECONNRESET; -- stream->m->id, stream->id); -- if (set_closed(stream)) { -- return h2_request_close(stream->request); +++ return stream->scheduled; +++} +++ +++static apr_status_t h2_stream_input_flush(h2_stream *stream) +++{ +++ apr_status_t status = APR_SUCCESS; +++ if (stream->bbin && !APR_BRIGADE_EMPTY(stream->bbin)) { +++ +++ status = h2_mplx_in_write(stream->session->mplx, stream->id, stream->bbin); +++ if (status != APR_SUCCESS) { +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->mplx->c, +++ "h2_stream(%ld-%d): flushing input data", +++ stream->session->id, stream->id); +++ } + } -- return APR_SUCCESS; +++ return status; +++} +++ +++static apr_status_t input_flush(apr_bucket_brigade *bb, void *ctx) +++{ +++ (void)bb; +++ return h2_stream_input_flush(ctx); + } + --apr_status_t h2_stream_write_header(h2_stream *stream, -- const char *name, size_t nlen, -- const char *value, size_t vlen) +++static apr_status_t input_add_data(h2_stream *stream, +++ const char *data, size_t len, int chunked) + { +++ apr_status_t status = APR_SUCCESS; +++ +++ if (chunked) { +++ status = apr_brigade_printf(stream->bbin, input_flush, stream, +++ "%lx\r\n", (unsigned long)len); +++ if (status == APR_SUCCESS) { +++ status = apr_brigade_write(stream->bbin, input_flush, stream, data, len); +++ if (status == APR_SUCCESS) { +++ status = apr_brigade_puts(stream->bbin, input_flush, stream, "\r\n"); +++ } +++ } ++ } - if (set_closed(stream)) { - return h2_request_close(stream->request); +++ else { +++ status = apr_brigade_write(stream->bbin, input_flush, stream, data, len); ++ } - return APR_SUCCESS; +++ return status; ++} ++ - apr_status_t h2_stream_write_header(h2_stream *stream, - const char *name, size_t nlen, - const char *value, size_t vlen) +++apr_status_t h2_stream_close_input(h2_stream *stream) ++{ +++ apr_status_t status = APR_SUCCESS; +++ AP_DEBUG_ASSERT(stream); -- switch (stream->state) { -- case H2_STREAM_ST_IDLE: -- set_state(stream, H2_STREAM_ST_OPEN); -- break; -- case H2_STREAM_ST_OPEN: -- break; -- default: -- return APR_EINVAL; +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c, +++ "h2_stream(%ld-%d): closing input", +++ stream->session->id, stream->id); +++ ++ if (stream->rst_error) { ++ return APR_ECONNRESET; } - switch (stream->state) { - case H2_STREAM_ST_IDLE: - set_state(stream, H2_STREAM_ST_OPEN); - break; - case H2_STREAM_ST_OPEN: - break; - default: - return APR_EINVAL; -- return h2_request_write_header(stream->request, name, nlen, -- value, vlen, stream->m); +++ +++ H2_STREAM_IN(APLOG_TRACE2, stream, "close_pre"); +++ if (close_input(stream) && stream->bbin) { +++ if (stream->request->chunked) { +++ status = input_add_data(stream, "0\r\n\r\n", 5, 0); +++ } +++ +++ if (status == APR_SUCCESS) { +++ status = h2_stream_input_flush(stream); +++ } +++ if (status == APR_SUCCESS) { +++ status = h2_mplx_in_close(stream->session->mplx, stream->id); +++ } ++ } - return h2_request_write_header(stream->request, name, nlen, - value, vlen, stream->session->mplx); +++ H2_STREAM_IN(APLOG_TRACE2, stream, "close_post"); +++ return status; } apr_status_t h2_stream_write_data(h2_stream *stream, const char *data, size_t len) { +++ apr_status_t status = APR_SUCCESS; +++ AP_DEBUG_ASSERT(stream); - if (stream->rst_error) { - return APR_ECONNRESET; -- AP_DEBUG_ASSERT(stream); -- switch (stream->state) { -- case H2_STREAM_ST_OPEN: -- break; -- default: -- return APR_EINVAL; +++ if (input_closed(stream) || !stream->request->eoh || !stream->bbin) { +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c, +++ "h2_stream(%ld-%d): writing denied, closed=%d, eoh=%d, bbin=%d", +++ stream->session->id, stream->id, input_closed(stream), +++ stream->request->eoh, !!stream->bbin); +++ return APR_EINVAL; ++ } - switch (stream->state) { - case H2_STREAM_ST_OPEN: - break; - default: - return APR_EINVAL; +++ +++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c, +++ "h2_stream(%ld-%d): add %ld input bytes", +++ stream->session->id, stream->id, (long)len); +++ +++ H2_STREAM_IN(APLOG_TRACE2, stream, "write_data_pre"); +++ if (stream->request->chunked) { +++ /* if input may have a body and we have not seen any +++ * content-length header, we need to chunk the input data. +++ */ +++ status = input_add_data(stream, data, len, 1); +++ } +++ else { +++ stream->input_remaining -= len; +++ if (stream->input_remaining < 0) { +++ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c, +++ APLOGNO(02961) +++ "h2_stream(%ld-%d): got %ld more content bytes than announced " +++ "in content-length header: %ld", +++ stream->session->id, stream->id, +++ (long)stream->request->content_length, +++ -(long)stream->input_remaining); +++ h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR); +++ return APR_ECONNABORTED; +++ } +++ status = input_add_data(stream, data, len, 0); + } -- return h2_request_write_data(stream->request, data, len); +++ if (status == APR_SUCCESS) { +++ status = h2_stream_input_flush(stream); ++ } - return h2_request_write_data(stream->request, data, len); +++ H2_STREAM_IN(APLOG_TRACE2, stream, "write_data_post"); +++ return status; } apr_status_t h2_stream_prep_read(h2_stream *stream, --- apr_size_t *plen, int *peos) +++ apr_off_t *plen, int *peos) { apr_status_t status = APR_SUCCESS; const char *src; +++ int test_read = (*plen == 0); -- if (stream->bbout && !APR_BRIGADE_EMPTY(stream->bbout)) { ++ if (stream->rst_error) { ++ return APR_ECONNRESET; ++ } ++ +++ H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream prep_read_pre"); ++ if (!APR_BRIGADE_EMPTY(stream->bbout)) { src = "stream"; status = h2_util_bb_avail(stream->bbout, plen, peos); --- if (status == APR_SUCCESS && !*peos && !*plen) { +++ if (!test_read && status == APR_SUCCESS && !*peos && !*plen) { apr_brigade_cleanup(stream->bbout); return h2_stream_prep_read(stream, plen, peos); } } else { src = "mplx"; -- status = h2_mplx_out_readx(stream->m, stream->id, ++ status = h2_mplx_out_readx(stream->session->mplx, stream->id, NULL, NULL, plen, peos); } --- if (status == APR_SUCCESS && !*peos && !*plen) { +++ if (!test_read && status == APR_SUCCESS && !*peos && !*plen) { status = APR_EAGAIN; } -- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->m->c, +++ H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream prep_read_post"); ++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c, "h2_stream(%ld-%d): prep_read %s, len=%ld eos=%d", -- stream->m->id, stream->id, -- src, (long)*plen, *peos); ++ stream->session->id, stream->id, src, (long)*plen, *peos); return status; } apr_status_t h2_stream_readx(h2_stream *stream, h2_io_data_cb *cb, void *ctx, --- apr_size_t *plen, int *peos) +++ apr_off_t *plen, int *peos) { -- if (stream->bbout && !APR_BRIGADE_EMPTY(stream->bbout)) { -- return h2_util_bb_readx(stream->bbout, cb, ctx, plen, peos); ++ apr_status_t status = APR_SUCCESS; ++ const char *src; ++ +++ H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream readx_pre"); ++ if (stream->rst_error) { ++ return APR_ECONNRESET; ++ } ++ *peos = 0; ++ if (!APR_BRIGADE_EMPTY(stream->bbout)) { - apr_size_t origlen = *plen; +++ apr_off_t origlen = *plen; ++ ++ src = "stream"; ++ status = h2_util_bb_readx(stream->bbout, cb, ctx, plen, peos); ++ if (status == APR_SUCCESS && !*peos && !*plen) { ++ apr_brigade_cleanup(stream->bbout); ++ *plen = origlen; ++ return h2_stream_readx(stream, cb, ctx, plen, peos); ++ } ++ } ++ else { ++ src = "mplx"; ++ status = h2_mplx_out_readx(stream->session->mplx, stream->id, ++ cb, ctx, plen, peos); ++ } ++ ++ if (status == APR_SUCCESS && !*peos && !*plen) { ++ status = APR_EAGAIN; } -- return h2_mplx_out_readx(stream->m, stream->id, -- cb, ctx, plen, peos); ++ +++ H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream prep_readx_post"); ++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c, ++ "h2_stream(%ld-%d): readx %s, len=%ld eos=%d", ++ stream->session->id, stream->id, src, (long)*plen, *peos); +++ H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream readx_post"); +++ ++ return status; } ++apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb, - apr_size_t *plen, int *peos) +++ apr_off_t *plen, int *peos) ++{ ++ apr_status_t status = APR_SUCCESS; ++ +++ H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream read_to_pre"); ++ if (stream->rst_error) { ++ return APR_ECONNRESET; ++ } ++ ++ if (APR_BRIGADE_EMPTY(stream->bbout)) { - apr_size_t tlen = *plen; +++ apr_off_t tlen = *plen; ++ int eos; ++ status = h2_mplx_out_read_to(stream->session->mplx, stream->id, ++ stream->bbout, &tlen, &eos); ++ } ++ ++ if (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(stream->bbout)) { ++ status = h2_transfer_brigade(bb, stream->bbout, stream->pool, ++ plen, peos); ++ } ++ else { ++ *plen = 0; ++ *peos = 0; ++ } ++ ++ if (status == APR_SUCCESS && !*peos && !*plen) { ++ status = APR_EAGAIN; ++ } +++ H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream read_to_post"); ++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c, ++ "h2_stream(%ld-%d): read_to, len=%ld eos=%d", ++ stream->session->id, stream->id, (long)*plen, *peos); ++ return status; ++} void h2_stream_set_suspended(h2_stream *stream, int suspended) { AP_DEBUG_ASSERT(stream); stream->suspended = !!suspended; +++ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c, +++ "h2_stream(%ld-%d): suspended=%d", +++ stream->session->id, stream->id, stream->suspended); } int h2_stream_is_suspended(h2_stream *stream) @@@@ -351,3 -271,3 -271,3 +570,43 @@@@ return stream->suspended; } +++int h2_stream_input_is_open(h2_stream *stream) +++{ +++ return input_open(stream); +++} +++ +++int h2_stream_needs_submit(h2_stream *stream) +++{ +++ switch (stream->state) { +++ case H2_STREAM_ST_OPEN: +++ case H2_STREAM_ST_CLOSED_INPUT: +++ case H2_STREAM_ST_CLOSED_OUTPUT: +++ case H2_STREAM_ST_CLOSED: +++ return !stream->submitted; +++ default: +++ return 0; +++ } +++} +++ +++apr_status_t h2_stream_submit_pushes(h2_stream *stream) +++{ +++ apr_status_t status = APR_SUCCESS; +++ apr_array_header_t *pushes; +++ int i; +++ +++ pushes = h2_push_collect(stream->pool, stream->request, stream->response); +++ if (pushes && !apr_is_empty_array(pushes)) { +++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c, +++ "h2_stream(%ld-%d): found %d push candidates", +++ stream->session->id, stream->id, pushes->nelts); +++ for (i = 0; i < pushes->nelts; ++i) { +++ h2_push *push = APR_ARRAY_IDX(pushes, i, h2_push*); +++ h2_stream *s = h2_session_push(stream->session, stream, push); +++ if (!s) { +++ status = APR_ECONNRESET; +++ break; +++ } +++ } +++ } +++ return status; +++} diff --cc modules/http2/h2_stream.h index d09d848a66a,0608f2f3400,0608f2f3400..e5990a2bf38 --- a/modules/http2/h2_stream.h +++ b/modules/http2/h2_stream.h @@@@ -19,19 -19,19 -19,19 +19,14 @@@@ /** * A HTTP/2 stream, e.g. a client request+response in HTTP/1.1 terms. * --- * Ok, not quite, but close enough, since we do not implement server --- * pushes yet. --- * * A stream always belongs to a h2_session, the one managing the * connection to the client. The h2_session writes to the h2_stream, * adding HEADERS and DATA and finally an EOS. When headers are done, --- * h2_stream can create a h2_task that can be scheduled to fullfill the --- * request. +++ * h2_stream is scheduled for handling, which is expected to produce +++ * a h2_response. * --- * This response headers are added directly to the h2_mplx of the session, --- * but the response DATA can be read via h2_stream. Reading data will --- * never block but return APR_EAGAIN when there currently is no data (and --- * no eos) in the multiplexer for this stream. +++ * The h2_response gives the HEADER frames to sent to the client, followed +++ * by DATA frames read from the h2_stream until EOS is reached. */ #include "h2_io.h" @@@@ -55,64 -54,54 -54,54 +50,244 @@@@ typedef struct h2_stream h2_stream struct h2_stream { int id; /* http2 stream id */ +++ int initiated_on; /* http2 stream id this was initiated on or 0 */ h2_stream_state_t state; /* http/2 state of this stream */ -- struct h2_mplx *m; /* the multiplexer to work with */ ++ struct h2_session *session; /* the session this stream belongs to */ ++ +++ apr_pool_t *pool; /* the memory pool for this stream */ +++ struct h2_request *request; /* the request made in this stream */ +++ struct h2_response *response; /* the response, once ready */ + int aborted; /* was aborted */ int suspended; /* DATA sending has been suspended */ ++ int rst_error; /* stream error for RST_STREAM */ +++ int scheduled; /* stream has been scheduled */ +++ int submitted; /* response HEADER has been sent */ --- apr_pool_t *pool; /* the memory pool for this stream */ --- struct h2_request *request; /* the request made in this stream */ - - struct h2_response *response; /* the response, once ready */ +++ apr_off_t input_remaining; /* remaining bytes on input as advertised via content-length */ +++ apr_bucket_brigade *bbin; /* input DATA */ -- struct h2_task *task; /* task created for this stream */ -- struct h2_response *response; /* the response, once ready */ apr_bucket_brigade *bbout; /* output DATA */ - apr_size_t data_frames_sent;/* # of DATA frames sent out for this stream */ +++ apr_off_t data_frames_sent; /* # of DATA frames sent out for this stream */ }; --h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_mplx *m); ++#define H2_STREAM_RST(s, def) (s->rst_error? s->rst_error : (def)) +++/** +++ * Create a stream in IDLE state. +++ * @param id the stream identifier +++ * @param pool the memory pool to use for this stream +++ * @param session the session this stream belongs to +++ * @return the newly created IDLE stream +++ */ ++h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_session *session); ++ +++/** +++ * Create a stream in OPEN state. +++ * @param id the stream identifier +++ * @param pool the memory pool to use for this stream +++ * @param session the session this stream belongs to +++ * @return the newly opened stream +++ */ +++h2_stream *h2_stream_open(int id, apr_pool_t *pool, struct h2_session *session); +++ +++/** +++ * Destroy any resources held by this stream. Will destroy memory pool +++ * if still owned by the stream. +++ * +++ * @param stream the stream to destroy +++ */ apr_status_t h2_stream_destroy(h2_stream *stream); +++/** +++ * Removes stream from h2_session and destroys it. +++ * +++ * @param stream the stream to cleanup +++ */ ++void h2_stream_cleanup(h2_stream *stream); ++ - void h2_stream_rst(h2_stream *streamm, int error_code); - +++/** +++ * Detach the memory pool from the stream. Will prevent stream +++ * destruction to take the pool with it. +++ * +++ * @param stream the stream to detach the pool from +++ * @param the detached memmory pool or NULL if stream no longer has one +++ */ apr_pool_t *h2_stream_detach_pool(h2_stream *stream); --void h2_stream_attach_pool(h2_stream *stream, apr_pool_t *pool); - apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r); --void h2_stream_abort(h2_stream *stream); - apr_status_t h2_stream_write_eos(h2_stream *stream); --apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r); +++/** +++ * Initialize stream->request with the given request_rec. +++ * +++ * @param stream stream to write request to +++ * @param r the request with all the meta data +++ */ +++apr_status_t h2_stream_set_request(h2_stream *stream, request_rec *r); - apr_status_t h2_stream_write_header(h2_stream *stream, - const char *name, size_t nlen, - const char *value, size_t vlen); --apr_status_t h2_stream_write_eos(h2_stream *stream); +++/** +++ * Initialize stream->request with the given h2_request. +++ * +++ * @param stream the stream to init the request for +++ * @param req the request for initializing, will be copied +++ */ +++void h2_stream_set_h2_request(h2_stream *stream, int initiated_on, +++ const struct h2_request *req); - apr_status_t h2_stream_schedule(h2_stream *stream, int eos, - h2_stream_pri_cmp *cmp, void *ctx); --apr_status_t h2_stream_write_header(h2_stream *stream, -- const char *name, size_t nlen, -- const char *value, size_t vlen); +++/* +++ * Add a HTTP/2 header (including pseudo headers) or trailer +++ * to the given stream, depending on stream state. +++ * +++ * @param stream stream to write the header to +++ * @param name the name of the HTTP/2 header +++ * @param nlen the number of characters in name +++ * @param value the header value +++ * @param vlen the number of characters in value +++ */ +++apr_status_t h2_stream_add_header(h2_stream *stream, +++ const char *name, size_t nlen, +++ const char *value, size_t vlen); + --apr_status_t h2_stream_write_eoh(h2_stream *stream, int eos); +++/** +++ * Closes the stream's input. +++ * +++ * @param stream stream to close intput of +++ */ +++apr_status_t h2_stream_close_input(h2_stream *stream); +++/* +++ * Write a chunk of DATA to the stream. +++ * +++ * @param stream stream to write the data to +++ * @param data the beginning of the bytes to write +++ * @param len the number of bytes to write +++ */ apr_status_t h2_stream_write_data(h2_stream *stream, const char *data, size_t len); +++/** +++ * Reset the stream. Stream write/reads will return errors afterwards. +++ * +++ * @param stream the stream to reset +++ * @param error_code the HTTP/2 error code +++ */ +++void h2_stream_rst(h2_stream *streamm, int error_code); +++ +++/** +++ * Schedule the stream for execution. All header information must be +++ * present. Use the given priority comparision callback to determine +++ * order in queued streams. +++ * +++ * @param stream the stream to schedule +++ * @param eos != 0 iff no more input will arrive +++ * @param cmp priority comparision +++ * @param ctx context for comparision +++ */ +++apr_status_t h2_stream_schedule(h2_stream *stream, int eos, +++ h2_stream_pri_cmp *cmp, void *ctx); +++ +++/** +++ * Determine if stream has been scheduled already. +++ * @param stream the stream to check on +++ * @return != 0 iff stream has been scheduled +++ */ +++int h2_stream_is_scheduled(h2_stream *stream); +++ +++/** +++ * Set the response for this stream. Invoked when all meta data for +++ * the stream response has been collected. +++ * +++ * @param stream the stream to set the response for +++ * @param resonse the response data for the stream +++ * @param bb bucket brigade with output data for the stream. Optional, +++ * may be incomplete. +++ */ apr_status_t h2_stream_set_response(h2_stream *stream, struct h2_response *response, apr_bucket_brigade *bb); +++/** +++ * Do a speculative read on the stream output to determine the +++ * amount of data that can be read. +++ * +++ * @param stream the stream to speculatively read from +++ * @param plen (in-/out) number of bytes requested and on return amount of bytes that +++ * may be read without blocking +++ * @param peos (out) != 0 iff end of stream will be reached when reading plen +++ * bytes (out value). +++ * @return APR_SUCCESS if out information was computed successfully. +++ * APR_EAGAIN if not data is available and end of stream has not been +++ * reached yet. +++ */ apr_status_t h2_stream_prep_read(h2_stream *stream, --- apr_size_t *plen, int *peos); +++ apr_off_t *plen, int *peos); +++/** +++ * Read data from the stream output. +++ * +++ * @param stream the stream to read from +++ * @param cb callback to invoke for byte chunks read. Might be invoked +++ * multiple times (with different values) for one read operation. +++ * @param ctx context data for callback +++ * @param plen (in-/out) max. number of bytes to read and on return actual +++ * number of bytes read +++ * @param peos (out) != 0 iff end of stream has been reached while reading +++ * @return APR_SUCCESS if out information was computed successfully. +++ * APR_EAGAIN if not data is available and end of stream has not been +++ * reached yet. +++ */ apr_status_t h2_stream_readx(h2_stream *stream, h2_io_data_cb *cb, --- void *ctx, apr_size_t *plen, int *peos); +++ void *ctx, apr_off_t *plen, int *peos); +++/** +++ * Read a maximum number of bytes into the bucket brigade. +++ * +++ * @param stream the stream to read from +++ * @param bb the brigade to append output to +++ * @param plen (in-/out) max. number of bytes to append and on return actual +++ * number of bytes appended to brigade +++ * @param peos (out) != 0 iff end of stream has been reached while reading +++ * @return APR_SUCCESS if out information was computed successfully. +++ * APR_EAGAIN if not data is available and end of stream has not been +++ * reached yet. +++ */ ++apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb, - apr_size_t *plen, int *peos); - +++ apr_off_t *plen, int *peos); ++ +++/** +++ * Set the suspended state of the stream. +++ * @param stream the stream to change state on +++ * @param suspended boolean value if stream is suspended +++ */ void h2_stream_set_suspended(h2_stream *stream, int suspended); +++ +++/** +++ * Check if the stream has been suspended. +++ * @param stream the stream to check +++ * @return != 0 iff stream is suspended. +++ */ int h2_stream_is_suspended(h2_stream *stream); +++/** +++ * Check if the stream has open input. +++ * @param stream the stream to check +++ * @return != 0 iff stream has open input. +++ */ +++int h2_stream_input_is_open(h2_stream *stream); +++ +++/** +++ * Check if the stream has not submitted a response or RST yet. +++ * @param stream the stream to check +++ * @return != 0 iff stream has not submitted a response or RST. +++ */ +++int h2_stream_needs_submit(h2_stream *stream); +++ +++/** +++ * Submit any server push promises on this stream and schedule +++ * the tasks connection with these. +++ * +++ * @param stream the stream for which to submit +++ */ +++apr_status_t h2_stream_submit_pushes(h2_stream *stream); +++ #endif /* defined(__mod_h2__h2_stream__) */ diff --cc modules/http2/h2_stream_set.c index 5ef48a13e17,dddd2e39908,dddd2e39908..aa0f8c65019 --- a/modules/http2/h2_stream_set.c +++ b/modules/http2/h2_stream_set.c @@@@ -16,34 -16,34 -16,34 +16,31 @@@@ #include #include +++#include #include #include ---#include ---#include #include #include "h2_private.h" ---#include "h2_session.h" #include "h2_stream.h" ---#include "h2_task.h" #include "h2_stream_set.h" ---#define H2_STREAM_IDX(list, i) ((h2_stream**)(list)->elts)[i] struct h2_stream_set { --- apr_array_header_t *list; +++ apr_hash_t *hash; }; ---h2_stream_set *h2_stream_set_create(apr_pool_t *pool) +++static unsigned int stream_hash(const char *key, apr_ssize_t *klen) +++{ +++ return (unsigned int)(*((int*)key)); +++} +++ +++h2_stream_set *h2_stream_set_create(apr_pool_t *pool, int max) { h2_stream_set *sp = apr_pcalloc(pool, sizeof(h2_stream_set)); --- if (sp) { --- sp->list = apr_array_make(pool, 100, sizeof(h2_stream*)); --- if (!sp->list) { --- return NULL; --- } --- } +++ sp->hash = apr_hash_make_custom(pool, stream_hash); +++ return sp; } @@@@ -52,111 -52,113 -52,113 +49,97 @@@@ void h2_stream_set_destroy(h2_stream_se (void)sp; } ---static int h2_stream_id_cmp(const void *s1, const void *s2) +++h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id) { - return (*((h2_stream **)s1))->id - (*((h2_stream **)s2))->id; -- h2_stream **pstream1 = (h2_stream **)s1; -- h2_stream **pstream2 = (h2_stream **)s2; -- return (*pstream1)->id - (*pstream2)->id; +++ return apr_hash_get(sp->hash, &stream_id, sizeof(stream_id)); } ---h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id) +++void h2_stream_set_add(h2_stream_set *sp, h2_stream *stream) { --- /* we keep the array sorted by id, so lookup can be done --- * by bsearch. --- */ --- h2_stream key; --- h2_stream *pkey, **ps; --- memset(&key, 0, sizeof(key)); --- key.id = stream_id; --- pkey = &key; --- ps = bsearch(&pkey, sp->list->elts, sp->list->nelts, --- sp->list->elt_size, h2_stream_id_cmp); --- return ps? *ps : NULL; ---} --- ---static void h2_stream_set_sort(h2_stream_set *sp) ---{ --- qsort(sp->list->elts, sp->list->nelts, sp->list->elt_size, --- h2_stream_id_cmp); ---} --- ---apr_status_t h2_stream_set_add(h2_stream_set *sp, h2_stream *stream) ---{ --- h2_stream *existing = h2_stream_set_get(sp, stream->id); --- if (!existing) { --- int last; --- APR_ARRAY_PUSH(sp->list, h2_stream*) = stream; --- /* Normally, streams get added in ascending order if id. We --- * keep the array sorted, so we just need to check of the newly --- * appended stream has a lower id than the last one. if not, --- * sorting is not necessary. --- */ --- last = sp->list->nelts - 1; --- if (last > 0 --- && (H2_STREAM_IDX(sp->list, last)->id --- < H2_STREAM_IDX(sp->list, last-1)->id)) { --- h2_stream_set_sort(sp); --- } --- } --- return APR_SUCCESS; ---} --- - h2_stream *h2_stream_set_remove(h2_stream_set *sp, int stream_id) --h2_stream *h2_stream_set_remove(h2_stream_set *sp, h2_stream *stream) ---{ --- int i; --- for (i = 0; i < sp->list->nelts; ++i) { --- h2_stream *s = H2_STREAM_IDX(sp->list, i); - if (s->id == stream_id) { -- if (s == stream) { --- int n; --- --sp->list->nelts; --- n = sp->list->nelts - i; --- if (n > 0) { --- /* Close the hole in the array by moving the upper --- * parts down one step. --- */ --- h2_stream **selts = (h2_stream**)sp->list->elts; --- memmove(selts+i, selts+i+1, n * sizeof(h2_stream*)); --- } --- return s; --- } --- } --- return NULL; +++ apr_hash_set(sp->hash, &stream->id, sizeof(stream->id), stream); } ---void h2_stream_set_remove_all(h2_stream_set *sp) +++void h2_stream_set_remove(h2_stream_set *sp, int stream_id) { --- sp->list->nelts = 0; +++ apr_hash_set(sp->hash, &stream_id, sizeof(stream_id), NULL); } int h2_stream_set_is_empty(h2_stream_set *sp) { --- AP_DEBUG_ASSERT(sp); --- return sp->list->nelts == 0; +++ return apr_hash_count(sp->hash) == 0; } ---h2_stream *h2_stream_set_find(h2_stream_set *sp, --- h2_stream_set_match_fn *match, void *ctx) +++apr_size_t h2_stream_set_size(h2_stream_set *sp) { --- h2_stream *s = NULL; --- int i; --- for (i = 0; !s && i < sp->list->nelts; ++i) { --- s = match(ctx, H2_STREAM_IDX(sp->list, i)); --- } --- return s; +++ return apr_hash_count(sp->hash); +++} +++ +++typedef struct { +++ h2_stream_set_iter_fn *iter; +++ void *ctx; +++} iter_ctx; +++ +++static int hash_iter(void *ctx, const void *key, apr_ssize_t klen, +++ const void *val) +++{ +++ iter_ctx *ictx = ctx; +++ return ictx->iter(ictx->ctx, (h2_stream*)val); } void h2_stream_set_iter(h2_stream_set *sp, h2_stream_set_iter_fn *iter, void *ctx) { --- int i; --- for (i = 0; i < sp->list->nelts; ++i) { --- h2_stream *s = H2_STREAM_IDX(sp->list, i); --- if (!iter(ctx, s)) { --- break; --- } +++ iter_ctx ictx; +++ ictx.iter = iter; +++ ictx.ctx = ctx; +++ apr_hash_do(hash_iter, &ictx, sp->hash); +++} +++ +++static int unsubmitted_iter(void *ctx, h2_stream *stream) +++{ +++ if (h2_stream_needs_submit(stream)) { +++ *((int *)ctx) = 1; +++ return 0; } +++ return 1; } ---apr_size_t h2_stream_set_size(h2_stream_set *sp) +++int h2_stream_set_has_unsubmitted(h2_stream_set *sp) +++{ +++ int has_unsubmitted = 0; +++ h2_stream_set_iter(sp, unsubmitted_iter, &has_unsubmitted); +++ return has_unsubmitted; +++} +++ +++static int input_open_iter(void *ctx, h2_stream *stream) +++{ +++ if (h2_stream_input_is_open(stream)) { +++ *((int *)ctx) = 1; +++ return 0; +++ } +++ return 1; +++} +++ +++int h2_stream_set_has_open_input(h2_stream_set *sp) +++{ +++ int has_input_open = 0; +++ h2_stream_set_iter(sp, input_open_iter, &has_input_open); +++ return has_input_open; +++} +++ +++static int suspended_iter(void *ctx, h2_stream *stream) +++{ +++ if (h2_stream_is_suspended(stream)) { +++ *((int *)ctx) = 1; +++ return 0; +++ } +++ return 1; +++} +++ +++int h2_stream_set_has_suspended(h2_stream_set *sp) { --- return sp->list->nelts; +++ int has_suspended = 0; +++ h2_stream_set_iter(sp, suspended_iter, &has_suspended); +++ return has_suspended; } diff --cc modules/http2/h2_stream_set.h index 8bc6409223b,56075834555,56075834555..d0041c48432 --- a/modules/http2/h2_stream_set.h +++ b/modules/http2/h2_stream_set.h @@@@ -27,26 -27,26 -27,26 +27,25 @@@@ typedef int h2_stream_set_iter_fn(void typedef struct h2_stream_set h2_stream_set; ---h2_stream_set *h2_stream_set_create(apr_pool_t *pool); +++h2_stream_set *h2_stream_set_create(apr_pool_t *pool, int max); void h2_stream_set_destroy(h2_stream_set *sp); ---apr_status_t h2_stream_set_add(h2_stream_set *sp, h2_stream *stream); +++void h2_stream_set_add(h2_stream_set *sp, h2_stream *stream); h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id); - h2_stream *h2_stream_set_remove(h2_stream_set *sp, int stream_id); --h2_stream *h2_stream_set_remove(h2_stream_set *sp,h2_stream *stream); +++void h2_stream_set_remove(h2_stream_set *sp, int stream_id); ---void h2_stream_set_remove_all(h2_stream_set *sp); +++void h2_stream_set_iter(h2_stream_set *sp, +++ h2_stream_set_iter_fn *iter, void *ctx); int h2_stream_set_is_empty(h2_stream_set *sp); apr_size_t h2_stream_set_size(h2_stream_set *sp); ---h2_stream *h2_stream_set_find(h2_stream_set *sp, --- h2_stream_set_match_fn *match, void *ctx); --- ---void h2_stream_set_iter(h2_stream_set *sp, --- h2_stream_set_iter_fn *iter, void *ctx); +++int h2_stream_set_has_unsubmitted(h2_stream_set *sp); +++int h2_stream_set_has_open_input(h2_stream_set *sp); +++int h2_stream_set_has_suspended(h2_stream_set *sp); #endif /* defined(__mod_h2__h2_stream_set__) */ diff --cc modules/http2/h2_switch.c index 97f07f891d7,23c34490eaa,23c34490eaa..c107db8e73a --- a/modules/http2/h2_switch.c +++ b/modules/http2/h2_switch.c @@@@ -154,7 -154,7 -154,7 +154,7 @@@@ static int h2_protocol_switch(conn_rec ap_remove_input_filter_byhandle(r->input_filters, "reqtimeout"); /* Ok, start an h2_conn on this one. */ --- status = h2_conn_rprocess(r); +++ status = h2_conn_process(r->connection, r); if (status != DONE) { /* Nothing really to do about this. */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, diff --cc modules/http2/h2_task.c index ee78f4347e1,bbea7b20f8a,bbea7b20f8a..b7d48a1bf6f --- a/modules/http2/h2_task.c +++ b/modules/http2/h2_task.c @@@@ -38,6 -38,6 -38,6 +38,7 @@@@ #include "h2_from_h1.h" #include "h2_h2.h" #include "h2_mplx.h" +++#include "h2_request.h" #include "h2_session.h" #include "h2_stream.h" #include "h2_task_input.h" @@@@ -152,46 -156,48 -156,48 +153,30 @@@@ static int h2_task_process_conn(conn_re } ---h2_task *h2_task_create(long session_id, --- int stream_id, --- apr_pool_t *stream_pool, --- h2_mplx *mplx, conn_rec *c) +++h2_task *h2_task_create(long session_id, const h2_request *req, +++ apr_pool_t *pool, h2_mplx *mplx, int eos) { --- h2_task *task = apr_pcalloc(stream_pool, sizeof(h2_task)); +++ h2_task *task = apr_pcalloc(pool, sizeof(h2_task)); if (task == NULL) { --- ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, stream_pool, +++ ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, pool, APLOGNO(02941) "h2_task(%ld-%d): create stream task", --- session_id, stream_id); --- h2_mplx_out_close(mplx, stream_id); +++ session_id, req->id); +++ h2_mplx_out_close(mplx, req->id); return NULL; } -- APR_RING_ELEM_INIT(task, link); -- --- task->id = apr_psprintf(stream_pool, "%ld-%d", session_id, stream_id); --- task->stream_id = stream_id; +++ task->id = apr_psprintf(pool, "%ld-%d", session_id, req->id); +++ task->stream_id = req->id; +++ task->pool = pool; task->mplx = mplx; +++ task->c = h2_conn_create(mplx->c, task->pool); +++ +++ task->request = req; +++ task->input_eos = eos; --- task->c = c; --- --- ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, stream_pool, --- "h2_task(%s): created", task->id); return task; } ---void h2_task_set_request(h2_task *task, --- const char *method, --- const char *scheme, --- const char *authority, --- const char *path, --- apr_table_t *headers, int eos) ---{ --- task->method = method; --- task->scheme = scheme; --- task->authority = authority; --- task->path = path; --- task->headers = headers; --- task->input_eos = eos; ---} --- apr_status_t h2_task_destroy(h2_task *task) { (void)task; @@@@ -205,63 -211,83 -211,83 +190,46 @@@@ apr_status_t h2_task_do(h2_task *task, AP_DEBUG_ASSERT(task); -- memset(&env, 0, sizeof(env)); -- -- env.id = task->id; -- env.stream_id = task->stream_id; -- env.mplx = task->mplx; -- task->mplx = NULL; -- -- env.input_eos = task->input_eos; -- env.serialize_headers = h2_config_geti(cfg, H2_CONF_SER_HEADERS); -- -- /* Create a subpool from the worker one to be used for all things -- * with life-time of this task_env execution. -- */ -- apr_pool_create(&env.pool, h2_worker_get_pool(worker)); ++ task->serialize_headers = h2_config_geti(cfg, H2_CONF_SER_HEADERS); - - /* Create a subpool from the worker one to be used for all things - * with life-time of this task execution. - */ - apr_pool_create(&task->pool, h2_worker_get_pool(worker)); - /* Link the task to the worker which provides useful things such -- /* Link the env to the worker which provides useful things such --- * as mutex, a socket etc. */ - task->io = h2_worker_get_cond(worker); -- env.io = h2_worker_get_cond(worker); --- - status = h2_conn_setup(task, worker); -- /* Clone fields, so that lifetimes become (more) independent. */ -- env.method = apr_pstrdup(env.pool, task->method); -- env.scheme = apr_pstrdup(env.pool, task->scheme); -- env.authority = apr_pstrdup(env.pool, task->authority); -- env.path = apr_pstrdup(env.pool, task->path); -- env.headers = apr_table_clone(env.pool, task->headers); +++ status = h2_worker_setup_task(worker, task); -- /* Setup the pseudo connection to use our own pool and bucket_alloc */ -- env.c = *task->c; -- task->c = NULL; -- status = h2_conn_setup(&env, worker); -- --- /* save in connection that this one is a pseudo connection, prevents --- * other hooks from messing with it. */ -- h2_ctx_create_for(&env.c, &env); +++ /* save in connection that this one is a pseudo connection */ ++ h2_ctx_create_for(task->c, task); if (status == APR_SUCCESS) { -- env.input = h2_task_input_create(&env, env.pool, -- env.c.bucket_alloc); -- env.output = h2_task_output_create(&env, env.pool, -- env.c.bucket_alloc); -- status = h2_conn_process(&env.c, h2_worker_get_socket(worker)); -- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, &env.c, -- "h2_task(%s): processing done", env.id); ++ task->input = h2_task_input_create(task, task->pool, ++ task->c->bucket_alloc); - task->output = h2_task_output_create(task, task->pool, - task->c->bucket_alloc); - status = h2_conn_process(task->c, h2_worker_get_socket(worker)); - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, task->c, +++ task->output = h2_task_output_create(task, task->pool); +++ +++ ap_process_connection(task->c, h2_worker_get_socket(worker)); +++ +++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c, ++ "h2_task(%s): processing done", task->id); } else { -- ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, &env.c, -- APLOGNO(02957) "h2_task(%s): error setting up h2_task_env", -- env.id); ++ ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, task->c, ++ APLOGNO(02957) "h2_task(%s): error setting up h2_task", ++ task->id); } -- if (env.input) { -- h2_task_input_destroy(env.input); -- env.input = NULL; ++ if (task->input) { ++ h2_task_input_destroy(task->input); ++ task->input = NULL; } -- if (env.output) { -- h2_task_output_close(env.output); -- h2_task_output_destroy(env.output); -- env.output = NULL; ++ if (task->output) { ++ h2_task_output_close(task->output); ++ h2_task_output_destroy(task->output); ++ task->output = NULL; } -- h2_task_set_finished(task); -- if (env.io) { -- apr_thread_cond_signal(env.io); ++ if (task->io) { ++ apr_thread_cond_signal(task->io); } - if (task->pool) { - apr_pool_destroy(task->pool); - task->pool = NULL; -- if (env.pool) { -- apr_pool_destroy(env.pool); -- env.pool = NULL; --- } --- - if (task->c->id) { - h2_conn_post(task->c, worker); -- if (env.c.id) { -- h2_conn_post(&env.c, worker); --- } --- -- h2_mplx_task_done(env.mplx, env.stream_id); ++ h2_mplx_task_done(task->mplx, task->stream_id); +++ h2_worker_release_task(worker, task); return status; } @@@@ -286,7 -340,7 -340,7 +254,7 @@@@ static request_rec *h2_task_create_requ r->allowed_methods = ap_make_method_list(p, 2); - r->headers_in = apr_table_copy(r->pool, task->headers); -- r->headers_in = apr_table_copy(r->pool, env->headers); +++ r->headers_in = apr_table_copy(r->pool, task->request->headers); r->trailers_in = apr_table_make(r->pool, 5); r->subprocess_env = apr_table_make(r->pool, 25); r->headers_out = apr_table_make(r->pool, 12); @@@@ -325,19 -379,18 -379,18 +293,19 @@@@ /* Time to populate r with the data we have. */ r->request_time = apr_time_now(); - r->method = task->method; -- r->the_request = apr_psprintf(r->pool, "%s %s HTTP/1.1", -- env->method, env->path); -- r->method = env->method; +++ r->method = task->request->method; /* Provide quick information about the request method as soon as known */ r->method_number = ap_method_number_of(r->method); if (r->method_number == M_GET && r->method[0] == 'H') { r->header_only = 1; } - ap_parse_uri(r, task->path); -- ap_parse_uri(r, env->path); +++ ap_parse_uri(r, task->request->path); r->protocol = (char*)"HTTP/2"; r->proto_num = HTTP_VERSION(2, 0); ++ ++ r->the_request = apr_psprintf(r->pool, "%s %s %s", - r->method, task->path, r->protocol); +++ r->method, task->request->path, r->protocol); /* update what we think the virtual host is based on the headers we've * now read. may update status. @@@@ -401,6 -454,6 -454,6 +369,8 @@@@ static apr_status_t h2_task_process_req * will result in a segfault immediately instead * of nondeterministic failures later. */ +++ if (cs) +++ cs->state = CONN_STATE_WRITE_COMPLETION; r = NULL; } ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, NULL); diff --cc modules/http2/h2_task.h index 1877a3920b5,b66ce38c251,b66ce38c251..7cf0f20de2a --- a/modules/http2/h2_task.h +++ b/modules/http2/h2_task.h @@@@ -39,6 -39,6 -39,6 +39,7 @@@@ struct apr_thread_cond_t struct h2_conn; struct h2_mplx; struct h2_task; +++struct h2_request; struct h2_resp_head; struct h2_worker; @@@@ -49,41 -52,136 -52,136 +50,28 @@@@ struct h2_task int stream_id; struct h2_mplx *mplx; -- volatile apr_uint32_t has_started; -- volatile apr_uint32_t has_finished; -- --- const char *method; --- const char *scheme; --- const char *authority; --- const char *path; --- apr_table_t *headers; +++ const struct h2_request *request; int input_eos; -- struct conn_rec *c; --}; ++ int serialize_headers; --typedef struct h2_task_env h2_task_env; ++ struct conn_rec *c; --struct h2_task_env { -- const char *id; -- int stream_id; -- struct h2_mplx *mplx; -- apr_pool_t *pool; /* pool for task lifetime things */ apr_bucket_alloc_t *bucket_alloc; -- -- const char *method; -- const char *scheme; -- const char *authority; -- const char *path; -- apr_table_t *headers; -- int input_eos; -- -- int serialize_headers; -- -- struct conn_rec c; struct h2_task_input *input; struct h2_task_output *output; struct apr_thread_cond_t *io; /* used to wait for events on */ }; --/** -- * The magic pointer value that indicates the head of a h2_task list -- * @param b The task list -- * @return The magic pointer value -- */ --#define H2_TASK_LIST_SENTINEL(b) APR_RING_SENTINEL((b), h2_task, link) -- --/** -- * Determine if the task list is empty -- * @param b The list to check -- * @return true or false -- */ --#define H2_TASK_LIST_EMPTY(b) APR_RING_EMPTY((b), h2_task, link) -- --/** -- * Return the first task in a list -- * @param b The list to query -- * @return The first task in the list -- */ --#define H2_TASK_LIST_FIRST(b) APR_RING_FIRST(b) -- --/** -- * Return the last task in a list -- * @param b The list to query -- * @return The last task int he list -- */ --#define H2_TASK_LIST_LAST(b) APR_RING_LAST(b) -- --/** -- * Insert a single task at the front of a list -- * @param b The list to add to -- * @param e The task to insert -- */ --#define H2_TASK_LIST_INSERT_HEAD(b, e) do { \ -- h2_task *ap__b = (e); \ -- APR_RING_INSERT_HEAD((b), ap__b, h2_task, link); \ --} while (0) -- --/** -- * Insert a single task at the end of a list -- * @param b The list to add to -- * @param e The task to insert -- */ --#define H2_TASK_LIST_INSERT_TAIL(b, e) do { \ -- h2_task *ap__b = (e); \ -- APR_RING_INSERT_TAIL((b), ap__b, h2_task, link); \ --} while (0) -- --/** -- * Get the next task in the list -- * @param e The current task -- * @return The next task -- */ --#define H2_TASK_NEXT(e) APR_RING_NEXT((e), link) --/** -- * Get the previous task in the list -- * @param e The current task -- * @return The previous task -- */ --#define H2_TASK_PREV(e) APR_RING_PREV((e), link) -- --/** -- * Remove a task from its list -- * @param e The task to remove -- */ --#define H2_TASK_REMOVE(e) APR_RING_REMOVE((e), link) -- -- ---h2_task *h2_task_create(long session_id, int stream_id, --- apr_pool_t *pool, struct h2_mplx *mplx, --- conn_rec *c); +++h2_task *h2_task_create(long session_id, const struct h2_request *req, +++ apr_pool_t *pool, struct h2_mplx *mplx, int eos); apr_status_t h2_task_destroy(h2_task *task); ---void h2_task_set_request(h2_task *task, --- const char *method, --- const char *scheme, --- const char *authority, --- const char *path, --- apr_table_t *headers, int eos); --- --- apr_status_t h2_task_do(h2_task *task, struct h2_worker *worker); --apr_status_t h2_task_process_request(h2_task_env *env); -- --int h2_task_has_started(h2_task *task); --void h2_task_set_started(h2_task *task); --int h2_task_has_finished(h2_task *task); --void h2_task_set_finished(h2_task *task); void h2_task_register_hooks(void); --void h2_task_die(h2_task_env *env, int status, request_rec *r); #endif /* defined(__mod_h2__h2_task__) */ diff --cc modules/http2/h2_task_input.c index 1eac749f42a,487f7e60692,487f7e60692..49be7cfd228 --- a/modules/http2/h2_task_input.c +++ b/modules/http2/h2_task_input.c @@@@ -23,6 -23,6 -23,6 +23,7 @@@@ #include "h2_private.h" #include "h2_conn.h" #include "h2_mplx.h" +++#include "h2_request.h" #include "h2_session.h" #include "h2_stream.h" #include "h2_task_input.h" @@@@ -47,19 -47,19 -47,19 +48,19 @@@@ h2_task_input *h2_task_input_create(h2_ { h2_task_input *input = apr_pcalloc(pool, sizeof(h2_task_input)); if (input) { -- input->env = env; ++ input->task = task; input->bb = NULL; -- if (env->serialize_headers) { -- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, &env->c, ++ if (task->serialize_headers) { ++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c, "h2_task_input(%s): serialize request %s %s", - task->id, task->method, task->path); -- env->id, env->method, env->path); +++ task->id, task->request->method, task->request->path); input->bb = apr_brigade_create(pool, bucket_alloc); apr_brigade_printf(input->bb, NULL, NULL, "%s %s HTTP/1.1\r\n", - task->method, task->path); - apr_table_do(ser_header, input, task->headers, NULL); -- env->method, env->path); -- apr_table_do(ser_header, input, env->headers, NULL); +++ task->request->method, task->request->path); +++ apr_table_do(ser_header, input, task->request->headers, NULL); apr_brigade_puts(input->bb, NULL, NULL, "\r\n"); -- if (input->env->input_eos) { ++ if (input->task->input_eos) { APR_BRIGADE_INSERT_TAIL(input->bb, apr_bucket_eos_create(bucket_alloc)); } } @@@@ -70,21 -70,21 -70,21 +71,6 @@@@ /* We do not serialize and have eos already, no need to * create a bucket brigade. */ } --- - if (APLOGcdebug(task->c)) { -- if (APLOGcdebug(&env->c)) { --- char buffer[1024]; --- apr_size_t len = sizeof(buffer)-1; --- if (input->bb) { --- apr_brigade_flatten(input->bb, buffer, &len); --- } --- else { --- len = 0; --- } --- buffer[len] = 0; - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, -- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, &env->c, --- "h2_task_input(%s): request is: %s", - task->id, buffer); -- env->id, buffer); --- } } return input; } @@@@ -104,21 -104,21 -104,21 +90,20 @@@@ apr_status_t h2_task_input_read(h2_task apr_status_t status = APR_SUCCESS; apr_off_t bblen = 0; --- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, +++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, "h2_task_input(%s): read, block=%d, mode=%d, readbytes=%ld", -- input->env->id, block, mode, (long)readbytes); ++ input->task->id, block, mode, (long)readbytes); ++ +++ if (mode == AP_MODE_INIT) { +++ return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes); +++ } + if (is_aborted(f)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, --- "h2_task_input(%s): is aborted", - input->task->id); -- input->env->id); +++ "h2_task_input(%s): is aborted", input->task->id); return APR_ECONNABORTED; } --- if (mode == AP_MODE_INIT) { - return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes); -- return APR_SUCCESS; --- } --- if (input->bb) { status = apr_brigade_length(input->bb, 1, &bblen); if (status != APR_SUCCESS) { @@@@ -160,7 -160,7 -160,7 +145,7 @@@@ return status; } if ((bblen == 0) && (block == APR_NONBLOCK_READ)) { --- return h2_util_has_eos(input->bb, 0)? APR_EOF : APR_EAGAIN; +++ return h2_util_has_eos(input->bb, -1)? APR_EOF : APR_EAGAIN; } ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, "h2_task_input(%s): mplx in read, %ld bytes in brigade", diff --cc modules/http2/h2_task_output.c index 053e2d69e4e,879cb5fa21b,879cb5fa21b..06a5d7aafbb --- a/modules/http2/h2_task_output.c +++ b/modules/http2/h2_task_output.c @@@@ -24,6 -24,6 -24,6 +24,7 @@@@ #include "h2_private.h" #include "h2_conn.h" #include "h2_mplx.h" +++#include "h2_request.h" #include "h2_session.h" #include "h2_stream.h" #include "h2_from_h1.h" @@@@ -33,16 -33,16 -33,16 +34,14 @@@@ #include "h2_util.h" - h2_task_output *h2_task_output_create(h2_task *task, apr_pool_t *pool, --h2_task_output *h2_task_output_create(h2_task_env *env, apr_pool_t *pool, --- apr_bucket_alloc_t *bucket_alloc) +++h2_task_output *h2_task_output_create(h2_task *task, apr_pool_t *pool) { h2_task_output *output = apr_pcalloc(pool, sizeof(h2_task_output)); --- (void)bucket_alloc; if (output) { -- output->env = env; ++ output->task = task; output->state = H2_TASK_OUT_INIT; -- output->from_h1 = h2_from_h1_create(env->stream_id, pool); ++ output->from_h1 = h2_from_h1_create(task->stream_id, pool); if (!output->from_h1) { return NULL; } @@@@ -73,12 -73,12 -73,12 +72,13 @@@@ static apr_status_t open_if_needed(h2_t ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, "h2_task_output(%s): write without response " "for %s %s %s", - output->task->id, output->task->method, - output->task->authority, output->task->path); -- output->env->id, output->env->method, -- output->env->authority, output->env->path); +++ output->task->id, output->task->request->method, +++ output->task->request->authority, +++ output->task->request->path); f->c->aborted = 1; } -- if (output->env->io) { -- apr_thread_cond_broadcast(output->env->io); ++ if (output->task->io) { ++ apr_thread_cond_broadcast(output->task->io); } return APR_ECONNABORTED; } diff --cc modules/http2/h2_task_output.h index 79cb6816c7a,86571a1e107,86571a1e107..a326c49096b --- a/modules/http2/h2_task_output.h +++ b/modules/http2/h2_task_output.h @@@@ -40,8 -40,8 -40,8 +40,7 @@@@ struct h2_task_output struct h2_from_h1 *from_h1; }; - h2_task_output *h2_task_output_create(struct h2_task *task, apr_pool_t *pool, --h2_task_output *h2_task_output_create(struct h2_task_env *env, apr_pool_t *pool, --- apr_bucket_alloc_t *bucket_alloc); +++h2_task_output *h2_task_output_create(struct h2_task *task, apr_pool_t *pool); void h2_task_output_destroy(h2_task_output *output); diff --cc modules/http2/h2_task_queue.c index 1653aa26e28,a81cc100fb9,a81cc100fb9..23bad194b9e --- a/modules/http2/h2_task_queue.c +++ b/modules/http2/h2_task_queue.c @@@@ -41,121 -34,54 -34,54 +41,140 @@@@ h2_task_queue *h2_tq_create(apr_pool_t return q; } --void h2_tq_destroy(h2_task_queue *q) ++int h2_tq_empty(h2_task_queue *q) ++{ ++ return q->nelts == 0; ++} ++ - void h2_tq_add(h2_task_queue *q, struct h2_task *task, - h2_tq_cmp *cmp, void *ctx) +++void h2_tq_add(h2_task_queue *q, int sid, h2_tq_cmp *cmp, void *ctx) { -- while (!H2_TASK_LIST_EMPTY(&q->tasks)) { -- h2_task *task = H2_TASK_LIST_FIRST(&q->tasks); -- H2_TASK_REMOVE(task); ++ int i; ++ ++ if (q->nelts >= q->nalloc) { ++ tq_grow(q, q->nalloc * 2); } ++ ++ i = (q->head + q->nelts) % q->nalloc; - q->elts[i] = task; +++ q->elts[i] = sid; ++ ++q->nelts; ++ ++ /* bubble it to the front of the queue */ ++ tq_bubble_up(q, i, q->head, cmp, ctx); } --static int in_list(h2_task_queue *q, h2_task *task) +++int h2_tq_remove(h2_task_queue *q, int sid) + { -- h2_task *e; -- for (e = H2_TASK_LIST_FIRST(&q->tasks); -- e != H2_TASK_LIST_SENTINEL(&q->tasks); -- e = H2_TASK_NEXT(e)) { -- if (e == task) { -- return 1; +++ int i; +++ for (i = 0; i < q->nelts; ++i) { +++ if (sid == q->elts[(q->head + i) % q->nalloc]) { +++ break; + } + } +++ +++ if (i < q->nelts) { +++ ++i; +++ for (; i < q->nelts; ++i) { +++ q->elts[(q->head+i-1)%q->nalloc] = q->elts[(q->head+i)%q->nalloc]; +++ } +++ --q->nelts; +++ return 1; +++ } + return 0; + } + --int h2_tq_empty(h2_task_queue *q) ++void h2_tq_sort(h2_task_queue *q, h2_tq_cmp *cmp, void *ctx) { -- return H2_TASK_LIST_EMPTY(&q->tasks); ++ /* Assume that changes in ordering are minimal. This needs, ++ * best case, q->nelts - 1 comparisions to check that nothing ++ * changed. ++ */ ++ 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 = tq_bubble_up(q, i, prev, cmp, ctx); ++ if (ni == prev) { ++ /* i bubbled one up, bubble the new i down, which ++ * keeps all tasks below i sorted. */ ++ tq_bubble_down(q, i, last, cmp, ctx); ++ } ++ i = prev; ++ }; ++ } ++} ++ ++ - h2_task *h2_tq_shift(h2_task_queue *q) +++int h2_tq_shift(h2_task_queue *q) ++{ - h2_task *t; +++ int sid; ++ ++ if (q->nelts <= 0) { - return NULL; +++ return 0; ++ } ++ - t = q->elts[q->head]; +++ sid = q->elts[q->head]; ++ q->head = (q->head + 1) % q->nalloc; ++ q->nelts--; ++ - return t; +++ return sid; ++} ++ ++static void tq_grow(h2_task_queue *q, int nlen) ++{ ++ AP_DEBUG_ASSERT(q->nalloc <= nlen); ++ if (nlen > q->nalloc) { - h2_task **nq = apr_pcalloc(q->pool, sizeof(h2_task *) * nlen); +++ int *nq = apr_pcalloc(q->pool, sizeof(h2_task *) * nlen); ++ if (q->nelts > 0) { ++ int l = ((q->head + q->nelts) % q->nalloc) - q->head; ++ - memmove(nq, q->elts + q->head, sizeof(h2_task *) * l); +++ memmove(nq, q->elts + q->head, sizeof(int) * l); ++ if (l < q->nelts) { ++ /* elts wrapped, append elts in [0, remain] to nq */ ++ int remain = q->nelts - l; - memmove(nq + l, q->elts, sizeof(h2_task *) * remain); +++ memmove(nq + l, q->elts, sizeof(int) * remain); ++ } ++ } ++ q->elts = nq; ++ q->nalloc = nlen; ++ q->head = 0; ++ } } --void h2_tq_append(h2_task_queue *q, struct h2_task *task) ++static void tq_swap(h2_task_queue *q, int i, int j) { - h2_task *t = q->elts[i]; -- H2_TASK_LIST_INSERT_TAIL(&q->tasks, task); +++ int x = q->elts[i]; ++ q->elts[i] = q->elts[j]; - q->elts[j] = t; +++ q->elts[j] = x; } --apr_status_t h2_tq_remove(h2_task_queue *q, struct h2_task *task) ++static int tq_bubble_up(h2_task_queue *q, int i, int top, ++ h2_tq_cmp *cmp, void *ctx) { -- if (in_list(q, task)) { -- H2_TASK_REMOVE(task); -- return APR_SUCCESS; ++ int prev; ++ while (((prev = (q->nalloc + i - 1) % q->nalloc), i != top) ++ && (*cmp)(q->elts[i], q->elts[prev], ctx) < 0) { ++ tq_swap(q, prev, i); ++ i = prev; } -- return APR_NOTFOUND; ++ return i; } --h2_task *h2_tq_pop_first(h2_task_queue *q) ++static int tq_bubble_down(h2_task_queue *q, int i, int bottom, ++ h2_tq_cmp *cmp, void *ctx) { -- if (!H2_TASK_LIST_EMPTY(&q->tasks)) { -- h2_task *task = H2_TASK_LIST_FIRST(&q->tasks); -- H2_TASK_REMOVE(task); -- return task; ++ int next; ++ while (((next = (q->nalloc + i + 1) % q->nalloc), i != bottom) ++ && (*cmp)(q->elts[i], q->elts[next], ctx) > 0) { ++ tq_swap(q, next, i); ++ i = next; } -- return NULL; ++ return i; } diff --cc modules/http2/h2_task_queue.h index 36fad2c4d8d,d93d74ac502,d93d74ac502..dcc46d037af --- a/modules/http2/h2_task_queue.h +++ b/modules/http2/h2_task_queue.h @@@@ -24,33 -25,23 -25,23 +24,33 @@@@ struct h2_task typedef struct h2_task_queue h2_task_queue; struct h2_task_queue { - struct h2_task **elts; -- APR_RING_ENTRY(h2_task_queue) link; -- APR_RING_HEAD(h2_tasks, h2_task) tasks; -- long id; +++ int *elts; ++ int head; ++ int nelts; ++ int nalloc; ++ apr_pool_t *pool; }; /** -- * Allocate a new queue from the pool and initialize. -- * @param id the identifier of the queue -- * @param pool the memory pool ++ * Comparator for two task to determine their order. ++ * - * @param t1 task to compare - * @param t2 task to compare +++ * @param s1 stream id to compare +++ * @param s2 stream id to compare ++ * @param ctx provided user data ++ * @return value is the same as for strcmp() and has the effect: - * == 0: t1 and t2 are treated equal in ordering - * < 0: t1 should be sorted before t2 - * > 0: t2 should be sorted before t1 +++ * == 0: s1 and s2 are treated equal in ordering +++ * < 0: s1 should be sorted before s2 +++ * > 0: s2 should be sorted before s1 */ - typedef int h2_tq_cmp(struct h2_task *t1, struct h2_task *t2, void *ctx); --h2_task_queue *h2_tq_create(long id, apr_pool_t *pool); +++typedef int h2_tq_cmp(int s1, int s2, void *ctx); ++ /** -- * Release all queue tasks. -- * @param q the queue to destroy ++ * Allocate a new queue from the pool and initialize. ++ * @param id the identifier of the queue ++ * @param pool the memory pool */ --void h2_tq_destroy(h2_task_queue *q); ++h2_task_queue *h2_tq_create(apr_pool_t *pool, int capacity); /** * Return != 0 iff there are no tasks in the queue. @@@@ -59,33 -50,99 -50,99 +59,41 @@@@ int h2_tq_empty(h2_task_queue *q); /** - * Add the task to the queue. -- * Append the task to the end of the queue. +++ * Add a stream idto the queue. ++ * * @param q the queue to append the task to - * @param task the task to add -- * @param task the task to append -- */ --void h2_tq_append(h2_task_queue *q, struct h2_task *task); -- --/** -- * Remove a task from the queue. Return APR_SUCCESS if the task -- * was indeed queued, APR_NOTFOUND otherwise. -- * @param q the queue to remove from -- * @param task the task to remove -- */ --apr_status_t h2_tq_remove(h2_task_queue *q, struct h2_task *task); -- --/** -- * Get the first task from the queue or NULL if the queue is empty. The -- * task will be removed. -- * @param q the queue to pop the first task from -- */ --h2_task *h2_tq_pop_first(h2_task_queue *q); -- --/******************************************************************************* -- * Queue Manipulation. -- ******************************************************************************/ -- --/** -- * The magic pointer value that indicates the head of a h2_task_queue list -- * @param b The queue list -- * @return The magic pointer value +++ * @param sid the stream id to add ++ * @param cmp the comparator for sorting ++ * @param ctx user data for comparator */ - void h2_tq_add(h2_task_queue *q, struct h2_task *task, - h2_tq_cmp *cmp, void *ctx); --#define H2_TQ_LIST_SENTINEL(b) APR_RING_SENTINEL((b), h2_task_queue, link) +++void h2_tq_add(h2_task_queue *q, int sid, h2_tq_cmp *cmp, void *ctx); /** - * Sort the tasks queue again. Call if the task ordering -- * Determine if the queue list is empty -- * @param b The list to check -- * @return true or false +++ * Remove the stream id from the queue. Return != 0 iff task +++ * was found in queue. +++ * @param q the task queue +++ * @param sid the stream id to remove +++ * @return != 0 iff task was found in queue + */ --#define H2_TQ_LIST_EMPTY(b) APR_RING_EMPTY((b), h2_task_queue, link) +++int h2_tq_remove(h2_task_queue *q, int sid); + + /** -- * Return the first queue in a list -- * @param b The list to query -- * @return The first queue in the list -- */ --#define H2_TQ_LIST_FIRST(b) APR_RING_FIRST(b) -- --/** -- * Return the last queue in a list -- * @param b The list to query -- * @return The last queue int he list -- */ --#define H2_TQ_LIST_LAST(b) APR_RING_LAST(b) -- --/** -- * Insert a single queue at the front of a list -- * @param b The list to add to -- * @param e The queue to insert -- */ --#define H2_TQ_LIST_INSERT_HEAD(b, e) do { \ --h2_task_queue *ap__b = (e); \ --APR_RING_INSERT_HEAD((b), ap__b, h2_task_queue, link); \ --} while (0) -- --/** -- * Insert a single queue at the end of a list -- * @param b The list to add to -- * @param e The queue to insert -- */ --#define H2_TQ_LIST_INSERT_TAIL(b, e) do { \ --h2_task_queue *ap__b = (e); \ --APR_RING_INSERT_TAIL((b), ap__b, h2_task_queue, link); \ --} while (0) -- --/** -- * Get the next queue in the list -- * @param e The current queue -- * @return The next queue -- */ --#define H2_TQ_NEXT(e) APR_RING_NEXT((e), link) --/** -- * Get the previous queue in the list -- * @param e The current queue -- * @return The previous queue +++ * Sort the stream idqueue again. Call if the task ordering ++ * has changed. ++ * ++ * @param q the queue to sort ++ * @param cmp the comparator for sorting ++ * @param ctx user data for the comparator */ --#define H2_TQ_PREV(e) APR_RING_PREV((e), link) ++void h2_tq_sort(h2_task_queue *q, h2_tq_cmp *cmp, void *ctx); /** - * Get the first task from the queue or NULL if the queue is empty. -- * Remove a queue from its list -- * @param e The queue to remove +++ * Get the first stream id from the queue or NULL if the queue is empty. ++ * The task will be removed. ++ * ++ * @param q the queue to get the first task from - * @return the first task of the queue, NULL if empty +++ * @return the first stream id of the queue, 0 if empty */ - h2_task *h2_tq_shift(h2_task_queue *q); --#define H2_TQ_REMOVE(e) APR_RING_REMOVE((e), link) -- -- --#define H2_TQ_EMPTY(e) H2_TASK_LIST_EMPTY(&(e)->tasks) +++int h2_tq_shift(h2_task_queue *q); #endif /* defined(__mod_h2__h2_task_queue__) */ diff --cc modules/http2/h2_util.c index e83ed4ee669,9d141be93bf,9d141be93bf..e80a0268804 --- a/modules/http2/h2_util.c +++ b/modules/http2/h2_util.c @@@@ -25,6 -24,6 -24,6 +25,7 @@@@ #include #include "h2_private.h" +++#include "h2_request.h" #include "h2_util.h" size_t h2_util_hex_dump(char *buffer, size_t maxlen, @@@@ -205,6 -204,6 -204,6 +206,10 @@@@ const char *h2_util_first_token_match(a return NULL; } +++/******************************************************************************* +++ * h2_util for bucket brigades +++ ******************************************************************************/ +++ /* DEEP_COPY==0 crashes under load. I think the setaside is fine, * however buckets moved to another thread will still be * free'd against the old bucket_alloc. *And* if the old @@@@ -215,7 -214,7 -214,7 +220,7 @@@@ static const int DEEP_COPY = 1 static const int FILE_MOVE = 1; static apr_status_t last_not_included(apr_bucket_brigade *bb, --- apr_size_t maxlen, +++ apr_off_t maxlen, int same_alloc, int *pfile_buckets_allowed, apr_bucket **pend) @@@@ -224,7 -223,7 -223,7 +229,7 @@@@ apr_status_t status = APR_SUCCESS; int files_allowed = pfile_buckets_allowed? *pfile_buckets_allowed : 0; --- if (maxlen > 0) { +++ if (maxlen >= 0) { /* Find the bucket, up to which we reach maxlen/mem bytes */ for (b = APR_BRIGADE_FIRST(bb); (b != APR_BRIGADE_SENTINEL(bb)); @@@@ -275,7 -274,7 -274,7 +280,7 @@@@ #define LOG_LEVEL APLOG_INFO apr_status_t h2_util_move(apr_bucket_brigade *to, apr_bucket_brigade *from, --- apr_size_t maxlen, int *pfile_handles_allowed, +++ apr_off_t maxlen, int *pfile_handles_allowed, const char *msg) { apr_status_t status = APR_SUCCESS; @@@@ -407,7 -406,7 -406,7 +412,7 @@@@ } apr_status_t h2_util_copy(apr_bucket_brigade *to, apr_bucket_brigade *from, --- apr_size_t maxlen, const char *msg) +++ apr_off_t maxlen, const char *msg) { apr_status_t status = APR_SUCCESS; int same_alloc; @@@@ -483,7 -482,7 -482,7 +488,7 @@@@ int h2_util_has_flush_or_eos(apr_bucket return 0; } ---int h2_util_has_eos(apr_bucket_brigade *bb, apr_size_t len) +++int h2_util_has_eos(apr_bucket_brigade *bb, apr_off_t len) { apr_bucket *b, *end; @@@@ -537,7 -536,7 -536,7 +542,7 @@@@ int h2_util_bb_has_data_or_eos(apr_buck } apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb, --- apr_size_t *plen, int *peos) +++ apr_off_t *plen, int *peos) { apr_status_t status; apr_off_t blen = 0; @@@@ -550,36 -549,36 -549,36 +555,29 @@@@ else if (blen == 0) { /* empty brigade, does it have an EOS bucket somwhere? */ *plen = 0; --- *peos = h2_util_has_eos(bb, 0); +++ *peos = h2_util_has_eos(bb, -1); } --- else if (blen > 0) { +++ else { /* data in the brigade, limit the length returned. Check for EOS * bucket only if we indicate data. This is required since plen == 0 * means "the whole brigade" for h2_util_hash_eos() */ --- if (blen < (apr_off_t)*plen) { +++ if (blen < *plen || *plen < 0) { *plen = blen; } --- *peos = (*plen > 0)? h2_util_has_eos(bb, *plen) : 0; --- } --- else if (blen < 0) { --- /* famous SHOULD NOT HAPPEN, sinc we told apr_brigade_length to readall --- */ --- *plen = 0; --- *peos = h2_util_has_eos(bb, 0); --- return APR_EINVAL; +++ *peos = h2_util_has_eos(bb, *plen); } return APR_SUCCESS; } apr_status_t h2_util_bb_readx(apr_bucket_brigade *bb, h2_util_pass_cb *cb, void *ctx, --- apr_size_t *plen, int *peos) +++ apr_off_t *plen, int *peos) { apr_status_t status = APR_SUCCESS; int consume = (cb != NULL); --- apr_size_t written = 0; --- apr_size_t avail = *plen; +++ apr_off_t written = 0; +++ apr_off_t avail = *plen; apr_bucket *next, *b; /* Pass data in our brigade through the callback until the length @@@@ -607,8 -606,8 -606,8 +605,7 @@@@ if (b->length == ((apr_size_t)-1)) { /* read to determine length */ --- status = apr_bucket_read(b, &data, &data_len, --- APR_NONBLOCK_READ); +++ status = apr_bucket_read(b, &data, &data_len, APR_NONBLOCK_READ); } else { data_len = b->length; @@@@ -648,145 -647,3 -647,3 +645,237 @@@@ return status; } ++void h2_util_bb_log(conn_rec *c, int stream_id, int level, ++ const char *tag, apr_bucket_brigade *bb) ++{ ++ char buffer[16 * 1024]; ++ const char *line = "(null)"; ++ apr_size_t bmax = sizeof(buffer)/sizeof(buffer[0]); ++ int off = 0; ++ apr_bucket *b; ++ ++ if (bb) { ++ memset(buffer, 0, bmax--); ++ for (b = APR_BRIGADE_FIRST(bb); ++ bmax && (b != APR_BRIGADE_SENTINEL(bb)); ++ b = APR_BUCKET_NEXT(b)) { ++ ++ if (APR_BUCKET_IS_METADATA(b)) { ++ if (APR_BUCKET_IS_EOS(b)) { ++ off += apr_snprintf(buffer+off, bmax-off, "eos "); ++ } ++ else if (APR_BUCKET_IS_FLUSH(b)) { ++ off += apr_snprintf(buffer+off, bmax-off, "flush "); ++ } ++ else if (AP_BUCKET_IS_EOR(b)) { ++ off += apr_snprintf(buffer+off, bmax-off, "eor "); ++ } ++ else { ++ off += apr_snprintf(buffer+off, bmax-off, "meta(unknown) "); ++ } ++ } ++ else { ++ const char *btype = "data"; ++ if (APR_BUCKET_IS_FILE(b)) { ++ btype = "file"; ++ } ++ else if (APR_BUCKET_IS_PIPE(b)) { ++ btype = "pipe"; ++ } ++ else if (APR_BUCKET_IS_SOCKET(b)) { ++ btype = "socket"; ++ } ++ else if (APR_BUCKET_IS_HEAP(b)) { ++ btype = "heap"; ++ } ++ else if (APR_BUCKET_IS_TRANSIENT(b)) { ++ btype = "transient"; ++ } ++ else if (APR_BUCKET_IS_IMMORTAL(b)) { ++ btype = "immortal"; ++ } ++#if APR_HAS_MMAP ++ else if (APR_BUCKET_IS_MMAP(b)) { ++ btype = "mmap"; ++ } ++#endif ++ else if (APR_BUCKET_IS_POOL(b)) { ++ btype = "pool"; ++ } ++ ++ off += apr_snprintf(buffer+off, bmax-off, "%s[%ld] ", ++ btype, ++ (long)(b->length == ((apr_size_t)-1)? ++ -1 : b->length)); ++ } ++ } ++ line = *buffer? buffer : "(empty)"; ++ } ++ ap_log_cerror(APLOG_MARK, level, 0, c, "bb_dump(%ld-%d)-%s: %s", ++ c->id, stream_id, tag, line); ++ ++} ++ ++apr_status_t h2_transfer_brigade(apr_bucket_brigade *to, ++ apr_bucket_brigade *from, ++ apr_pool_t *p, - apr_size_t *plen, +++ apr_off_t *plen, ++ int *peos) ++{ ++ apr_bucket *e; - apr_size_t len = 0, remain = *plen; +++ apr_off_t len = 0, remain = *plen; ++ apr_status_t rv; ++ ++ *peos = 0; ++ ++ while (!APR_BRIGADE_EMPTY(from)) { ++ e = APR_BRIGADE_FIRST(from); ++ ++ if (APR_BUCKET_IS_METADATA(e)) { ++ if (APR_BUCKET_IS_EOS(e)) { ++ *peos = 1; ++ } ++ } ++ else { ++ if (remain > 0 && e->length == ((apr_size_t)-1)) { ++ const char *ign; ++ apr_size_t ilen; ++ rv = apr_bucket_read(e, &ign, &ilen, APR_BLOCK_READ); ++ if (rv != APR_SUCCESS) { ++ return rv; ++ } ++ } ++ ++ if (remain < e->length) { ++ if (remain <= 0) { ++ return APR_SUCCESS; ++ } ++ apr_bucket_split(e, remain); ++ } ++ } ++ ++ rv = apr_bucket_setaside(e, p); ++ ++ /* If the bucket type does not implement setaside, then ++ * (hopefully) morph it into a bucket type which does, and set ++ * *that* aside... */ ++ if (rv == APR_ENOTIMPL) { ++ const char *s; ++ apr_size_t n; ++ ++ rv = apr_bucket_read(e, &s, &n, APR_BLOCK_READ); ++ if (rv == APR_SUCCESS) { ++ rv = apr_bucket_setaside(e, p); ++ } ++ } ++ ++ if (rv != APR_SUCCESS) { ++ /* Return an error but still save the brigade if ++ * ->setaside() is really not implemented. */ ++ if (rv != APR_ENOTIMPL) { ++ return rv; ++ } ++ } ++ ++ APR_BUCKET_REMOVE(e); ++ APR_BRIGADE_INSERT_TAIL(to, e); ++ len += e->length; ++ remain -= e->length; ++ } ++ ++ *plen = len; ++ return APR_SUCCESS; ++} ++ +++/******************************************************************************* +++ * h2_ngheader +++ ******************************************************************************/ +++ +++int h2_util_ignore_header(const char *name) +++{ +++ /* never forward, ch. 8.1.2.2 */ +++ return (H2_HD_MATCH_LIT_CS("connection", name) +++ || H2_HD_MATCH_LIT_CS("proxy-connection", name) +++ || H2_HD_MATCH_LIT_CS("upgrade", name) +++ || H2_HD_MATCH_LIT_CS("keep-alive", name) +++ || H2_HD_MATCH_LIT_CS("transfer-encoding", name)); +++} +++ +++static int count_header(void *ctx, const char *key, const char *value) +++{ +++ if (!h2_util_ignore_header(key)) { +++ (*((size_t*)ctx))++; +++ } +++ return 1; +++} +++ +++#define NV_ADD_LIT_CS(nv, k, v) add_header(nv, k, sizeof(k) - 1, v, strlen(v)) +++#define NV_ADD_CS_CS(nv, k, v) add_header(nv, k, strlen(k), v, strlen(v)) +++ +++static int add_header(h2_ngheader *ngh, +++ const char *key, size_t key_len, +++ const char *value, size_t val_len) +++{ +++ nghttp2_nv *nv = &ngh->nv[ngh->nvlen++]; +++ +++ nv->name = (uint8_t*)key; +++ nv->namelen = key_len; +++ nv->value = (uint8_t*)value; +++ nv->valuelen = val_len; +++ return 1; +++} +++ +++static int add_table_header(void *ctx, const char *key, const char *value) +++{ +++ if (!h2_util_ignore_header(key)) { +++ add_header(ctx, key, strlen(key), value, strlen(value)); +++ } +++ return 1; +++} +++ +++ +++h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p, +++ int http_status, +++ apr_table_t *header) +++{ +++ h2_ngheader *ngh; +++ size_t n; +++ +++ n = 1; +++ apr_table_do(count_header, &n, header, NULL); +++ +++ ngh = apr_pcalloc(p, sizeof(h2_ngheader)); +++ ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); +++ NV_ADD_LIT_CS(ngh, ":status", apr_psprintf(p, "%d", http_status)); +++ apr_table_do(add_table_header, ngh, header, NULL); +++ +++ return ngh; +++} +++ +++h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, +++ const struct h2_request *req) +++{ +++ +++ h2_ngheader *ngh; +++ size_t n; +++ +++ AP_DEBUG_ASSERT(req); +++ AP_DEBUG_ASSERT(req->scheme); +++ AP_DEBUG_ASSERT(req->authority); +++ AP_DEBUG_ASSERT(req->path); +++ AP_DEBUG_ASSERT(req->method); +++ +++ n = 4; +++ apr_table_do(count_header, &n, req->headers, NULL); +++ +++ ngh = apr_pcalloc(p, sizeof(h2_ngheader)); +++ ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); +++ NV_ADD_LIT_CS(ngh, ":scheme", req->scheme); +++ NV_ADD_LIT_CS(ngh, ":authority", req->authority); +++ NV_ADD_LIT_CS(ngh, ":path", req->path); +++ NV_ADD_LIT_CS(ngh, ":method", req->method); +++ apr_table_do(add_table_header, ngh, req->headers, NULL); +++ +++ return ngh; +++} +++ diff --cc modules/http2/h2_util.h index 0612af6ba32,9a1b5c6d35b,9a1b5c6d35b..51efb8cf2d8 --- a/modules/http2/h2_util.h +++ b/modules/http2/h2_util.h @@@@ -16,6 -16,6 -16,6 +16,7 @@@@ #ifndef __mod_h2__h2_util__ #define __mod_h2__h2_util__ +++struct h2_request; struct nghttp2_frame; size_t h2_util_hex_dump(char *buffer, size_t maxlen, @@@@ -67,19 -67,19 -67,19 +68,32 @@@@ apr_size_t h2_util_base64url_decode(con nv->value = (uint8_t *)VALUE; \ nv->valuelen = strlen(VALUE) +++int h2_util_ignore_header(const char *name); +++ +++typedef struct h2_ngheader { +++ nghttp2_nv *nv; +++ apr_size_t nvlen; +++} h2_ngheader; +++ +++h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p, +++ int http_status, +++ apr_table_t *header); +++h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, +++ const struct h2_request *req); +++ /** * Moves data from one brigade into another. If maxlen > 0, it only * moves up to maxlen bytes into the target brigade, making bucket splits * if needed. * @param to the brigade to move the data to * @param from the brigade to get the data from --- * @param maxlen of bytes to move, 0 for all +++ * @param maxlen of bytes to move, <= 0 for all * @param pfile_buckets_allowed how many file buckets may be moved, * may be 0 or NULL * @param msg message for use in logging */ apr_status_t h2_util_move(apr_bucket_brigade *to, apr_bucket_brigade *from, --- apr_size_t maxlen, int *pfile_buckets_allowed, +++ apr_off_t maxlen, int *pfile_buckets_allowed, const char *msg); /** @@@@ -88,11 -88,11 -88,11 +102,11 @@@@ * if needed. * @param to the brigade to copy the data to * @param from the brigade to get the data from --- * @param maxlen of bytes to copy, 0 for all +++ * @param maxlen of bytes to copy, <= 0 for all * @param msg message for use in logging */ apr_status_t h2_util_copy(apr_bucket_brigade *to, apr_bucket_brigade *from, --- apr_size_t maxlen, const char *msg); +++ apr_off_t maxlen, const char *msg); /** * Return != 0 iff there is a FLUSH or EOS bucket in the brigade. @@@@ -100,7 -100,7 -100,7 +114,7 @@@@ * @return != 0 iff brigade holds FLUSH or EOS bucket (or both) */ int h2_util_has_flush_or_eos(apr_bucket_brigade *bb); ---int h2_util_has_eos(apr_bucket_brigade *bb, apr_size_t len); +++int h2_util_has_eos(apr_bucket_brigade *bb, apr_off_t len); int h2_util_bb_has_data(apr_bucket_brigade *bb); int h2_util_bb_has_data_or_eos(apr_bucket_brigade *bb); @@@@ -112,52 -112,13 -112,13 +126,52 @@@@ * @param on return, if eos has been reached */ apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb, --- apr_size_t *plen, int *peos); +++ apr_off_t *plen, int *peos); typedef apr_status_t h2_util_pass_cb(void *ctx, --- const char *data, apr_size_t len); +++ const char *data, apr_off_t len); ++/** ++ * Read at most *plen bytes from the brigade and pass them into the ++ * given callback. If cb is NULL, just return the amount of data that ++ * could have been read. ++ * If an EOS was/would be encountered, set *peos != 0. ++ * @param bb the brigade to read from ++ * @param cb the callback to invoke for the read data ++ * @param ctx optional data passed to callback ++ * @param plen inout, as input gives the maximum number of bytes to read, ++ * on return specifies the actual/would be number of bytes ++ * @param peos != 0 iff an EOS bucket was/would be encountered. ++ */ apr_status_t h2_util_bb_readx(apr_bucket_brigade *bb, h2_util_pass_cb *cb, void *ctx, --- apr_size_t *plen, int *peos); +++ apr_off_t *plen, int *peos); ++ ++/** ++ * Logs the bucket brigade (which bucket types with what length) ++ * to the log at the given level. ++ * @param c the connection to log for ++ * @param stream_id the stream identifier this brigade belongs to ++ * @param level the log level (as in APLOG_*) ++ * @param tag a short message text about the context ++ * @param bb the brigade to log ++ */ ++void h2_util_bb_log(conn_rec *c, int stream_id, int level, ++ const char *tag, apr_bucket_brigade *bb); ++ ++/** ++ * Transfer buckets from one brigade to another with a limit on the ++ * maximum amount of bytes transfered. ++ * @param to brigade to transfer buckets to ++ * @param from brigades to remove buckets from ++ * @param p pool that buckets should be setaside to ++ * @param plen maximum bytes to transfer, actual bytes transferred ++ * @param peos if an EOS bucket was transferred ++ */ ++apr_status_t h2_transfer_brigade(apr_bucket_brigade *to, ++ apr_bucket_brigade *from, ++ apr_pool_t *p, - apr_size_t *plen, +++ apr_off_t *plen, ++ int *peos); #endif /* defined(__mod_h2__h2_util__) */ diff --cc modules/http2/h2_version.h index e72f6628739,7a03865c87c,7a03865c87c..98a431b3bf1 --- a/modules/http2/h2_version.h +++ b/modules/http2/h2_version.h @@@@ -20,7 -20,7 -20,7 +20,7 @@@@ * @macro * Version number of the h2 module as c string */ - #define MOD_HTTP2_VERSION "1.0.3-DEV" --#define MOD_HTTP2_VERSION "1.0.0" +++#define MOD_HTTP2_VERSION "1.0.5-DEV" /** * @macro @@@@ -28,7 -28,7 -28,7 +28,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 0x010003 --#define MOD_HTTP2_VERSION_NUM 0x010000 +++#define MOD_HTTP2_VERSION_NUM 0x010005 #endif /* mod_h2_h2_version_h */ diff --cc modules/http2/h2_worker.c index 297b4b21fe6,8145b7aaa5f,8145b7aaa5f..b11e8549fff --- a/modules/http2/h2_worker.c +++ b/modules/http2/h2_worker.c @@@@ -22,7 -22,7 -22,7 +22,9 @@@@ #include #include "h2_private.h" +++#include "h2_conn.h" #include "h2_mplx.h" +++#include "h2_request.h" #include "h2_task.h" #include "h2_worker.h" @@@@ -55,7 -55,7 -55,7 +57,7 @@@@ static void* APR_THREAD_FUNC execute(ap if (worker->task) { h2_task_do(worker->task, worker); worker->task = NULL; --- apr_thread_cond_signal(h2_worker_get_cond(worker)); +++ apr_thread_cond_signal(worker->io); } } @@@@ -112,7 -99,7 -99,7 +114,6 @@@@ h2_worker *h2_worker_create(int id w->id = id; w->pool = pool; --- w->bucket_alloc = apr_bucket_alloc_create(pool); w->get_next = get_next; w->worker_done = worker_done; @@@@ -123,8 -110,7 -110,7 +124,9 @@@@ return NULL; } - apr_pool_pre_cleanup_register(pool, w, cleanup_join_thread); --- apr_thread_create(&w->thread, attr, execute, w, pool); +++ apr_pool_pre_cleanup_register(w->pool, w, cleanup_join_thread); +++ apr_thread_create(&w->thread, attr, execute, w, w->pool); +++ apr_pool_create(&w->task_pool, w->pool); } return w; } @@@@ -157,28 -143,28 -143,28 +159,42 @@@@ int h2_worker_is_aborted(h2_worker *wor return worker->aborted; } ---apr_thread_t *h2_worker_get_thread(h2_worker *worker) +++h2_task *h2_worker_create_task(h2_worker *worker, h2_mplx *m, +++ const h2_request *req, int eos) { --- return worker->thread; +++ h2_task *task; +++ +++ /* Create a subpool from the worker one to be used for all things +++ * with life-time of this task execution. +++ */ +++ task = h2_task_create(m->id, req, worker->task_pool, m, eos); +++ /* Link the task to the worker which provides useful things such +++ * as mutex, a socket etc. */ +++ task->io = worker->io; +++ +++ return task; } ---apr_thread_cond_t *h2_worker_get_cond(h2_worker *worker) ---{ --- return worker->io; +++apr_status_t h2_worker_setup_task(h2_worker *worker, h2_task *task) { +++ apr_status_t status; +++ +++ +++ status = h2_conn_setup(task, apr_bucket_alloc_create(task->pool), +++ worker->thread, worker->socket); +++ +++ return status; } ---apr_socket_t *h2_worker_get_socket(h2_worker *worker) +++void h2_worker_release_task(h2_worker *worker, struct h2_task *task) { --- return worker->socket; +++ task->io = NULL; +++ task->pool = NULL; +++ apr_pool_clear(worker->task_pool); } ---apr_pool_t *h2_worker_get_pool(h2_worker *worker) +++apr_socket_t *h2_worker_get_socket(h2_worker *worker) { --- return worker->pool; +++ return worker->socket; } ---apr_bucket_alloc_t *h2_worker_get_bucket_alloc(h2_worker *worker) ---{ --- return worker->bucket_alloc; ---} diff --cc modules/http2/h2_worker.h index 9c69e6b57a8,9c69e6b57a8,9c69e6b57a8..035448e5dbb --- a/modules/http2/h2_worker.h +++ b/modules/http2/h2_worker.h @@@@ -18,6 -18,6 -18,6 +18,7 @@@@ struct apr_thread_cond_t; struct h2_mplx; +++struct h2_request; struct h2_task; /* h2_worker is a basically a apr_thread_t that reads fromt he h2_workers @@@@ -44,7 -44,7 -44,7 +45,7 @@@@ struct h2_worker int id; apr_thread_t *thread; apr_pool_t *pool; --- apr_bucket_alloc_t *bucket_alloc; +++ apr_pool_t *task_pool; struct apr_thread_cond_t *io; apr_socket_t *socket; @@@@ -142,14 -142,14 -142,14 +143,11 @@@@ int h2_worker_get_id(h2_worker *worker) int h2_worker_is_aborted(h2_worker *worker); ---apr_pool_t *h2_worker_get_pool(h2_worker *worker); --- ---apr_bucket_alloc_t *h2_worker_get_bucket_alloc(h2_worker *worker); +++struct h2_task *h2_worker_create_task(h2_worker *worker, struct h2_mplx *m, +++ const struct h2_request *req, int eos); +++apr_status_t h2_worker_setup_task(h2_worker *worker, struct h2_task *task); +++void h2_worker_release_task(h2_worker *worker, struct h2_task *task); apr_socket_t *h2_worker_get_socket(h2_worker *worker); ---apr_thread_t *h2_worker_get_thread(h2_worker *worker); --- ---struct apr_thread_cond_t *h2_worker_get_cond(h2_worker *worker); --- #endif /* defined(__mod_h2__h2_worker__) */ diff --cc modules/http2/h2_workers.c index 18f39d136c9,cf3009585b7,cf3009585b7..3c08ff35d1c --- a/modules/http2/h2_workers.c +++ b/modules/http2/h2_workers.c @@@@ -79,7 -63,7 -63,7 +79,7 @@@@ static apr_status_t get_mplx_next(h2_wo if (*pm && ptask != NULL) { /* We have a h2_mplx instance and the worker wants the next task. * Try to get one from the given mplx. */ --- *ptask = h2_mplx_pop_task(*pm, &has_more); +++ *ptask = h2_mplx_pop_task(*pm, worker, &has_more); if (*ptask) { return APR_SUCCESS; } @@@@ -124,7 -108,7 -108,7 +124,7 @@@@ m = H2_MPLX_LIST_FIRST(&workers->mplxs); H2_MPLX_REMOVE(m); --- task = h2_mplx_pop_task(m, &has_more); +++ task = h2_mplx_pop_task(m, worker, &has_more); if (task) { if (has_more) { H2_MPLX_LIST_INSERT_TAIL(&workers->mplxs, m); @@@@ -174,7 -163,7 -163,7 +174,7 @@@@ * needed to give up with more than enough workers. */ if (task) { --- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s, +++ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s, "h2_worker(%d): start task(%s)", h2_worker_get_id(worker), task->id); /* Since we hand out a reference to the worker, we increase @@@@ -326,7 -300,7 -300,7 +326,7 @@@@ apr_status_t h2_workers_register(h2_wor { apr_status_t status = apr_thread_mutex_lock(workers->lock); if (status == APR_SUCCESS) { --- ap_log_error(APLOG_MARK, APLOG_DEBUG, status, workers->s, +++ ap_log_error(APLOG_MARK, APLOG_TRACE2, status, workers->s, "h2_workers: register mplx(%ld)", m->id); if (in_list(workers, m)) { status = APR_EAGAIN; diff --cc modules/http2/mod_http2.dsp index fbff711df77,c3e139e9553,c3e139e9553..12a3abfe98c --- a/modules/http2/mod_http2.dsp +++ b/modules/http2/mod_http2.dsp @@@@ -149,6 -141,6 -141,6 +149,10 @@@@ SOURCE=./h2_mplx. # End Source File # Begin Source File +++SOURCE=./h2_push.c +++# End Source File +++# Begin Source File +++ SOURCE=./h2_request.c # End Source File # Begin Source File @@@@ -189,10 -181,10 -181,10 +193,6 @@@@ SOURCE=./h2_task_queue. # End Source File # Begin Source File ---SOURCE=./h2_to_h1.c ---# End Source File ---# Begin Source File --- SOURCE=./h2_util.c # End Source File # Begin Source File