]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
latest changes from trunk re mod_http2
authorStefan Eissing <icing@apache.org>
Thu, 19 Nov 2015 17:14:03 +0000 (17:14 +0000)
committerStefan Eissing <icing@apache.org>
Thu, 19 Nov 2015 17:14:03 +0000 (17:14 +0000)
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4-http2-alpha@1715218 13f79535-47bb-0310-9956-ffa450edef68

45 files changed:
1  2  3 
CHANGES
modules/http2/config.m4
modules/http2/h2_bucket_eoc.c
modules/http2/h2_bucket_eos.c
modules/http2/h2_config.c
modules/http2/h2_config.h
modules/http2/h2_conn.c
modules/http2/h2_conn.h
modules/http2/h2_conn_io.c
modules/http2/h2_conn_io.h
modules/http2/h2_from_h1.c
modules/http2/h2_from_h1.h
modules/http2/h2_h2.c
modules/http2/h2_h2.h
modules/http2/h2_io.c
modules/http2/h2_io.h
modules/http2/h2_mplx.c
modules/http2/h2_mplx.h
modules/http2/h2_push.c
modules/http2/h2_push.h
modules/http2/h2_request.c
modules/http2/h2_request.h
modules/http2/h2_response.c
modules/http2/h2_response.h
modules/http2/h2_session.c
modules/http2/h2_session.h
modules/http2/h2_stream.c
modules/http2/h2_stream.h
modules/http2/h2_stream_set.c
modules/http2/h2_stream_set.h
modules/http2/h2_switch.c
modules/http2/h2_task.c
modules/http2/h2_task.h
modules/http2/h2_task_input.c
modules/http2/h2_task_output.c
modules/http2/h2_task_output.h
modules/http2/h2_task_queue.c
modules/http2/h2_task_queue.h
modules/http2/h2_util.c
modules/http2/h2_util.h
modules/http2/h2_version.h
modules/http2/h2_worker.c
modules/http2/h2_worker.h
modules/http2/h2_workers.c
modules/http2/mod_http2.dsp

diff --cc CHANGES
Simple merge
index 35b25e11a3addedf0a3fc9e6520fdb413573888c,9c5eb86740e9a30820a34515be0e7356e79eab50,9c5eb86740e9a30820a34515be0e7356e79eab50..f383b3cd83853f79163b0271bffc6169222d028b
@@@@ -31,6 -29,6 -29,6 +31,7 @@@@ h2_h2.lo dn
   h2_io.lo dnl
   h2_io_set.lo dnl
   h2_mplx.lo dnl
+++h2_push.lo dnl
   h2_request.lo dnl
   h2_response.lo dnl
   h2_session.lo dnl
@@@@ -41,7 -39,7 -39,7 +42,6 @@@@ h2_task.lo dn
   h2_task_input.lo dnl
   h2_task_output.lo dnl
   h2_task_queue.lo dnl
---h2_to_h1.lo dnl
   h2_util.lo dnl
   h2_worker.lo dnl
   h2_workers.lo dnl
index eccc067805366e2a7206b880332c59adc3663e5b,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8b145cf29edf778b2ef978fa1839c17cf1ce4b2c
mode 100644,000000,000000..100644
--- /dev/null
--- /dev/null
@@@@ -1,106 -1,0 -1,0 +1,108 @@@@
-              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
 ++};
 ++
index 029cd1034e34de04f53782fe89c69c9331a8a20f,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..98a0b365c689899f3ed14097a9337e8e665e540f
mode 100644,000000,000000..100644
--- /dev/null
--- /dev/null
@@@@ -1,107 -1,0 -1,0 +1,109 @@@@
 ++/* Licensed to the Apache Software Foundation (ASF) under one or more
 ++ * contributor license agreements.  See the NOTICE file distributed with
 ++ * this work for additional information regarding copyright ownership.
 ++ * The ASF licenses this file to You under the Apache License, Version 2.0
 ++ * (the "License"); you may not use this file except in compliance with
 ++ * the License.  You may obtain a copy of the License at
 ++ *
 ++ *     http://www.apache.org/licenses/LICENSE-2.0
 ++ *
 ++ * Unless required by applicable law or agreed to in writing, software
 ++ * distributed under the License is distributed on an "AS IS" BASIS,
 ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ++ * See the License for the specific language governing permissions and
 ++ * limitations under the License.
 ++ */
 ++
 ++#include <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
 ++};
 ++
index 25fdd29908f4bbba769c1da012fec6bb900468f5,6db702eca80345a997420b4ae62c6b5b78fbd721,6db702eca80345a997420b4ae62c6b5b78fbd721..7ac4297b32868649099a88d79ec7b6b8a6e22ab4
   #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;
@@@@ -104,11 -100,6 -100,6 +106,12 @@@@ static void *h2_config_create(apr_pool_
       conf->serialize_headers    = DEF_VAL;
       conf->h2_direct            = DEF_VAL;
       conf->session_extra_files  = DEF_VAL;
 ++    conf->modern_tls_only      = DEF_VAL;
 ++    conf->h2_upgrade           = DEF_VAL;
 ++    conf->tls_warmup_size      = DEF_VAL;
 ++    conf->tls_cooldown_secs    = DEF_VAL;
+++    conf->h2_push              = DEF_VAL;
 ++    
       return conf;
   }
   
@@@@ -136,21 -127,17 -127,17 +139,22 @@@@ void *h2_config_merge(apr_pool_t *pool
       strcat(name, "]");
       n->name = name;
   
 --    n->h2_max_streams = H2_CONFIG_GET(add, base, h2_max_streams);
 --    n->h2_window_size = H2_CONFIG_GET(add, base, h2_window_size);
 --    n->min_workers    = H2_CONFIG_GET(add, base, min_workers);
 --    n->max_workers    = H2_CONFIG_GET(add, base, max_workers);
 ++    n->h2_max_streams       = H2_CONFIG_GET(add, base, h2_max_streams);
 ++    n->h2_window_size       = H2_CONFIG_GET(add, base, h2_window_size);
 ++    n->min_workers          = H2_CONFIG_GET(add, base, min_workers);
 ++    n->max_workers          = H2_CONFIG_GET(add, base, max_workers);
       n->max_worker_idle_secs = H2_CONFIG_GET(add, base, max_worker_idle_secs);
 --    n->stream_max_mem_size = H2_CONFIG_GET(add, base, stream_max_mem_size);
 --    n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs;
 --    n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age);
 --    n->serialize_headers = H2_CONFIG_GET(add, base, serialize_headers);
 --    n->h2_direct      = H2_CONFIG_GET(add, base, h2_direct);
 --    n->session_extra_files = H2_CONFIG_GET(add, base, session_extra_files);
 ++    n->stream_max_mem_size  = H2_CONFIG_GET(add, base, stream_max_mem_size);
 ++    n->alt_svcs             = add->alt_svcs? add->alt_svcs : base->alt_svcs;
 ++    n->alt_svc_max_age      = H2_CONFIG_GET(add, base, alt_svc_max_age);
 ++    n->serialize_headers    = H2_CONFIG_GET(add, base, serialize_headers);
 ++    n->h2_direct            = H2_CONFIG_GET(add, base, h2_direct);
 ++    n->session_extra_files  = H2_CONFIG_GET(add, base, session_extra_files);
 ++    n->modern_tls_only      = H2_CONFIG_GET(add, base, modern_tls_only);
 ++    n->h2_upgrade           = H2_CONFIG_GET(add, base, h2_upgrade);
 ++    n->tls_warmup_size      = H2_CONFIG_GET(add, base, tls_warmup_size);
 ++    n->tls_cooldown_secs    = H2_CONFIG_GET(add, base, tls_cooldown_secs);
+++    n->h2_push              = H2_CONFIG_GET(add, base, h2_push);
       
       return n;
   }
@@@@ -192,10 -170,6 -170,6 +196,12 @@@@ apr_int64_t h2_config_geti64(h2_config 
                   n = files_per_session;
               }
               return n;
 ++        case H2_CONF_TLS_WARMUP_SIZE:
 ++            return H2_CONFIG_GET(conf, &defconf, tls_warmup_size);
 ++        case H2_CONF_TLS_COOLDOWN_SECS:
 ++            return H2_CONFIG_GET(conf, &defconf, tls_cooldown_secs);
+++        case H2_CONF_PUSH:
+++            return H2_CONFIG_GET(conf, &defconf, h2_push);
           default:
               return DEF_VAL;
       }
@@@@ -358,60 -332,8 -332,8 +364,77 @@@@ static const char *h2_conf_set_direct(c
       return "value must be On or Off";
   }
   
 --#define AP_END_CMD     AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
+++static const char *h2_conf_set_push(cmd_parms *parms,
+++                                    void *arg, const char *value)
+++{
+++    h2_config *cfg = h2_config_sget(parms->server);
+++    if (!strcasecmp(value, "On")) {
+++        cfg->h2_push = 1;
+++        return NULL;
+++    }
+++    else if (!strcasecmp(value, "Off")) {
+++        cfg->h2_push = 0;
+++        return NULL;
+++    }
+++    
+++    (void)arg;
+++    return "value must be On or Off";
+++}
+++
 ++static const char *h2_conf_set_modern_tls_only(cmd_parms *parms,
 ++                                               void *arg, const char *value)
 ++{
 ++    h2_config *cfg = h2_config_sget(parms->server);
 ++    if (!strcasecmp(value, "On")) {
 ++        cfg->modern_tls_only = 1;
 ++        return NULL;
 ++    }
 ++    else if (!strcasecmp(value, "Off")) {
 ++        cfg->modern_tls_only = 0;
 ++        return NULL;
 ++    }
 ++    
 ++    (void)arg;
 ++    return "value must be On or Off";
 ++}
 ++
 ++static const char *h2_conf_set_upgrade(cmd_parms *parms,
 ++                                       void *arg, const char *value)
 ++{
 ++    h2_config *cfg = h2_config_sget(parms->server);
 ++    if (!strcasecmp(value, "On")) {
 ++        cfg->h2_upgrade = 1;
 ++        return NULL;
 ++    }
 ++    else if (!strcasecmp(value, "Off")) {
 ++        cfg->h2_upgrade = 0;
 ++        return NULL;
 ++    }
 ++    
 ++    (void)arg;
 ++    return "value must be On or Off";
 ++}
 ++
 ++static const char *h2_conf_set_tls_warmup_size(cmd_parms *parms,
 ++                                               void *arg, const char *value)
 ++{
 ++    h2_config *cfg = h2_config_sget(parms->server);
 ++    cfg->tls_warmup_size = apr_atoi64(value);
 ++    (void)arg;
 ++    return NULL;
 ++}
   
 ++static const char *h2_conf_set_tls_cooldown_secs(cmd_parms *parms,
 ++                                                 void *arg, const char *value)
 ++{
 ++    h2_config *cfg = h2_config_sget(parms->server);
 ++    cfg->tls_cooldown_secs = (int)apr_atoi64(value);
 ++    (void)arg;
 ++    return NULL;
 ++}
 ++
 ++
 ++#define AP_END_CMD     AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
   
   const command_rec h2_cmds[] = {
       AP_INIT_TAKE1("H2MaxSessionStreams", h2_conf_set_max_streams, NULL,
                     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
   };
   
index 9c59817a992b153fab52ddd3e8eac3af52473111,931af59d302145819c7fe463ae760dad282b56ae,931af59d302145819c7fe463ae760dad282b56ae..7d2ed0fa28e1ee5c7479d731b3afe10d9ecdeae4
@@@@ -34,10 -34,6 -34,6 +34,11 @@@@ typedef enum 
       H2_CONF_SER_HEADERS,
       H2_CONF_DIRECT,
       H2_CONF_SESSION_FILES,
 ++    H2_CONF_MODERN_TLS_ONLY,
 ++    H2_CONF_UPGRADE,
 ++    H2_CONF_TLS_WARMUP_SIZE,
 ++    H2_CONF_TLS_COOLDOWN_SECS,
+++    H2_CONF_PUSH,
   } h2_config_var_t;
   
   /* Apache httpd module configuration for h2. */
@@@@ -55,10 -51,6 -51,6 +56,11 @@@@ typedef struct h2_config 
                                        processing, better compatibility */
       int h2_direct;                /* if mod_h2 is active directly */
       int session_extra_files;      /* # of extra files a session may keep open */  
 ++    int modern_tls_only;          /* Accept only modern TLS in HTTP/2 connections */  
 ++    int h2_upgrade;               /* Allow HTTP/1 upgrade to h2/h2c */
 ++    apr_int64_t tls_warmup_size;  /* Amount of TLS data to send before going full write size */
 ++    int tls_cooldown_secs;        /* Seconds of idle time before going back to small TLS records */
+++    int h2_push;                  /* if HTTP/2 server push is enabled */
   } h2_config;
   
   
index 877447e2ffeb6d5772f1f73b7aa00879923f2df3,8bffbc42693cad31e50ff0b3b86e15046c9922da,8bffbc42693cad31e50ff0b3b86e15046c9922da..d4f56c66306846cce865134cdaefef7ce21764e9
   
   static struct h2_workers *workers;
   
-  static apr_status_t h2_conn_loop(h2_session *session);
 --static apr_status_t h2_session_process(h2_session *session);
---
   static h2_mpm_type_t mpm_type = H2_MPM_UNKNOWN;
   static module *mpm_module;
 --static module *ssl_module;
   static int checked;
   
   static void check_modules(void) 
@@@@ -133,246 -139,228 -139,228 +131,70 @@@@ static module *h2_conn_mpm_module(void
       return mpm_module;
   }
   
---apr_status_t h2_conn_rprocess(request_rec *r)
+++apr_status_t h2_conn_process(conn_rec *c, request_rec *r)
   {
---    h2_config *config = h2_config_rget(r);
+++    apr_status_t status;
       h2_session *session;
+++    h2_config *config;
+++    int rv;
       
---    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "h2_conn_process start");
       if (!workers) {
---        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02911) 
+++        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911) 
                         "workers not initialized");
           return APR_EGENERAL;
       }
       
---    session = h2_session_rcreate(r, config, workers);
---    if (!session) {
---        return APR_EGENERAL;
---    }
---    
-      return h2_conn_loop(session);
 --    return h2_session_process(session);
---}
---
---apr_status_t h2_conn_main(conn_rec *c)
---{
---    h2_config *config = h2_config_get(c);
---    h2_session *session;
---    apr_status_t status;
+++    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "h2_conn_process start");
       
---    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "h2_conn_main start");
---    if (!workers) {
---        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02912) 
---                      "workers not initialized");
---        return APR_EGENERAL;
+++    if (r) {
+++        config = h2_config_rget(r);
+++        session = h2_session_rcreate(r, config, workers);
       }
---    
---    session = h2_session_create(c, config, workers);
---    if (!session) {
---        return APR_EGENERAL;
+++    else {
+++        config = h2_config_get(c);
+++        session = h2_session_create(c, config, workers);
       }
       
 --    status = h2_session_process(session);
 --
 --    /* Make sure this connection gets closed properly. */
 --    c->keepalive = AP_CONN_CLOSE;
 --    if (c->cs) {
 --        c->cs->state = CONN_STATE_WRITE_COMPLETION;
 --    }
 ++    if (!h2_is_acceptable_connection(c, 1)) {
 ++        nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 0,
 ++                              NGHTTP2_INADEQUATE_SECURITY, NULL, 0);
 ++    } 
   
-      status = h2_conn_loop(session);
-  
-      /* Make sure this connection gets closed properly. */
-      c->keepalive = AP_CONN_CLOSE;
-      if (c->cs) {
-          c->cs->state = CONN_STATE_WRITE_COMPLETION;
-      }
-  
---    return status;
---}
---
-  static apr_status_t h2_conn_loop(h2_session *session)
 --apr_status_t h2_session_process(h2_session *session)
---{
---    apr_status_t status = APR_SUCCESS;
---    int rv = 0;
---    apr_interval_time_t wait_micros = 0;
---    static const int MAX_WAIT_MICROS = 200 * 1000;
---    
---    /* Start talking to the client. Apart from protocol meta data,
---     * we mainly will see new http/2 streams opened by the client, which
---     * basically are http requests we need to dispatch.
---     *
---     * There will be bursts of new streams, to be served concurrently,
---     * followed by long pauses of no activity.
---     *
---     * Since the purpose of http/2 is to allow siumultaneous streams, we
---     * need to dispatch the handling of each stream into a separate worker
---     * thread, keeping this thread open for sending responses back as
---     * soon as they arrive.
---     * At the same time, we need to continue reading new frames from
---     * our client, which may be meta (WINDOWS_UPDATEs, PING, SETTINGS) or
---     * new streams.
---     *
---     * As long as we have streams open in this session, we cannot really rest
---     * since there are two conditions to wait on: 1. new data from the client,
---     * 2. new data from the open streams to send back.
---     *
---     * Only when we have no more streams open, can we do a blocking read
---     * on our connection.
---     *
---     * TODO: implement graceful GO_AWAY after configurable idle time
---     */
---    
---    ap_update_child_status_from_conn(session->c->sbh, SERVER_BUSY_READ, 
---                                     session->c);
---
---    if (APLOGctrace2(session->c)) {
---        ap_filter_t *filter = session->c->input_filters;
---        while (filter) {
---            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
---                          "h2_conn(%ld), has connection filter %s",
---                          session->id, filter->frec->name);
---            filter = filter->next;
---        }
---    }
---    
+++    ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c);
       status = h2_session_start(session, &rv);
       
       ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
                     "h2_session(%ld): starting on %s:%d", session->id,
---                  session->c->base_server->defn_name,
+++                  session->c->base_server->server_hostname,
                     session->c->local_addr->port);
       if (status != APR_SUCCESS) {
           h2_session_abort(session, status, rv);
-          h2_session_cleanup(session);
 --        h2_session_destroy(session);
+++        h2_session_eoc_callback(session);
           return status;
       }
       
---    while (!h2_session_is_done(session)) {
---        int have_written = 0;
---        int have_read = 0;
---        int got_streams;
---        
---        status = h2_session_write(session, wait_micros);
---        if (status == APR_SUCCESS) {
---            have_written = 1;
---            wait_micros = 0;
---        }
-          else if (APR_STATUS_IS_EAGAIN(status)) {
 --        else if (status == APR_EAGAIN) {
---            /* nop */
---        }
---        else if (status == APR_TIMEUP) {
---            wait_micros *= 2;
---            if (wait_micros > MAX_WAIT_MICROS) {
---                wait_micros = MAX_WAIT_MICROS;
---            }
---        }
---        else {
---            ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
---                          "h2_session(%ld): writing, terminating",
---                          session->id);
---            h2_session_abort(session, status, 0);
---            break;
---        }
---        
---        /* We would like to do blocking reads as often as possible as they
---         * are more efficient in regard to server resources.
---         * We can do them under the following circumstances:
---         * - we have no open streams and therefore have nothing to write
---         * - we have just started the session and are waiting for the first
---         *   two frames to come in. There will always be at least 2 frames as
---         *   * h2 will send SETTINGS and SETTINGS-ACK
---         *   * h2c will count the header settings as one frame and we
---         *     submit our settings and need the ACK.
---         */
---        got_streams = !h2_stream_set_is_empty(session->streams);
-          if (!got_streams || session->frames_received <= 1) {
-              if (session->c->cs) {
-                  session->c->cs->state = CONN_STATE_WRITE_COMPLETION;
-              }
-              status = h2_session_read(session, APR_BLOCK_READ);
-          }
-          else {
-              if (session->c->cs) {
-                  session->c->cs->state = CONN_STATE_HANDLER;
-              }
-              status = h2_session_read(session, APR_NONBLOCK_READ);
-          }
-          
 --        status = h2_session_read(session, 
 --                                 (!got_streams 
 --                                  || session->frames_received <= 1)?
 --                                 APR_BLOCK_READ : APR_NONBLOCK_READ);
---        switch (status) {
---            case APR_SUCCESS:       /* successful read, reset our idle timers */
---                have_read = 1;
---                wait_micros = 0;
---                break;
---            case APR_EAGAIN:              /* non-blocking read, nothing there */
 --                break;
 --            case APR_EBADF:               /* connection is not there any more */
 --            case APR_EOF:
 --            case APR_ECONNABORTED:
 --            case APR_ECONNRESET:
 --            case APR_TIMEUP:                       /* blocked read, timed out */
 --                ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
 --                              "h2_session(%ld): reading",
 --                              session->id);
 --                h2_session_abort(session, status, 0);
---                break;
---            default:
-                  if (APR_STATUS_IS_ETIMEDOUT(status)
-                      || APR_STATUS_IS_ECONNABORTED(status)
-                      || APR_STATUS_IS_ECONNRESET(status)
-                      || APR_STATUS_IS_EOF(status)
-                      || APR_STATUS_IS_EBADF(status)) {
-                      /* common status for a client that has left */
-                      ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
-                                    "h2_session(%ld): terminating",
-                                    session->id);
-                      /* Stolen from mod_reqtimeout to speed up lingering when
-                       * a read timeout happened.
-                       */
-                      apr_table_setn(session->c->notes, "short-lingering-close", "1");
-                  }
-                  else {
-                      /* uncommon status, log on INFO so that we see this */
-                      ap_log_cerror( APLOG_MARK, APLOG_INFO, status, session->c,
-                                    APLOGNO(02950) 
-                                    "h2_session(%ld): error reading, terminating",
-                                    session->id);
-                  }
 --                ap_log_cerror( APLOG_MARK, APLOG_INFO, status, session->c,
 --                              APLOGNO(02950) 
 --                              "h2_session(%ld): error reading, terminating",
 --                              session->id);
