You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by jo...@apache.org on 2016/11/14 10:26:34 UTC

svn commit: r1769588 [14/17] - in /httpd/httpd/branches/2.4.x-openssl-1.1.0-compat: ./ docs/conf/ docs/manual/ docs/manual/howto/ docs/manual/mod/ docs/manual/platform/ docs/manual/programs/ docs/manual/rewrite/ include/ modules/ modules/aaa/ modules/a...

Modified: httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_request.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_request.c?rev=1769588&r1=1769587&r2=1769588&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_request.c (original)
+++ httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_request.c Mon Nov 14 10:26:31 2016
@@ -30,27 +30,44 @@
 #include <scoreboard.h>
 
 #include "h2_private.h"
+#include "h2_config.h"
 #include "h2_push.h"
 #include "h2_request.h"
 #include "h2_util.h"
 
 
-static apr_status_t inspect_clen(h2_request *req, const char *s)
+typedef struct {
+    apr_table_t *headers;
+    apr_pool_t *pool;
+} h1_ctx;
+
+static int set_h1_header(void *ctx, const char *key, const char *value)
 {
-    char *end;
-    req->content_length = apr_strtoi64(s, &end, 10);
-    return (s == end)? APR_EINVAL : APR_SUCCESS;
+    h1_ctx *x = ctx;
+    size_t klen = strlen(key);
+    if (!h2_req_ignore_header(key, klen)) {
+        h2_headers_add_h1(x->headers, x->pool, key, klen, value, strlen(value));
+    }
+    return 1;
 }
 
-apr_status_t h2_request_rwrite(h2_request *req, apr_pool_t *pool, 
-                               request_rec *r)
+apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, 
+                                request_rec *r)
 {
-    apr_status_t status;
-    const char *scheme, *authority;
+    h2_request *req;
+    const char *scheme, *authority, *path;
+    h1_ctx x;
     
+    *preq = NULL;
     scheme = apr_pstrdup(pool, r->parsed_uri.scheme? r->parsed_uri.scheme
               : ap_http_scheme(r));
     authority = apr_pstrdup(pool, r->hostname);
+    path = apr_uri_unparse(pool, &r->parsed_uri, APR_URI_UNP_OMITSITEPART);
+    
+    if (!r->method || !scheme || !r->hostname || !path) {
+        return APR_EINVAL;
+    }
+
     if (!ap_strchr_c(authority, ':') && r->server && r->server->port) {
         apr_port_t defport = apr_uri_port_of_scheme(scheme);
         if (defport != r->server->port) {
@@ -60,11 +77,23 @@ apr_status_t h2_request_rwrite(h2_reques
         }
     }
     
-    status = h2_req_make(req, pool, apr_pstrdup(pool, r->method), scheme, 
-                         authority, apr_uri_unparse(pool, &r->parsed_uri, 
-                                                    APR_URI_UNP_OMITSITEPART),
-                         r->headers_in);
-    return status;
+    req = apr_pcalloc(pool, sizeof(*req));
+    req->method    = apr_pstrdup(pool, r->method);
+    req->scheme    = scheme;
+    req->authority = authority;
+    req->path      = path;
+    req->headers   = apr_table_make(pool, 10);
+    if (r->server) {
+        req->serialize = h2_config_geti(h2_config_sget(r->server), 
+                                        H2_CONF_SER_HEADERS);
+    }
+
+    x.pool = pool;
+    x.headers = req->headers;
+    apr_table_do(set_h1_header, &x, r->headers_in, NULL);
+    
+    *preq = req;
+    return APR_SUCCESS;
 }
 
 apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, 
@@ -82,8 +111,7 @@ apr_status_t h2_request_add_header(h2_re
         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);
+                          "h2_request: pseudo header after request start");
             return APR_EGENERAL;
         }
         
@@ -109,8 +137,8 @@ apr_status_t h2_request_add_header(h2_re
             strncpy(buffer, name, (nlen > 31)? 31 : nlen);
             ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, pool,
                           APLOGNO(02954) 
-                          "h2_request(%d): ignoring unknown pseudo header %s",
-                          req->id, buffer);
+                          "h2_request: ignoring unknown pseudo header %s",
+                          buffer);
         }
     }
     else {
@@ -121,16 +149,10 @@ apr_status_t h2_request_add_header(h2_re
     return status;
 }
 
-apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, 
-                                    int eos, int push)
+apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos)
 {
     const char *s;
     
-    if (req->eoh) {
-        /* already done */
-        return APR_SUCCESS;
-    }
-
     /* rfc7540, ch. 8.1.2.3:
      * - if we have :authority, it overrides any Host header 
      * - :authority MUST be ommited when converting h1->h2, so we
@@ -147,18 +169,8 @@ apr_status_t h2_request_end_headers(h2_r
     }
 
     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 {
+    if (!s) {
         /* 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,
@@ -168,68 +180,16 @@ apr_status_t h2_request_end_headers(h2_r
             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
+            /* If we have a content-type, but already seen eos, no more
              * data will come. Signal a zero content length explicitly.
              */
             apr_table_setn(req->headers, "Content-Length", "0");
         }
     }
 
-    req->eoh = 1;
-    h2_push_policy_determine(req, pool, push);
-    
-    /* 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");
-        }
-    }
-    
-    return APR_SUCCESS;
-}
-
-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)
-{
-    char *hname, *hvalue;
-    
-    if (h2_req_ignore_trailer(name, nlen)) {
-        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;
 }
 
-
-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)
-{
-    if (!req->trailers) {
-        ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, pool, APLOGNO(03059)
-                      "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, APLOGNO(03060)
-                      "h2_request(%d): pseudo header in trailer",
-                      req->id);
-        return APR_EINVAL;
-    }
-    return add_h1_trailer(req, pool, name, nlen, value, vlen);
-}
-
 h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src)
 {
     h2_request *dst = apr_pmemdup(p, src, sizeof(*dst));
@@ -238,25 +198,24 @@ h2_request *h2_request_clone(apr_pool_t
     dst->authority    = apr_pstrdup(p, src->authority);
     dst->path         = apr_pstrdup(p, src->path);
     dst->headers      = apr_table_clone(p, src->headers);
-    if (src->trailers) {
-        dst->trailers = apr_table_clone(p, src->trailers);
-    }
     return dst;
 }
 
-request_rec *h2_request_create_rec(const h2_request *req, conn_rec *conn)
+request_rec *h2_request_create_rec(const h2_request *req, conn_rec *c)
 {
-    request_rec *r;
-    apr_pool_t *p;
     int access_status = HTTP_OK;    
-    
-    apr_pool_create(&p, conn->pool);
+    const char *rpath;
+    apr_pool_t *p;
+    request_rec *r;
+    const char *s;
+
+    apr_pool_create(&p, c->pool);
     apr_pool_tag(p, "request");
     r = apr_pcalloc(p, sizeof(request_rec));
-    AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)conn);
+    AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)c);
     r->pool            = p;
-    r->connection      = conn;
-    r->server          = conn->base_server;
+    r->connection      = c;
+    r->server          = c->base_server;
     
     r->user            = NULL;
     r->ap_auth_type    = NULL;
@@ -274,9 +233,9 @@ request_rec *h2_request_create_rec(const
     r->request_config  = ap_create_request_config(r->pool);
     /* Must be set before we run create request hook */
     
-    r->proto_output_filters = conn->output_filters;
+    r->proto_output_filters = c->output_filters;
     r->output_filters  = r->proto_output_filters;
-    r->proto_input_filters = conn->input_filters;
+    r->proto_input_filters = c->input_filters;
     r->input_filters   = r->proto_input_filters;
     ap_run_create_request(r);
     r->per_dir_config  = r->server->lookup_defaults;
@@ -295,10 +254,10 @@ request_rec *h2_request_create_rec(const
      */
     r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
     
-    r->useragent_addr = conn->client_addr;
-    r->useragent_ip = conn->client_ip;
+    r->useragent_addr = c->client_addr;
+    r->useragent_ip = c->client_ip;
     
-    ap_run_pre_read_request(r, conn);
+    ap_run_pre_read_request(r, c);
     
     /* Time to populate r with the data we have. */
     r->request_time = req->request_time;
@@ -309,12 +268,13 @@ request_rec *h2_request_create_rec(const
         r->header_only = 1;
     }
 
-    ap_parse_uri(r, req->path);
-    r->protocol = "HTTP/2.0";
+    rpath = (req->path ? req->path : "");
+    ap_parse_uri(r, rpath);
+    r->protocol = (char*)"HTTP/2.0";
     r->proto_num = HTTP_VERSION(2, 0);
 
     r->the_request = apr_psprintf(r->pool, "%s %s %s", 
-                                  r->method, req->path, r->protocol);
+                                  r->method, rpath, r->protocol);
     
     /* update what we think the virtual host is based on the headers we've
      * now read. may update status.
@@ -327,6 +287,17 @@ request_rec *h2_request_create_rec(const
     /* we may have switched to another server */
     r->per_dir_config = r->server->lookup_defaults;
     
+    s = apr_table_get(r->headers_in, "Expect");
+    if (s && s[0]) {
+        if (ap_cstr_casecmp(s, "100-continue") == 0) {
+            r->expecting_100 = 1;
+        }
+        else {
+            r->status = HTTP_EXPECTATION_FAILED;
+            ap_send_error_response(r, 0);
+        }
+    }
+
     /*
      * Add the HTTP_IN filter here to ensure that ap_discard_request_body
      * called by ap_die and by ap_send_error_response works correctly on
@@ -341,16 +312,16 @@ request_rec *h2_request_create_rec(const
         /* Request check post hooks failed. An example of this would be a
          * request for a vhost where h2 is disabled --> 421.
          */
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, conn, APLOGNO()
-                      "h2_request(%d): access_status=%d, request_create failed",
-                      req->id, access_status);
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03367)
+                      "h2_request: access_status=%d, request_create failed",
+                      access_status);
         ap_die(access_status, r);
-        ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+        ap_update_child_status(c->sbh, SERVER_BUSY_LOG, r);
         ap_run_log_transaction(r);
         r = NULL;
         goto traceout;
     }
