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
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
--- /dev/null
--- /dev/null
- h2_session_cleanup(session);
++/* Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#include <assert.h>
++#include <stddef.h>
++
++#include <httpd.h>
++#include <http_core.h>
++#include <http_connection.h>
++#include <http_log.h>
++
++#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_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
++};
++
--- /dev/null
--- /dev/null
++/* Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#include <assert.h>
++#include <stddef.h>
++
++#include <httpd.h>
++#include <http_core.h>
++#include <http_connection.h>
++#include <http_log.h>
++
++#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
++};
++
#include "h2_ctx.h"
#include "h2_conn.h"
#include "h2_config.h"
+++#include "h2_h2.h"
#include "h2_private.h"
#define DEF_VAL (-1)
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;
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;
}
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;
}
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;
}
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,
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
};
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. */
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;
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)
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)
{
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
* 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
#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
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__) */
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);
- status = h2_conn_io_flush(io);
++
++ if (!APR_BRIGADE_EMPTY(io->output)) {
+++ 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) {
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)
{
- int flush_now = 0;
++ apr_status_t status = APR_SUCCESS;
- * As long as we do not have found out the "best" way to deal with
++
++ /* 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.
- * of data which seems to work nicely.
+++ * 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.
++ */
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);
+++}
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;
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__) */
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) {
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;
}
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') {
}
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");
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__) */
static int (*opt_ssl_engine_disable)(conn_rec*);
static int (*opt_ssl_is_https)(conn_rec*);
- const char *h2_h2_err_description(int h2_error)
++/*******************************************************************************
++ * 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",
++};
++
- if (h2_error >= 0
- && h2_error < (sizeof(h2_err_descr)/sizeof(h2_err_descr[0]))) {
+++const char *h2_h2_err_description(unsigned int h2_error)
++{
- int i;
+++ 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;
+++ 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
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));
}
/*******************************************************************************
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");
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
*/
extern const char *H2_MAGIC_TOKEN;
- const char *h2_h2_err_description(int h2_error);
++#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(unsigned int h2_error);
++
/*
* One time, post config intialization.
*/
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;
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;
}
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.
*
* 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 */
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;
}
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;
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;
};
*/
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);
* 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__) */
#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) {
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);
}
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) {
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) {
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);
}
}
--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;
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);
}
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 {
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);
}
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);
}
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);
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);
- apr_size_t *plen, int *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,
- if (io) {
+++ 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 && !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);
}
}
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);
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);
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;
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
return status;
}
- if (io && !io->rst_error) {
++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 && !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;
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);
}
}
}
- 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;
}
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;
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;
******************************************************************************/
/**
-- * 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.
* 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
*/
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.
--- /dev/null
--- /dev/null
--- /dev/null
+++/* 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 <assert.h>
+++#include <stdio.h>
+++
+++#include <apr_strings.h>
+++#include <apr_lib.h>
+++
+++#include <httpd.h>
+++#include <http_core.h>
+++#include <http_log.h>
+++
+++#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 <https://tools.ietf.org/html/rfc5988#section-6.2.1>
+++ 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 <https://tools.ietf.org/html/rfc5987>
+++ 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;
+++}
--- /dev/null
--- /dev/null
--- /dev/null
+++/* 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__) */
#include <httpd.h>
#include <http_core.h>
+++#include <http_protocol.h>
#include <http_config.h>
#include <http_log.h>
#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;
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);
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);
}
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;
}
/* 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;
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__) */
#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)
{
}
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) {
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;
header = apr_table_make(pool, 0);
}
--- response->rheader = header;
+++ response->header = header;
return response;
}
}
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;
}
{
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;
---}
#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);
#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"
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.
*/
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,
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;
}
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)
{
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,
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)
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;
}
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,
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];
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);
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,
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;
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)
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;
}
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)
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);
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,
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);
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);
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));
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);
"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);
}
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 */
}
/* 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",
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,
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;
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
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,
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;
(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);
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);
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];
}
}
+++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;
+++}
struct apr_thread_cond_t;
struct h2_config;
struct h2_mplx;
+++struct h2_push;
struct h2_response;
struct h2_session;
struct h2_stream;
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 */
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 */
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.
*/
void h2_session_destroy(h2_session *session);
- void h2_session_cleanup(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_eoc_callback(h2_session *session);
++
/**
* Called once at start of session.
* Sets up the session and sends the initial SETTINGS frame.
*/
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
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. */
/* 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__) */
#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"
#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;
--h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_mplx *m)
+++ 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 *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);
+++static int close_input(h2_stream *stream)
+ {
-- return 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;
+ }
--static void h2_stream_cleanup(h2_stream *stream)
+++ return 1;
+ }
+
-- if (stream->request) {
-- h2_request_destroy(stream->request);
-- stream->request = NULL;
+++static int input_closed(h2_stream *stream)
+ {
+++ 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;
}
}
- 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,
++h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session)
++{
++ h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));
- 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);
- }
+++ 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);
+++
+++ 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);
}
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);
- if (stream->rst_error) {
+++ AP_DEBUG_ASSERT(stream->session);
+++ AP_DEBUG_ASSERT(stream->session->mplx);
++
+++ 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);
}
- "h2_mplx(%ld-%d): start stream, task %s %s (%s)",
+++
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c,
- stream->request->method, stream->request->path,
- stream->request->authority);
+++ "h2_stream(%ld-%d): scheduled %s %s://%s%s",
++ stream->session->id, stream->id,
+++ 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_size_t *plen, int *peos)
++apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb,
- apr_size_t tlen = *plen;
+++ 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_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)
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;
+++}
/**
* 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"
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);
- void h2_stream_rst(h2_stream *streamm, int error_code);
-
+++/**
+++ * Removes stream from h2_session and destroys it.
+++ *
+++ * @param stream the stream to cleanup
+++ */
++void h2_stream_cleanup(h2_stream *stream);
++
+++/**
+++ * 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);
- apr_size_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_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__) */
#include <assert.h>
#include <stddef.h>
+++#include <apr_hash.h>
#include <apr_strings.h>
#include <httpd.h>
---#include <http_core.h>
---#include <http_connection.h>
#include <http_log.h>
#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;
}
(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;
}
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__) */
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,
#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"
}
---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;
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;
}
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);
/* 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->method, task->path, r->protocol);
++
++ r->the_request = apr_psprintf(r->pool, "%s %s %s",
+++ 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.
* 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);
struct h2_conn;
struct h2_mplx;
struct h2_task;
+++struct h2_request;
struct h2_resp_head;
struct h2_worker;
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__) */
#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"
{
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));
}
}
/* 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;
}
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) {
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",
#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"
#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;
}
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;
}
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);
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);
}
- q->elts[i] = task;
++
++ i = (q->head + q->nelts) % q->nalloc;
+++ 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;
}
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.
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__) */
#include <nghttp2/nghttp2.h>
#include "h2_private.h"
+++#include "h2_request.h"
#include "h2_util.h"
size_t h2_util_hex_dump(char *buffer, size_t maxlen,
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
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)
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));
#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;
}
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;
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;
}
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;
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
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;
return status;
}
- apr_size_t *plen,
++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 len = 0, remain = *plen;
+++ apr_off_t *plen,
++ int *peos)
++{
++ apr_bucket *e;
+++ 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;
+++}
+++
#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,
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);
/**
* 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.
* @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);
* @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__) */
* @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
* 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 */
#include <http_log.h>
#include "h2_private.h"
+++#include "h2_conn.h"
#include "h2_mplx.h"
+++#include "h2_request.h"
#include "h2_task.h"
#include "h2_worker.h"
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);
}
}
w->id = id;
w->pool = pool;
--- w->bucket_alloc = apr_bucket_alloc_create(pool);
w->get_next = get_next;
w->worker_done = worker_done;
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;
}
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;
---}
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
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;
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__) */
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;
}
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);
* 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
{
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;
# 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
# 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