---                h2_session_abort(session, status, 0);
---                break;
---        }
---        
---        if (!have_read && !have_written
---            && !h2_stream_set_is_empty(session->streams)) {
---            /* Nothing to read or write, we have streams, but
---             * the have no data yet ready to be delivered. Slowly
---             * back off to give others a chance to do their work.
---             */
---            if (wait_micros == 0) {
---                wait_micros = 10;
---            }
---        }
---    }
---    
+++    status = h2_session_process(session);
+++
       ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
                     "h2_session(%ld): done", session->id);
---    
 --    ap_update_child_status_from_conn(session->c->sbh, SERVER_CLOSING, 
 --                                     session->c);
 --
       h2_session_close(session);
-      ap_update_child_status_from_conn(session->c->sbh, SERVER_CLOSING, 
-                                       session->c);
-      return DONE;
 --    h2_session_destroy(session);
+++    h2_session_flush(session);
+++    /* hereafter session might be gone */
+      
 --    return DONE;
+++    /* Make sure this connection gets closed properly. */
+++    ap_update_child_status_from_conn(c->sbh, SERVER_CLOSING, c);
+++    c->keepalive = AP_CONN_CLOSE;
+++    if (c->cs) {
+++        c->cs->state = CONN_STATE_WRITE_COMPLETION;
+++    }
+++
+++    return status;
   }
   
   
   static void fix_event_conn(conn_rec *c, conn_rec *master);
   