-    
+
     AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method, 
                             (char *)r->uri, (char *)r->server->defn_name, 
                             r->status);

Modified: httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_request.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_request.h?rev=1769588&r1=1769587&r2=1769588&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_request.h (original)
+++ httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_request.h Mon Nov 14 10:26:31 2016
@@ -18,8 +18,8 @@
 
 #include "h2.h"
 
-apr_status_t h2_request_rwrite(h2_request *req, apr_pool_t *pool, 
-                               request_rec *r);
+apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, 
+                                request_rec *r);
 
 apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
                                    const char *name, size_t nlen,
@@ -29,8 +29,7 @@ apr_status_t h2_request_add_trailer(h2_r
                                     const char *name, size_t nlen,
                                     const char *value, size_t vlen);
 
-apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, 
-                                    int eos, int push);
+apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos);
 
 h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src);
 

Modified: httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_session.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_session.c?rev=1769588&r1=1769587&r2=1769588&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_session.c (original)
+++ httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_session.c Mon Nov 14 10:26:31 2016
@@ -38,9 +38,8 @@
 #include "h2_mplx.h"
 #include "h2_push.h"
 #include "h2_request.h"
-#include "h2_response.h"
+#include "h2_headers.h"
 #include "h2_stream.h"
-#include "h2_from_h1.h"
 #include "h2_task.h"
 #include "h2_session.h"
 #include "h2_util.h"
@@ -75,7 +74,6 @@ static apr_status_t h2_session_receive(v
                                        const char *data, apr_size_t len,
                                        apr_size_t *readlen);
 
-static int is_accepting_streams(h2_session *session); 
 static void dispatch_event(h2_session *session, h2_session_event_t ev, 
                              int err, const char *msg);
 
@@ -84,7 +82,6 @@ apr_status_t h2_session_stream_done(h2_s
     ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
                   "h2_stream(%ld-%d): EOS bucket cleanup -> done", 
                   session->id, stream->id);
-    h2_ihash_remove(session->streams, stream->id);
     h2_mplx_stream_done(session->mplx, stream);
     
     dispatch_event(session, H2_SESSION_EV_STREAM_DONE, 0, NULL);
@@ -96,10 +93,9 @@ typedef struct stream_sel_ctx {
     h2_stream *candidate;
 } stream_sel_ctx;
 