---/*
--- * We would like to create the connection more lightweight like
--- * slave connections in 2.5-DEV. But we get 500 responses on long
--- * cgi tests in modules/h2.t as the script parsing seems to see an
--- * EOF from the cgi before anything is sent. 
--- *
---conn_rec *h2_conn_create(conn_rec *master, apr_pool_t *pool)
---{
---    conn_rec *c = (conn_rec *) apr_palloc(pool, sizeof(conn_rec));
---    
---    memcpy(c, master, sizeof(conn_rec));
---    c->id = (master->id & (long)pool);
---    c->slaves = NULL;
---    c->master = master;
---    c->input_filters = NULL;
---    c->output_filters = NULL;
---    c->pool = pool;
---    
---    return c;
---}
---*/
+++static int SLAVE_CONN_25DEV_STYLE = 1;
   
   conn_rec *h2_conn_create(conn_rec *master, apr_pool_t *pool)
   {
       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 
index 752f28a74cba78be1e812aaa1bbf394b9eb6f2a7,49a70db85075358a5950935a61c2e6d205aee500,49a70db85075358a5950935a61c2e6d205aee500..84cf8d83c407e2f6164e9a68935500c0e972f5fc
   #define __mod_h2__h2_conn__
   
   struct h2_task;
 --struct h2_task_env;
---struct h2_worker;
   
---/* Process the connection that is now starting the HTTP/2
--- * conversation. Return when the HTTP/2 session is done
--- * and the connection will close.
--- */
---apr_status_t h2_conn_main(conn_rec *c);
---
---/* Process the request that has been upgraded to a HTTP/2
+++/**
+++ * Process the connection that is now starting the HTTP/2
    * conversation. Return when the HTTP/2 session is done
    * and the connection will close.
+++ *
+++ * @param c the connection HTTP/2 is starting on
+++ * @param r the upgrad requestion that still awaits an answer, optional
    */
---apr_status_t h2_conn_rprocess(request_rec *r);
+++apr_status_t h2_conn_process(conn_rec *c, request_rec *r);
   
   /* Initialize this child process for h2 connection work,
    * to be called once during child init before multi processing
@@@@ -51,9 -52,9 -52,9 +48,7 @@@@ h2_mpm_type_t h2_conn_mpm_type(void)
   
   conn_rec *h2_conn_create(conn_rec *master, apr_pool_t *stream_pool);
   
-  apr_status_t h2_conn_setup(struct h2_task *task, struct h2_worker *worker);
 --apr_status_t h2_conn_setup(struct h2_task_env *env, struct h2_worker *worker);
---apr_status_t h2_conn_post(conn_rec *c, struct h2_worker *worker);
---
---apr_status_t h2_conn_process(conn_rec *c, apr_socket_t *socket);
+++apr_status_t h2_conn_setup(struct h2_task *task, apr_bucket_alloc_t *bucket_alloc,
+++                           apr_thread_t *thread, apr_socket_t *socket);
   
   #endif /* defined(__mod_h2__h2_conn__) */
index 0299343063cf6b9f2caf69888bbad4e6dc2c3f2a,08d00a4f3a6924e903d853e4d54f111a918ac5e1,08d00a4f3a6924e903d853e4d54f111a918ac5e1..aa8d4d580285df74038cd74805afb9ca1e6f63db
@@@@ -271,17 -238,11 -238,11 +271,17 @@@@ apr_status_t h2_conn_io_write(h2_conn_i
                                 const char *buf, size_t length)
   {
       apr_status_t status = APR_SUCCESS;
 --    io->unflushed = 1;
       
 --    if (io->buffer_output) {
 ++    io->unflushed = 1;
 ++    if (io->bufsize > 0) {
           ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
                         "h2_conn_io: buffering %ld bytes", (long)length);
-              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);
+++}
index c5a861605c67adcb2301adb62a6bdac27024df06,084445ef2b00d317b528faca30fd8356e4cd3c7f,084445ef2b00d317b528faca30fd8356e4cd3c7f..4406261a33b1f594428582cfbe34c4255f869c92
@@@@ -26,16 -26,11 -26,11 +26,16 @@@@ typedef struct 
       conn_rec *connection;
       apr_bucket_brigade *input;
       apr_bucket_brigade *output;
 --    int buffer_output;
 --    int write_size;
 ++
 ++    int is_tls;
 ++    apr_time_t cooldown_usecs;
 ++    apr_int64_t warmup_size;
 ++    
-      int write_size;
+++    apr_size_t write_size;
       apr_time_t last_write;
 --    apr_size_t bytes_written;
 ++    apr_int64_t bytes_written;
       
 ++    int buffer_output;
       char *buffer;
       apr_size_t buflen;
       apr_size_t bufsize;
@@@@ -58,11 -52,7 -52,7 +58,12 @@@@ apr_status_t h2_conn_io_read(h2_conn_i
   apr_status_t h2_conn_io_write(h2_conn_io *io,
                            const char *buf,
                            size_t length);
 ++                         
 ++apr_status_t h2_conn_io_writeb(h2_conn_io *io, apr_bucket *b);
 ++
 ++apr_status_t h2_conn_io_consider_flush(h2_conn_io *io);
   
+++apr_status_t h2_conn_io_pass(h2_conn_io *io);
   apr_status_t h2_conn_io_flush(h2_conn_io *io);
   
   #endif /* defined(__mod_h2__h2_conn_io__) */
index c763ca56ea24c849275ebb818fba73d3a96cabaf,be11f5c317e9f742809aadbf320e715e6f5ab576,be11f5c317e9f742809aadbf320e715e6f5ab576..43a4f0822b3ced81769dba92de441bb832995c04
@@@@ -59,11 -59,11 -59,11 +59,6 @@@@ apr_status_t h2_from_h1_destroy(h2_from
       return APR_SUCCESS;
   }
   
---h2_from_h1_state_t h2_from_h1_get_state(h2_from_h1 *from_h1)
---{
---    return from_h1->state;
---}
---
   static void set_state(h2_from_h1 *from_h1, h2_from_h1_state_t state)
   {
       if (from_h1->state != state) {
@@@@ -78,16 -78,16 -78,16 +73,9 @@@@ h2_response *h2_from_h1_get_response(h2
   
   static apr_status_t make_h2_headers(h2_from_h1 *from_h1, request_rec *r)
   {
 --    from_h1->response = h2_response_create(from_h1->stream_id, 
 --                                       from_h1->status, from_h1->hlines,
 --                                       from_h1->pool);
 --    if (from_h1->response == NULL) {
 --        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, r->connection,
 --                      APLOGNO(02915) 
 --                      "h2_from_h1(%d): unable to create resp_head",
 --                      from_h1->stream_id);
 --        return APR_EINVAL;
 --    }
 ++    from_h1->response = h2_response_create(from_h1->stream_id, 0,
-                                             from_h1->status, from_h1->hlines,
+++                                           from_h1->http_status, from_h1->hlines,
 ++                                           from_h1->pool);
-      if (from_h1->response == NULL) {
-          ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, r->connection,
-                        APLOGNO(02915) 
-                        "h2_from_h1(%d): unable to create resp_head",
-                        from_h1->stream_id);
-          return APR_EINVAL;
-      }
       from_h1->content_length = from_h1->response->content_length;
       from_h1->chunked = r->chunked;
       
@@@@ -202,8 -202,8 -202,8 +190,7 @@@@ apr_status_t h2_from_h1_read_response(h
                   }
                   if (from_h1->state == H2_RESP_ST_STATUS_LINE) {
                       /* instead of parsing, just take it directly */
---                    from_h1->status = apr_psprintf(from_h1->pool, 
---                                                   "%d", f->r->status);
+++                    from_h1->http_status = f->r->status;
                       from_h1->state = H2_RESP_ST_HEADERS;
                   }
                   else if (line[0] == '\0') {
@@@@ -417,7 -417,7 -417,7 +404,7 @@@@ static h2_response *create_response(h2_
       }
       
       if (!apr_is_empty_array(r->content_languages)) {
---        int i;
+++        unsigned int i;
           char *token;
           char **languages = (char **)(r->content_languages->elts);
           const char *field = apr_table_get(r->headers_out, "Content-Language");
index 115a3142668ccd051907588d1918e9c5032a3942,115a3142668ccd051907588d1918e9c5032a3942,115a3142668ccd051907588d1918e9c5032a3942..4f5ebad618b2db8142ea56b4dd5486de93632e31
@@@@ -48,35 -48,35 -48,35 +48,25 @@@@ struct h2_from_h1 
       apr_pool_t *pool;
       apr_bucket_brigade *bb;
       
---    apr_size_t content_length;
+++    apr_off_t content_length;
       int chunked;
       
---    const char *status;
+++    int http_status;
       apr_array_header_t *hlines;
       
       struct h2_response *response;
   };
   
   
---typedef void h2_from_h1_state_change_cb(struct h2_from_h1 *resp,
---                                         h2_from_h1_state_t prevstate,
---                                         void *cb_ctx);
---
   h2_from_h1 *h2_from_h1_create(int stream_id, apr_pool_t *pool);
   
   apr_status_t h2_from_h1_destroy(h2_from_h1 *response);
   
---void h2_from_h1_set_state_change_cb(h2_from_h1 *from_h1,
---                                     h2_from_h1_state_change_cb *callback,
---                                     void *cb_ctx);
---
   apr_status_t h2_from_h1_read_response(h2_from_h1 *from_h1,
                                         ap_filter_t* f, apr_bucket_brigade* bb);
   
   struct h2_response *h2_from_h1_get_response(h2_from_h1 *from_h1);
   
---h2_from_h1_state_t h2_from_h1_get_state(h2_from_h1 *from_h1);
---
   apr_status_t h2_response_output_filter(ap_filter_t *f, apr_bucket_brigade *bb);
   
   #endif /* defined(__mod_h2__h2_from_h1__) */
index 64ac321db19cab59250f2bc1bd935900901dbc7a,221f5118df9eba4844e05d3cf45e8669a60a4cbc,221f5118df9eba4844e05d3cf45e8669a60a4cbc..54fe9e0fa0a5631367b8c19c01b829fba8ef04db
@@@@ -53,384 -53,6 -53,6 +53,383 @@@@ APR_DECLARE_OPTIONAL_FN(int, ssl_is_htt
   
   static int (*opt_ssl_engine_disable)(conn_rec*);
   static int (*opt_ssl_is_https)(conn_rec*);
-  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
@@@@ -467,88 -86,12 -86,12 +466,88 @@@@ int h2_h2_is_tls(conn_rec *c
       return opt_ssl_is_https && opt_ssl_is_https(c);
   }
   
 --int h2_tls_disable(conn_rec *c)
 ++int h2_is_acceptable_connection(conn_rec *c, int require_all) 
   {
 --    if (opt_ssl_engine_disable) {
 --        return opt_ssl_engine_disable(c);
 ++    int is_tls = h2_h2_is_tls(c);
 ++    h2_config *cfg = h2_config_get(c);
 ++
 ++    if (is_tls && h2_config_geti(cfg, H2_CONF_MODERN_TLS_ONLY) > 0) {
 ++        /* Check TLS connection for modern TLS parameters, as defined in
 ++         * RFC 7540 and https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
 ++         */
 ++        apr_pool_t *pool = c->pool;
 ++        server_rec *s = c->base_server;
 ++        char *val;
 ++        
 ++        if (!opt_ssl_var_lookup) {
 ++            /* unable to check */
 ++            return 0;
 ++        }
 ++        
 ++        /* Need Tlsv1.2 or higher, rfc 7540, ch. 9.2
 ++         */
-          val = opt_ssl_var_lookup(pool, s, c, NULL, "SSL_PROTOCOL");
+++        val = opt_ssl_var_lookup(pool, s, c, NULL, (char*)"SSL_PROTOCOL");
 ++        if (val && *val) {
 ++            if (strncmp("TLS", val, 3) 
 ++                || !strcmp("TLSv1", val) 
 ++                || !strcmp("TLSv1.1", val)) {
 ++            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
 ++                          "h2_h2(%ld): tls protocol not suitable: %s", 
 ++                          (long)c->id, val);
 ++                return 0;
 ++            }
 ++        }
 ++        else if (require_all) {
 ++            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
 ++                          "h2_h2(%ld): tls protocol is indetermined", (long)c->id);
 ++            return 0;
 ++        }
 ++
 ++        /* Check TLS cipher blacklist
 ++         */
-          val = opt_ssl_var_lookup(pool, s, c, NULL, "SSL_CIPHER");
+++        val = opt_ssl_var_lookup(pool, s, c, NULL, (char*)"SSL_CIPHER");
 ++        if (val && *val) {
 ++            const char *source;
 ++            if (cipher_is_blacklisted(val, &source)) {
 ++                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
 ++                              "h2_h2(%ld): tls cipher %s blacklisted by %s", 
 ++                              (long)c->id, val, source);
 ++                return 0;
 ++            }
 ++        }
 ++        else if (require_all) {
 ++            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
 ++                          "h2_h2(%ld): tls cipher is indetermined", (long)c->id);
 ++            return 0;
 ++        }
       }
 --    return 0;
 ++    return 1;
 ++}
 ++
 ++int h2_allows_h2_direct(conn_rec *c)
 ++{
 ++    h2_config *cfg = h2_config_get(c);
 ++    int h2_direct = h2_config_geti(cfg, H2_CONF_DIRECT);
 ++    
 ++    if (h2_direct < 0) {
 ++        if (h2_h2_is_tls(c)) {
 ++            /* disabled by default on TLS */
 ++            h2_direct = 0;
 ++        }
 ++        else {
 ++            /* enabled if "Protocols h2c" is configured */
 ++            h2_direct = ap_is_allowed_protocol(c, NULL, NULL, "h2c");
 ++        }
 ++    }
 ++    return !!h2_direct;
 ++}
 ++
 ++int h2_allows_h2_upgrade(conn_rec *c)
 ++{
 ++    h2_config *cfg = h2_config_get(c);
 ++    int h2_upgrade = h2_config_geti(cfg, H2_CONF_UPGRADE);
 ++    
 ++    return h2_upgrade > 0 || (h2_upgrade < 0 && !h2_h2_is_tls(c));
   }
   
   /*******************************************************************************
@@@@ -652,7 -199,7 -199,7 +651,7 @@@@ int h2_h2_process_conn(conn_rec* c
           ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                         "h2_h2, connection, h2 active");
           
---        return h2_conn_main(c);
+++        return h2_conn_process(c, NULL);
       }
       
       ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined");
   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
index 4cf2785929da64ec60feb702e68758b1ac3e5444,9a1184d8b697d37f1b24de6faedadc5216905078,9a1184d8b697d37f1b24de6faedadc5216905078..4974d86611550f3e1156fa9bb038a921d3c136f7
@@@@ -34,31 -34,6 -34,6 +34,37 @@@@ extern const char *h2_tls_protos[]
    */
   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.
    */
index e952ad0f6b4cb29884acbbf6009761f27395568a,42734430fa48f517a7aafa699918ee300efdaa9c,42734430fa48f517a7aafa699918ee300efdaa9c..b33faee1f3d94d009dfb7416c5a878ac32faf4a5
@@@@ -32,53 -31,33 -31,33 +32,50 @@@@ h2_io *h2_io_create(int id, apr_pool_t 
       if (io) {
           io->id = id;
           io->pool = pool;
+++        io->bucket_alloc = bucket_alloc;
           io->bbin = NULL;
---        io->bbout = apr_brigade_create(pool, bucket_alloc);
+++        io->bbout = NULL;
       }
       return io;
   }
   
---static void h2_io_cleanup(h2_io *io)
-  {
-      if (io->task) {
-          h2_task_destroy(io->task);
-          io->task = NULL;
-      }
-  }
-  
 ++void h2_io_destroy(h2_io *io)
   {
-      h2_io_cleanup(io);
 --    (void)io;
+++    if (io->pool) {
+++        apr_pool_destroy(io->pool);
+++        /* gone */
+++    }
   }
   
 --void h2_io_destroy(h2_io *io)
 ++void h2_io_set_response(h2_io *io, h2_response *response) 
 ++{
+++    AP_DEBUG_ASSERT(io->pool);
 ++    AP_DEBUG_ASSERT(response);
 ++    AP_DEBUG_ASSERT(!io->response);
 ++    io->response = h2_response_copy(io->pool, response);
 ++    if (response->rst_error) {
 ++        h2_io_rst(io, response->rst_error);
 ++    }
 ++}
 ++
 ++
 ++void h2_io_rst(h2_io *io, int error)
   {
 --    h2_io_cleanup(io);
 ++    io->rst_error = error;
 ++    io->eos_in = 1;
   }
   
   int h2_io_in_has_eos_for(h2_io *io)
   {
---    return io->eos_in || (io->bbin && h2_util_has_eos(io->bbin, 0));
+++    return io->eos_in || (io->bbin && h2_util_has_eos(io->bbin, -1));
   }
   
   int h2_io_out_has_data(h2_io *io)
   {
---    return h2_util_bb_has_data_or_eos(io->bbout);
+++    return io->bbout && h2_util_bb_has_data_or_eos(io->bbout);
   }
   
---apr_size_t h2_io_out_length(h2_io *io)
+++apr_off_t h2_io_out_length(h2_io *io)
   {
       if (io->bbout) {
           apr_off_t len = 0;
@@@@ -127,13 -99,13 -99,13 +124,12 @@@@ apr_status_t h2_io_in_write(h2_io *io, 
       if (io->eos_in) {
           return APR_EOF;
       }
---    io->eos_in = h2_util_has_eos(bb, 0);
+++    io->eos_in = h2_util_has_eos(bb, -1);
       if (!APR_BRIGADE_EMPTY(bb)) {
           if (!io->bbin) {
---            io->bbin = apr_brigade_create(io->bbout->p, 
---                                          io->bbout->bucket_alloc);
+++            io->bbin = apr_brigade_create(io->pool, io->bucket_alloc);
           }
-          return h2_util_move(io->bbin, bb, 0, NULL, "h2_io_in_write");
 --        return h2_util_move(io->bbin, bb, 0, 0, "h2_io_in_write");
+++        return h2_util_move(io->bbin, bb, -1, NULL, "h2_io_in_write");
       }
       return APR_SUCCESS;
   }
@@@@ -154,80 -122,18 -122,18 +150,93 @@@@ apr_status_t h2_io_in_close(h2_io *io
   
   apr_status_t h2_io_out_readx(h2_io *io,  
                                h2_io_data_cb *cb, void *ctx, 
---                             apr_size_t *plen, int *peos)
+++                             apr_off_t *plen, int *peos)
   {
 ++    apr_status_t status;
 ++    
 ++    if (io->rst_error) {
 ++        return APR_ECONNABORTED;
 ++    }
 ++    
 ++    if (io->eos_out) {
 ++        *plen = 0;
 ++        *peos = 1;
 ++        return APR_SUCCESS;
 ++    }
+++    else if (!io->bbout) {
+++        *plen = 0;
+++        *peos = 0;
+++        return APR_EAGAIN;
+++    }
 ++    
       if (cb == NULL) {
           /* just checking length available */
 --        return h2_util_bb_avail(io->bbout, plen, peos);
 ++        status = h2_util_bb_avail(io->bbout, plen, peos);
 ++    }
 ++    else {
 ++        status = h2_util_bb_readx(io->bbout, cb, ctx, plen, peos);
 ++        if (status == APR_SUCCESS) {
 ++            io->eos_out = *peos;
 ++        }
       }
 --    return h2_util_bb_readx(io->bbout, cb, ctx, plen, peos);
 ++    
 ++    return status;
 ++}
 ++
 ++apr_status_t h2_io_out_read_to(h2_io *io, apr_bucket_brigade *bb, 
-                                 apr_size_t *plen, int *peos)
+++                               apr_off_t *plen, int *peos)
 ++{
 ++    if (io->rst_error) {
 ++        return APR_ECONNABORTED;
 ++    }
 ++    
 ++    if (io->eos_out) {
 ++        *plen = 0;
 ++        *peos = 1;
 ++        return APR_SUCCESS;
 ++    }
-      
+++    else if (!io->bbout) {
+++        *plen = 0;
+++        *peos = 0;
+++        return APR_EAGAIN;
+++    }
 ++
 ++    io->eos_out = *peos = h2_util_has_eos(io->bbout, *plen);
 ++    return h2_util_move(bb, io->bbout, *plen, NULL, "h2_io_read_to");
   }
   
   apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb, 
                                apr_size_t maxlen, int *pfile_handles_allowed)
   {
 ++    apr_status_t status;
 ++    int start_allowed;
 ++    
 ++    if (io->rst_error) {
 ++        return APR_ECONNABORTED;
 ++    }
 ++
 ++    if (io->eos_out) {
 ++        apr_off_t len;
 ++        /* We have already delivered an EOS bucket to a reader, no
 ++         * sense in storing anything more here.
 ++         */
 ++        status = apr_brigade_length(bb, 1, &len);
 ++        if (status == APR_SUCCESS) {
 ++            if (len > 0) {
 ++                /* someone tries to write real data after EOS, that
 ++                 * does not look right. */
 ++                status = APR_EOF;
 ++            }
 ++            /* cleanup, as if we had moved the data */
 ++            apr_brigade_cleanup(bb);
 ++        }
 ++        return status;
 ++    }
+++
+++    if (!io->bbout) {
+++        io->bbout = apr_brigade_create(io->pool, io->bucket_alloc);
+++    }
 ++    
       /* Let's move the buckets from the request processing in here, so
        * that the main thread can read them when it has time/capacity.
        *
        * 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;
   }
index d8769dd1182c6c7f1f19cfcdf9af31bbbe3479db,946ee44334e8d995a3a0e8ab05ad4b7f86549c34,946ee44334e8d995a3a0e8ab05ad4b7f86549c34..1fd1167747a7034876e36b7d37c170ce63991e38
   
   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;
   };
   
@@@@ -121,11 -105,7 -105,7 +123,11 @@@@ apr_status_t h2_io_in_close(h2_io *io)
    */
   apr_status_t h2_io_out_readx(h2_io *io,  
                                h2_io_data_cb *cb, void *ctx, 
---                             apr_size_t *plen, int *peos);
+++                             apr_off_t *plen, int *peos);
 ++
 ++apr_status_t h2_io_out_read_to(h2_io *io, 
 ++                               apr_bucket_brigade *bb, 
-                                 apr_size_t *plen, int *peos);
+++                               apr_off_t *plen, int *peos);
   
   apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb, 
                                apr_size_t maxlen, int *pfile_buckets_allowed);
@@@@ -140,7 -120,7 -120,7 +142,7 @@@@ apr_status_t h2_io_out_close(h2_io *io)
    * Gives the overall length of the data that is currently queued for
    * output.
    */
---apr_size_t h2_io_out_length(h2_io *io);
+++apr_off_t h2_io_out_length(h2_io *io);
   
   
   #endif /* defined(__mod_h2__h2_io__) */
index ad6bd30e32d037de69890a0dc83975d9f3210b7b,2d07b1eb6c344c64ccbb6b87c3781adbe3076204,2d07b1eb6c344c64ccbb6b87c3781adbe3076204..3908590985a52b1eac82315f058c4808b9132b17
   #include "h2_task_input.h"
   #include "h2_task_output.h"
   #include "h2_task_queue.h"
+++#include "h2_worker.h"
   #include "h2_workers.h"
 ++#include "h2_util.h"
 ++
 ++
+++#define H2_MPLX_IO_OUT(lvl,m,io,msg) \
+++    do { \
+++        if (APLOG_C_IS_LEVEL((m)->c,lvl)) \
+++        h2_util_bb_log((m)->c,(io)->id,lvl,msg,(io)->bbout); \
+++    } while(0)
+++    
+++#define H2_MPLX_IO_IN(lvl,m,io,msg) \
+++    do { \
+++        if (APLOG_C_IS_LEVEL((m)->c,lvl)) \
+++        h2_util_bb_log((m)->c,(io)->id,lvl,msg,(io)->bbin); \
+++    } while(0)
+  
+  
   static int is_aborted(h2_mplx *m, apr_status_t *pstatus) {
       AP_DEBUG_ASSERT(m);
       if (m->aborted) {
@@@@ -74,6 -76,6 -76,6 +88,10 @@@@ static void h2_mplx_destroy(h2_mplx *m
           m->lock = NULL;
       }
       
+++    if (m->spare_pool) {
+++        apr_pool_destroy(m->spare_pool);
+++        m->spare_pool = NULL;
+++    }
       if (m->pool) {
           apr_pool_destroy(m->pool);
       }
@@@@ -107,7 -109,7 -109,7 +125,7 @@@@ h2_mplx *h2_mplx_create(conn_rec *c, ap
       if (m) {
           m->id = c->id;
           APR_RING_ELEM_INIT(m, link);
---        apr_atomic_set32(&m->refs, 1);
+++        m->refs = 1;
           m->c = c;
           apr_pool_create_ex(&m->pool, parent, NULL, allocator);
           if (!m->pool) {
       return m;
   }
   
---static void reference(h2_mplx *m)
-  {
-      apr_atomic_inc32(&m->refs);
-  }
-  
 ++static void release(h2_mplx *m, int lock)
   {
 --    apr_atomic_inc32(&m->refs);
 --}
 --
 --static void release(h2_mplx *m)
 --{
---    if (!apr_atomic_dec32(&m->refs)) {
-          if (lock) {
-              apr_thread_mutex_lock(m->lock);
-          }
+++    if (lock) {
+++        apr_thread_mutex_lock(m->lock);
+++        --m->refs;
           if (m->join_wait) {
               apr_thread_cond_signal(m->join_wait);
           }
-          if (lock) {
-              apr_thread_mutex_unlock(m->lock);
-          }
+++        apr_thread_mutex_unlock(m->lock);
+++    }
+++    else {
+++        --m->refs;
       }
   }
   
   void h2_mplx_reference(h2_mplx *m)
   {
---    reference(m);
+++    apr_thread_mutex_lock(m->lock);
+++    ++m->refs;
+++    apr_thread_mutex_unlock(m->lock);
   }
+++
   void h2_mplx_release(h2_mplx *m)
   {
 --    release(m);
 ++    release(m, 1);
   }
   
   static void workers_register(h2_mplx *m) {
@@@@ -190,17 -187,29 -187,29 +206,17 @@@@ apr_status_t h2_mplx_release_and_join(h
   
       status = apr_thread_mutex_lock(m->lock);
       if (APR_SUCCESS == status) {
 --        int attempts = 0;
 --        
 --        release(m);
 --        while (apr_atomic_read32(&m->refs) > 0) {
 ++        release(m, 0);
-          while (apr_atomic_read32(&m->refs) > 0) {
+++        while (m->refs > 0) {
               m->join_wait = wait;
 --            ap_log_cerror(APLOG_MARK, (attempts? APLOG_INFO : APLOG_DEBUG), 
 --                          0, m->c,
 ++            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
                             "h2_mplx(%ld): release_join, refs=%d, waiting...", 
                             m->id, m->refs);
 --            apr_thread_cond_timedwait(wait, m->lock, apr_time_from_sec(10));
 --            if (++attempts >= 6) {
 --                ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c,
 --                              APLOGNO(02952) 
 --                              "h2_mplx(%ld): join attempts exhausted, refs=%d", 
 --                              m->id, m->refs);
 --                break;
 --            }
 ++            apr_thread_cond_wait(wait, m->lock);
           }
 --        if (m->join_wait) {
 --            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
 --                          "h2_mplx(%ld): release_join -> destroy", m->id);
 --        }
---        m->join_wait = NULL;
 ++        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
 ++                      "h2_mplx(%ld): release_join -> destroy", m->id);
+++        m->pool = NULL;
           apr_thread_mutex_unlock(m->lock);
           h2_mplx_destroy(m);
       }
@@@@ -221,58 -230,77 -230,77 +237,60 @@@@ void h2_mplx_abort(h2_mplx *m
   }
   
   
 --h2_stream *h2_mplx_open_io(h2_mplx *m, int stream_id)
 ++static void io_destroy(h2_mplx *m, h2_io *io)
   {
-      if (io) {
-          apr_pool_t *pool = io->pool;
-          if (pool) {
-              io->pool = NULL;
-              apr_pool_clear(pool);
-              if (m->spare_pool) {
-                  apr_pool_destroy(m->spare_pool);
-              }
-              m->spare_pool = pool;
 --    h2_stream *stream = NULL;
 --    apr_status_t status; 
 --    h2_io *io;
 --
 --    if (m->aborted) {
 --        return NULL;
 --    }
 --    status = apr_thread_mutex_lock(m->lock);
 --    if (APR_SUCCESS == status) {
 --        apr_pool_t *stream_pool = m->spare_pool;
 --        
 --        if (!stream_pool) {
 --            apr_pool_create(&stream_pool, m->pool);
 --        }
 --        else {
 --            m->spare_pool = NULL;
 --        }
 --        
 --        stream = h2_stream_create(stream_id, stream_pool, m);
 --        stream->state = H2_STREAM_ST_OPEN;
 --        
 --        io = h2_io_set_get(m->stream_ios, stream_id);
 --        if (!io) {
 --            io = h2_io_create(stream_id, stream_pool, m->bucket_alloc);
 --            h2_io_set_add(m->stream_ios, io);
 --        }
 --        status = io? APR_SUCCESS : APR_ENOMEM;
 --        apr_thread_mutex_unlock(m->lock);
 --    }
 --    return stream;
 --}
 --
 --static void stream_destroy(h2_mplx *m, h2_stream *stream, h2_io *io)
 --{
 --    apr_pool_t *pool = h2_stream_detach_pool(stream);
+++    apr_pool_t *pool = io->pool;
+++    
+++    io->pool = NULL;    
+++    /* The pool is cleared/destroyed which also closes all
+++     * allocated file handles. Give this count back to our
+++     * file handle pool. */
+++    m->file_handles_allowed += io->files_handles_owned;
+++    h2_io_set_remove(m->stream_ios, io);
+++    h2_io_set_remove(m->ready_ios, io);
+++    h2_io_destroy(io);
+++    
+      if (pool) {
+          apr_pool_clear(pool);
+          if (m->spare_pool) {
+              apr_pool_destroy(m->spare_pool);
           }
-          /* The pool is cleared/destroyed which also closes all
-           * allocated file handles. Give this count back to our
-           * file handle pool. */
-          m->file_handles_allowed += io->files_handles_owned;
-          h2_io_set_remove(m->stream_ios, io);
-          h2_io_set_remove(m->ready_ios, io);
-          h2_io_destroy(io);
+          m->spare_pool = pool;
       }
 --    h2_stream_destroy(stream);
 --    if (io) {
 --        /* The pool is cleared/destroyed which also closes all
 --         * allocated file handles. Give this count back to our
 --         * file handle pool. */
 --        m->file_handles_allowed += io->files_handles_owned;
 --        h2_io_set_remove(m->stream_ios, io);
 --        h2_io_set_remove(m->ready_ios, io);
 --        h2_io_destroy(io);
 --    }
   }
   
 --apr_status_t h2_mplx_cleanup_stream(h2_mplx *m, h2_stream *stream)
 ++apr_status_t h2_mplx_stream_done(h2_mplx *m, int stream_id, int rst_error)
   {
       apr_status_t status;
 ++    
       AP_DEBUG_ASSERT(m);
 ++    if (m->aborted) {
 ++        return APR_ECONNABORTED;
 ++    }
       status = apr_thread_mutex_lock(m->lock);
       if (APR_SUCCESS == status) {
 --        h2_io *io = h2_io_set_get(m->stream_ios, stream->id);
 --        if (!io || io->task_done) {
 --            /* No more io or task already done -> cleanup immediately */
 --            stream_destroy(m, stream, io);
 --        }
 --        else {
 --            /* Add stream to closed set for cleanup when task is done */
 --            h2_stream_set_add(m->closed, stream);
 ++        h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
-          
+++
+++        /* there should be an h2_io, once the stream has been scheduled
+++         * for processing, e.g. when we received all HEADERs. But when
+++         * a stream is cancelled very early, it will not exist. */
 ++        if (io) {
 ++            /* Remove io from ready set, we will never submit it */
 ++            h2_io_set_remove(m->ready_ios, io);
-              
-              if (io->task_done) {
+++            if (io->task_done || h2_tq_remove(m->q, io->id)) {
+++                /* already finished or not even started yet */
 ++                io_destroy(m, io);
 ++            }
 ++            else {
 ++                /* cleanup once task is done */
-                  io->zombie = 1;
+++                io->orphaned = 1;
 ++                if (rst_error) {
-                      /* Forward error code to fail any further attempt to
-                       * write to io */
 ++                    h2_io_rst(io, rst_error);
 ++                }
 ++            }
+++            
           }
 ++        
           apr_thread_mutex_unlock(m->lock);
       }
       return status;
@@@@ -285,14 -313,18 -313,18 +303,14 @@@@ void h2_mplx_task_done(h2_mplx *m, int 
           h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
           ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
                         "h2_mplx(%ld): task(%d) done", m->id, stream_id);
 --        if (stream) {
 --            /* stream was already closed by main connection and is in 
 --             * zombie state. Now that the task is done with it, we
 --             * can free its resources. */
 --            h2_stream_set_remove(m->closed, stream);
 --            stream_destroy(m, stream, io);
 --        }
 --        else if (io) {
 --            /* main connection has not finished stream. Mark task as done
 --             * so that eventual cleanup can start immediately. */
 ++        if (io) {
               io->task_done = 1;
-              if (io->zombie) {
+++            if (io->orphaned) {
 ++                io_destroy(m, io);
 ++            }
 ++            else {
 ++                /* hang around until the stream deregisteres */
 ++            }
           }
           apr_thread_mutex_unlock(m->lock);
       }
@@@@ -310,15 -342,15 -342,15 +328,17 @@@@ apr_status_t h2_mplx_in_read(h2_mplx *m
       status = apr_thread_mutex_lock(m->lock);
       if (APR_SUCCESS == status) {
           h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
---        if (io) {
+++        if (io && !io->orphaned) {
               io->input_arrived = iowait;
---            status = h2_io_in_read(io, bb, 0);
 --            while (status == APR_EAGAIN 
+++            H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_read_pre");
+++            status = h2_io_in_read(io, bb, -1);
 ++            while (APR_STATUS_IS_EAGAIN(status) 
                      && !is_aborted(m, &status)
                      && block == APR_BLOCK_READ) {
                   apr_thread_cond_wait(io->input_arrived, m->lock);
---                status = h2_io_in_read(io, bb, 0);
+++                status = h2_io_in_read(io, bb, -1);
               }
+++            H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_read_post");
               io->input_arrived = NULL;
           }
           else {
@@@@ -340,8 -372,8 -372,8 +360,10 @@@@ apr_status_t h2_mplx_in_write(h2_mplx *
       status = apr_thread_mutex_lock(m->lock);
       if (APR_SUCCESS == status) {
           h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
---        if (io) {
+++        if (io && !io->orphaned) {
+++            H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_write_pre");
               status = h2_io_in_write(io, bb);
+++            H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_write_post");
               if (io->input_arrived) {
                   apr_thread_cond_signal(io->input_arrived);
               }
@@@@ -364,8 -396,8 -396,8 +386,9 @@@@ apr_status_t h2_mplx_in_close(h2_mplx *
       status = apr_thread_mutex_lock(m->lock);
       if (APR_SUCCESS == status) {
           h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
---        if (io) {
+++        if (io && !io->orphaned) {
               status = h2_io_in_close(io);
+++            H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_close");
               if (io->input_arrived) {
                   apr_thread_cond_signal(io->input_arrived);
               }
@@@@ -422,16 -454,9 -454,9 +445,9 @@@@ apr_status_t h2_mplx_in_update_windows(
       return status;
   }
   
-  #define H2_MPLX_IO_OUT(lvl,m,io,msg) \
-      do { \
-          if (APLOG_C_IS_LEVEL((m)->c,lvl)) \
-          h2_util_bb_log((m)->c,(io)->id,lvl,msg,(io)->bbout); \
-      } while(0)
-  
-  
   apr_status_t h2_mplx_out_readx(h2_mplx *m, int stream_id, 
                                  h2_io_data_cb *cb, void *ctx, 
---                               apr_size_t *plen, int *peos)
+++                               apr_off_t *plen, int *peos)
   {
       apr_status_t status;
       AP_DEBUG_ASSERT(m);
       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);
               }
@@@@ -499,33 -490,24 -490,24 +515,42 @@@@ h2_stream *h2_mplx_next_submit(h2_mplx 
       }
       status = apr_thread_mutex_lock(m->lock);
       if (APR_SUCCESS == status) {
 --        h2_io *io = h2_io_set_get_highest_prio(m->ready_ios);
 ++        h2_io *io = h2_io_set_pop_highest_prio(m->ready_ios);
           if (io) {
 --            h2_response *response = io->response;
 --            
 --            AP_DEBUG_ASSERT(response);
 --            h2_io_set_remove(m->ready_ios, io);
 --            
 --            stream = h2_stream_set_get(streams, response->stream_id);
 ++            stream = h2_stream_set_get(streams, io->id);
               if (stream) {
 --                h2_stream_set_response(stream, response, io->bbout);
 --                if (io->output_drained) {
 --                    apr_thread_cond_signal(io->output_drained);
 ++                if (io->rst_error) {
 ++                    h2_stream_rst(stream, io->rst_error);
 ++                }
 ++                else {
 ++                    AP_DEBUG_ASSERT(io->response);
+++                    H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_next_submit_pre");
 ++                    h2_stream_set_response(stream, io->response, io->bbout);
+++                    H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_next_submit_post");
                   }
 ++                
               }
               else {
 --                ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_NOTFOUND, m->c,
 --                              APLOGNO(02953) "h2_mplx(%ld): stream for response %d",
 --                              m->id, response->stream_id);
 ++                /* We have the io ready, but the stream has gone away, maybe
 ++                 * reset by the client. Should no longer happen since such
 ++                 * streams should clear io's from the ready queue.
 ++                 */
 ++                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, m->c, APLOGNO(02953) 
 ++                              "h2_mplx(%ld): stream for response %d closed, "
 ++                              "resetting io to close request processing",
 ++                              m->id, io->id);
-                  h2_io_rst(io, H2_ERR_STREAM_CLOSED);
+++                io->orphaned = 1;
+++                if (io->task_done) {
+++                    io_destroy(m, io);
+++                }
+++                else {
+++                    /* hang around until the h2_task is done */
+++                    h2_io_rst(io, H2_ERR_STREAM_CLOSED);
+++                }
 ++            }
 ++            
 ++            if (io->output_drained) {
 ++                apr_thread_cond_signal(io->output_drained);
               }
           }
           apr_thread_mutex_unlock(m->lock);
@@@@ -577,15 -559,15 -559,15 +602,15 @@@@ static apr_status_t out_open(h2_mplx *m
       apr_status_t status = APR_SUCCESS;
       
       h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
---    if (io) {
+++    if (io && !io->orphaned) {
           if (f) {
---            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
-                            "h2_mplx(%ld-%d): open response: %s, rst=%d",
-                            m->id, stream_id, response->status, 
 --                          "h2_mplx(%ld-%d): open response: %s",
 --                          m->id, stream_id, response->status);
+++            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+++                          "h2_mplx(%ld-%d): open response: %d, rst=%d",
+++                          m->id, stream_id, response->http_status, 
 ++                          response->rst_error);
           }
           
 --        io->response = h2_response_copy(io->pool, response);
 --        AP_DEBUG_ASSERT(io->response);
 ++        h2_io_set_response(io, response);
           h2_io_set_add(m->ready_ios, io);
           if (bb) {
               status = out_write(m, io, f, bb, iowait);
@@@@ -635,10 -614,8 -614,8 +660,10 @@@@ apr_status_t h2_mplx_out_write(h2_mplx 
       if (APR_SUCCESS == status) {
           if (!m->aborted) {
               h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
---            if (io) {
+++            if (io && !io->orphaned) {
                   status = out_write(m, io, f, bb, iowait);
 ++                H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_write");
 ++                
                   have_out_data_for(m, stream_id);
                   if (m->aborted) {
                       return APR_ECONNABORTED;
@@@@ -667,22 -644,17 -644,17 +692,22 @@@@ apr_status_t h2_mplx_out_close(h2_mplx 
       if (APR_SUCCESS == status) {
           if (!m->aborted) {
               h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
---            if (io) {
 --                if (!io->response || !io->response->ngheader) {
+++            if (io && !io->orphaned) {
 ++                if (!io->response && !io->rst_error) {
                       /* In case a close comes before a response was created,
                        * insert an error one so that our streams can properly
                        * reset.
                        */
 --                    h2_response *r = h2_response_create(stream_id, 
 --                                                        "500", NULL, m->pool);
 ++                    h2_response *r = h2_response_create(stream_id, 0, 
-                                                          "500", NULL, m->pool);
+++                                                        500, NULL, m->pool);
                       status = out_open(m, stream_id, r, NULL, NULL, NULL);
 ++                    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, m->c,
 ++                                  "h2_mplx(%ld-%d): close, no response, no rst", 
 ++                                  m->id, io->id);
                   }
                   status = h2_io_out_close(io);
 ++                H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_close");
 ++                
                   have_out_data_for(m, stream_id);
                   if (m->aborted) {
                       /* if we were the last output, the whole session might
       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);
       }
@@@@ -802,116 -742,61 -742,61 +827,104 @@@@ static void have_out_data_for(h2_mplx *
       }
   }
   
-  typedef struct {
-      h2_stream_pri_cmp *cmp;
-      void *ctx;
-  } cmp_ctx;
-  
-  static int task_cmp(h2_task *t1, h2_task *t2, void *ctx)
-  {
-      cmp_ctx *x = ctx;
-      return x->cmp(t1->stream_id, t2->stream_id, x->ctx);
-  }
-  
 --apr_status_t h2_mplx_do_task(h2_mplx *m, struct h2_task *task)
 ++apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx)
   {
       apr_status_t status;
 ++    
       AP_DEBUG_ASSERT(m);
       if (m->aborted) {
           return APR_ECONNABORTED;
       }
       status = apr_thread_mutex_lock(m->lock);
       if (APR_SUCCESS == status) {
-          cmp_ctx x;
-          
-          x.cmp = cmp;
-          x.ctx = ctx;
-          h2_tq_sort(m->q, task_cmp, &x);
 --        /* TODO: needs to sort queue by priority */
+++        h2_tq_sort(m->q, cmp, ctx);
 ++        
           ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
 --                      "h2_mplx: do task(%s)", task->id);
 --        h2_tq_append(m->q, task);
 ++                      "h2_mplx(%ld): reprioritize tasks", m->id);
           apr_thread_mutex_unlock(m->lock);
       }
       workers_register(m);
       return status;
   }
   
 --h2_task *h2_mplx_pop_task(h2_mplx *m, int *has_more)
 ++static h2_io *open_io(h2_mplx *m, int stream_id)
 ++{
 ++    apr_pool_t *io_pool = m->spare_pool;
 ++    h2_io *io;
 ++    
 ++    if (!io_pool) {
 ++        apr_pool_create(&io_pool, m->pool);
 ++    }
 ++    else {
 ++        m->spare_pool = NULL;
 ++    }
 ++    
 ++    io = h2_io_create(stream_id, io_pool, m->bucket_alloc);
 ++    h2_io_set_add(m->stream_ios, io);
 ++    
 ++    return io;
 ++}
 ++
 ++
 ++apr_status_t h2_mplx_process(h2_mplx *m, int stream_id,
-                               struct h2_request *r, int eos, 
+++                             const h2_request *req, int eos, 
 ++                             h2_stream_pri_cmp *cmp, void *ctx)
   {
 --    h2_task *task = NULL;
       apr_status_t status;
 ++    
       AP_DEBUG_ASSERT(m);
       if (m->aborted) {
 --        *has_more = 0;
 --        return NULL;
 ++        return APR_ECONNABORTED;
       }
       status = apr_thread_mutex_lock(m->lock);
       if (APR_SUCCESS == status) {
-          conn_rec *c;
-          h2_io *io;
-          cmp_ctx x;
-          
-          io = open_io(m, stream_id);
-          c = h2_conn_create(m->c, io->pool);
-          io->task = h2_task_create(m->id, stream_id, io->pool, m, c);
-              
-          status = h2_request_end_headers(r, m, io->task, eos);
-          if (status == APR_SUCCESS && eos) {
 --        task = h2_tq_pop_first(m->q);
 --        if (task) {
 --            h2_task_set_started(task);
+++        h2_io *io = open_io(m, stream_id);
+++        io->request = req;
+++        io->request_body = !eos;
+++
+++        if (eos) {
 ++            status = h2_io_in_close(io);
           }
 --        *has_more = !h2_tq_empty(m->q);
 ++        
-          if (status == APR_SUCCESS) {
-              x.cmp = cmp;
-              x.ctx = ctx;
-              h2_tq_add(m->q, io->task, task_cmp, &x);
-          }
+++        h2_tq_add(m->q, io->id, cmp, ctx);
+++
 ++        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, m->c,
 ++                      "h2_mplx(%ld-%d): process", m->c->id, stream_id);
+++        H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_process");
           apr_thread_mutex_unlock(m->lock);
       }
 --    return task;
 ++    
 ++    if (status == APR_SUCCESS) {
 ++        workers_register(m);
 ++    }
 ++    return status;
   }
   
-  h2_task *h2_mplx_pop_task(h2_mplx *m, int *has_more)
 --apr_status_t h2_mplx_create_task(h2_mplx *m, struct h2_stream *stream)
+++h2_task *h2_mplx_pop_task(h2_mplx *m, h2_worker *w, int *has_more)
   {
 ++    h2_task *task = NULL;
       apr_status_t status;
+++    
       AP_DEBUG_ASSERT(m);
       if (m->aborted) {
 --        return APR_ECONNABORTED;
 ++        *has_more = 0;
 ++        return NULL;
       }
       status = apr_thread_mutex_lock(m->lock);
       if (APR_SUCCESS == status) {
-          task = h2_tq_shift(m->q);
 --        conn_rec *c = h2_conn_create(m->c, stream->pool);
 --        stream->task = h2_task_create(m->id, stream->id, 
 --                                      stream->pool, m, c);
 --        
+++        int sid;
+++        while (!task && (sid = h2_tq_shift(m->q)) > 0) {
+++            /* Anything not already setup correctly in the task
+++             * needs to be so now, as task will be executed right about 
+++             * when this method returns. */
+++            h2_io *io = h2_io_set_get(m->stream_ios, sid);
+++            if (io) {
+++                task = h2_worker_create_task(w, m, io->request, !io->request_body);
+++            }
+++        }
 ++        *has_more = !h2_tq_empty(m->q);
           apr_thread_mutex_unlock(m->lock);
       }
 --    return status;
 ++    return task;
   }
   
index 2bb650535efb34de3510a9af47b8b45b0c14d159,62977d6157cecd675eb3fce9dcfcd8c706bfe191,62977d6157cecd675eb3fce9dcfcd8c706bfe191..5c950b9c271e41df288a6f5a041bbd75d40dfec9
@@@@ -41,9 -41,8 -41,8 +41,10 @@@@ struct h2_config
   struct h2_response;
   struct h2_task;
   struct h2_stream;
 ++struct h2_request;
   struct h2_io_set;
   struct apr_thread_cond_t;
+++struct h2_worker;
   struct h2_workers;
   struct h2_stream_set;
   struct h2_task_queue;
@@@@ -55,7 -54,7 -54,7 +56,7 @@@@ typedef struct h2_mplx h2_mplx
   struct h2_mplx {
       long id;
       APR_RING_ENTRY(h2_mplx) link;
---    volatile apr_uint32_t refs;
+++    volatile int refs;
       conn_rec *c;
       apr_pool_t *pool;
       apr_bucket_alloc_t *bucket_alloc;
@@@@ -147,29 -143,13 -143,13 +148,29 @@@@ apr_status_t h2_mplx_out_trywait(h2_mpl
    ******************************************************************************/
   
   /**
 -- * Perform the task on the given stream.
 ++ * Process a stream request.
 ++ * 
 ++ * @param m the multiplexer
 ++ * @param stream_id the identifier of the stream
 ++ * @param r the request to be processed
 ++ * @param eos if input is complete
 ++ * @param cmp the stream priority compare function
 ++ * @param ctx context data for the compare function
    */
 --apr_status_t h2_mplx_do_task(h2_mplx *mplx, struct h2_task *task);
 ++apr_status_t h2_mplx_process(h2_mplx *m, int stream_id,
-                               struct h2_request *r, int eos, 
+++                             const struct h2_request *r, int eos, 
 ++                             h2_stream_pri_cmp *cmp, void *ctx);
   
 --struct h2_task *h2_mplx_pop_task(h2_mplx *mplx, int *has_more);
 ++/**
 ++ * Stream priorities have changed, reschedule pending tasks.
 ++ * 
 ++ * @param m the multiplexer
 ++ * @param cmp the stream priority compare function
 ++ * @param ctx context data for the compare function
 ++ */
 ++apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx);
   
-  struct h2_task *h2_mplx_pop_task(h2_mplx *mplx, int *has_more);
 --apr_status_t h2_mplx_create_task(h2_mplx *mplx, struct h2_stream *stream);
+++struct h2_task *h2_mplx_pop_task(h2_mplx *mplx, struct h2_worker *w, int *has_more);
   
   /*******************************************************************************
    * Input handling of streams.
@@@@ -208,7 -188,7 -188,7 +209,7 @@@@ int h2_mplx_in_has_eos_for(h2_mplx *m, 
    * Callback invoked for every stream that had input data read since
    * the last invocation.
    */
---typedef void h2_mplx_consumed_cb(void *ctx, int stream_id, apr_size_t consumed);
+++typedef void h2_mplx_consumed_cb(void *ctx, int stream_id, apr_off_t consumed);
   
   /**
    * Invoke the callback for all streams that had bytes read since the last
@@@@ -239,15 -219,7 -219,7 +240,15 @@@@ struct h2_stream *h2_mplx_next_submit(h
    */
   apr_status_t h2_mplx_out_readx(h2_mplx *mplx, int stream_id, 
                                  h2_io_data_cb *cb, void *ctx, 
---                               apr_size_t *plen, int *peos);
+++                               apr_off_t *plen, int *peos);
 ++
 ++/**
 ++ * Reads output data into the given brigade. Will never block, but
 ++ * return APR_EAGAIN until data arrives or the stream is closed.
 ++ */
 ++apr_status_t h2_mplx_out_read_to(h2_mplx *mplx, int stream_id, 
 ++                                 apr_bucket_brigade *bb, 
-                                   apr_size_t *plen, int *peos);
+++                                 apr_off_t *plen, int *peos);
   
   /**
    * Opens the output for the given stream with the specified response.
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..703ea761a042b9980449ffa778772de036273229
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,400 @@@@
+++/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+++ *
+++ * Licensed under the Apache License, Version 2.0 (the "License");
+++ * you may not use this file except in compliance with the License.
+++ * You may obtain a copy of the License at
+++ *
+++ * http://www.apache.org/licenses/LICENSE-2.0
+++ 
+++ * Unless required by applicable law or agreed to in writing, software
+++ * distributed under the License is distributed on an "AS IS" BASIS,
+++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+++ * See the License for the specific language governing permissions and
+++ * limitations under the License.
+++ */
+++
+++#include <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;
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..64edee65d194ce8d60609030ca18f8f4851d79f0
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,33 @@@@
+++/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+++ *
+++ * Licensed under the Apache License, Version 2.0 (the "License");
+++ * you may not use this file except in compliance with the License.
+++ * You may obtain a copy of the License at
+++ *
+++ * http://www.apache.org/licenses/LICENSE-2.0
+++ 
+++ * Unless required by applicable law or agreed to in writing, software
+++ * distributed under the License is distributed on an "AS IS" BASIS,
+++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+++ * See the License for the specific language governing permissions and
+++ * limitations under the License.
+++ */
+++#ifndef __mod_h2__h2_push__
+++#define __mod_h2__h2_push__
+++
+++struct h2_request;
+++struct h2_response;
+++struct h2_ngheader;
+++
+++typedef struct h2_push {
+++    int initiating_id;
+++    const struct h2_request *req;
+++    const char *as;
+++} h2_push;
+++
+++
+++apr_array_header_t *h2_push_collect(apr_pool_t *p, 
+++                                    const struct h2_request *req, 
+++                                    const struct h2_response *res);
+++
+++#endif /* defined(__mod_h2__h2_push__) */
index 4a1e19058a627d34d5027f91ed04b0411f7f787f,ca9a362cb56612ff56342a2924090ab4b678bb23,ca9a362cb56612ff56342a2924090ab4b678bb23..2a697a0eda8461e72b8b654091b82613d0270e10
   
   #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;
   }
   
index aa5e0bc3c0961ed4036c239fd545345c618b4540,aa5e0bc3c0961ed4036c239fd545345c618b4540,aa5e0bc3c0961ed4036c239fd545345c618b4540..19005a88e6d660ca25e6536477a2c4dc1d09a917
   /* h2_request is the transformer of HTTP2 streams into HTTP/1.1 internal
    * format that will be fed to various httpd input filters to finally
    * become a request_rec to be handled by soemone.
--- *
--- * Ideally, we would make a request_rec without serializing the headers
--- * we have only to make someone else parse them back.
    */
   struct h2_to_h1;
   struct h2_mplx;
@@@@ -30,38 -30,38 -30,38 +27,45 @@@@ struct h2_task
   typedef struct h2_request h2_request;
   
   struct h2_request {
---    int id;                 /* http2 stream id */
---    apr_pool_t *pool;
---    apr_bucket_alloc_t *bucket_alloc;
---    struct h2_to_h1 *to_h1; /* Converter to HTTP/1.1 format*/
---    
+++    int id;                 /* stream id */
+++
       /* pseudo header values, see ch. 8.1.2.3 */
       const char *method;
       const char *scheme;
       const char *authority;
       const char *path;
+++    
+++    apr_table_t *headers;
+++    apr_table_t *trailers;
+++
+++    apr_off_t content_length;
+++    int chunked;
+++    int eoh;
   };
   
---h2_request *h2_request_create(int id, apr_pool_t *pool, 
---                              apr_bucket_alloc_t *bucket_alloc);
+++h2_request *h2_request_create(int id, apr_pool_t *pool);
+++
+++h2_request *h2_request_createn(int id, apr_pool_t *pool,
+++                               const char *method, const char *scheme,
+++                               const char *authority, const char *path,
+++                               apr_table_t *headers);
+++
   void h2_request_destroy(h2_request *req);
   
---apr_status_t h2_request_flush(h2_request *req);
+++apr_status_t h2_request_rwrite(h2_request *req, request_rec *r);
+++
+++apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
+++                                   const char *name, size_t nlen,
+++                                   const char *value, size_t vlen);
   
---apr_status_t h2_request_write_header(h2_request *req,
---                                     const char *name, size_t nlen,
---                                     const char *value, size_t vlen,
---                                     struct h2_mplx *m);
+++apr_status_t h2_request_add_trailer(h2_request *req, apr_pool_t *pool,
+++                                    const char *name, size_t nlen,
+++                                    const char *value, size_t vlen);
   
---apr_status_t h2_request_write_data(h2_request *request,
---                                   const char *data, size_t len);
+++apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos);
   
---apr_status_t h2_request_end_headers(h2_request *req, struct h2_mplx *m, 
---                                    struct h2_task *task, int eos);
+++void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src);
   
---apr_status_t h2_request_close(h2_request *req);
   
---apr_status_t h2_request_rwrite(h2_request *req, request_rec *r,
---                               struct h2_mplx *m);
   
   #endif /* defined(__mod_h2__h2_request__) */
index bd0a5fba9795c345804ffdf95aa4204d30a4a5aa,9cedd855615549cf80e1c86d5f58cc13c7b5e34d,9cedd855615549cf80e1c86d5f58cc13c7b5e34d..2751f2d377cab075af21908f2ff38384cef50195
   #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;
   }
   
@@@@ -112,22 -109,9 -109,9 +99,22 @@@@ h2_response *h2_response_rcreate(int st
       }
       
       response->stream_id = stream_id;
---    response->status = apr_psprintf(pool, "%d", r->status);
+++    response->http_status = r->status;
       response->content_length = -1;
---    response->rheader = header;
+++    response->header = header;
 ++
-      if (r->status == HTTP_FORBIDDEN) {
+++    if (response->http_status == HTTP_FORBIDDEN) {
 ++        const char *cause = apr_table_get(r->notes, "ssl-renegotiate-forbidden");
 ++        if (cause) {
 ++            /* This request triggered a TLS renegotiation that is now allowed 
 ++             * in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
 ++             */
-              ap_log_rerror(APLOG_MARK, APLOG_DEBUG, r->status, r, 
+++            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, response->http_status, r, 
 ++                          "h2_response(%ld-%d): renegotiate forbidden, cause: %s",
 ++                          (long)r->connection->id, stream_id, cause);
 ++            response->rst_error = H2_ERR_HTTP_1_1_REQUIRED;
 ++        }
 ++    }
       
       return response;
   }
@@@@ -141,108 -125,108 -125,108 +128,12 @@@@ h2_response *h2_response_copy(apr_pool_
   {
       h2_response *to = apr_pcalloc(pool, sizeof(h2_response));
       to->stream_id = from->stream_id;
---    to->status = apr_pstrdup(pool, from->status);
+++    to->http_status = from->http_status;
       to->content_length = from->content_length;
---    if (from->rheader) {
---        to->ngheader = make_ngheader(pool, to->status, from->rheader);
+++    if (from->header) {
+++        to->header = apr_table_clone(pool, from->header);
       }
       return to;
   }
   
---typedef struct {
---    nghttp2_nv *nv;
---    size_t nvlen;
---    size_t nvstrlen;
---    size_t offset;
---    char *strbuf;
---    apr_pool_t *pool;
---} nvctx_t;
---
---static int count_header(void *ctx, const char *key, const char *value)
---{
---    if (!ignore_header(key)) {
---        nvctx_t *nvctx = (nvctx_t*)ctx;
---        nvctx->nvlen++;
---        nvctx->nvstrlen += strlen(key) + strlen(value) + 2;
---    }
---    return 1;
---}
---
---#define NV_ADD_LIT_CS(nv, k, v)     addnv_lit_cs(nv, k, sizeof(k) - 1, v, strlen(v))
---#define NV_ADD_CS_CS(nv, k, v)      addnv_cs_cs(nv, k, strlen(k), v, strlen(v))
---#define NV_BUF_ADD(nv, s, len)      memcpy(nv->strbuf, s, len); \
---s = nv->strbuf; \
---nv->strbuf += len + 1
---
---static void addnv_cs_cs(nvctx_t *ctx, const char *key, size_t key_len,
---                        const char *value, size_t val_len)
---{
---    nghttp2_nv *nv = &ctx->nv[ctx->offset];
---    
---    NV_BUF_ADD(ctx, key, key_len);
---    NV_BUF_ADD(ctx, value, val_len);
---    
---    nv->name = (uint8_t*)key;
---    nv->namelen = key_len;
---    nv->value = (uint8_t*)value;
---    nv->valuelen = val_len;
---    
---    ctx->offset++;
---}
---
---static void addnv_lit_cs(nvctx_t *ctx, const char *key, size_t key_len,
---                         const char *value, size_t val_len)
---{
---    nghttp2_nv *nv = &ctx->nv[ctx->offset];
---    
---    NV_BUF_ADD(ctx, value, val_len);
---    
---    nv->name = (uint8_t*)key;
---    nv->namelen = key_len;
---    nv->value = (uint8_t*)value;
---    nv->valuelen = val_len;
---    
---    ctx->offset++;
---}
---
---static int add_header(void *ctx, const char *key, const char *value)
---{
---    if (!ignore_header(key)) {
---        nvctx_t *nvctx = (nvctx_t*)ctx;
---        NV_ADD_CS_CS(nvctx, key, value);
---    }
---    return 1;
---}
---
---static h2_ngheader *make_ngheader(apr_pool_t *pool, const char *status,
---                                  apr_table_t *header)
---{
---    size_t n;
---    h2_ngheader *h;
---    nvctx_t ctx;
---    
---    ctx.nv       = NULL;
---    ctx.nvlen    = 1;
---    ctx.nvstrlen = strlen(status) + 1;
---    ctx.offset   = 0;
---    ctx.strbuf   = NULL;
---    ctx.pool     = pool;
---    
---    apr_table_do(count_header, &ctx, header, NULL);
---    
---    n =  (sizeof(h2_ngheader)
---                 + (ctx.nvlen * sizeof(nghttp2_nv)) + ctx.nvstrlen); 
---    h = apr_pcalloc(pool, n);
---    if (h) {
---        ctx.nv = (nghttp2_nv*)(h + 1);
---        ctx.strbuf = (char*)&ctx.nv[ctx.nvlen];
---        
---        NV_ADD_LIT_CS(&ctx, ":status", status);
---        apr_table_do(add_header, &ctx, header, NULL);
---        
---        h->nv = ctx.nv;
---        h->nvlen = ctx.nvlen;
---    }
---    return h;
---}
   
index 64d68cc9c2295547d105aff4d8d98e20744286c3,456d2226ed28a9c4748484d54992a13560a5bcb1,456d2226ed28a9c4748484d54992a13560a5bcb1..59f7b035aa5f84d782ccfa05b3b9122170b97c76
   #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);
index fc0287042bb1b2e1ac015f3f98464166bc938a18,c3456a0654da6254ae50e3e3e9787d20238f4c1b,c3456a0654da6254ae50e3e3e9787d20238f4c1b..46898c43160211cbe4b99bde2cbb00af494a71fe
   #include "h2_config.h"
   #include "h2_h2.h"
   #include "h2_mplx.h"
+++#include "h2_push.h"
+++#include "h2_request.h"
   #include "h2_response.h"
   #include "h2_stream.h"
   #include "h2_stream_set.h"
@@@@ -43,109 -41,57 -41,57 +45,110 @@@@ static int frame_print(const nghttp2_fr
   
   static int h2_session_status_from_apr_status(apr_status_t rv)
   {
 --    switch (rv) {
 --        case APR_SUCCESS:
 --            return NGHTTP2_NO_ERROR;
 --        case APR_EAGAIN:
 --        case APR_TIMEUP:
 --            return NGHTTP2_ERR_WOULDBLOCK;
 --        case APR_EOF:
 ++    if (rv == APR_SUCCESS) {
 ++        return NGHTTP2_NO_ERROR;
 ++    }
 ++    else if (APR_STATUS_IS_EAGAIN(rv)) {
 ++        return NGHTTP2_ERR_WOULDBLOCK;
 ++    }
 ++    else if (APR_STATUS_IS_EOF(rv)) {
               return NGHTTP2_ERR_EOF;
 --        default:
 --            return NGHTTP2_ERR_PROTO;
       }
 ++    return NGHTTP2_ERR_PROTO;
   }
   
---static int stream_open(h2_session *session, int stream_id)
+++h2_stream *h2_session_open_stream(h2_session *session, int stream_id)
   {
       h2_stream * stream;
 ++    apr_pool_t *stream_pool;
       if (session->aborted) {
---        return NGHTTP2_ERR_CALLBACK_FAILURE;
+++        return NULL;
       }
       
 --    stream = h2_mplx_open_io(session->mplx, stream_id);
 --    if (stream) {
 --        h2_stream_set_add(session->streams, stream);
 --        if (stream->id > session->max_stream_received) {
 --            session->max_stream_received = stream->id;
 --        }
 --        
 --        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
 --                      "h2_session: stream(%ld-%d): opened",
 --                      session->id, stream_id);
 ++    if (session->spare) {
 ++        stream_pool = session->spare;
 ++        session->spare = NULL;
 ++    }
 ++    else {
 ++        apr_pool_create(&stream_pool, session->pool);
 ++    }
 ++    
-      stream = h2_stream_create(stream_id, stream_pool, session);
-      stream->state = H2_STREAM_ST_OPEN;
+++    stream = h2_stream_open(stream_id, stream_pool, session);
 ++    
 ++    h2_stream_set_add(session->streams, stream);
-      if (stream->id > session->max_stream_received) {
+++    if (H2_STREAM_CLIENT_INITIATED(stream_id)
+++        && stream_id > session->max_stream_received) {
 ++        session->max_stream_received = stream->id;
 ++    }
 ++    
-      ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
-                    "h2_session: stream(%ld-%d): opened",
-                    session->id, stream_id);
-      
-      return 0;
+++    return stream;
+++}
+++
+++apr_status_t h2_session_flush(h2_session *session) 
+++{
+++    return h2_conn_io_flush(&session->io);
 ++}
 ++
 ++/**
 ++ * Determine the importance of streams when scheduling tasks.
 ++ * - if both stream depend on the same one, compare weights
 ++ * - if one stream is closer to the root, prioritize that one
 ++ * - if both are on the same level, use the weight of their root
 ++ *   level ancestors
 ++ */
 ++static int spri_cmp(int sid1, nghttp2_stream *s1, 
 ++                    int sid2, nghttp2_stream *s2, h2_session *session)
 ++{
 ++    nghttp2_stream *p1, *p2;
 ++    
 ++    p1 = nghttp2_stream_get_parent(s1);
 ++    p2 = nghttp2_stream_get_parent(s2);
 ++    
 ++    if (p1 == p2) {
 ++        int32_t w1, w2;
           
 --        return 0;
 ++        w1 = nghttp2_stream_get_weight(s1);
 ++        w2 = nghttp2_stream_get_weight(s2);
 ++        return w2 - w1;
 ++    }
 ++    else if (!p1) {
 ++        /* stream 1 closer to root */
 ++        return -1;
       }
 ++    else if (!p2) {
 ++        /* stream 2 closer to root */
 ++        return 1;
 ++    }
 ++    return spri_cmp(sid1, p1, sid2, p2, session);
 ++}
 ++
 ++static int stream_pri_cmp(int sid1, int sid2, void *ctx)
 ++{
 ++    h2_session *session = ctx;
 ++    nghttp2_stream *s1, *s2;
       
 --    ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, session->c,
 --                  APLOGNO(02918) 
 --                  "h2_session: stream(%ld-%d): unable to create",
 --                  session->id, stream_id);
 --    return NGHTTP2_ERR_INVALID_STREAM_ID;
 ++    s1 = nghttp2_session_find_stream(session->ngh2, sid1);
 ++    s2 = nghttp2_session_find_stream(session->ngh2, sid2);
 ++
 ++    if (s1 == s2) {
 ++        return 0;
 ++    }
 ++    else if (!s1) {
 ++        return 1;
 ++    }
 ++    else if (!s2) {
 ++        return -1;
 ++    }
 ++    return spri_cmp(sid1, s1, sid2, s2, session);
   }
   
---static apr_status_t stream_end_headers(h2_session *session,
---                                       h2_stream *stream, int eos)
+++static apr_status_t stream_schedule(h2_session *session,
+++                                    h2_stream *stream, int eos)
   {
       (void)session;
 --    return h2_stream_write_eoh(stream, eos);
 ++    return h2_stream_schedule(stream, eos, stream_pri_cmp, session);
   }
   
 --static apr_status_t send_data(h2_session *session, const char *data, 
 --                              apr_size_t length);
 --
   /*
    * Callback when nghttp2 wants to send bytes back to the client.
    */
@@@@ -195,19 -140,19 -140,19 +198,19 @@@@ static int on_data_chunk_recv_cb(nghttp
                                    int32_t stream_id,
                                    const uint8_t *data, size_t len, void *userp)
   {
---    int rv;
       h2_session *session = (h2_session *)userp;
+++    apr_status_t status = APR_SUCCESS;
       h2_stream * stream;
---    apr_status_t status;
+++    int rv;
       
       (void)flags;
       if (session->aborted) {
           return NGHTTP2_ERR_CALLBACK_FAILURE;
       }
---    stream = h2_stream_set_get(session->streams, stream_id);
+++    
+++    stream = h2_session_get_stream(session, stream_id);
       if (!stream) {
---        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
---                      APLOGNO(02919) 
+++        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
                         "h2_session:  stream(%ld-%d): on_data_chunk for unknown stream",
                         session->id, (int)stream_id);
           rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id,
       
       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) 
   {
@@@@ -335,33 -259,33 -259,33 +270,30 @@@@ static int on_stream_close_cb(nghttp2_s
       if (session->aborted) {
           return NGHTTP2_ERR_CALLBACK_FAILURE;
       }
---    stream = h2_stream_set_get(session->streams, stream_id);
+++    stream = h2_session_get_stream(session, stream_id);
       if (stream) {
---        stream_destroy(session, stream, error_code);
-      }
-      
-      if (error_code) {
-          ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
-                        "h2_stream(%ld-%d): close error %d",
-                        session->id, (int)stream_id, error_code);
+++        stream_release(session, stream, error_code);
       }
 --    
 --    if (error_code) {
 --        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
 --                      "h2_stream(%ld-%d): close error %d",
 --                      session->id, (int)stream_id, error_code);
 --    }
---    
       return 0;
   }
   
   static int on_begin_headers_cb(nghttp2_session *ngh2,
                                  const nghttp2_frame *frame, void *userp)
   {
---    /* This starts a new stream. */
---    int rv;
+++    h2_session *session = (h2_session *)userp;
+++    h2_stream *s;
+++    
+++    /* We may see HEADERs at the start of a stream or after all DATA
+++     * streams to carry trailers. */
       (void)ngh2;
---    rv = stream_open((h2_session *)userp, frame->hd.stream_id);
---    if (rv != NGHTTP2_ERR_CALLBACK_FAILURE) {
---      /* on_header_cb or on_frame_recv_cb will dectect that stream
---         does not exist and submit RST_STREAM. */
---      return 0;
+++    s = h2_session_get_stream(session, frame->hd.stream_id);
+++    if (s) {
+++        /* nop */
 ++    }
-      return NGHTTP2_ERR_CALLBACK_FAILURE;
+++    else {
+++        s = h2_session_open_stream((h2_session *)userp, frame->hd.stream_id);
+      }
 --    return NGHTTP2_ERR_CALLBACK_FAILURE;
+++    return s? 0 : NGHTTP2_ERR_CALLBACK_FAILURE;
   }
   
   static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame,
       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;
       }
@@@@ -407,64 -331,63 -331,63 +339,67 @@@@ static int on_frame_recv_cb(nghttp2_ses
                               const nghttp2_frame *frame,
                               void *userp)
   {
---    int rv;
       h2_session *session = (h2_session *)userp;
       apr_status_t status = APR_SUCCESS;
+++    h2_stream *stream;
+++    
       if (session->aborted) {
           return NGHTTP2_ERR_CALLBACK_FAILURE;
       }
       
---    ++session->frames_received;
---    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
---                  "h2_session(%ld): on_frame_rcv #%ld, type=%d", session->id,
+++    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+++                  "h2_stream(%ld-%d): on_frame_rcv #%ld, type=%d", 
+++                  session->id, frame->hd.stream_id, 
                     (long)session->frames_received, frame->hd.type);
+++
+++    ++session->frames_received;
       switch (frame->hd.type) {
---        case NGHTTP2_HEADERS: {
---            int eos;
---            h2_stream * stream = h2_stream_set_get(session->streams,
---                                                   frame->hd.stream_id);
---            if (stream == NULL) {
---                ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
---                              APLOGNO(02921) 
---                              "h2_session:  stream(%ld-%d): HEADERS frame "
---                              "for unknown stream", session->id,
---                              (int)frame->hd.stream_id);
---                rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
---                                               frame->hd.stream_id,
---                                               NGHTTP2_INTERNAL_ERROR);
---                if (nghttp2_is_fatal(rv)) {
---                    return NGHTTP2_ERR_CALLBACK_FAILURE;
+++        case NGHTTP2_HEADERS:
+++            /* This can be HEADERS for a new stream, defining the request,
+++             * or HEADER may come after DATA at the end of a stream as in
+++             * trailers */
+++            stream = h2_session_get_stream(session, frame->hd.stream_id);
+++            if (stream) {
+++                int eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
+++                
+++                if (h2_stream_is_scheduled(stream)) {
+++                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+++                                  "h2_stream(%ld-%d): TRAILER, eos=%d", 
+++                                  session->id, frame->hd.stream_id, eos);
+++                    if (eos) {
+++                        status = h2_stream_close_input(stream);
+++                    }
+++                }
+++                else {
+++                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+++                                  "h2_stream(%ld-%d): HEADER, eos=%d", 
+++                                  session->id, frame->hd.stream_id, eos);
+++                    status = stream_schedule(session, stream, eos);
                   }
---                return 0;
               }
---
---            eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
---            status = stream_end_headers(session, stream, eos);
---
+++            else {
+++                status = APR_EINVAL;
+++            }
               break;
---        }
---        case NGHTTP2_DATA: {
---            h2_stream * stream = h2_stream_set_get(session->streams,
---                                                   frame->hd.stream_id);
---            if (stream == NULL) {
---                ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
---                              APLOGNO(02922) 
---                              "h2_session:  stream(%ld-%d): DATA frame "
---                              "for unknown stream", session->id,
---                              (int)frame->hd.stream_id);
---                rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
---                                               frame->hd.stream_id,
---                                               NGHTTP2_INTERNAL_ERROR);
---                if (nghttp2_is_fatal(rv)) {
---                    return NGHTTP2_ERR_CALLBACK_FAILURE;
+++        case NGHTTP2_DATA:
+++            stream = h2_session_get_stream(session, frame->hd.stream_id);
+++            if (stream) {
+++                int eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
+++                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+++                              "h2_stream(%ld-%d): DATA, len=%ld, eos=%d", 
+++                              session->id, frame->hd.stream_id, 
+++                              (long)frame->hd.length, eos);
+++                if (eos) {
+++                    status = h2_stream_close_input(stream);
                   }
---                return 0;
+++            }
+++            else {
+++                status = APR_EINVAL;
               }
               break;
---        }
---        case NGHTTP2_PRIORITY: {
 --            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+++        case NGHTTP2_PRIORITY:
 ++            session->reprioritize = 1;
-              ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+++            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
                             "h2_session:  stream(%ld-%d): PRIORITY frame "
                             " weight=%d, dependsOn=%d, exclusive=%d", 
                             session->id, (int)frame->hd.stream_id,
                             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)
   
@@@@ -656,9 -537,9 -537,9 +572,6 @@@@ static apr_status_t init_callbacks(conn
       NGH2_SET_CALLBACK(*pcb, on_frame_recv, on_frame_recv_cb);
       NGH2_SET_CALLBACK(*pcb, on_invalid_frame_recv, on_invalid_frame_recv_cb);
       NGH2_SET_CALLBACK(*pcb, on_data_chunk_recv, on_data_chunk_recv_cb);
---    NGH2_SET_CALLBACK(*pcb, before_frame_send, before_frame_send_cb);
---    NGH2_SET_CALLBACK(*pcb, on_frame_send, on_frame_send_cb);
---    NGH2_SET_CALLBACK(*pcb, on_frame_not_send, on_frame_not_send_cb);
       NGH2_SET_CALLBACK(*pcb, on_stream_close, on_stream_close_cb);
       NGH2_SET_CALLBACK(*pcb, on_begin_headers, on_begin_headers_cb);
       NGH2_SET_CALLBACK(*pcb, on_header, on_header_cb);
       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);
@@@@ -761,15 -641,15 -641,15 +685,37 @@@@ h2_session *h2_session_rcreate(request_
       return h2_session_create_int(r->connection, r, config, workers);
   }
   
---void h2_session_destroy(h2_session *session)
+++static void h2_session_cleanup(h2_session *session)
   {
       AP_DEBUG_ASSERT(session);
+++    /* This is an early cleanup of the session that may
+++     * discard what is no longer necessary for *new* streams
+++     * and general HTTP/2 processing.
+++     * At this point, all frames are in transit or somehwere in
+++     * our buffers or passed down output filters.
+++     * h2 streams might still being written out.
+++     */
+++    if (session->ngh2) {
+++        nghttp2_session_del(session->ngh2);
+++        session->ngh2 = NULL;
+++    }
+++    if (session->spare) {
+++        apr_pool_destroy(session->spare);
+++        session->spare = NULL;
+++    }
       if (session->mplx) {
           h2_mplx_release_and_join(session->mplx, session->iowait);
           session->mplx = NULL;
       }
+++}
+++
+++void h2_session_destroy(h2_session *session)
+++{
+++    AP_DEBUG_ASSERT(session);
+++    h2_session_cleanup(session);
+++    
       if (session->streams) {
---        if (h2_stream_set_size(session->streams)) {
+++        if (!h2_stream_set_is_empty(session->streams)) {
               ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
                             "h2_session(%ld): destroy, %d streams open",
                             session->id, (int)h2_stream_set_size(session->streams));
           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);
       }
@@@@ -863,6 -746,6 -746,6 +808,8 @@@@ apr_status_t h2_session_start(h2_sessio
       apr_status_t status = APR_SUCCESS;
       h2_config *config;
       nghttp2_settings_entry settings[3];
+++    size_t slen;
+++    int i;
       
       AP_DEBUG_ASSERT(session);
       /* Start the conversation by submitting our SETTINGS frame */
           }
           
           /* 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;
@@@@ -1003,102 -886,98 -886,98 +940,18 @@@@ static int h2_session_resume_streams_wi
       return 0;
   }
   
---static void update_window(void *ctx, int stream_id, apr_size_t bytes_read)
+++static void update_window(void *ctx, int stream_id, apr_off_t bytes_read)
   {
       h2_session *session = (h2_session*)ctx;
       nghttp2_session_consume(session->ngh2, stream_id, bytes_read);
   }
   
-  static apr_status_t h2_session_flush(h2_session *session) 
-  {
-      session->flush = 0;
-      return h2_conn_io_flush(&session->io);
-  }
-  
---static apr_status_t h2_session_update_windows(h2_session *session)
---{
---    return h2_mplx_in_update_windows(session->mplx, update_window, session);
---}
---
---apr_status_t h2_session_write(h2_session *session, apr_interval_time_t timeout)
---{
---    apr_status_t status = APR_EAGAIN;
---    h2_stream *stream = NULL;
 --    int flush_output = 0;
---    
---    AP_DEBUG_ASSERT(session);
-      
-      if (session->reprioritize) {
-          h2_mplx_reprioritize(session->mplx, stream_pri_cmp, session);
-          session->reprioritize = 0;
-      }
---    
---    /* Check that any pending window updates are sent. */
---    status = h2_session_update_windows(session);
-      if (status != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(status)) {
 --    if (status == APR_SUCCESS) {
 --        flush_output = 1;
 --    }
 --    else if (status != APR_EAGAIN) {
---        return status;
---    }
---    
---    if (h2_session_want_write(session)) {
---        int rv;
---        status = APR_SUCCESS;
---        rv = nghttp2_session_send(session->ngh2);
---        if (rv != 0) {
---            ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c,
---                          "h2_session: send: %s", nghttp2_strerror(rv));
---            if (nghttp2_is_fatal(rv)) {
---                h2_session_abort_int(session, rv);
---                status = APR_ECONNABORTED;
---            }
---        }
 --        flush_output = 1;
---    }
---    
---    /* If we have responses ready, submit them now. */
-      while (!session->aborted 
-             && (stream = h2_mplx_next_submit(session->mplx, session->streams)) != NULL) {
 --    while ((stream = h2_mplx_next_submit(session->mplx, 
 --                                         session->streams)) != NULL) {
---        status = h2_session_handle_response(session, stream);
 --        flush_output = 1;
---    }
---    
-      if (!session->aborted && h2_session_resume_streams_with_data(session) > 0) {
 --    if (h2_session_resume_streams_with_data(session) > 0) {
 --        flush_output = 1;
---    }
---    
-      if (!session->aborted && !session->flush && timeout > 0 
-          && !h2_session_want_write(session)) {
-          h2_session_flush(session);
 --    if (!flush_output && timeout > 0 && !h2_session_want_write(session)) {
---        status = h2_mplx_out_trywait(session->mplx, timeout, session->iowait);
---
---        if (status != APR_TIMEUP
---            && h2_session_resume_streams_with_data(session) > 0) {
 --            flush_output = 1;
---        }
---        else {
---            /* nothing happened to ongoing streams, do some house-keeping */
---        }
---    }
---    
---    if (h2_session_want_write(session)) {
---        int rv;
---        status = APR_SUCCESS;
---        rv = nghttp2_session_send(session->ngh2);
---        if (rv != 0) {
---            ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c,
---                          "h2_session: send2: %s", nghttp2_strerror(rv));
---            if (nghttp2_is_fatal(rv)) {
---                h2_session_abort_int(session, rv);
---                status = APR_ECONNABORTED;
---            }
---        }
 --        flush_output = 1;
---    }
---    
-      if (session->flush) {
-          h2_session_flush(session);
 --    if (flush_output) {
 --        h2_conn_io_flush(&session->io);
---    }
---    
---    return status;
---}
---
   h2_stream *h2_session_get_stream(h2_session *session, int stream_id)
   {
---    AP_DEBUG_ASSERT(session);
---    return h2_stream_set_get(session->streams, stream_id);
+++    if (!session->last_stream || stream_id != session->last_stream->id) {
+++        session->last_stream = h2_stream_set_get(session->streams, stream_id);
+++    }
+++    return session->last_stream;
   }
   
   /* h2_io_on_read_cb implementation that offers the data read
@@@@ -1131,31 -1010,20 -1010,20 +984,21 @@@@ static apr_status_t session_receive(con
       return APR_SUCCESS;
   }
   
---apr_status_t h2_session_read(h2_session *session, apr_read_type_e block)
---{
---    AP_DEBUG_ASSERT(session);
-      if (block == APR_BLOCK_READ) {
-          /* before we do a blocking read, make sure that all our output
-           * is send out. Otherwise we might deadlock. */
-          h2_session_flush(session);
-      }
---    return h2_conn_io_read(&session->io, block, session_receive, session);
---}
---
   apr_status_t h2_session_close(h2_session *session)
   {
       AP_DEBUG_ASSERT(session);
 --    return session->aborted? APR_SUCCESS : h2_conn_io_flush(&session->io);
 ++    if (!session->aborted) {
 ++        h2_session_abort_int(session, 0);
 ++    }
 ++    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0,session->c,
 ++                  "h2_session: closing, writing eoc");
-      h2_conn_io_writeb(&session->io,
-                        h2_bucket_eoc_create(session->c->bucket_alloc, 
-                                             session));
-      return h2_conn_io_flush(&session->io);
+++    
+++    h2_session_cleanup(session);              
+++    return h2_conn_io_writeb(&session->io,
+++                             h2_bucket_eoc_create(session->c->bucket_alloc, 
+++                                                  session));
   }
   
 --/* The session wants to send more DATA for the given stream.
 -- */
   static ssize_t stream_data_cb(nghttp2_session *ng2s,
                                 int32_t stream_id,
                                 uint8_t *buf,
                                 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);
@@@@ -1245,58 -1100,57 -1100,57 +1088,75 @@@@ typedef struct 
       size_t offset;
   } nvctx_t;
   
---static int submit_response(h2_session *session, h2_response *response)
---{
---    nghttp2_data_provider provider;
---    int rv;
---    
---    memset(&provider, 0, sizeof(provider));
---    provider.source.fd = response->stream_id;
---    provider.read_callback = stream_data_cb;
---    
---    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
---                  "h2_stream(%ld-%d): submitting response %s",
---                  session->id, response->stream_id, response->status);
---    
---    rv = nghttp2_submit_response(session->ngh2, response->stream_id,
---                                 response->ngheader->nv, 
---                                 response->ngheader->nvlen, &provider);
---    
---    if (rv != 0) {
---        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
---                      APLOGNO(02939) "h2_stream(%ld-%d): submit_response: %s",
---                      session->id, response->stream_id, nghttp2_strerror(rv));
---    }
---    else {
---        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
---                      "h2_stream(%ld-%d): submitted response %s, rv=%d",
---                      session->id, response->stream_id, 
---                      response->status, rv);
---    }
---    return rv;
---}
---
---/* Start submitting the response to a stream request. This is possible
+++/**
+++ * Start submitting the response to a stream request. This is possible
    * once we have all the response headers. The response body will be
    * read by the session using the callback we supply.
    */
---apr_status_t h2_session_handle_response(h2_session *session, h2_stream *stream)
+++static apr_status_t submit_response(h2_session *session, h2_stream *stream)
   {
       apr_status_t status = APR_SUCCESS;
       int rv = 0;
       AP_DEBUG_ASSERT(session);
       AP_DEBUG_ASSERT(stream);
 --    AP_DEBUG_ASSERT(stream->response);
 ++    AP_DEBUG_ASSERT(stream->response || stream->rst_error);
       
-      if (stream->response && stream->response->ngheader) {
 --    if (stream->response->ngheader) {
---        rv = submit_response(session, stream->response);
+++    if (stream->submitted) {
+++        rv = NGHTTP2_PROTOCOL_ERROR;
+++    }
+++    else if (stream->response && stream->response->header) {
+++        nghttp2_data_provider provider;
+++        h2_response *response = stream->response;
+++        h2_ngheader *ngh;
+++        
+++        memset(&provider, 0, sizeof(provider));
+++        provider.source.fd = stream->id;
+++        provider.read_callback = stream_data_cb;
+++        
+++        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+++                      "h2_stream(%ld-%d): submit response %d",
+++                      session->id, stream->id, response->http_status);
+++        
+++        ngh = h2_util_ngheader_make_res(stream->pool, response->http_status, 
+++                                        response->header);
+++        rv = nghttp2_submit_response(session->ngh2, response->stream_id,
+++                                     ngh->nv, ngh->nvlen, &provider);
+++        
+++        /* If the submit worked,
+++         * and this stream is not a pushed one itself,
+++         * and HTTP/2 server push is enabled here,
+++         * and the response is in the range 200-299 *),
+++         * and the remote side has pushing enabled,
+++         * -> find and perform any pushes on this stream
+++         * 
+++         * *) the response code is relevant, as we do not want to 
+++         *    make pushes on 401 or 403 codes, neiterh on 301/302
+++         *    and friends. And if we see a 304, we do not push either
+++         *    as the client, having this resource in its cache, might
+++         *    also have the pushed ones as well.
+++         */
+++        if (!rv 
+++            && !stream->initiated_on
+++            && h2_config_geti(h2_config_get(session->c), H2_CONF_PUSH)
+++            && H2_HTTP_2XX(response->http_status)
+++            && h2_session_push_enabled(session)) {
+++            
+++            h2_stream_submit_pushes(stream);
+++        }
       }
       else {
+++        int err = H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR);
+++        
+++        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+++                      "h2_stream(%ld-%d): RST_STREAM, err=%d",
+++                      session->id, stream->id, err);
+++
           rv = nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
-                                         stream->id, 
-                                         H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR));
 --                                       stream->id, NGHTTP2_PROTOCOL_ERROR);
+++                                       stream->id, err);
       }
       
+++    stream->submitted = 1;
+++
       if (nghttp2_is_fatal(rv)) {
           status = APR_EGENERAL;
           h2_session_abort_int(session, rv);
                         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;
+++}
index a1d718a9ca6b8c42e5c1cefd37e480a77e5a47a5,12768a90fb374e6e157a112fcf4889be98b650bb,12768a90fb374e6e157a112fcf4889be98b650bb..90052fc9e7fe99ab41c115c524c8ec4a4bdf98e6
@@@@ -41,6 -41,6 -41,6 +41,7 @@@@ struct apr_thread_mutext_t
   struct apr_thread_cond_t;
   struct h2_config;
   struct h2_mplx;
+++struct h2_push;
   struct h2_response;
   struct h2_session;
   struct h2_stream;
@@@@ -58,9 -58,6 -58,6 +59,8 @@@@ struct h2_session 
       request_rec *r;                 /* the request that started this in case
                                        * of 'h2c', NULL otherwise */
       int aborted;                    /* this session is being aborted */
-      int flush;                      /* if != 0, flush output on next occasion */
 ++    int reprioritize;               /* scheduled streams priority needs to 
 ++                                     * be re-evaluated */
       apr_size_t frames_received;     /* number of http/2 frames received */
       apr_size_t max_stream_count;    /* max number of open streams */
       apr_size_t max_stream_mem;      /* max buffer memory for a single stream */
       h2_conn_io io;                  /* io on httpd conn filters */
       struct h2_mplx *mplx;           /* multiplexer for stream data */
       
+++    struct h2_stream *last_stream;  /* last stream worked with */
       struct h2_stream_set *streams;  /* streams handled by this session */
       
       int max_stream_received;        /* highest stream id created */
@@@@ -106,6 -101,6 -101,6 +107,14 @@@@ h2_session *h2_session_create(conn_rec 
   h2_session *h2_session_rcreate(request_rec *r, struct h2_config *cfg,
                                  struct h2_workers *workers);
   
+++/**
+++ * Process the given HTTP/2 session until it is ended or a fatal
+++ * error occured.
+++ *
+++ * @param session the sessionm to process
+++ */
+++apr_status_t h2_session_process(h2_session *session);
+++
   /**
    * Destroy the session and all objects it still contains. This will not
    * destroy h2_task instances that have not finished yet. 
    */
   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. */
@@@@ -169,12 -158,6 -158,6 +166,39 @@@@ apr_status_t h2_session_handle_response
   /* Get the h2_stream for the given stream idenrtifier. */
   struct h2_stream *h2_session_get_stream(h2_session *session, int stream_id);
   
 --void h2_session_log_stats(h2_session *session);
+++/**
+++ * Create and register a new stream under the given id.
+++ * 
+++ * @param session the session to register in
+++ * @param stream_id the new stream identifier
+++ * @return the new stream
+++ */
+++struct h2_stream *h2_session_open_stream(h2_session *session, int stream_id);
+++
+++/**
+++ * Returns if client settings have push enabled.
+++ * @param != 0 iff push is enabled in client settings
+++ */
+++int h2_session_push_enabled(h2_session *session);
+++
 ++/**
 ++ * Destroy the stream and release it everywhere. Reclaim all resources.
 ++ * @param session the session to which the stream belongs
 ++ * @param stream the stream to destroy
 ++ */
 ++apr_status_t h2_session_stream_destroy(h2_session *session, 
 ++                                       struct h2_stream *stream);
 ++
+++/**
+++ * Submit a push promise on the stream and schedule the new steam for
+++ * processing..
+++ * 
+++ * @param session the session to work in
+++ * @param is the stream initiating the push
+++ * @param push the push to promise
+++ * @return the new promised stream or NULL
+++ */
+++struct h2_stream *h2_session_push(h2_session *session, 
+++                                  struct h2_stream *is, struct h2_push *push);
+  
   #endif /* defined(__mod_h2__h2_session__) */
index 5a5af6372b7dd1849e4ac50b45bc9a75d2b1d0f4,52781d8474fec50392e95dbf3417dc5eaf722cf1,52781d8474fec50392e95dbf3417dc5eaf722cf1..594175a78541e459bc8639f56eb1a344b33433cb
   
   #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);
       }
@@@@ -93,256 -98,171 -98,171 +195,373 @@@@ apr_pool_t *h2_stream_detach_pool(h2_st
       return pool;
   }
   
 --void h2_stream_abort(h2_stream *stream)
 ++void h2_stream_rst(h2_stream *stream, int error_code)
   {
 --    AP_DEBUG_ASSERT(stream);
 --    stream->aborted = 1;
 ++    stream->rst_error = error_code;
+++    close_input(stream);
+++    close_output(stream);
 ++    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c,
 ++                  "h2_stream(%ld-%d): reset, error=%d", 
 ++                  stream->session->id, stream->id, error_code);
   }
   
   apr_status_t h2_stream_set_response(h2_stream *stream, h2_response *response,
                                       apr_bucket_brigade *bb)
   {
 ++    apr_status_t status = APR_SUCCESS;
+++    if (!output_open(stream)) {
+++        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c,
+++                      "h2_stream(%ld-%d): output closed", 
+++                      stream->session->id, stream->id);
+++        return APR_ECONNRESET;
+++    }
 ++    
       stream->response = response;
       if (bb && !APR_BRIGADE_EMPTY(bb)) {
 --        if (!stream->bbout) {
 --            stream->bbout = apr_brigade_create(stream->pool, 
 --                                               stream->m->c->bucket_alloc);
 --        }
 --        return h2_util_move(stream->bbout, bb, 16 * 1024, NULL,  
 --                            "h2_stream_set_response");
 ++        int move_all = INT_MAX;
 ++        /* we can move file handles from h2_mplx into this h2_stream as many
 ++         * as we want, since the lifetimes are the same and we are not freeing
 ++         * the ones in h2_mplx->io before this stream is done. */
-          status = h2_util_move(stream->bbout, bb, 16 * 1024, &move_all,  
+++        H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream set_response_pre");
+++        status = h2_util_move(stream->bbout, bb, -1, &move_all,  
 ++                              "h2_stream_set_response");
+++        H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream set_response_post");
       }
-      if (APLOGctrace1(stream->session->c)) {
-          apr_size_t len = 0;
-          int eos = 0;
-          h2_util_bb_avail(stream->bbout, &len, &eos);
-          ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c,
-                        "h2_stream(%ld-%d): set_response(%s), len=%ld, eos=%d", 
-                        stream->session->id, stream->id, response->status,
-                        (long)len, (int)eos);
-      }
 --    return APR_SUCCESS;
+++    
+++    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c,
+++                  "h2_stream(%ld-%d): set_response(%d)", 
+++                  stream->session->id, stream->id, response->http_status);
 ++    return status;
   }
   
---static int set_closed(h2_stream *stream) 
-  {
-      switch (stream->state) {
-          case H2_STREAM_ST_CLOSED_INPUT:
-          case H2_STREAM_ST_CLOSED:
-              return 0; /* ignore, idempotent */
-          case H2_STREAM_ST_CLOSED_OUTPUT:
-              /* both closed now */
-              set_state(stream, H2_STREAM_ST_CLOSED);
-              break;
-          default:
-              /* everything else we jump to here */
-              set_state(stream, H2_STREAM_ST_CLOSED_INPUT);
-              break;
-      }
-      return 1;
-  }
-  
-  apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r)
+++apr_status_t h2_stream_set_request(h2_stream *stream, request_rec *r)
   {
 --    switch (stream->state) {
 --        case H2_STREAM_ST_CLOSED_INPUT:
 --        case H2_STREAM_ST_CLOSED:
 --            return 0; /* ignore, idempotent */
 --        case H2_STREAM_ST_CLOSED_OUTPUT:
 --            /* both closed now */
 --            set_state(stream, H2_STREAM_ST_CLOSED);
 --            break;
 --        default:
 --            /* everything else we jump to here */
 --            set_state(stream, H2_STREAM_ST_CLOSED_INPUT);
 --            break;
 ++    apr_status_t status;
 ++    AP_DEBUG_ASSERT(stream);
 ++    if (stream->rst_error) {
 ++        return APR_ECONNRESET;
       }
 --    return 1;
 ++    set_state(stream, H2_STREAM_ST_OPEN);
-      status = h2_request_rwrite(stream->request, r, stream->session->mplx);
+++    status = h2_request_rwrite(stream->request, r);
 ++    return status;
   }
   
 --apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r)
+++void h2_stream_set_h2_request(h2_stream *stream, int initiated_on,
+++                              const h2_request *req)
+++{
+++    h2_request_copy(stream->pool, stream->request, req);
+++    stream->initiated_on = initiated_on;
+++    stream->request->eoh = 0;
+++}
+++
+++apr_status_t h2_stream_add_header(h2_stream *stream,
+++                                  const char *name, size_t nlen,
+++                                  const char *value, size_t vlen)
+  {
 --    apr_status_t status;
+      AP_DEBUG_ASSERT(stream);
 --    set_state(stream, H2_STREAM_ST_OPEN);
 --    status = h2_request_rwrite(stream->request, r, stream->m);
 --    return status;
+++    if (h2_stream_is_scheduled(stream)) {
+++        return h2_request_add_trailer(stream->request, stream->pool,
+++                                      name, nlen, value, vlen);
+++    }
+++    else {
+++        if (!input_open(stream)) {
+++            return APR_ECONNRESET;
+++        }
+++        return h2_request_add_header(stream->request, stream->pool,
+++                                     name, nlen, value, vlen);
+++    }
+  }
+  
 --apr_status_t h2_stream_write_eoh(h2_stream *stream, int eos)
 ++apr_status_t h2_stream_schedule(h2_stream *stream, int eos,
 ++                                h2_stream_pri_cmp *cmp, void *ctx)
   {
       apr_status_t status;
       AP_DEBUG_ASSERT(stream);
-      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;
+++}
index d09d848a66a3c1b650e16781c418b8dd6c156e6d,0608f2f34006fcfeb28f2624637512b28ce523b4,0608f2f34006fcfeb28f2624637512b28ce523b4..e5990a2bf38c2ddd1a392819c5811f1a92eb2e94
   /**
    * A HTTP/2 stream, e.g. a client request+response in HTTP/1.1 terms.
    * 
--- * Ok, not quite, but close enough, since we do not implement server
--- * pushes yet.
--- *
    * A stream always belongs to a h2_session, the one managing the
    * connection to the client. The h2_session writes to the h2_stream,
    * adding HEADERS and DATA and finally an EOS. When headers are done,
--- * h2_stream can create a h2_task that can be scheduled to fullfill the
--- * request.
+++ * h2_stream is scheduled for handling, which is expected to produce
+++ * a h2_response.
    * 
--- * This response headers are added directly to the h2_mplx of the session,
--- * but the response DATA can be read via h2_stream. Reading data will
--- * never block but return APR_EAGAIN when there currently is no data (and
--- * no eos) in the multiplexer for this stream.
+++ * The h2_response gives the HEADER frames to sent to the client, followed
+++ * by DATA frames read from the h2_stream until EOS is reached.
    */
   #include "h2_io.h"
   
@@@@ -55,64 -54,54 -54,54 +50,244 @@@@ typedef struct h2_stream h2_stream
   
   struct h2_stream {
       int id;                     /* http2 stream id */
+++    int initiated_on;           /* http2 stream id this was initiated on or 0 */
       h2_stream_state_t state;    /* http/2 state of this stream */
 --    struct h2_mplx *m;          /* the multiplexer to work with */
 ++    struct h2_session *session; /* the session this stream belongs to */
 ++    
+++    apr_pool_t *pool;           /* the memory pool for this stream */
+++    struct h2_request *request; /* the request made in this stream */
+++    struct h2_response *response; /* the response, once ready */
+      
       int aborted;                /* was aborted */
       int suspended;              /* DATA sending has been suspended */
 ++    int rst_error;              /* stream error for RST_STREAM */
+++    int scheduled;              /* stream has been scheduled */
+++    int submitted;              /* response HEADER has been sent */
       
---    apr_pool_t *pool;           /* the memory pool for this stream */
---    struct h2_request *request; /* the request made in this stream */
-      
-      struct h2_response *response; /* the response, once ready */
+++    apr_off_t input_remaining;  /* remaining bytes on input as advertised via content-length */
+++    apr_bucket_brigade *bbin;   /* input DATA */
       
 --    struct h2_task *task;       /* task created for this stream */
 --    struct h2_response *response; /* the response, once ready */
       apr_bucket_brigade *bbout;  /* output DATA */
-      apr_size_t data_frames_sent;/* # of DATA frames sent out for this stream */
+++    apr_off_t data_frames_sent; /* # of DATA frames sent out for this stream */
   };
   
   
 --h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_mplx *m);
 ++#define H2_STREAM_RST(s, def)    (s->rst_error? s->rst_error : (def))
   
+++/**
+++ * Create a stream in IDLE state.
+++ * @param id      the stream identifier
+++ * @param pool    the memory pool to use for this stream
+++ * @param session the session this stream belongs to
+++ * @return the newly created IDLE stream
+++ */
 ++h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_session *session);
 ++
+++/**
+++ * Create a stream in OPEN state.
+++ * @param id      the stream identifier
+++ * @param pool    the memory pool to use for this stream
+++ * @param session the session this stream belongs to
+++ * @return the newly opened stream
+++ */
+++h2_stream *h2_stream_open(int id, apr_pool_t *pool, struct h2_session *session);
+++
+++/**
+++ * Destroy any resources held by this stream. Will destroy memory pool
+++ * if still owned by the stream.
+++ *
+++ * @param stream the stream to destroy
+++ */
   apr_status_t h2_stream_destroy(h2_stream *stream);
   
-  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__) */
index 5ef48a13e17d384a40f1f33844da0c1ed7a7ee67,dddd2e3990820e0ff6ea4c56181172ad1f651ec2,dddd2e3990820e0ff6ea4c56181172ad1f651ec2..aa0f8c65019a278f2b76e57585524c0cdbd9f3f2
   #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;
   }
   
@@@@ -52,111 -52,113 -52,113 +49,97 @@@@ void h2_stream_set_destroy(h2_stream_se
       (void)sp;
   }
   
---static int h2_stream_id_cmp(const void *s1, const void *s2)
+++h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id)
   {
-      return (*((h2_stream **)s1))->id - (*((h2_stream **)s2))->id;
 --    h2_stream **pstream1 = (h2_stream **)s1;
 --    h2_stream **pstream2 = (h2_stream **)s2;
 --    return (*pstream1)->id - (*pstream2)->id;
+++    return apr_hash_get(sp->hash, &stream_id, sizeof(stream_id));
   }
   
---h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id)
+++void h2_stream_set_add(h2_stream_set *sp, h2_stream *stream)
   {
---    /* we keep the array sorted by id, so lookup can be done
---     * by bsearch.
---     */
---    h2_stream key;
---    h2_stream *pkey, **ps;
---    memset(&key, 0, sizeof(key));
---    key.id = stream_id;
---    pkey = &key;
---    ps = bsearch(&pkey, sp->list->elts, sp->list->nelts, 
---                             sp->list->elt_size, h2_stream_id_cmp);
---    return ps? *ps : NULL;
---}
---
---static void h2_stream_set_sort(h2_stream_set *sp)
---{
---    qsort(sp->list->elts, sp->list->nelts, sp->list->elt_size, 
---          h2_stream_id_cmp);
---}
---
---apr_status_t h2_stream_set_add(h2_stream_set *sp, h2_stream *stream)
---{
---    h2_stream *existing = h2_stream_set_get(sp, stream->id);
---    if (!existing) {
---        int last;
---        APR_ARRAY_PUSH(sp->list, h2_stream*) = stream;
---        /* Normally, streams get added in ascending order if id. We
---         * keep the array sorted, so we just need to check of the newly
---         * appended stream has a lower id than the last one. if not,
---         * sorting is not necessary.
---         */
---        last = sp->list->nelts - 1;
---        if (last > 0 
---            && (H2_STREAM_IDX(sp->list, last)->id 
---                < H2_STREAM_IDX(sp->list, last-1)->id)) {
---            h2_stream_set_sort(sp);
---        }
---    }
---    return APR_SUCCESS;
---}
---
-  h2_stream *h2_stream_set_remove(h2_stream_set *sp, int stream_id)
 --h2_stream *h2_stream_set_remove(h2_stream_set *sp, h2_stream *stream)
---{
---    int i;
---    for (i = 0; i < sp->list->nelts; ++i) {
---        h2_stream *s = H2_STREAM_IDX(sp->list, i);
-          if (s->id == stream_id) {
 --        if (s == stream) {
---            int n;
---            --sp->list->nelts;
---            n = sp->list->nelts - i;
---            if (n > 0) {
---                /* Close the hole in the array by moving the upper
---                 * parts down one step.
---                 */
---                h2_stream **selts = (h2_stream**)sp->list->elts;
---                memmove(selts+i, selts+i+1, n * sizeof(h2_stream*));
---            }
---            return s;
---        }
---    }
---    return NULL;
+++    apr_hash_set(sp->hash, &stream->id, sizeof(stream->id), stream);
   }
   
---void h2_stream_set_remove_all(h2_stream_set *sp)
+++void h2_stream_set_remove(h2_stream_set *sp, int stream_id)
   {
---    sp->list->nelts = 0;
+++    apr_hash_set(sp->hash, &stream_id, sizeof(stream_id), NULL);
   }
   
   int h2_stream_set_is_empty(h2_stream_set *sp)
   {
---    AP_DEBUG_ASSERT(sp);
---    return sp->list->nelts == 0;
+++    return apr_hash_count(sp->hash) == 0;
   }
   
---h2_stream *h2_stream_set_find(h2_stream_set *sp,
---                              h2_stream_set_match_fn *match, void *ctx)
+++apr_size_t h2_stream_set_size(h2_stream_set *sp)
   {
---    h2_stream *s = NULL;
---    int i;
---    for (i = 0; !s && i < sp->list->nelts; ++i) {
---        s = match(ctx, H2_STREAM_IDX(sp->list, i));
---    }
---    return s;
+++    return apr_hash_count(sp->hash);
+++}
+++
+++typedef struct {
+++    h2_stream_set_iter_fn *iter;
+++    void *ctx;
+++} iter_ctx;
+++
+++static int hash_iter(void *ctx, const void *key, apr_ssize_t klen, 
+++                     const void *val)
+++{
+++    iter_ctx *ictx = ctx;
+++    return ictx->iter(ictx->ctx, (h2_stream*)val);
   }
   
   void h2_stream_set_iter(h2_stream_set *sp,
                           h2_stream_set_iter_fn *iter, void *ctx)
   {
---    int i;
---    for (i = 0; i < sp->list->nelts; ++i) {
---        h2_stream *s = H2_STREAM_IDX(sp->list, i);
---        if (!iter(ctx, s)) {
---            break;
---        }
+++    iter_ctx ictx;
+++    ictx.iter = iter;
+++    ictx.ctx = ctx;
+++    apr_hash_do(hash_iter, &ictx, sp->hash);
+++}
+++
+++static int unsubmitted_iter(void *ctx, h2_stream *stream)
+++{
+++    if (h2_stream_needs_submit(stream)) {
+++        *((int *)ctx) = 1;
+++        return 0;
       }
+++    return 1;
   }
   
---apr_size_t h2_stream_set_size(h2_stream_set *sp)
+++int h2_stream_set_has_unsubmitted(h2_stream_set *sp)
+++{
+++    int has_unsubmitted = 0;
+++    h2_stream_set_iter(sp, unsubmitted_iter, &has_unsubmitted);
+++    return has_unsubmitted;
+++}
+++
+++static int input_open_iter(void *ctx, h2_stream *stream)
+++{
+++    if (h2_stream_input_is_open(stream)) {
+++        *((int *)ctx) = 1;
+++        return 0;
+++    }
+++    return 1;
+++}
+++
+++int h2_stream_set_has_open_input(h2_stream_set *sp)
+++{
+++    int has_input_open = 0;
+++    h2_stream_set_iter(sp, input_open_iter, &has_input_open);
+++    return has_input_open;
+++}
+++
+++static int suspended_iter(void *ctx, h2_stream *stream)
+++{
+++    if (h2_stream_is_suspended(stream)) {
+++        *((int *)ctx) = 1;
+++        return 0;
+++    }
+++    return 1;
+++}
+++
+++int h2_stream_set_has_suspended(h2_stream_set *sp)
   {
---    return sp->list->nelts;
+++    int has_suspended = 0;
+++    h2_stream_set_iter(sp, suspended_iter, &has_suspended);
+++    return has_suspended;
   }
   
index 8bc6409223b186b09b0e1121039c58e9e08e17c1,56075834555ab2674a7ca497235275d2c796f4cd,56075834555ab2674a7ca497235275d2c796f4cd..d0041c484329714235724af0ef9ea842938dfb78
@@@@ -27,26 -27,26 -27,26 +27,25 @@@@ typedef int h2_stream_set_iter_fn(void 
   typedef struct h2_stream_set h2_stream_set;
   
   
---h2_stream_set *h2_stream_set_create(apr_pool_t *pool);
+++h2_stream_set *h2_stream_set_create(apr_pool_t *pool, int max);
   
   void h2_stream_set_destroy(h2_stream_set *sp);
   
---apr_status_t h2_stream_set_add(h2_stream_set *sp, h2_stream *stream);
+++void h2_stream_set_add(h2_stream_set *sp, h2_stream *stream);
   
   h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id);
   
-  h2_stream *h2_stream_set_remove(h2_stream_set *sp, int stream_id);
 --h2_stream *h2_stream_set_remove(h2_stream_set *sp,h2_stream *stream);
+++void h2_stream_set_remove(h2_stream_set *sp, int stream_id);
   
---void h2_stream_set_remove_all(h2_stream_set *sp);
+++void h2_stream_set_iter(h2_stream_set *sp,
+++                        h2_stream_set_iter_fn *iter, void *ctx);
   
   int h2_stream_set_is_empty(h2_stream_set *sp);
   
   apr_size_t h2_stream_set_size(h2_stream_set *sp);
   
---h2_stream *h2_stream_set_find(h2_stream_set *sp,
---                              h2_stream_set_match_fn *match, void *ctx);
---
---void h2_stream_set_iter(h2_stream_set *sp,
---                        h2_stream_set_iter_fn *iter, void *ctx);
+++int h2_stream_set_has_unsubmitted(h2_stream_set *sp);
+++int h2_stream_set_has_open_input(h2_stream_set *sp);
+++int h2_stream_set_has_suspended(h2_stream_set *sp);
   
   #endif /* defined(__mod_h2__h2_stream_set__) */
index 97f07f891d71eeb1381a5517fbd004034f981f3a,23c34490eaa27c4414ee30a9e3111c30df9f9f55,23c34490eaa27c4414ee30a9e3111c30df9f9f55..c107db8e73a40eff93b42138663efbbd1985057a
@@@@ -154,7 -154,7 -154,7 +154,7 @@@@ static int h2_protocol_switch(conn_rec 
               ap_remove_input_filter_byhandle(r->input_filters, "reqtimeout");
               
               /* Ok, start an h2_conn on this one. */
---            status = h2_conn_rprocess(r);
+++            status = h2_conn_process(r->connection, r);
               if (status != DONE) {
                   /* Nothing really to do about this. */
                   ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
index ee78f4347e1b3a5302bbbe2441151405cc51a6f6,bbea7b20f8aa1ae48309e38cd9a04368b5d703ea,bbea7b20f8aa1ae48309e38cd9a04368b5d703ea..b7d48a1bf6fe524ab443e2219ed8bc98bd7cee53
   #include "h2_from_h1.h"
   #include "h2_h2.h"
   #include "h2_mplx.h"
+++#include "h2_request.h"
   #include "h2_session.h"
   #include "h2_stream.h"
   #include "h2_task_input.h"
@@@@ -152,46 -156,48 -156,48 +153,30 @@@@ static int h2_task_process_conn(conn_re
   }
   
   
---h2_task *h2_task_create(long session_id,
---                        int stream_id,
---                        apr_pool_t *stream_pool,
---                        h2_mplx *mplx, conn_rec *c)
+++h2_task *h2_task_create(long session_id, const h2_request *req, 
+++                        apr_pool_t *pool, h2_mplx *mplx, int eos)
   {
---    h2_task *task = apr_pcalloc(stream_pool, sizeof(h2_task));
+++    h2_task *task = apr_pcalloc(pool, sizeof(h2_task));
       if (task == NULL) {
---        ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, stream_pool,
+++        ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, pool,
                         APLOGNO(02941) "h2_task(%ld-%d): create stream task", 
---                      session_id, stream_id);
---        h2_mplx_out_close(mplx, stream_id);
+++                      session_id, req->id);
+++        h2_mplx_out_close(mplx, req->id);
           return NULL;
       }
       
 --    APR_RING_ELEM_INIT(task, link);
 --
---    task->id = apr_psprintf(stream_pool, "%ld-%d", session_id, stream_id);
---    task->stream_id = stream_id;
+++    task->id = apr_psprintf(pool, "%ld-%d", session_id, req->id);
+++    task->stream_id = req->id;
+++    task->pool = pool;
       task->mplx = mplx;
+++    task->c = h2_conn_create(mplx->c, task->pool);
+++
+++    task->request = req;
+++    task->input_eos = eos;    
       
---    task->c = c;
---    
---    ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, stream_pool,
---                  "h2_task(%s): created", task->id);
       return task;
   }
   
---void h2_task_set_request(h2_task *task, 
---                         const char *method, 
---                         const char *scheme, 
---                         const char *authority, 
---                         const char *path, 
---                         apr_table_t *headers, int eos)
---{
---    task->method = method;
---    task->scheme = scheme;
---    task->authority = authority;
---    task->path = path;
---    task->headers = headers;
---    task->input_eos = eos;
---}
---
   apr_status_t h2_task_destroy(h2_task *task)
   {
       (void)task;
@@@@ -205,63 -211,83 -211,83 +190,46 @@@@ apr_status_t h2_task_do(h2_task *task, 
       
       AP_DEBUG_ASSERT(task);
       
 --    memset(&env, 0, sizeof(env));
 --    
 --    env.id = task->id;
 --    env.stream_id = task->stream_id;
 --    env.mplx = task->mplx;
 --    task->mplx = NULL;
 --    
 --    env.input_eos = task->input_eos;
 --    env.serialize_headers = h2_config_geti(cfg, H2_CONF_SER_HEADERS);
 --    
 --    /* Create a subpool from the worker one to be used for all things
 --     * with life-time of this task_env execution.
 --     */
 --    apr_pool_create(&env.pool, h2_worker_get_pool(worker));
 ++    task->serialize_headers = h2_config_geti(cfg, H2_CONF_SER_HEADERS);
-      
-      /* Create a subpool from the worker one to be used for all things
-       * with life-time of this task execution.
-       */
-      apr_pool_create(&task->pool, h2_worker_get_pool(worker));
   
-      /* Link the task to the worker which provides useful things such
 --    /* Link the env to the worker which provides useful things such
---     * as mutex, a socket etc. */
-      task->io = h2_worker_get_cond(worker);
 --    env.io = h2_worker_get_cond(worker);
---    
-      status = h2_conn_setup(task, worker);
 --    /* Clone fields, so that lifetimes become (more) independent. */
 --    env.method    = apr_pstrdup(env.pool, task->method);
 --    env.scheme    = apr_pstrdup(env.pool, task->scheme);
 --    env.authority = apr_pstrdup(env.pool, task->authority);
 --    env.path      = apr_pstrdup(env.pool, task->path);
 --    env.headers   = apr_table_clone(env.pool, task->headers);
+++    status = h2_worker_setup_task(worker, task);
       
 --    /* Setup the pseudo connection to use our own pool and bucket_alloc */
 --    env.c = *task->c;
 --    task->c = NULL;
 --    status = h2_conn_setup(&env, worker);
 --    
---    /* save in connection that this one is a pseudo connection, prevents
---     * other hooks from messing with it. */
 --    h2_ctx_create_for(&env.c, &env);
+++    /* save in connection that this one is a pseudo connection */
 ++    h2_ctx_create_for(task->c, task);
   
       if (status == APR_SUCCESS) {
 --        env.input = h2_task_input_create(&env, env.pool, 
 --                                         env.c.bucket_alloc);
 --        env.output = h2_task_output_create(&env, env.pool,
 --                                           env.c.bucket_alloc);
 --        status = h2_conn_process(&env.c, h2_worker_get_socket(worker));
 --        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, &env.c,
 --                      "h2_task(%s): processing done", env.id);
 ++        task->input = h2_task_input_create(task, task->pool, 
 ++                                           task->c->bucket_alloc);
-          task->output = h2_task_output_create(task, task->pool,
-                                               task->c->bucket_alloc);
-          status = h2_conn_process(task->c, h2_worker_get_socket(worker));
-          ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, task->c,
+++        task->output = h2_task_output_create(task, task->pool);
+++        
+++        ap_process_connection(task->c, h2_worker_get_socket(worker));
+++        
+++        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
 ++                      "h2_task(%s): processing done", task->id);
       }
       else {
 --        ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, &env.c,
 --                      APLOGNO(02957) "h2_task(%s): error setting up h2_task_env", 
 --                      env.id);
 ++        ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, task->c,
 ++                      APLOGNO(02957) "h2_task(%s): error setting up h2_task", 
 ++                      task->id);
       }
       
 --    if (env.input) {
 --        h2_task_input_destroy(env.input);
 --        env.input = NULL;
 ++    if (task->input) {
 ++        h2_task_input_destroy(task->input);
 ++        task->input = NULL;
       }
       
 --    if (env.output) {
 --        h2_task_output_close(env.output);
 --        h2_task_output_destroy(env.output);
 --        env.output = NULL;
 ++    if (task->output) {
 ++        h2_task_output_close(task->output);
 ++        h2_task_output_destroy(task->output);
 ++        task->output = NULL;
       }
   
 --    h2_task_set_finished(task);
 --    if (env.io) {
 --        apr_thread_cond_signal(env.io);
 ++    if (task->io) {
 ++        apr_thread_cond_signal(task->io);
       }
       
-      if (task->pool) {
-          apr_pool_destroy(task->pool);
-          task->pool = NULL;
 --    if (env.pool) {
 --        apr_pool_destroy(env.pool);
 --        env.pool = NULL;
---    }
---    
-      if (task->c->id) {
-          h2_conn_post(task->c, worker);
 --    if (env.c.id) {
 --        h2_conn_post(&env.c, worker);
---    }
---    
 --    h2_mplx_task_done(env.mplx, env.stream_id);
 ++    h2_mplx_task_done(task->mplx, task->stream_id);
+++    h2_worker_release_task(worker, task);
       
       return status;
   }
@@@@ -286,7 -340,7 -340,7 +254,7 @@@@ static request_rec *h2_task_create_requ
       
       r->allowed_methods = ap_make_method_list(p, 2);
       
-      r->headers_in = apr_table_copy(r->pool, task->headers);
 --    r->headers_in = apr_table_copy(r->pool, env->headers);
+++    r->headers_in      = apr_table_copy(r->pool, task->request->headers);
       r->trailers_in     = apr_table_make(r->pool, 5);
       r->subprocess_env  = apr_table_make(r->pool, 25);
       r->headers_out     = apr_table_make(r->pool, 12);
       
       /* 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.
@@@@ -401,6 -454,6 -454,6 +369,8 @@@@ static apr_status_t h2_task_process_req
            * will result in a segfault immediately instead
            * of nondeterministic failures later.
            */
+++        if (cs)
+++            cs->state = CONN_STATE_WRITE_COMPLETION;
           r = NULL;
       }
       ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, NULL);
index 1877a3920b5c56a6fe6748f0cc92ace0e9a449c2,b66ce38c251ba611ada400f0e2e33546a0979499,b66ce38c251ba611ada400f0e2e33546a0979499..7cf0f20de2af3a945c8a963d0ac0e71de4ef22c8
@@@@ -39,6 -39,6 -39,6 +39,7 @@@@ struct apr_thread_cond_t
   struct h2_conn;
   struct h2_mplx;
   struct h2_task;
+++struct h2_request;
   struct h2_resp_head;
   struct h2_worker;
   
@@@@ -49,41 -52,136 -52,136 +50,28 @@@@ struct h2_task 
       int stream_id;
       struct h2_mplx *mplx;
       
 --    volatile apr_uint32_t has_started;
 --    volatile apr_uint32_t has_finished;
 --    
---    const char *method;
---    const char *scheme;
---    const char *authority;
---    const char *path;
---    apr_table_t *headers;
+++    const struct h2_request *request;
       int input_eos;
   
 --    struct conn_rec *c;
 --};
 ++    int serialize_headers;
   
 --typedef struct h2_task_env h2_task_env;
 ++    struct conn_rec *c;
   
 --struct h2_task_env {
 --    const char *id;
 --    int stream_id;
 --    struct h2_mplx *mplx;
 --    
       apr_pool_t *pool;              /* pool for task lifetime things */
       apr_bucket_alloc_t *bucket_alloc;
 --    
 --    const char *method;
 --    const char *scheme;
 --    const char *authority;
 --    const char *path;
 --    apr_table_t *headers;
 --    int input_eos;
 --    
 --    int serialize_headers;
 --
 --    struct conn_rec c;
       struct h2_task_input *input;
       struct h2_task_output *output;
       
       struct apr_thread_cond_t *io;   /* used to wait for events on */
   };
   
 --/**
 -- * The magic pointer value that indicates the head of a h2_task list
 -- * @param  b The task list
 -- * @return The magic pointer value
 -- */
 --#define H2_TASK_LIST_SENTINEL(b)     APR_RING_SENTINEL((b), h2_task, link)
 --
 --/**
 -- * Determine if the task list is empty
 -- * @param b The list to check
 -- * @return true or false
 -- */
 --#define H2_TASK_LIST_EMPTY(b)        APR_RING_EMPTY((b), h2_task, link)
 --
 --/**
 -- * Return the first task in a list
 -- * @param b The list to query
 -- * @return The first task in the list
 -- */
 --#define H2_TASK_LIST_FIRST(b)        APR_RING_FIRST(b)
 --
 --/**
 -- * Return the last task in a list
 -- * @param b The list to query
 -- * @return The last task int he list
 -- */
 --#define H2_TASK_LIST_LAST(b) APR_RING_LAST(b)
 --
 --/**
 -- * Insert a single task at the front of a list
 -- * @param b The list to add to
 -- * @param e The task to insert
 -- */
 --#define H2_TASK_LIST_INSERT_HEAD(b, e) do {                          \
 --    h2_task *ap__b = (e);                                        \
 --    APR_RING_INSERT_HEAD((b), ap__b, h2_task, link); \
 --} while (0)
 --
 --/**
 -- * Insert a single task at the end of a list
 -- * @param b The list to add to
 -- * @param e The task to insert
 -- */
 --#define H2_TASK_LIST_INSERT_TAIL(b, e) do {                          \
 --    h2_task *ap__b = (e);                                    \
 --    APR_RING_INSERT_TAIL((b), ap__b, h2_task, link); \
 --} while (0)
 --
 --/**
 -- * Get the next task in the list
 -- * @param e The current task
 -- * @return The next task
 -- */
 --#define H2_TASK_NEXT(e)      APR_RING_NEXT((e), link)
 --/**
 -- * Get the previous task in the list
 -- * @param e The current task
 -- * @return The previous task
 -- */
 --#define H2_TASK_PREV(e)      APR_RING_PREV((e), link)
 --
 --/**
 -- * Remove a task from its list
 -- * @param e The task to remove
 -- */
 --#define H2_TASK_REMOVE(e)    APR_RING_REMOVE((e), link)
 --
 --
---h2_task *h2_task_create(long session_id, int stream_id, 
---                        apr_pool_t *pool, struct h2_mplx *mplx,
---                        conn_rec *c);
+++h2_task *h2_task_create(long session_id, const struct h2_request *req, 
+++                        apr_pool_t *pool, struct h2_mplx *mplx, int eos);
   
   apr_status_t h2_task_destroy(h2_task *task);
   
---void h2_task_set_request(h2_task *task, 
---                         const char *method, 
---                         const char *scheme, 
---                         const char *authority, 
---                         const char *path, 
---                         apr_table_t *headers, int eos);
---
---
   apr_status_t h2_task_do(h2_task *task, struct h2_worker *worker);
 --apr_status_t h2_task_process_request(h2_task_env *env);
 --
 --int h2_task_has_started(h2_task *task);
 --void h2_task_set_started(h2_task *task);
 --int h2_task_has_finished(h2_task *task);
 --void h2_task_set_finished(h2_task *task);
   
   void h2_task_register_hooks(void);
 --void h2_task_die(h2_task_env *env, int status, request_rec *r);
   
   #endif /* defined(__mod_h2__h2_task__) */
index 1eac749f42a9fd6d6ab87ebd8c52ba701881bff5,487f7e606920c26fce185bbdf2b357f52f885c5d,487f7e606920c26fce185bbdf2b357f52f885c5d..49be7cfd228128af6b412f1e36a9d1b899b0a8ec
   #include "h2_private.h"
   #include "h2_conn.h"
   #include "h2_mplx.h"
+++#include "h2_request.h"
   #include "h2_session.h"
   #include "h2_stream.h"
   #include "h2_task_input.h"
@@@@ -47,19 -47,19 -47,19 +48,19 @@@@ h2_task_input *h2_task_input_create(h2_
   {
       h2_task_input *input = apr_pcalloc(pool, sizeof(h2_task_input));
       if (input) {
 --        input->env = env;
 ++        input->task = task;
           input->bb = NULL;
           
 --        if (env->serialize_headers) {
 --            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, &env->c,
 ++        if (task->serialize_headers) {
 ++            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
                             "h2_task_input(%s): serialize request %s %s", 
-                            task->id, task->method, task->path);
 --                          env->id, env->method, env->path);
+++                          task->id, task->request->method, task->request->path);
               input->bb = apr_brigade_create(pool, bucket_alloc);
               apr_brigade_printf(input->bb, NULL, NULL, "%s %s HTTP/1.1\r\n", 
-                                 task->method, task->path);
-              apr_table_do(ser_header, input, task->headers, NULL);
 --                               env->method, env->path);
 --            apr_table_do(ser_header, input, env->headers, NULL);
+++                               task->request->method, task->request->path);
+++            apr_table_do(ser_header, input, task->request->headers, NULL);
               apr_brigade_puts(input->bb, NULL, NULL, "\r\n");
 --            if (input->env->input_eos) {
 ++            if (input->task->input_eos) {
                   APR_BRIGADE_INSERT_TAIL(input->bb, apr_bucket_eos_create(bucket_alloc));
               }
           }
               /* We do not serialize and have eos already, no need to
                * create a bucket brigade. */
           }
---        
-          if (APLOGcdebug(task->c)) {
 --        if (APLOGcdebug(&env->c)) {
---            char buffer[1024];
---            apr_size_t len = sizeof(buffer)-1;
---            if (input->bb) {
---                apr_brigade_flatten(input->bb, buffer, &len);
---            }
---            else {
---                len = 0;
---            }
---            buffer[len] = 0;
-              ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
 --            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, &env->c,
---                          "h2_task_input(%s): request is: %s", 
-                            task->id, buffer);
 --                          env->id, buffer);
---        }
       }
       return input;
   }
@@@@ -104,21 -104,21 -104,21 +90,20 @@@@ apr_status_t h2_task_input_read(h2_task
       apr_status_t status = APR_SUCCESS;
       apr_off_t bblen = 0;
       
---    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
+++    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
                     "h2_task_input(%s): read, block=%d, mode=%d, readbytes=%ld", 
 --                  input->env->id, block, mode, (long)readbytes);
 ++                  input->task->id, block, mode, (long)readbytes);
 ++    
+++    if (mode == AP_MODE_INIT) {
+++        return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
+++    }
+      
       if (is_aborted(f)) {
           ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
---                      "h2_task_input(%s): is aborted", 
-                        input->task->id);
 --                      input->env->id);
+++                      "h2_task_input(%s): is aborted", input->task->id);
           return APR_ECONNABORTED;
       }
       
---    if (mode == AP_MODE_INIT) {
-          return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
 --        return APR_SUCCESS;
---    }
---    
       if (input->bb) {
           status = apr_brigade_length(input->bb, 1, &bblen);
           if (status != APR_SUCCESS) {
               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",
index 053e2d69e4ea6a5f43ee804796979524c245116b,879cb5fa21b6f3358c108cbb06a15a4142a20eea,879cb5fa21b6f3358c108cbb06a15a4142a20eea..06a5d7aafbbcbed813733348591f6f7d388271ea
   #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;
           }
@@@@ -73,12 -73,12 -73,12 +72,13 @@@@ static apr_status_t open_if_needed(h2_t
                   ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
                                 "h2_task_output(%s): write without response "
                                 "for %s %s %s",
-                                output->task->id, output->task->method, 
-                                output->task->authority, output->task->path);
 --                              output->env->id, output->env->method, 
 --                              output->env->authority, output->env->path);
+++                              output->task->id, output->task->request->method, 
+++                              output->task->request->authority, 
+++                              output->task->request->path);
                   f->c->aborted = 1;
               }
 --            if (output->env->io) {
 --                apr_thread_cond_broadcast(output->env->io);
 ++            if (output->task->io) {
 ++                apr_thread_cond_broadcast(output->task->io);
               }
               return APR_ECONNABORTED;
           }