-static int find_cleanup_stream(void *udata, void *sdata)
+static int find_cleanup_stream(h2_stream *stream, void *ictx)
 {
-    stream_sel_ctx *ctx = udata;
-    h2_stream *stream = sdata;
+    stream_sel_ctx *ctx = ictx;
     if (H2_STREAM_CLIENT_INITIATED(stream->id)) {
         if (!ctx->session->local.accepting
             && stream->id > ctx->session->local.accepted_max) {
@@ -123,7 +119,7 @@ static void cleanup_streams(h2_session *
     ctx.session = session;
     ctx.candidate = NULL;
     while (1) {
-        h2_ihash_iter(session->streams, find_cleanup_stream, &ctx);
+        h2_mplx_stream_do(session->mplx, find_cleanup_stream, &ctx);
         if (ctx.candidate) {
             h2_session_stream_done(session, ctx.candidate);
             ctx.candidate = NULL;
@@ -144,9 +140,12 @@ h2_stream *h2_session_open_stream(h2_ses
     apr_pool_tag(stream_pool, "h2_stream");
     
     stream = h2_stream_open(stream_id, stream_pool, session, 
-                            initiated_on, req);
+                            initiated_on);
     nghttp2_session_set_stream_user_data(session->ngh2, stream_id, stream);
-    h2_ihash_add(session->streams, stream);
+    
+    if (req) {
+        h2_stream_set_request(stream, req);
+    }
     
     if (H2_STREAM_CLIENT_INITIATED(stream_id)) {
         if (stream_id > session->remote.emitted_max) {
@@ -285,11 +284,6 @@ static int on_data_chunk_recv_cb(nghttp2
     int rv;
     
     (void)flags;
-    if (!is_accepting_streams(session)) {
-        /* ignore */
-        return 0;
-    }
-    
     stream = get_stream(session, stream_id);
     if (!stream) {
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03064)
@@ -398,11 +392,6 @@ static int on_header_cb(nghttp2_session
     apr_status_t status;
     
     (void)flags;
-    if (!is_accepting_streams(session)) {
-        /* just ignore */
-        return 0;
-    }
-    
     stream = get_stream(session, frame->hd.stream_id);
     if (!stream) {
         ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
@@ -414,7 +403,7 @@ static int on_header_cb(nghttp2_session
     
     status = h2_stream_add_header(stream, (const char *)name, namelen,
                                   (const char *)value, valuelen);
-    if (status != APR_SUCCESS && !stream->response) {
+    if (status != APR_SUCCESS && !h2_stream_is_ready(stream)) {
         return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
     }
     return 0;
@@ -511,7 +500,7 @@ static int on_frame_recv_cb(nghttp2_sess
                           session->id, (int)frame->hd.stream_id,
                           (int)frame->rst_stream.error_code);
             stream = get_stream(session, frame->hd.stream_id);
-            if (stream && stream->request && stream->request->initiated_on) {
+            if (stream && stream->initiated_on) {
                 ++session->pushes_reset;
             }
             else {
@@ -519,9 +508,16 @@ static int on_frame_recv_cb(nghttp2_sess
             }
             break;
         case NGHTTP2_GOAWAY:
-            session->remote.accepted_max = frame->goaway.last_stream_id;
-            session->remote.error = frame->goaway.error_code;
-            dispatch_event(session, H2_SESSION_EV_REMOTE_GOAWAY, 0, NULL);
+            if (frame->goaway.error_code == 0 
+                && frame->goaway.last_stream_id == ((1u << 31) - 1)) {
+                /* shutdown notice. Should not come from a client... */
+                session->remote.accepting = 0;
+            }
+            else {
+                session->remote.accepted_max = frame->goaway.last_stream_id;
+                dispatch_event(session, H2_SESSION_EV_REMOTE_GOAWAY, 
+                               frame->goaway.error_code, NULL);
+            }
             break;
         default:
             if (APLOGctrace2(session->c)) {
@@ -608,6 +604,7 @@ static int on_send_data_cb(nghttp2_sessi
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
                       "h2_stream(%ld-%d): send_data_cb, reading stream",
                       session->id, (int)stream_id);
+        apr_brigade_cleanup(session->bbtmp);
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
     else if (len != length) {
@@ -615,6 +612,7 @@ static int on_send_data_cb(nghttp2_sessi
                       "h2_stream(%ld-%d): send_data_cb, wanted %ld bytes, "
                       "got %ld from stream",
                       session->id, (int)stream_id, (long)length, (long)len);
+        apr_brigade_cleanup(session->bbtmp);
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
     
@@ -625,10 +623,11 @@ static int on_send_data_cb(nghttp2_sessi
     }
     
     status = h2_conn_io_pass(&session->io, session->bbtmp);
-        
     apr_brigade_cleanup(session->bbtmp);
+    
     if (status == APR_SUCCESS) {
-        stream->data_frames_sent++;
+        stream->out_data_frames++;
+        stream->out_data_octets += length;
         return 0;
     }
     else {
@@ -658,6 +657,27 @@ static int on_frame_send_cb(nghttp2_sess
     return 0;
 }
 
+#ifdef H2_NG2_INVALID_HEADER_CB
+static int on_invalid_header_cb(nghttp2_session *ngh2, 
+                                const nghttp2_frame *frame, 
+                                const uint8_t *name, size_t namelen, 
+                                const uint8_t *value, size_t valuelen, 
+                                uint8_t flags, void *user_data)
+{
+    h2_session *session = user_data;
+    if (APLOGcdebug(session->c)) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03456)
+                      "h2_session(%ld-%d): denying stream with invalid header "
+                      "'%s: %s'", session->id, (int)frame->hd.stream_id,
+                      apr_pstrndup(session->pool, (const char *)name, namelen),
+                      apr_pstrndup(session->pool, (const char *)value, valuelen));
+    }
+    return nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
+                                     frame->hd.stream_id, 
+                                     NGHTTP2_PROTOCOL_ERROR);
+}
+#endif
+
 #define NGH2_SET_CALLBACK(callbacks, name, fn)\
 nghttp2_session_callbacks_set_##name##_callback(callbacks, fn)
 
@@ -680,15 +700,16 @@ static apr_status_t init_callbacks(conn_
     NGH2_SET_CALLBACK(*pcb, on_header, on_header_cb);
     NGH2_SET_CALLBACK(*pcb, send_data, on_send_data_cb);
     NGH2_SET_CALLBACK(*pcb, on_frame_send, on_frame_send_cb);
-
+#ifdef H2_NG2_INVALID_HEADER_CB
+    NGH2_SET_CALLBACK(*pcb, on_invalid_header, on_invalid_header_cb);
+#endif
     return APR_SUCCESS;
 }
 
 static void h2_session_destroy(h2_session *session)
 {
-    AP_DEBUG_ASSERT(session);    
+    ap_assert(session);    
 
-    h2_ihash_clear(session->streams);
     if (session->mplx) {
         h2_mplx_set_consumed_cb(session->mplx, NULL, NULL);
         h2_mplx_release_and_join(session->mplx, session->iowait);
@@ -714,12 +735,35 @@ static void h2_session_destroy(h2_sessio
     }
 }
 
+static apr_status_t h2_session_shutdown_notice(h2_session *session)
+{
+    apr_status_t status;
+    
+    ap_assert(session);
+    if (!session->local.accepting) {
+        return APR_SUCCESS;
+    }
+    
+    nghttp2_submit_shutdown_notice(session->ngh2);
+    session->local.accepting = 0;
+    status = nghttp2_session_send(session->ngh2);
+    if (status == APR_SUCCESS) {
+        status = h2_conn_io_flush(&session->io);
+    }
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03457)
+                  "session(%ld): sent shutdown notice", session->id);
+    return status;
+}
+
 static apr_status_t h2_session_shutdown(h2_session *session, int error, 
                                         const char *msg, int force_close)
 {
     apr_status_t status = APR_SUCCESS;
     
-    AP_DEBUG_ASSERT(session);
+    ap_assert(session);
+    if (session->local.shutdown) {
+        return APR_SUCCESS;
+    }
     if (!msg && error) {
         msg = nghttp2_strerror(error);
     }
@@ -743,6 +787,8 @@ static apr_status_t h2_session_shutdown(
     nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 
                           session->local.accepted_max, 
                           error, (uint8_t*)msg, msg? strlen(msg):0);
+    session->local.accepting = 0;
+    session->local.shutdown = 1;
     status = nghttp2_session_send(session->ngh2);
     if (status == APR_SUCCESS) {
         status = h2_conn_io_flush(&session->io);
@@ -753,6 +799,7 @@ static apr_status_t h2_session_shutdown(
     dispatch_event(session, H2_SESSION_EV_LOCAL_GOAWAY, error, msg);
     
     if (force_close) {
+        apr_brigade_cleanup(session->bbtmp);
         h2_mplx_abort(session->mplx);
     }
     
@@ -772,8 +819,7 @@ static apr_status_t session_pool_cleanup
     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
                   "session(%ld): pool_cleanup", session->id);
     
-    if (session->state != H2_SESSION_ST_DONE 
-        && session->state != H2_SESSION_ST_LOCAL_SHUTDOWN) {
+    if (session->state != H2_SESSION_ST_DONE) {
         /* Not good. The connection is being torn down and we have
          * not sent a goaway. This is considered a protocol error and
          * the client has to assume that any streams "in flight" may have
@@ -877,8 +923,6 @@ static h2_session *h2_session_create_int
             return NULL;
         }
         
-        session->streams = h2_ihash_create(session->pool,
-                                           offsetof(h2_stream, id));
         session->mplx = h2_mplx_create(c, session->pool, session->config, 
                                        session->s->timeout, workers);
         
@@ -984,7 +1028,7 @@ static apr_status_t h2_session_start(h2_
     size_t slen;
     int win_size;
     
-    AP_DEBUG_ASSERT(session);
+    ap_assert(session);
     /* Start the conversation by submitting our SETTINGS frame */
     *rv = 0;
     if (session->r) {
@@ -1031,7 +1075,7 @@ static apr_status_t h2_session_start(h2_
             return status;
         }
         
-        status = h2_stream_set_request(stream, session->r);
+        status = h2_stream_set_request_rec(stream, session->r);
         if (status != APR_SUCCESS) {
             return status;
         }
@@ -1087,6 +1131,10 @@ static apr_status_t h2_session_start(h2_
     return status;
 }
 
+static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream,  
+                                      h2_headers *headers, apr_off_t len,
+                                      int eos);
+
 static ssize_t stream_data_cb(nghttp2_session *ng2s,
                               int32_t stream_id,
                               uint8_t *buf,
@@ -1100,7 +1148,7 @@ static ssize_t stream_data_cb(nghttp2_se
     int eos = 0;
     apr_status_t status;
     h2_stream *stream;
-    AP_DEBUG_ASSERT(session);
+    ap_assert(session);
     
     /* The session wants to send more DATA for the stream. We need
      * to find out how much of the requested length we can send without
@@ -1120,10 +1168,8 @@ static ssize_t stream_data_cb(nghttp2_se
                       session->id, (int)stream_id);
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
-    
-    AP_DEBUG_ASSERT(!h2_stream_is_suspended(stream));
-    
-    status = h2_stream_out_prepare(stream, &nread, &eos);
+
+    status = h2_stream_out_prepare(stream, &nread, &eos, NULL);
     if (nread) {
         *data_flags |=  NGHTTP2_DATA_FLAG_NO_COPY;
     }
@@ -1142,7 +1188,6 @@ static ssize_t stream_data_cb(nghttp2_se
              * it. Remember at our h2_stream that we need to do this.
              */
             nread = 0;
-            h2_mplx_suspend_stream(session->mplx, stream->id);
             ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03071)
                           "h2_stream(%ld-%d): suspending",
                           session->id, (int)stream_id);
@@ -1157,34 +1202,11 @@ static ssize_t stream_data_cb(nghttp2_se
     }
     
     if (eos) {
-        apr_table_t *trailers = h2_stream_get_trailers(stream);
-        if (trailers && !apr_is_empty_table(trailers)) {
-            h2_ngheader *nh;
-            int rv;
-            
-            nh = h2_util_ngheader_make(stream->pool, trailers);
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03072)
-                          "h2_stream(%ld-%d): submit %d trailers",
-                          session->id, (int)stream_id,(int) nh->nvlen);
-            rv = nghttp2_submit_trailer(ng2s, stream->id, nh->nv, nh->nvlen);
-            if (rv < 0) {
-                nread = rv;
-            }
-            *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
-        }
-        
         *data_flags |= NGHTTP2_DATA_FLAG_EOF;
     }
-    
     return (ssize_t)nread;
 }
 
-typedef struct {
-    nghttp2_nv *nv;
-    size_t nvlen;
-    size_t offset;
-} nvctx_t;
-
 struct h2_stream *h2_session_push(h2_session *session, h2_stream *is,
                                   h2_push *push)
 {
@@ -1260,8 +1282,9 @@ apr_status_t h2_session_set_prio(h2_sess
     s_parent = nghttp2_stream_get_parent(s);
     if (s_parent) {
         nghttp2_priority_spec ps;
-        int id_parent, id_grandpa, w_parent, w, rv = 0;
-        char *ptype = "AFTER";
+        int id_parent, id_grandpa, w_parent, w;
+        int rv = 0;
+        const char *ptype = "AFTER";
         h2_dependency dep = prio->dependency;
         
         id_parent = nghttp2_stream_get_stream_id(s_parent);
@@ -1379,63 +1402,50 @@ static apr_status_t h2_session_send(h2_s
 }
 
 /**
- * A stream was resumed as new output data arrived.
- */
-static apr_status_t on_stream_resume(void *ctx, int stream_id)
-{
-    h2_session *session = ctx;
-    h2_stream *stream = get_stream(session, stream_id);
-    apr_status_t status = APR_SUCCESS;
-    
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
-                  "h2_stream(%ld-%d): on_resume", session->id, stream_id);
-    if (stream) {
-        int rv = nghttp2_session_resume_data(session->ngh2, stream_id);
-        session->have_written = 1;
-        ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
-                      APLOG_ERR : APLOG_DEBUG, 0, session->c,
-                      APLOGNO(02936) 
-                      "h2_stream(%ld-%d): resuming %s",
-                      session->id, stream->id, rv? nghttp2_strerror(rv) : "");
-    }
-    return status;
-}
-
-/**
- * A response for the stream is ready.
+ * headers for the stream are ready.
  */
-static apr_status_t on_stream_response(void *ctx, int stream_id)
+static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream,  
+                                      h2_headers *headers, apr_off_t len,
+                                      int eos)
 {
-    h2_session *session = ctx;
-    h2_stream *stream = get_stream(session, stream_id);
     apr_status_t status = APR_SUCCESS;
-    h2_response *response;
     int rv = 0;
 
-    AP_DEBUG_ASSERT(session);
+    ap_assert(session);
     ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
-                  "h2_stream(%ld-%d): on_response", session->id, stream_id);
-    if (!stream) {
-        return APR_NOTFOUND;
-    }
-    
-    response = h2_stream_get_response(stream);
-    AP_DEBUG_ASSERT(response || stream->rst_error);
-    
-    if (stream->submitted) {
-        rv = NGHTTP2_PROTOCOL_ERROR;
+                  "h2_stream(%ld-%d): on_headers", session->id, stream->id);
+    if (headers->status < 100) {
+        int err = H2_STREAM_RST(stream, headers->status);
+        rv = nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
+                                       stream->id, err);
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                  "h2_stream(%ld-%d): unpexected header status %d, stream rst", 
+                  session->id, stream->id, headers->status);
+        goto leave;
+    }
+    else if (stream->has_response) {
+        h2_ngheader *nh;
+        
+        nh = h2_util_ngheader_make(stream->pool, headers->headers);
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03072)
+                      "h2_stream(%ld-%d): submit %d trailers",
+                      session->id, (int)stream->id,(int) nh->nvlen);
+        rv = nghttp2_submit_trailer(session->ngh2, stream->id, nh->nv, nh->nvlen);
+        goto leave;
     }
-    else if (response && response->headers) {
+    else {
         nghttp2_data_provider provider, *pprovider = NULL;
         h2_ngheader *ngh;
+        apr_table_t *hout;
         const h2_priority *prio;
+        const char *note;
         
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03073)
                       "h2_stream(%ld-%d): submit response %d, REMOTE_WINDOW_SIZE=%u",
-                      session->id, stream->id, response->http_status,
+                      session->id, stream->id, headers->status,
                       (unsigned int)nghttp2_session_get_stream_remote_window_size(session->ngh2, stream->id));
         
-        if (response->content_length != 0) {
+        if (!eos || len > 0) {
             memset(&provider, 0, sizeof(provider));
             provider.source.fd = stream->id;
             provider.read_callback = stream_data_cb;
@@ -1457,45 +1467,48 @@ static apr_status_t on_stream_response(v
          *    as the client, having this resource in its cache, might
          *    also have the pushed ones as well.
          */
-        if (stream->request && !stream->request->initiated_on
-            && H2_HTTP_2XX(response->http_status)
+        if (!stream->initiated_on
+            && h2_headers_are_response(headers)
+            && H2_HTTP_2XX(headers->status)
             && h2_session_push_enabled(session)) {
             
-            h2_stream_submit_pushes(stream);
+            h2_stream_submit_pushes(stream, headers);
         }
         
-        prio = h2_stream_get_priority(stream);
+        prio = h2_stream_get_priority(stream, headers);
         if (prio) {
             h2_session_set_prio(session, stream, prio);
-            /* no showstopper if that fails for some reason */
         }
         
-        ngh = h2_util_ngheader_make_res(stream->pool, response->http_status, 
-                                        response->headers);
-        rv = nghttp2_submit_response(session->ngh2, response->stream_id,
+        hout = headers->headers;
+        note = apr_table_get(headers->notes, H2_FILTER_DEBUG_NOTE);
+        if (note && !strcmp("on", note)) {
+            int32_t connFlowIn, connFlowOut;
+
+            connFlowIn = nghttp2_session_get_effective_local_window_size(session->ngh2); 
+            connFlowOut = nghttp2_session_get_remote_window_size(session->ngh2);
+            hout = apr_table_clone(stream->pool, hout);
+            apr_table_setn(hout, "conn-flow-in", 
+                           apr_itoa(stream->pool, connFlowIn));
+            apr_table_setn(hout, "conn-flow-out", 
+                           apr_itoa(stream->pool, connFlowOut));
+        }
+        
+        ngh = h2_util_ngheader_make_res(stream->pool, headers->status, hout);
+        rv = nghttp2_submit_response(session->ngh2, stream->id,
                                      ngh->nv, ngh->nvlen, pprovider);
-    }
-    else {
-        int err = H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR);
+        stream->has_response = h2_headers_are_response(headers);
+        session->have_written = 1;
         
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03074)
-                      "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, err);
-    }
-    
-    stream->submitted = 1;
-    session->have_written = 1;
-    
-    if (stream->request && stream->request->initiated_on) {
-        ++session->pushes_submitted;
-    }
-    else {
-        ++session->responses_submitted;
+        if (stream->initiated_on) {
+            ++session->pushes_submitted;
+        }
+        else {
+            ++session->responses_submitted;
+        }
     }
     
+leave:
     if (nghttp2_is_fatal(rv)) {
         status = APR_EGENERAL;
         dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, rv, nghttp2_strerror(rv));
@@ -1520,6 +1533,56 @@ static apr_status_t on_stream_response(v
     return status;
 }
 
+/**
+ * A stream was resumed as new response/output data arrived.
+ */
+static apr_status_t on_stream_resume(void *ctx, h2_stream *stream)
+{
+    h2_session *session = ctx;
+    apr_status_t status = APR_EAGAIN;
+    int rv;
+    apr_off_t len = 0;
+    int eos = 0;
+    h2_headers *headers;
+    
+    ap_assert(stream);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
+                  "h2_stream(%ld-%d): on_resume", session->id, stream->id);
+        
+send_headers:
+    headers = NULL;
+    status = h2_stream_out_prepare(stream, &len, &eos, &headers);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c, 
+                  "h2_stream(%ld-%d): prepared len=%ld, eos=%d", 
+                  session->id, stream->id, (long)len, eos);
+    if (headers) {
+        status = on_stream_headers(session, stream, headers, len, eos);
+        if (status != APR_SUCCESS || stream->rst_error) {
+            return status;
+        }
+        goto send_headers;
+    }
+    else if (status != APR_EAGAIN) {
+        if (!stream->has_response) {
+            int err = H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR);
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03466)
+                          "h2_stream(%ld-%d): no response, RST_STREAM, err=%d",
+                          session->id, stream->id, err);
+            nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
+                                      stream->id, err);
+            return APR_SUCCESS;
+        } 
+        rv = nghttp2_session_resume_data(session->ngh2, stream->id);
+        session->have_written = 1;
+        ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
+                      APLOG_ERR : APLOG_DEBUG, 0, session->c,
+                      APLOGNO(02936) 
+                      "h2_stream(%ld-%d): resuming %s",
+                      session->id, stream->id, rv? nghttp2_strerror(rv) : "");
+    }
+    return status;
+}
+
 static apr_status_t h2_session_receive(void *ctx, const char *data, 
                                        apr_size_t len, apr_size_t *readlen)
 {
@@ -1533,7 +1596,7 @@ static apr_status_t h2_session_receive(v
         n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len);
         if (n < 0) {
             if (nghttp2_is_fatal((int)n)) {
-                dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, (int)n, nghttp2_strerror(n));
+                dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, (int)n, nghttp2_strerror((int)n));
                 return APR_EGENERAL;
             }
         }
@@ -1566,7 +1629,7 @@ static apr_status_t h2_session_read(h2_s
                 /* successful read, reset our idle timers */
                 rstatus = APR_SUCCESS;
                 if (block) {
-                    /* successfull blocked read, try unblocked to
+                    /* successful blocked read, try unblocked to
                      * get more. */
                     block = 0;
                 }
@@ -1600,9 +1663,6 @@ static apr_status_t h2_session_read(h2_s
                  * status. */
                 return rstatus;
         }
-        if (!is_accepting_streams(session)) {
-            break;
-        }
         if ((session->io.bytes_read - read_start) > (64*1024)) {
             /* read enough in one go, give write a chance */
             ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
@@ -1613,48 +1673,12 @@ static apr_status_t h2_session_read(h2_s
     return rstatus;
 }
 
-static int unsubmitted_iter(void *ctx, void *val)
-{
-    h2_stream *stream = val;
-    if (h2_stream_needs_submit(stream)) {
-        *((int *)ctx) = 1;
-        return 0;
-    }
-    return 1;
-}
-
-static int has_unsubmitted_streams(h2_session *session)
-{
-    int has_unsubmitted = 0;
-    h2_ihash_iter(session->streams, unsubmitted_iter, &has_unsubmitted);
-    return has_unsubmitted;
-}
-
-static int suspended_iter(void *ctx, void *val)
-{
-    h2_stream *stream = val;
-    if (h2_stream_is_suspended(stream)) {
-        *((int *)ctx) = 1;
-        return 0;
-    }
-    return 1;
-}
-
-static int has_suspended_streams(h2_session *session)
-{
-    int has_suspended = 0;
-    h2_ihash_iter(session->streams, suspended_iter, &has_suspended);
-    return has_suspended;
-}
-
 static const char *StateNames[] = {
     "INIT",      /* H2_SESSION_ST_INIT */
     "DONE",      /* H2_SESSION_ST_DONE */
     "IDLE",      /* H2_SESSION_ST_IDLE */
     "BUSY",      /* H2_SESSION_ST_BUSY */
     "WAIT",      /* H2_SESSION_ST_WAIT */
-    "LSHUTDOWN", /* H2_SESSION_ST_LOCAL_SHUTDOWN */
-    "RSHUTDOWN", /* H2_SESSION_ST_REMOTE_SHUTDOWN */
 };
 
 static const char *state_name(h2_session_state state)
@@ -1665,18 +1689,6 @@ static const char *state_name(h2_session
     return StateNames[state];
 }
 
-static int is_accepting_streams(h2_session *session)
-{
-    switch (session->state) {
-        case H2_SESSION_ST_IDLE:
-        case H2_SESSION_ST_BUSY:
-        case H2_SESSION_ST_WAIT:
-            return 1;
-        default:
-            return 0;
-    }
-}
-
 static void update_child_status(h2_session *session, int status, const char *msg)
 {
     /* Assume that we also change code/msg when something really happened and
@@ -1698,7 +1710,12 @@ static void update_child_status(h2_sessi
 static void transit(h2_session *session, const char *action, h2_session_state nstate)
 {
     if (session->state != nstate) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03078)
+        int loglvl = APLOG_DEBUG;
+        if ((session->state == H2_SESSION_ST_BUSY && nstate == H2_SESSION_ST_WAIT)
+            || (session->state == H2_SESSION_ST_WAIT && nstate == H2_SESSION_ST_BUSY)){
+            loglvl = APLOG_TRACE1;
+        }
+        ap_log_cerror(APLOG_MARK, loglvl, 0, session->c, APLOGNO(03078)
                       "h2_session(%ld): transit [%s] -- %s --> [%s]", session->id,
                       state_name(session->state), action, state_name(nstate));
         session->state = nstate;
@@ -1708,12 +1725,6 @@ static void transit(h2_session *session,
                                               SERVER_BUSY_KEEPALIVE
                                               : SERVER_BUSY_READ), "idle");
                 break;
-            case H2_SESSION_ST_REMOTE_SHUTDOWN:
-                update_child_status(session, SERVER_CLOSING, "remote goaway");
-                break;
-            case H2_SESSION_ST_LOCAL_SHUTDOWN:
-                update_child_status(session, SERVER_CLOSING, "local goaway");
-                break;
             case H2_SESSION_ST_DONE:
                 update_child_status(session, SERVER_CLOSING, "done");
                 break;
@@ -1738,39 +1749,22 @@ static void h2_session_ev_init(h2_sessio
 
 static void h2_session_ev_local_goaway(h2_session *session, int arg, const char *msg)
 {
-    session->local.accepting = 0;
     cleanup_streams(session);
-    switch (session->state) {
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
-            /* already did that? */
-            break;
-        case H2_SESSION_ST_IDLE:
-        case H2_SESSION_ST_REMOTE_SHUTDOWN:
-            /* all done */
-            transit(session, "local goaway", H2_SESSION_ST_DONE);
-            break;
-        default:
-            transit(session, "local goaway", H2_SESSION_ST_LOCAL_SHUTDOWN);
-            break;
+    if (!session->remote.shutdown) {
+        update_child_status(session, SERVER_CLOSING, "local goaway");
     }
+    transit(session, "local goaway", H2_SESSION_ST_DONE);
 }
 
 static void h2_session_ev_remote_goaway(h2_session *session, int arg, const char *msg)
 {
-    session->remote.accepting = 0;
-    cleanup_streams(session);
-    switch (session->state) {
-        case H2_SESSION_ST_REMOTE_SHUTDOWN:
-            /* already received that? */
-            break;
-        case H2_SESSION_ST_IDLE:
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
-            /* all done */
-            transit(session, "remote goaway", H2_SESSION_ST_DONE);
-            break;
-        default:
-            transit(session, "remote goaway", H2_SESSION_ST_REMOTE_SHUTDOWN);
-            break;
+    if (!session->remote.shutdown) {
+        session->remote.error = arg;
+        session->remote.accepting = 0;
+        session->remote.shutdown = 1;
+        cleanup_streams(session);
+        update_child_status(session, SERVER_CLOSING, "remote goaway");
+        transit(session, "remote goaway", H2_SESSION_ST_DONE);
     }
 }
 
@@ -1779,7 +1773,6 @@ static void h2_session_ev_conn_error(h2_
     switch (session->state) {
         case H2_SESSION_ST_INIT:
         case H2_SESSION_ST_DONE:
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
             /* just leave */
             transit(session, "conn error", H2_SESSION_ST_DONE);
             break;
@@ -1794,31 +1787,18 @@ static void h2_session_ev_conn_error(h2_
 
 static void h2_session_ev_proto_error(h2_session *session, int arg, const char *msg)
 {
-    switch (session->state) {
-        case H2_SESSION_ST_DONE:
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
-            /* just leave */
-            transit(session, "proto error", H2_SESSION_ST_DONE);
-            break;
-        
-        default:
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03402)
-                          "h2_session(%ld): proto error -> shutdown", session->id);
-            h2_session_shutdown(session, arg, msg, 0);
-            break;
+    if (!session->local.shutdown) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03402)
+                      "h2_session(%ld): proto error -> shutdown", session->id);
+        h2_session_shutdown(session, arg, msg, 0);
     }
 }
 
 static void h2_session_ev_conn_timeout(h2_session *session, int arg, const char *msg)
 {
-    switch (session->state) {
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
-            transit(session, "conn timeout", H2_SESSION_ST_DONE);
-            break;
-        default:
-            h2_session_shutdown(session, arg, msg, 1);
-            transit(session, "conn timeout", H2_SESSION_ST_DONE);
-            break;
+    transit(session, msg, H2_SESSION_ST_DONE);
+    if (!session->local.shutdown) {
+        h2_session_shutdown(session, arg, msg, 1);
     }
 }
 
@@ -1826,8 +1806,6 @@ static void h2_session_ev_no_io(h2_sessi
 {
     switch (session->state) {
         case H2_SESSION_ST_BUSY:
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
-        case H2_SESSION_ST_REMOTE_SHUTDOWN:
             /* Nothing to READ, nothing to WRITE on the master connection.
              * Possible causes:
              * - we wait for the client to send us sth
@@ -1837,9 +1815,9 @@ static void h2_session_ev_no_io(h2_sessi
             ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
                           "h2_session(%ld): NO_IO event, %d streams open", 
                           session->id, session->open_streams);
+            h2_conn_io_flush(&session->io);
             if (session->open_streams > 0) {
-                if (has_unsubmitted_streams(session) 
-                    || has_suspended_streams(session)) {
+                if (h2_mplx_awaits_data(session->mplx)) {
                     /* waiting for at least one stream to produce data */
                     transit(session, "no io", H2_SESSION_ST_WAIT);
                 }
@@ -1860,7 +1838,7 @@ static void h2_session_ev_no_io(h2_sessi
                     }
                 }
             }
-            else if (is_accepting_streams(session)) {
+            else if (session->local.accepting) {
                 /* When we have no streams, but accept new, switch to idle */
                 apr_time_t now = apr_time_now();
                 transit(session, "no io (keepalive)", H2_SESSION_ST_IDLE);
@@ -1923,26 +1901,17 @@ static void h2_session_ev_mpm_stopping(h
 {
     switch (session->state) {
         case H2_SESSION_ST_DONE:
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
             /* nop */
             break;
         default:
-            h2_session_shutdown(session, arg, msg, 0);
+            h2_session_shutdown_notice(session);
             break;
     }
 }
 
 static void h2_session_ev_pre_close(h2_session *session, int arg, const char *msg)
 {
-    switch (session->state) {
-        case H2_SESSION_ST_DONE:
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
-            /* nop */
-            break;
-        default:
-            h2_session_shutdown(session, arg, msg, 1);
-            break;
-    }
+    h2_session_shutdown(session, arg, msg, 1);
 }
 
 static void h2_session_ev_stream_open(h2_session *session, int arg, const char *msg)
@@ -2030,6 +1999,7 @@ static void dispatch_event(h2_session *s
     }
     
     if (session->state == H2_SESSION_ST_DONE) {
+        apr_brigade_cleanup(session->bbtmp);
         h2_mplx_abort(session->mplx);
     }
 }
@@ -2052,14 +2022,14 @@ apr_status_t h2_session_process(h2_sessi
         c->cs->state = CONN_STATE_WRITE_COMPLETION;
     }
     
-    while (1) {
+    while (session->state != H2_SESSION_ST_DONE) {
         trace = APLOGctrace3(c);
         session->have_read = session->have_written = 0;
 
-        if (!ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
+        if (session->local.accepting 
+            && !ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
             if (mpm_state == AP_MPMQ_STOPPING) {
                 dispatch_event(session, H2_SESSION_EV_MPM_STOPPING, 0, NULL);
-                break;
             }
         }
         
@@ -2175,12 +2145,18 @@ apr_status_t h2_session_process(h2_sessi
                         }
                         /* continue reading handling */
                     }
+                    else if (APR_STATUS_IS_ECONNABORTED(status)
+                             || APR_STATUS_IS_ECONNRESET(status)
+                             || APR_STATUS_IS_EOF(status)
+                             || APR_STATUS_IS_EBADF(status)) {
+                        ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
+                                      "h2_session(%ld): input gone", session->id);
+                        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+                    }
                     else {
-                        if (trace) {
-                            ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
-                                          "h2_session(%ld): idle(1 sec timeout) "
-                                          "read failed", session->id);
-                        }
+                        ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
+                                      "h2_session(%ld): idle(1 sec timeout) "
+                                      "read failed", session->id);
                         dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, "error");
                     }
                 }
@@ -2188,8 +2164,6 @@ apr_status_t h2_session_process(h2_sessi
                 break;
                 
             case H2_SESSION_ST_BUSY:
-            case H2_SESSION_ST_LOCAL_SHUTDOWN:
-            case H2_SESSION_ST_REMOTE_SHUTDOWN:
                 if (nghttp2_session_want_read(session->ngh2)) {
                     ap_update_child_status(session->c->sbh, SERVER_BUSY_READ, NULL);
                     h2_filter_cin_timeout_set(session->cin, session->s->timeout);
@@ -2213,7 +2187,6 @@ apr_status_t h2_session_process(h2_sessi
                 /* trigger window updates, stream resumes and submits */
                 status = h2_mplx_dispatch_master_events(session->mplx, 
                                                         on_stream_resume,
-                                                        on_stream_response, 
                                                         session);
                 if (status != APR_SUCCESS) {
                     ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
@@ -2272,7 +2245,7 @@ apr_status_t h2_session_process(h2_sessi
                 else if (APR_STATUS_IS_TIMEUP(status)) {
                     /* go back to checking all inputs again */
                     transit(session, "wait cycle", session->local.accepting? 
-                            H2_SESSION_ST_BUSY : H2_SESSION_ST_LOCAL_SHUTDOWN);
+                            H2_SESSION_ST_BUSY : H2_SESSION_ST_DONE);
                 }
                 else if (APR_STATUS_IS_ECONNRESET(status) 
                          || APR_STATUS_IS_ECONNABORTED(status)) {
@@ -2288,10 +2261,6 @@ apr_status_t h2_session_process(h2_sessi
                 }
                 break;
                 
-            case H2_SESSION_ST_DONE:
-                status = APR_EOF;
-                goto out;
-                
             default:
                 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
                               APLOGNO(03080)
@@ -2321,11 +2290,12 @@ out:
         && (APR_STATUS_IS_EOF(status)
             || APR_STATUS_IS_ECONNRESET(status) 
             || APR_STATUS_IS_ECONNABORTED(status))) {
-            dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
-        }
+        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+    }
 
-    status = (session->state == H2_SESSION_ST_DONE)? APR_EOF : APR_SUCCESS;
+    status = APR_SUCCESS;
     if (session->state == H2_SESSION_ST_DONE) {
+        status = APR_EOF;
         if (!session->eoc_written) {
             session->eoc_written = 1;
             h2_conn_io_write_eoc(&session->io, session);
@@ -2339,6 +2309,7 @@ apr_status_t h2_session_pre_close(h2_ses
 {
     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, 
                   "h2_session(%ld): pre_close", session->id);
-    dispatch_event(session, H2_SESSION_EV_PRE_CLOSE, 0, "timeout");
+    dispatch_event(session, H2_SESSION_EV_PRE_CLOSE, 0, 
+        (session->state == H2_SESSION_ST_IDLE)? "timeout" : NULL);
     return APR_SUCCESS;
 }

Modified: httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_session.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_session.h?rev=1769588&r1=1769587&r2=1769588&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_session.h (original)
+++ httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_session.h Mon Nov 14 10:26:31 2016
@@ -31,7 +31,7 @@
  * New incoming HEADER frames are converted into a h2_stream+h2_task instance
  * that both represent a HTTP/2 stream, but may have separate lifetimes. This
  * allows h2_task to be scheduled in other threads without semaphores
- * all over the place. It allows task memory to be freed independant of
+ * all over the place. It allows task memory to be freed independent of
  * session lifetime and sessions may close down while tasks are still running.
  *
  *
@@ -49,7 +49,6 @@ struct h2_mplx;
 struct h2_priority;
 struct h2_push;
 struct h2_push_diary;
-struct h2_response;
 struct h2_session;
 struct h2_stream;
 struct h2_task;
@@ -87,7 +86,6 @@ typedef struct h2_session {
     struct h2_workers *workers;     /* for executing stream tasks */
     struct h2_filter_cin *cin;      /* connection input filter context */
     h2_conn_io io;                  /* io on httpd conn filters */
-    struct h2_ihash_t *streams;     /* streams handled by this session */
     struct nghttp2_session *ngh2;   /* the nghttp2 session (internal use) */
 
     h2_session_state state;         /* state session is in */
@@ -100,7 +98,7 @@ typedef struct h2_session {
     unsigned int flush         : 1; /* flushing output necessary */
     unsigned int have_read     : 1; /* session has read client data */
     unsigned int have_written  : 1; /* session did write data to client */
-    apr_interval_time_t  wait_us;   /* timout during BUSY_WAIT state, micro secs */
+    apr_interval_time_t  wait_us;   /* timeout during BUSY_WAIT state, micro secs */
     
     struct h2_push_diary *push_diary; /* remember pushes, avoid duplicates */
     
@@ -156,7 +154,7 @@ h2_session *h2_session_rcreate(request_r
 
 /**
  * Process the given HTTP/2 session until it is ended or a fatal
- * error occured.
+ * error occurred.
  *
  * @param session the sessionm to process
  */
@@ -175,7 +173,7 @@ apr_status_t h2_session_pre_close(h2_ses
 void h2_session_eoc_callback(h2_session *session);
 
 /**
- * Called when a serious error occured and the session needs to terminate
+ * Called when a serious error occurred and the session needs to terminate
  * without further connection io.
  * @param session the session to abort
  * @param reason  the apache status that caused the abort
@@ -187,11 +185,6 @@ void h2_session_abort(h2_session *sessio
  */
 void h2_session_close(h2_session *session);
 
-/* Start submitting the response to a stream request. This is possible
- * once we have all the response headers. */
-apr_status_t h2_session_handle_response(h2_session *session,
-                                        struct h2_stream *stream);
-
 /**
  * Create and register a new stream under the given id.
  * 

Modified: httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_stream.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_stream.c?rev=1769588&r1=1769587&r2=1769588&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_stream.c (original)
+++ httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/modules/http2/h2_stream.c Mon Nov 14 10:26:31 2016
@@ -16,6 +16,8 @@
 #include <assert.h>
 #include <stddef.h>
 
+#include <apr_strings.h>
+
 #include <httpd.h>
 #include <http_core.h>
 #include <http_connection.h>
@@ -29,11 +31,10 @@
 #include "h2_conn.h"
 #include "h2_config.h"
 #include "h2_h2.h"
-#include "h2_filter.h"
 #include "h2_mplx.h"
 #include "h2_push.h"
 #include "h2_request.h"
-#include "h2_response.h"
+#include "h2_headers.h"
 #include "h2_session.h"
 #include "h2_stream.h"
 #include "h2_task.h"
@@ -53,7 +54,7 @@ static int state_transition[][7] = {
 /*CL*/{  1, 1, 0, 0, 1, 1, 1 },
 };
 
-static void H2_STREAM_OUT_LOG(int lvl, h2_stream *s, char *tag)
+static void H2_STREAM_OUT_LOG(int lvl, h2_stream *s, const char *tag)
 {
     if (APLOG_C_IS_LEVEL(s->session->c, lvl)) {
         conn_rec *c = s->session->c;
@@ -61,9 +62,9 @@ static void H2_STREAM_OUT_LOG(int lvl, h
         const char *line = "(null)";
         apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]);
         
-        len = h2_util_bb_print(buffer, bmax, tag, "", s->buffer);
-        ap_log_cerror(APLOG_MARK, lvl, 0, c, "bb_dump(%ld-%d): %s", 
-                      c->id, s->id, len? buffer : line);
+        len = h2_util_bb_print(buffer, bmax, tag, "", s->out_buffer);
+        ap_log_cerror(APLOG_MARK, lvl, 0, c, "bb_dump(%s): %s", 
+                      c->log_id, len? buffer : line);
     }
 }
 
@@ -150,15 +151,29 @@ static int output_open(h2_stream *stream
     }
 }
 
+static void prep_output(h2_stream *stream) {
+    conn_rec *c = stream->session->c;
+    if (!stream->out_buffer) {
+        stream->out_buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
+    }
+}
+
+static void prepend_response(h2_stream *stream, h2_headers *response)
+{
+    conn_rec *c = stream->session->c;
+    apr_bucket *b;
+    
+    prep_output(stream);
+    b = h2_bucket_headers_create(c->bucket_alloc, response);
+    APR_BRIGADE_INSERT_HEAD(stream->out_buffer, b);
+}
+
 static apr_status_t stream_pool_cleanup(void *ctx)
 {
     h2_stream *stream = ctx;
     apr_status_t status;
     
-    if (stream->input) {
-        h2_beam_destroy(stream->input);
-        stream->input = NULL;
-    }
+    ap_assert(stream->can_be_cleaned);
     if (stream->files) {
         apr_file_t *file;
         int i;
@@ -175,29 +190,21 @@ static apr_status_t stream_pool_cleanup(
 }
 
 h2_stream *h2_stream_open(int id, apr_pool_t *pool, h2_session *session,
-                          int initiated_on, const h2_request *creq)
+                          int initiated_on)
 {
-    h2_request *req;
     h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));
     
-    stream->id        = id;
-    stream->state     = H2_STREAM_ST_IDLE;
-    stream->pool      = pool;
-    stream->session   = session;
-    set_state(stream, H2_STREAM_ST_OPEN);
-    
-    if (creq) {
-        /* take it into out pool and assure correct id's */
-        req = h2_request_clone(pool, creq);
-        req->id = id;
-        req->initiated_on = initiated_on;
-    }
-    else {
-        req = h2_req_create(id, pool, 
-                h2_config_geti(session->config, H2_CONF_SER_HEADERS));
-    }
-    stream->request = req; 
+    stream->id           = id;
+    stream->initiated_on = initiated_on;
+    stream->created      = apr_time_now();
+    stream->state        = H2_STREAM_ST_IDLE;
+    stream->pool         = pool;
+    stream->session      = session;
+
+    h2_beam_create(&stream->input, pool, id, "input", H2_BEAM_OWNER_SEND, 0);
+    h2_beam_create(&stream->output, pool, id, "output", H2_BEAM_OWNER_RECV, 0);
     
+    set_state(stream, H2_STREAM_ST_OPEN);
     apr_pool_cleanup_register(pool, stream, stream_pool_cleanup, 
                               apr_pool_cleanup_null);
     ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03082)
@@ -207,31 +214,35 @@ h2_stream *h2_stream_open(int id, apr_po
 
 void h2_stream_cleanup(h2_stream *stream)
 {
-    AP_DEBUG_ASSERT(stream);
-    if (stream->buffer) {
-        apr_brigade_cleanup(stream->buffer);
-    }
-    if (stream->input) {
-        apr_status_t status;
-        status = h2_beam_shutdown(stream->input, APR_NONBLOCK_READ, 1);
-        if (status == APR_EAGAIN) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c, 
-                          "h2_stream(%ld-%d): wait on input shutdown", 
-                          stream->session->id, stream->id);
-            status = h2_beam_shutdown(stream->input, APR_BLOCK_READ, 1);
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, stream->session->c, 
-                          "h2_stream(%ld-%d): input shutdown returned", 
-                          stream->session->id, stream->id);
-        }
+    apr_status_t status;
+    
+    ap_assert(stream);
+    if (stream->out_buffer) {
+        /* remove any left over output buckets that may still have
+         * references into request pools */
+        apr_brigade_cleanup(stream->out_buffer);
+    }
+    h2_beam_abort(stream->input);
+    status = h2_beam_wait_empty(stream->input, APR_NONBLOCK_READ);
+    if (status == APR_EAGAIN) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c, 
+                      "h2_stream(%ld-%d): wait on input drain", 
+                      stream->session->id, stream->id);
+        status = h2_beam_wait_empty(stream->input, APR_BLOCK_READ);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, stream->session->c, 
+                      "h2_stream(%ld-%d): input drain returned", 
+                      stream->session->id, stream->id);
     }
 }
 
 void h2_stream_destroy(h2_stream *stream)
 {
-    AP_DEBUG_ASSERT(stream);
+    ap_assert(stream);
+    ap_assert(!h2_mplx_stream_get(stream->session->mplx, stream->id));
     ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, stream->session->c, 
                   "h2_stream(%ld-%d): destroy", 
                   stream->session->id, stream->id);
+    stream->can_be_cleaned = 1;
     if (stream->pool) {
         apr_pool_destroy(stream->pool);
     }
@@ -255,42 +266,75 @@ void h2_stream_rst(h2_stream *stream, in
     stream->rst_error = error_code;
     close_input(stream);
     close_output(stream);
+    if (stream->out_buffer) {
+        apr_brigade_cleanup(stream->out_buffer);
+    }
     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
                   "h2_stream(%ld-%d): reset, error=%d", 
                   stream->session->id, stream->id, error_code);
 }
 
-struct h2_response *h2_stream_get_response(h2_stream *stream)
-{
-    return stream->response;
-}
-
-apr_status_t h2_stream_set_request(h2_stream *stream, request_rec *r)
+apr_status_t h2_stream_set_request_rec(h2_stream *stream, request_rec *r)
 {
+    h2_request *req;
     apr_status_t status;
-    AP_DEBUG_ASSERT(stream);
+
+    ap_assert(stream->request == NULL);
+    ap_assert(stream->rtmp == NULL);
     if (stream->rst_error) {
         return APR_ECONNRESET;
     }
-    set_state(stream, H2_STREAM_ST_OPEN);
-    status = h2_request_rwrite(stream->request, stream->pool, r);
-    stream->request->serialize = h2_config_geti(h2_config_sget(r->server), 
-                                                H2_CONF_SER_HEADERS);
+    status = h2_request_rcreate(&req, stream->pool, r);
     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(03058)
-                  "h2_request(%d): rwrite %s host=%s://%s%s",
-                  stream->request->id, stream->request->method, 
-                  stream->request->scheme, stream->request->authority, 
-                  stream->request->path);
-
+                  "h2_request(%d): set_request_rec %s host=%s://%s%s",
+                  stream->id, req->method, req->scheme, req->authority, 
+                  req->path);
+    stream->rtmp = req;
     return status;
 }
 
+apr_status_t h2_stream_set_request(h2_stream *stream, const h2_request *r)
+{
+    ap_assert(stream->request == NULL);
+    ap_assert(stream->rtmp == NULL);
+    stream->rtmp = h2_request_clone(stream->pool, r);
+    return APR_SUCCESS;
+}
+
+static apr_status_t add_trailer(h2_stream *stream,
+                                const char *name, size_t nlen,
+                                const char *value, size_t vlen)
+{
+    conn_rec *c = stream->session->c;
+    char *hname, *hvalue;
+
+    if (nlen == 0 || name[0] == ':') {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, c, APLOGNO(03060)
+                      "h2_request(%ld-%d): pseudo header in trailer",
+                      c->id, stream->id);
+        return APR_EINVAL;
+    }
+    if (h2_req_ignore_trailer(name, nlen)) {
+        return APR_SUCCESS;
+    }
+    if (!stream->trailers) {
+        stream->trailers = apr_table_make(stream->pool, 5);
+    }
+    hname = apr_pstrndup(stream->pool, name, nlen);
+    hvalue = apr_pstrndup(stream->pool, value, vlen);
+    h2_util_camel_case_header(hname, nlen);
+    apr_table_mergen(stream->trailers, hname, hvalue);
+    
+    return APR_SUCCESS;
+}
+
 apr_status_t h2_stream_add_header(h2_stream *stream,
                                   const char *name, size_t nlen,
                                   const char *value, size_t vlen)
 {
-    AP_DEBUG_ASSERT(stream);
-    if (!stream->response) {
+    ap_assert(stream);
+    
+    if (!stream->has_response) {
         if (name[0] == ':') {
             if ((vlen) > stream->session->s->limit_req_line) {
                 /* pseudo header: approximation of request line size check */
@@ -325,14 +369,17 @@ apr_status_t h2_stream_add_header(h2_str
     }
     
     if (h2_stream_is_scheduled(stream)) {
-        return h2_request_add_trailer(stream->request, stream->pool,
-                                      name, nlen, value, vlen);
+        return add_trailer(stream, name, nlen, value, vlen);
     }
     else {
-        if (!input_open(stream)) {
+        if (!stream->rtmp) {
+            stream->rtmp = h2_req_create(stream->id, stream->pool, 
+                                         NULL, NULL, NULL, NULL, NULL, 0);
+        }
+        if (stream->state != H2_STREAM_ST_OPEN) {
             return APR_ECONNRESET;
         }
-        return h2_request_add_header(stream->request, stream->pool,
+        return h2_request_add_header(stream->rtmp, stream->pool,
                                      name, nlen, value, vlen);
     }
 }
@@ -340,52 +387,57 @@ apr_status_t h2_stream_add_header(h2_str
 apr_status_t h2_stream_schedule(h2_stream *stream, int eos, int push_enabled, 
                                 h2_stream_pri_cmp *cmp, void *ctx)
 {
-    apr_status_t status;
-    AP_DEBUG_ASSERT(stream);
-    AP_DEBUG_ASSERT(stream->session);
-    AP_DEBUG_ASSERT(stream->session->mplx);
-    
-    if (!output_open(stream)) {
-        return APR_ECONNRESET;
-    }
-    if (stream->scheduled) {
-        return APR_EINVAL;
-    }
-    if (eos) {
-        close_input(stream);
-    }
-    
-    if (stream->response) {
-        /* already have a resonse, probably a HTTP error code */
-        return h2_mplx_process(stream->session->mplx, stream, cmp, ctx);
-    }
-    
-    /* Seeing the end-of-headers, we have everything we need to 
-     * start processing it.
-     */
-    status = h2_request_end_headers(stream->request, stream->pool, 
-                                    eos, push_enabled);
-    if (status == APR_SUCCESS) {
-        stream->request->body = !eos;
-        stream->scheduled = 1;
-        stream->input_remaining = stream->request->content_length;
-        
-        status = h2_mplx_process(stream->session->mplx, stream, cmp, ctx);
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
-                      "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);
-    }
-    else {
-        h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c,
-                      "h2_stream(%ld-%d): RST=2 (internal err) %s %s://%s%s",
-                      stream->session->id, stream->id,
-                      stream->request->method, stream->request->scheme,
-                      stream->request->authority, stream->request->path);
+    apr_status_t status = APR_EINVAL;
+    ap_assert(stream);
+    ap_assert(stream->session);
+    ap_assert(stream->session->mplx);
+    
+    if (!stream->scheduled) {
+        if (eos) {
+            close_input(stream);
+        }
+
+        if (h2_stream_is_ready(stream)) {
+            /* already have a resonse, probably a HTTP error code */
+            return h2_mplx_process(stream->session->mplx, stream, cmp, ctx);
+        }
+        else if (!stream->request && stream->rtmp) {
+            /* This is the common case: a h2_request was being assembled, now
+             * it gets finalized and checked for completness */
+            status = h2_request_end_headers(stream->rtmp, stream->pool, eos);
+            if (status == APR_SUCCESS) {
+                stream->rtmp->serialize = h2_config_geti(stream->session->config,
+                                                         H2_CONF_SER_HEADERS); 
+
+                stream->request = stream->rtmp;
+                stream->rtmp = NULL;
+                stream->scheduled = 1;
+                stream->push_policy = h2_push_policy_determine(stream->request->headers, 
+                                                               stream->pool, push_enabled);
+            
+                
+                status = h2_mplx_process(stream->session->mplx, stream, cmp, ctx);
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+                              "h2_stream(%ld-%d): scheduled %s %s://%s%s "
+                              "chunked=%d",
+                              stream->session->id, stream->id,
+                              stream->request->method, stream->request->scheme,
+                              stream->request->authority, stream->request->path,
+                              stream->request->chunked);
+                return status;
+            }
+        }
+        else {
+            status = APR_ECONNRESET;
+        }
     }
     
+    h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c,
+                  "h2_stream(%ld-%d): RST=2 (internal err) %s %s://%s%s",
+                  stream->session->id, stream->id,
+                  stream->request->method, stream->request->scheme,
+                  stream->request->authority, stream->request->path);
     return status;
 }
 
@@ -396,20 +448,31 @@ int h2_stream_is_scheduled(const h2_stre
 
 apr_status_t h2_stream_close_input(h2_stream *stream)
 {
-    apr_status_t status = APR_SUCCESS;
-    
-    AP_DEBUG_ASSERT(stream);
+    conn_rec *c = stream->session->c;
+    apr_status_t status;
+    apr_bucket_brigade *tmp;
+    apr_bucket *b;
+
     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
                   "h2_stream(%ld-%d): closing input",
                   stream->session->id, stream->id);
-                  
     if (stream->rst_error) {
         return APR_ECONNRESET;
     }
     
-    if (close_input(stream) && stream->input) {
-        status = h2_beam_close(stream->input);
+    tmp = apr_brigade_create(stream->pool, c->bucket_alloc);
+    if (stream->trailers && !apr_is_empty_table(stream->trailers)) {
+        h2_headers *r = h2_headers_create(HTTP_OK, stream->trailers, 
+                                          NULL, stream->pool);
+        b = h2_bucket_headers_create(c->bucket_alloc, r);
+        APR_BRIGADE_INSERT_TAIL(tmp, b);
+        stream->trailers = NULL;
     }
+    
+    b = apr_bucket_eos_create(c->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(tmp, b);
+    status = h2_beam_send(stream->input, tmp, APR_BLOCK_READ);
+    apr_brigade_destroy(tmp);
     return status;
 }
 
@@ -418,68 +481,39 @@ apr_status_t h2_stream_write_data(h2_str
 {
     conn_rec *c = stream->session->c;
     apr_status_t status = APR_SUCCESS;
+    apr_bucket_brigade *tmp;
     
-    AP_DEBUG_ASSERT(stream);
+    ap_assert(stream);
     if (!stream->input) {
         return APR_EOF;
     }
-    if (input_closed(stream) || !stream->request->eoh) {
+    if (input_closed(stream) || !stream->request) {
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                       "h2_stream(%ld-%d): writing denied, closed=%d, eoh=%d", 
                       stream->session->id, stream->id, input_closed(stream),
-                      stream->request->eoh);
+                      stream->request != NULL);
         return APR_EINVAL;
     }
 
     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                   "h2_stream(%ld-%d): add %ld input bytes", 
                   stream->session->id, stream->id, (long)len);
-
-    if (!stream->request->chunked) {
-        stream->input_remaining -= len;
-        if (stream->input_remaining < 0) {
-            ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, 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;
-        }
-    }
     
-    if (!stream->tmp) {
-        stream->tmp = apr_brigade_create(stream->pool, c->bucket_alloc);
-    }
-    apr_brigade_write(stream->tmp, NULL, NULL, data, len);
+    tmp = apr_brigade_create(stream->pool, c->bucket_alloc);
+    apr_brigade_write(tmp, NULL, NULL, data, len);
+    status = h2_beam_send(stream->input, tmp, APR_BLOCK_READ);
+    apr_brigade_destroy(tmp);
+    
+    stream->in_data_frames++;
+    stream->in_data_octets += len;
+    
     if (eos) {
-        APR_BRIGADE_INSERT_TAIL(stream->tmp, 
-                                apr_bucket_eos_create(c->bucket_alloc)); 
-        close_input(stream);
+        return h2_stream_close_input(stream);
     }
     
-    status = h2_beam_send(stream->input, stream->tmp, APR_BLOCK_READ);
-    apr_brigade_cleanup(stream->tmp);
     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(const h2_stream *stream)
-{
-    AP_DEBUG_ASSERT(stream);
-    return stream->suspended;
-}
-
 static apr_status_t fill_buffer(h2_stream *stream, apr_size_t amount)
 {
     conn_rec *c = stream->session->c;
@@ -489,9 +523,12 @@ static apr_status_t fill_buffer(h2_strea
     if (!stream->output) {
         return APR_EOF;
     }
-    status = h2_beam_receive(stream->output, stream->buffer, 
+    status = h2_beam_receive(stream->output, stream->out_buffer, 
                              APR_NONBLOCK_READ, amount);
-    /* The buckets we reveive are using the stream->buffer pool as
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, stream->session->c,
+                  "h2_stream(%ld-%d): beam_received",
+                  stream->session->id, stream->id);
+    /* The buckets we reveive are using the stream->out_buffer pool as
      * lifetime which is exactly what we want since this is stream->pool.
      *
      * However: when we send these buckets down the core output filters, the
@@ -502,8 +539,8 @@ static apr_status_t fill_buffer(h2_strea
      * file. Any split off buckets we sent afterwards will result in a 
      * APR_EBADF.
      */
-    for (b = APR_BRIGADE_FIRST(stream->buffer);
-         b != APR_BRIGADE_SENTINEL(stream->buffer);
+    for (b = APR_BRIGADE_FIRST(stream->out_buffer);
+         b != APR_BRIGADE_SENTINEL(stream->out_buffer);
          b = APR_BUCKET_NEXT(b)) {
         if (APR_BUCKET_IS_FILE(b)) {
             apr_bucket_file *f = (apr_bucket_file *)b->data;
@@ -521,77 +558,76 @@ static apr_status_t fill_buffer(h2_strea
     return status;
 }
 
-apr_status_t h2_stream_set_response(h2_stream *stream, h2_response *response,
-                                    h2_bucket_beam *output)
+apr_status_t h2_stream_set_error(h2_stream *stream, int http_status)
 {
-    apr_status_t status = APR_SUCCESS;
-    conn_rec *c = stream->session->c;
-    
-    if (!output_open(stream)) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                      "h2_stream(%ld-%d): output closed", 
-                      stream->session->id, stream->id);
-        return APR_ECONNRESET;
-    }
+    h2_headers *response;
     
-    stream->response = response;
-    stream->output = output;
-    stream->buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
-    
-    h2_stream_filter(stream);
-    if (stream->output) {
-        status = fill_buffer(stream, 0);
+    if (h2_stream_is_ready(stream)) {
+        return APR_EINVAL;
     }
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
-                  "h2_stream(%ld-%d): set_response(%d)", 
-                  stream->session->id, stream->id, 
-                  stream->response->http_status);
-    return status;
+    if (stream->rtmp) {
+        stream->request = stream->rtmp;
+        stream->rtmp = NULL;
+    }
+    response = h2_headers_die(http_status, stream->request, stream->pool);
+    prepend_response(stream, response);
+    h2_beam_close(stream->output);
+    return APR_SUCCESS;
 }
 
-apr_status_t h2_stream_set_error(h2_stream *stream, int http_status)
+static apr_bucket *get_first_headers_bucket(apr_bucket_brigade *bb)
 {
-    h2_response *response;
-    
-    if (stream->submitted) {
-        return APR_EINVAL;
+    if (bb) {
+        apr_bucket *b = APR_BRIGADE_FIRST(bb);
+        while (b != APR_BRIGADE_SENTINEL(bb)) {
+            if (H2_BUCKET_IS_HEADERS(b)) {
+                return b;
+            }
+            b = APR_BUCKET_NEXT(b);
+        }
     }
-    response = h2_response_die(stream->id, http_status, stream->request, 
-                               stream->pool);
-    return h2_stream_set_response(stream, response, NULL);
+    return NULL;
 }
 
-static const apr_size_t DATA_CHUNK_SIZE = ((16*1024) - 100 - 9); 
-
-apr_status_t h2_stream_out_prepare(h2_stream *stream,
-                                   apr_off_t *plen, int *peos)
+apr_status_t h2_stream_out_prepare(h2_stream *stream, apr_off_t *plen, 
+                                   int *peos, h2_headers **presponse)
 {
     conn_rec *c = stream->session->c;
     apr_status_t status = APR_SUCCESS;
     apr_off_t requested;
+    apr_bucket *b, *e;
 
+    if (presponse) {
+        *presponse = NULL;
+    }
+    
     if (stream->rst_error) {
         *plen = 0;
         *peos = 1;
         return APR_ECONNRESET;
     }
+    
+    if (!output_open(stream)) {
+        return APR_ECONNRESET;
+    }
+    prep_output(stream);
 
     if (*plen > 0) {
-        requested = H2MIN(*plen, DATA_CHUNK_SIZE);
+        requested = H2MIN(*plen, H2_DATA_CHUNK_SIZE);
     }
     else {
-        requested = DATA_CHUNK_SIZE;
+        requested = H2_DATA_CHUNK_SIZE;
     }
     *plen = requested;
     
     H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "h2_stream_out_prepare_pre");
-    h2_util_bb_avail(stream->buffer, plen, peos);
+    h2_util_bb_avail(stream->out_buffer, plen, peos);
     if (!*peos && *plen < requested) {
         /* try to get more data */
-        status = fill_buffer(stream, (requested - *plen) + DATA_CHUNK_SIZE);
+        status = fill_buffer(stream, (requested - *plen) + H2_DATA_CHUNK_SIZE);
         if (APR_STATUS_IS_EOF(status)) {
             apr_bucket *eos = apr_bucket_eos_create(c->bucket_alloc);
-            APR_BRIGADE_INSERT_TAIL(stream->buffer, eos);
+            APR_BRIGADE_INSERT_TAIL(stream->out_buffer, eos);
             status = APR_SUCCESS;
         }
         else if (status == APR_EAGAIN) {
@@ -599,20 +635,70 @@ apr_status_t h2_stream_out_prepare(h2_st
             status = APR_SUCCESS;
         }
         *plen = requested;
-        h2_util_bb_avail(stream->buffer, plen, peos);
+        h2_util_bb_avail(stream->out_buffer, plen, peos);
     }
     H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "h2_stream_out_prepare_post");
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
-                  "h2_stream(%ld-%d): prepare, len=%ld eos=%d, trailers=%s",
-                  c->id, stream->id, (long)*plen, *peos,
-                  (stream->response && stream->response->trailers)? 
-                  "yes" : "no");
-    if (!*peos && !*plen && status == APR_SUCCESS) {
-        return APR_EAGAIN;
+    
+    b = APR_BRIGADE_FIRST(stream->out_buffer);
+    while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+        e = APR_BUCKET_NEXT(b);
+        if (APR_BUCKET_IS_FLUSH(b)
+            || (!APR_BUCKET_IS_METADATA(b) && b->length == 0)) {
+            APR_BUCKET_REMOVE(b);
+            apr_bucket_destroy(b);
+        }
+        else {
+            break;
+        }
+        b = e;
     }
+    
+    b = get_first_headers_bucket(stream->out_buffer);
+    if (b) {
+        /* there are HEADERS to submit */
+        *peos = 0;
+        *plen = 0;
+        if (b == APR_BRIGADE_FIRST(stream->out_buffer)) {
+            if (presponse) {
+                *presponse = h2_bucket_headers_get(b);
+                APR_BUCKET_REMOVE(b);
+                apr_bucket_destroy(b);
+                status = APR_SUCCESS;
+            }
+            else {
+                /* someone needs to retrieve the response first */
+                h2_mplx_keep_active(stream->session->mplx, stream->id);
+                status = APR_EAGAIN;
+            }
+        }
+        else {
+            apr_bucket *e = APR_BRIGADE_FIRST(stream->out_buffer);
+            while (e != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+                if (e == b) {
+                    break;
+                }
+                else if (e->length != (apr_size_t)-1) {
+                    *plen += e->length;
+                }
+                e = APR_BUCKET_NEXT(e);
+            }
+        }
+    }
+    
+    if (!*peos && !*plen && status == APR_SUCCESS 
+        && (!presponse || !*presponse)) {
+        status = APR_EAGAIN;
+    }
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+                  "h2_stream(%ld-%d): prepare, len=%ld eos=%d",
+                  c->id, stream->id, (long)*plen, *peos);
     return status;
 }
 
+static int is_not_headers(apr_bucket *b)
+{
+    return !H2_BUCKET_IS_HEADERS(b);
+}
 
 apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb, 
                                apr_off_t *plen, int *peos)
@@ -623,7 +709,7 @@ apr_status_t h2_stream_read_to(h2_stream
     if (stream->rst_error) {
         return APR_ECONNRESET;
     }
-    status = h2_append_brigade(bb, stream->buffer, plen, peos);
+    status = h2_append_brigade(bb, stream->out_buffer, plen, peos, is_not_headers);
     if (status == APR_SUCCESS && !*peos && !*plen) {
         status = APR_EAGAIN;
     }
@@ -639,27 +725,13 @@ int h2_stream_input_is_open(const h2_str
     return input_open(stream);
 }
 
-int h2_stream_needs_submit(const 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 h2_stream_submit_pushes(h2_stream *stream, h2_headers *response)
 {
     apr_status_t status = APR_SUCCESS;
     apr_array_header_t *pushes;
     int i;
     
-    pushes = h2_push_collect_update(stream, stream->request, 
-                                    h2_stream_get_response(stream));
+    pushes = h2_push_collect_update(stream, stream->request, 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",
@@ -678,14 +750,13 @@ apr_status_t h2_stream_submit_pushes(h2_
 
 apr_table_t *h2_stream_get_trailers(h2_stream *stream)
 {
-    return stream->response? stream->response->trailers : NULL;
+    return NULL;
 }
 
-const h2_priority *h2_stream_get_priority(h2_stream *stream)
+const h2_priority *h2_stream_get_priority(h2_stream *stream, 
+                                          h2_headers *response)
 {
-    h2_response *response = h2_stream_get_response(stream);
-    
-    if (response && stream->request && stream->request->initiated_on) {
+    if (response && stream->initiated_on) {
         const char *ctype = apr_table_get(response->headers, "content-type");
         if (ctype) {
             /* FIXME: Not good enough, config needs to come from request->server */
@@ -695,3 +766,38 @@ const h2_priority *h2_stream_get_priorit
     return NULL;
 }
 
+const char *h2_stream_state_str(h2_stream *stream)
+{
+    switch (stream->state) {
+        case H2_STREAM_ST_IDLE:
+            return "IDLE";
+        case H2_STREAM_ST_OPEN:
+            return "OPEN";
+        case H2_STREAM_ST_RESV_LOCAL:
+            return "RESERVED_LOCAL";
+        case H2_STREAM_ST_RESV_REMOTE:
+            return "RESERVED_REMOTE";
+        case H2_STREAM_ST_CLOSED_INPUT:
+            return "HALF_CLOSED_REMOTE";
+        case H2_STREAM_ST_CLOSED_OUTPUT:
+            return "HALF_CLOSED_LOCAL";
+        case H2_STREAM_ST_CLOSED:
+            return "CLOSED";
+        default:
+            return "UNKNOWN";
+            
+    }
+}
+
+int h2_stream_is_ready(h2_stream *stream)
+{
+    if (stream->has_response) {
+        return 1;
+    }
+    else if (stream->out_buffer && get_first_headers_bucket(stream->out_buffer)) {
+        return 1;
+    }
+    return 0;
+}
+
+