index 79cb6816c7aac31a422d16f2fe6828fd8cce3bae,86571a1e1071a053d9833f7f9af444b90bc41fd2,86571a1e1071a053d9833f7f9af444b90bc41fd2..a326c49096bdc7eb922cb69c840b2f4b1325a245
@@@@ -40,8 -40,8 -40,8 +40,7 @@@@ struct h2_task_output 
       struct h2_from_h1 *from_h1;
   };
   
-  h2_task_output *h2_task_output_create(struct h2_task *task, apr_pool_t *pool,
 --h2_task_output *h2_task_output_create(struct h2_task_env *env, apr_pool_t *pool,
---                                      apr_bucket_alloc_t *bucket_alloc);
+++h2_task_output *h2_task_output_create(struct h2_task *task, apr_pool_t *pool);
   
   void h2_task_output_destroy(h2_task_output *output);
   
index 1653aa26e285a1aa2f3f6586f210dd66d34afd9b,a81cc100fb99fc82461e94c43c5e6c3e29b41040,a81cc100fb99fc82461e94c43c5e6c3e29b41040..23bad194b9e8ad396340f95b9c782f39c00af430
@@@@ -41,121 -34,54 -34,54 +41,140 @@@@ h2_task_queue *h2_tq_create(apr_pool_t 
       return q;
   }
   
 --void h2_tq_destroy(h2_task_queue *q)
 ++int h2_tq_empty(h2_task_queue *q)
 ++{
 ++    return q->nelts == 0;
 ++}
 ++
-  void h2_tq_add(h2_task_queue *q, struct h2_task *task,
-                 h2_tq_cmp *cmp, void *ctx)
+++void h2_tq_add(h2_task_queue *q, int sid, h2_tq_cmp *cmp, void *ctx)
   {
 --    while (!H2_TASK_LIST_EMPTY(&q->tasks)) {
 --        h2_task *task = H2_TASK_LIST_FIRST(&q->tasks);
 --        H2_TASK_REMOVE(task);
 ++    int i;
 ++    
 ++    if (q->nelts >= q->nalloc) {
 ++        tq_grow(q, q->nalloc * 2);
       }
-      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;
   }
   
   
index 36fad2c4d8db70cb47cf7eb3adac1216177960cb,d93d74ac5029846e3786bf859edf1c38b8979307,d93d74ac5029846e3786bf859edf1c38b8979307..dcc46d037af7ab4b168de0b670bd4f793c1edf07
@@@@ -24,33 -25,23 -25,23 +24,33 @@@@ struct h2_task
   typedef struct h2_task_queue h2_task_queue;
   
   struct h2_task_queue {
-      struct h2_task **elts;
 --    APR_RING_ENTRY(h2_task_queue) link;
 --    APR_RING_HEAD(h2_tasks, h2_task) tasks;
 --    long id;
+++    int *elts;
 ++    int head;
 ++    int nelts;
 ++    int nalloc;
 ++    apr_pool_t *pool;
   };
   
   /**
 -- * Allocate a new queue from the pool and initialize.
 -- * @param id the identifier of the queue
 -- * @param pool the memory pool
 ++ * Comparator for two task to determine their order.
 ++ *
-   * @param t1 task to compare
-   * @param t2 task to compare
+++ * @param s1 stream id to compare
+++ * @param s2 stream id to compare
 ++ * @param ctx provided user data
 ++ * @return value is the same as for strcmp() and has the effect:
-   *    == 0: t1 and t2 are treated equal in ordering
-   *     < 0: t1 should be sorted before t2
-   *     > 0: t2 should be sorted before t1
+++ *    == 0: s1 and s2 are treated equal in ordering
+++ *     < 0: s1 should be sorted before s2
+++ *     > 0: s2 should be sorted before s1
    */
-  typedef int h2_tq_cmp(struct h2_task *t1, struct h2_task *t2, void *ctx);
 --h2_task_queue *h2_tq_create(long id, apr_pool_t *pool);
+++typedef int h2_tq_cmp(int s1, int s2, void *ctx);
 ++
   
   /**
 -- * Release all queue tasks.
 -- * @param q the queue to destroy
 ++ * Allocate a new queue from the pool and initialize.
 ++ * @param id the identifier of the queue
 ++ * @param pool the memory pool
    */
 --void h2_tq_destroy(h2_task_queue *q);
 ++h2_task_queue *h2_tq_create(apr_pool_t *pool, int capacity);
   
   /**
    * Return != 0 iff there are no tasks in the queue.
   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__) */
index e83ed4ee6691e1eb9ed9d6e5cfaec82214a0ed4b,9d141be93bf160d5d83d8fca278dd69ba990cc10,9d141be93bf160d5d83d8fca278dd69ba990cc10..e80a0268804501f0211effb309517d192cf567a5
   #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,
@@@@ -205,6 -204,6 -204,6 +206,10 @@@@ const char *h2_util_first_token_match(a
       return NULL;
   }
   
+++/*******************************************************************************
+++ * h2_util for bucket brigades
+++ ******************************************************************************/
+++
   /* DEEP_COPY==0 crashes under load. I think the setaside is fine, 
    * however buckets moved to another thread will still be
    * free'd against the old bucket_alloc. *And* if the old
@@@@ -215,7 -214,7 -214,7 +220,7 @@@@ static const int DEEP_COPY = 1
   static const int FILE_MOVE = 1;
   
   static apr_status_t last_not_included(apr_bucket_brigade *bb, 
---                                      apr_size_t maxlen, 
+++                                      apr_off_t maxlen, 
                                         int same_alloc,
                                         int *pfile_buckets_allowed,
                                         apr_bucket **pend)
       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;
@@@@ -483,7 -482,7 -482,7 +488,7 @@@@ int h2_util_has_flush_or_eos(apr_bucket
       return 0;
   }
   
---int h2_util_has_eos(apr_bucket_brigade *bb, apr_size_t len)
+++int h2_util_has_eos(apr_bucket_brigade *bb, apr_off_t len)
   {
       apr_bucket *b, *end;
       
@@@@ -537,7 -536,7 -536,7 +542,7 @@@@ int h2_util_bb_has_data_or_eos(apr_buck
   }
   
   apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb, 
---                              apr_size_t *plen, int *peos)
+++                              apr_off_t *plen, int *peos)
   {
       apr_status_t status;
       apr_off_t blen = 0;
       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;
+++}
+++
index 0612af6ba328aec18d7b808efcee15cf0316f2ef,9a1b5c6d35bf9ae36b271b5c3235d592f77e0837,9a1b5c6d35bf9ae36b271b5c3235d592f77e0837..51efb8cf2d8bef14cfd9b747a5910d5b6fa4dc61
   #ifndef __mod_h2__h2_util__
   #define __mod_h2__h2_util__
   
+++struct h2_request;
   struct nghttp2_frame;
   
   size_t h2_util_hex_dump(char *buffer, size_t maxlen,
@@@@ -67,19 -67,19 -67,19 +68,32 @@@@ apr_size_t h2_util_base64url_decode(con
                                               nv->value = (uint8_t *)VALUE;     \
                                               nv->valuelen = strlen(VALUE)
   
+++int h2_util_ignore_header(const char *name);
+++
+++typedef struct h2_ngheader {
+++    nghttp2_nv *nv;
+++    apr_size_t nvlen;
+++} h2_ngheader;
+++
+++h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p, 
+++                                       int http_status, 
+++                                       apr_table_t *header);
+++h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, 
+++                                       const struct h2_request *req);
+++
   /**
    * Moves data from one brigade into another. If maxlen > 0, it only
    * moves up to maxlen bytes into the target brigade, making bucket splits
    * if needed.
    * @param to the brigade to move the data to
    * @param from the brigade to get the data from
--- * @param maxlen of bytes to move, 0 for all
+++ * @param maxlen of bytes to move, <= 0 for all
    * @param pfile_buckets_allowed how many file buckets may be moved, 
    *        may be 0 or NULL
    * @param msg message for use in logging
    */
   apr_status_t h2_util_move(apr_bucket_brigade *to, apr_bucket_brigade *from, 
---                          apr_size_t maxlen, int *pfile_buckets_allowed, 
+++                          apr_off_t maxlen, int *pfile_buckets_allowed, 
                             const char *msg);
   
   /**
    * 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__) */
index e72f66287399ea83efd60305f0e5aaba19c3770b,7a03865c87c2ed20a558d0f231738964ca4a8662,7a03865c87c2ed20a558d0f231738964ca4a8662..98a431b3bf1b0706418f005a8571d68e21879e45
    * @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 */
index 297b4b21fe6be1198ecbf2a8a153eb85c9d3bfd7,8145b7aaa5f72bdb5d5683651696970768f9f114,8145b7aaa5f72bdb5d5683651696970768f9f114..b11e8549fffefe444c0bb0e10a8de2086aa3ef87
   #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"
   
@@@@ -55,7 -55,7 -55,7 +57,7 @@@@ static void* APR_THREAD_FUNC execute(ap
           if (worker->task) {            
               h2_task_do(worker->task, worker);
               worker->task = NULL;
---            apr_thread_cond_signal(h2_worker_get_cond(worker));
+++            apr_thread_cond_signal(worker->io);
           }
       }
   
@@@@ -112,7 -99,7 -99,7 +114,6 @@@@ h2_worker *h2_worker_create(int id
           
           w->id = id;
           w->pool = pool;
---        w->bucket_alloc = apr_bucket_alloc_create(pool);
   
           w->get_next = get_next;
           w->worker_done = worker_done;
               return NULL;
           }
           
-          apr_pool_pre_cleanup_register(pool, w, cleanup_join_thread);
---        apr_thread_create(&w->thread, attr, execute, w, pool);
+++        apr_pool_pre_cleanup_register(w->pool, w, cleanup_join_thread);
+++        apr_thread_create(&w->thread, attr, execute, w, w->pool);
+++        apr_pool_create(&w->task_pool, w->pool);
       }
       return w;
   }
@@@@ -157,28 -143,28 -143,28 +159,42 @@@@ int h2_worker_is_aborted(h2_worker *wor
       return worker->aborted;
   }
   
---apr_thread_t *h2_worker_get_thread(h2_worker *worker)
+++h2_task *h2_worker_create_task(h2_worker *worker, h2_mplx *m, 
+++                               const h2_request *req, int eos)
   {
---    return worker->thread;
+++    h2_task *task;
+++    
+++    /* Create a subpool from the worker one to be used for all things
+++     * with life-time of this task execution.
+++     */
+++    task = h2_task_create(m->id, req, worker->task_pool, m, eos);
+++    /* Link the task to the worker which provides useful things such
+++     * as mutex, a socket etc. */
+++    task->io = worker->io;
+++    
+++    return task;
   }
   
---apr_thread_cond_t *h2_worker_get_cond(h2_worker *worker)
---{
---    return worker->io;
+++apr_status_t h2_worker_setup_task(h2_worker *worker, h2_task *task) {
+++    apr_status_t status;
+++    
+++    
+++    status = h2_conn_setup(task, apr_bucket_alloc_create(task->pool),
+++                           worker->thread, worker->socket);
+++    
+++    return status;
   }
   
---apr_socket_t *h2_worker_get_socket(h2_worker *worker)
+++void h2_worker_release_task(h2_worker *worker, struct h2_task *task)
   {
---    return worker->socket;
+++    task->io = NULL;
+++    task->pool = NULL;
+++    apr_pool_clear(worker->task_pool);
   }
   
---apr_pool_t *h2_worker_get_pool(h2_worker *worker)
+++apr_socket_t *h2_worker_get_socket(h2_worker *worker)
   {
---    return worker->pool;
+++    return worker->socket;
   }
   
---apr_bucket_alloc_t *h2_worker_get_bucket_alloc(h2_worker *worker)
---{
---    return worker->bucket_alloc;
---}
   
index 9c69e6b57a8cff8faaab950a119495549acc4a39,9c69e6b57a8cff8faaab950a119495549acc4a39,9c69e6b57a8cff8faaab950a119495549acc4a39..035448e5dbbcf24c35d4c6ce505ac8233d507d35
   
   struct apr_thread_cond_t;
   struct h2_mplx;
+++struct h2_request;
   struct h2_task;
   
   /* h2_worker is a basically a apr_thread_t that reads fromt he h2_workers
@@@@ -44,7 -44,7 -44,7 +45,7 @@@@ struct h2_worker 
       int id;
       apr_thread_t *thread;
       apr_pool_t *pool;
---    apr_bucket_alloc_t *bucket_alloc;
+++    apr_pool_t *task_pool;
       struct apr_thread_cond_t *io;
       apr_socket_t *socket;
       
@@@@ -142,14 -142,14 -142,14 +143,11 @@@@ int h2_worker_get_id(h2_worker *worker)
   
   int h2_worker_is_aborted(h2_worker *worker);
   
---apr_pool_t *h2_worker_get_pool(h2_worker *worker);
---
---apr_bucket_alloc_t *h2_worker_get_bucket_alloc(h2_worker *worker);
+++struct h2_task *h2_worker_create_task(h2_worker *worker, struct h2_mplx *m, 
+++                                      const struct h2_request *req, int eos);
+++apr_status_t h2_worker_setup_task(h2_worker *worker, struct h2_task *task);
+++void h2_worker_release_task(h2_worker *worker, struct h2_task *task);
   
   apr_socket_t *h2_worker_get_socket(h2_worker *worker);
   
---apr_thread_t *h2_worker_get_thread(h2_worker *worker);
---
---struct apr_thread_cond_t *h2_worker_get_cond(h2_worker *worker);
---
   #endif /* defined(__mod_h2__h2_worker__) */
index 18f39d136c9b64dd5f76bd7bf58cc3080175ab3e,cf3009585b7088c549aacc31ddcaeeb78818855d,cf3009585b7088c549aacc31ddcaeeb78818855d..3c08ff35d1cf34b5c5efc2bb053235ef2d775776
@@@@ -79,7 -63,7 -63,7 +79,7 @@@@ static apr_status_t get_mplx_next(h2_wo
       if (*pm && ptask != NULL) {
           /* We have a h2_mplx instance and the worker wants the next task. 
            * Try to get one from the given mplx. */
---        *ptask = h2_mplx_pop_task(*pm, &has_more);
+++        *ptask = h2_mplx_pop_task(*pm, worker, &has_more);
           if (*ptask) {
               return APR_SUCCESS;
           }
                   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
@@@@ -326,7 -300,7 -300,7 +326,7 @@@@ apr_status_t h2_workers_register(h2_wor
   {
       apr_status_t status = apr_thread_mutex_lock(workers->lock);
       if (status == APR_SUCCESS) {
---        ap_log_error(APLOG_MARK, APLOG_DEBUG, status, workers->s,
+++        ap_log_error(APLOG_MARK, APLOG_TRACE2, status, workers->s,
                        "h2_workers: register mplx(%ld)", m->id);
           if (in_list(workers, m)) {
               status = APR_EAGAIN;
index fbff711df77317b872a8e475987ee30505963a8b,c3e139e9553082c4501134cb1aa8edcb7cc8a168,c3e139e9553082c4501134cb1aa8edcb7cc8a168..12a3abfe98c096fb3203156ec4a542cc04c8d630
@@@@ -149,6 -141,6 -141,6 +149,10 @@@@ SOURCE=./h2_mplx.
   # End Source File
   # Begin Source File
   
+++SOURCE=./h2_push.c
+++# End Source File
+++# Begin Source File
+++
   SOURCE=./h2_request.c
   # End Source File
   # Begin Source File
@@@@ -189,10 -181,10 -181,10 +193,6 @@@@ SOURCE=./h2_task_queue.
   # End Source File
   # Begin Source File
   
---SOURCE=./h2_to_h1.c
---# End Source File
---# Begin Source File
---
   SOURCE=./h2_util.c
   # End Source File
   # Begin Source File