]> granicus.if.org Git - apache/commitdiff
some rework for server push
authorStefan Eissing <icing@apache.org>
Wed, 11 Nov 2015 16:40:18 +0000 (16:40 +0000)
committerStefan Eissing <icing@apache.org>
Wed, 11 Nov 2015 16:40:18 +0000 (16:40 +0000)
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1713887 13f79535-47bb-0310-9956-ffa450edef68

28 files changed:
modules/http2/config.m4
modules/http2/h2_from_h1.c
modules/http2/h2_from_h1.h
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 [new file with mode: 0644]
modules/http2/h2_push.h [new file with mode: 0644]
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_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_to_h1.c [deleted file]
modules/http2/h2_to_h1.h [deleted file]
modules/http2/h2_version.h
modules/http2/mod_http2.dsp

index 35b25e11a3addedf0a3fc9e6520fdb413573888c..f383b3cd83853f79163b0271bffc6169222d028b 100644 (file)
@@ -31,6 +31,7 @@ h2_h2.lo dnl
 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 +42,6 @@ h2_task.lo dnl
 h2_task_input.lo dnl
 h2_task_output.lo dnl
 h2_task_queue.lo dnl
-h2_to_h1.lo dnl
 h2_util.lo dnl
 h2_worker.lo dnl
 h2_workers.lo dnl
index ed7fff4a27d58a731edb8e8c6cf31d01a153364a..43a4f0822b3ced81769dba92de441bb832995c04 100644 (file)
@@ -59,11 +59,6 @@ apr_status_t h2_from_h1_destroy(h2_from_h1 *from_h1)
     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) {
@@ -79,15 +74,8 @@ h2_response *h2_from_h1_get_response(h2_from_h1 *from_h1)
 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, 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 +190,7 @@ apr_status_t h2_from_h1_read_response(h2_from_h1 *from_h1, ap_filter_t* f,
                 }
                 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') {
index 9a0eba0ceca9779d5cc555683be35a67c9aaa71d..4f5ebad618b2db8142ea56b4dd5486de93632e31 100644 (file)
@@ -51,32 +51,22 @@ struct h2_from_h1 {
     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 112fce9a2258b6aac31141033b20742a6bc5bf12..7cf06ea940d98a77b6063e490b5540974816a279 100644 (file)
@@ -52,6 +52,10 @@ extern const char *H2_MAGIC_TOKEN;
 /* Maximum number of padding bytes in a frame, rfc7540 */
 #define H2_MAX_PADLEN               256
 
+#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
index 9aed64ee9562d783cd811c18190a08142a23984f..6bd96371519d675f5cc4a7d96c295c3edb8dead2 100644 (file)
@@ -40,10 +40,6 @@ h2_io *h2_io_create(int id, apr_pool_t *pool, apr_bucket_alloc_t *bucket_alloc)
 
 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)
index 2cfa2980e54b6b4abd7ae78ead5f6c633db0dfe1..71cca986d00849b968ec84313a770d90cdd00b04 100644 (file)
@@ -31,22 +31,23 @@ 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 rst_error;
     int zombie;
     
-    struct h2_task *task;       /* task created for this io */
+    int task_done;
+    struct h2_task *task;        /* task created for this io */
 
-    apr_size_t input_consumed;   /* how many bytes have been read */
+    struct h2_response *response;/* submittable response created */
+    int rst_error;
+
+    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 */
     struct apr_thread_cond_t *output_drained; /* block on writing */
     
-    struct h2_response *response;/* submittable response created */
     int files_handles_owned;
 };
 
index 7d052c53ea30147d7c1311c5b98197cba25e306f..723ea268f2e1b01f209ac98c6f072c1ada397e47 100644 (file)
@@ -584,8 +584,8 @@ static apr_status_t out_open(h2_mplx *m, int stream_id, h2_response *response,
     if (io) {
         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: %d, rst=%d",
+                          m->id, stream_id, response->http_status, 
                           response->rst_error);
         }
         
@@ -678,7 +678,7 @@ apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id)
                      * reset.
                      */
                     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", 
@@ -861,7 +861,7 @@ static h2_io *open_io(h2_mplx *m, int stream_id)
 
 
 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)
 {
     apr_status_t status;
@@ -878,18 +878,16 @@ apr_status_t h2_mplx_process(h2_mplx *m, int stream_id,
         
         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) {
+        io->task = h2_task_create(m->id, req, io->pool, m, c, eos);
+
+        if (eos) {
             status = h2_io_in_close(io);
         }
         
-        if (status == APR_SUCCESS) {
-            x.cmp = cmp;
-            x.ctx = ctx;
-            h2_tq_add(m->q, io->task, task_cmp, &x);
-        }
+        x.cmp = cmp;
+        x.ctx = ctx;
+        h2_tq_add(m->q, io->task, task_cmp, &x);
+
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, m->c,
                       "h2_mplx(%ld-%d): process", m->c->id, stream_id);
         apr_thread_mutex_unlock(m->lock);
index e8ba965fa7a318acc3d2bbcdfde27b5504382244..fc68b1addc8e4f6ba1da2b43ab51194cf4c8205e 100644 (file)
@@ -157,7 +157,7 @@ apr_status_t h2_mplx_out_trywait(h2_mplx *m, apr_interval_time_t timeout,
  * @param ctx context data for the compare function
  */
 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);
 
 /**
diff --git a/modules/http2/h2_push.c b/modules/http2/h2_push.c
new file mode 100644 (file)
index 0000000..cced862
--- /dev/null
@@ -0,0 +1,114 @@
+/* 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 <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 {
+    apr_array_header_t *pushes;
+    apr_pool_t *pool;
+    const h2_request *req;
+} link_ctx;
+
+static size_t skip_ws(const char *s, size_t i, size_t max)
+{
+    char c;
+    while (i < max && (((c = s[i]) == ' ') || (c == '\t'))) {
+        ++i;
+    }
+    return i;
+}
+
+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
+     */
+     /* TODO */
+     (void)skip_ws;
+}
+
+apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req, 
+                                    const h2_response *res)
+{
+    link_ctx ctx;
+    
+    ctx.pushes = NULL;
+    ctx.pool = p;
+    ctx.req = req;
+    
+    /* 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->ngheader) {
+        int i;
+        for (i = 0; i < res->ngheader->nvlen; ++i) {
+            nghttp2_nv *nv = &res->ngheader->nv[i];
+            if (nv->namelen == 4 
+                && apr_strnatcasecmp("link", (const char *)nv->name)) {
+                inspect_link(&ctx, (const char *)nv->value, nv->valuelen);
+            }
+        }
+    }
+    
+    return ctx.pushes;
+}
diff --git a/modules/http2/h2_push.h b/modules/http2/h2_push.h
new file mode 100644 (file)
index 0000000..7b586d7
--- /dev/null
@@ -0,0 +1,34 @@
+/* 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 struct h2_ngheader *promise;
+    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..5d301415a88c90ca7324b57ac8029a58d3f9c274 100644 (file)
 
 #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)
 {
     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->headers = apr_table_make(pool, 10);
+    req->content_length = -1;
+    
     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 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("transfer-encoding", name, nlen)) {
+        if (!apr_strnatcasecmp("chunked", value)) {
+            /* This should never arrive here in a HTTP/2 request */
+            ap_log_perror(APLOG_MARK, APLOG_ERR, APR_BADARG, pool,
+                          APLOGNO(02945) 
+                          "h2_request: 'transfer-encoding: chunked' received");
+            return APR_BADARG;
+        }
+    }
+    else if (H2_HD_MATCH_LIT("content-length", name, nlen)) {
+        char *end;
+        req->content_length = apr_strtoi64(value, &end, 10);
+        if (value == end) {
+            ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool,
+                          APLOGNO(02959) 
+                          "h2_request(%d): content-length value not parsed: %s",
+                          req->id, value);
+            return APR_EINVAL;
+        }
+        req->chunked = 0;
+    }
+    else if (H2_HD_MATCH_LIT("content-type", name, nlen)) {
+        /* If we see a content-type and have no length (yet),
+         * we need to chunk. */
+        req->chunked = (req->content_length == -1);
+    }
+    else if ((req->seen_host && H2_HD_MATCH_LIT("host", name, nlen))
+             || 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("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 headers 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)) {
+        req->seen_host = 1;
+    }
+    
+    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;
 
-apr_status_t h2_request_rwrite(h2_request *req, request_rec *r, h2_mplx *m)
+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;
+}
+
+static apr_status_t add_h1_headers(h2_request *req, apr_pool_t *pool, 
+                                   apr_table_t *headers)
+{
+    h1_ctx x;
+    x.req = req;
+    x.pool = pool;
+    apr_table_do(set_h1_header, &x, headers, 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->authority = r->hostname;
-    req->path = r->uri;
+    req->path      = r->uri;
+    req->scheme    = (r->parsed_uri.scheme? r->parsed_uri.scheme
+                      : r->server->server_scheme);
+    
     if (!ap_strchr_c(req->authority, ':') && r->parsed_uri.port_str) {
-        req->authority = apr_psprintf(req->pool, "%s:%s", req->authority,
+        req->authority = apr_psprintf(r->pool, "%s:%s", req->authority,
                                       r->parsed_uri.port_str);
     }
-    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);
-    }
+    status = add_h1_headers(req, r->pool, r->headers_in);
 
     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
-                  "h2_request(%d): written request %s %s, host=%s",
-                  req->id, req->method, req->path, req->authority);
+                  "h2_request(%d): rwrite %s host=%s://%s%s",
+                  req->id, req->method, req->scheme, req->authority, req->path);
     
     return status;
 }
 
-apr_status_t h2_request_write_header(h2_request *req,
-                                     const char *name, size_t nlen,
-                                     const char *value, size_t vlen,
-                                     h2_mplx *m)
+apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, 
+                                   const char *name, size_t nlen,
+                                   const char *value, size_t vlen)
 {
     apr_status_t status = APR_SUCCESS;
     
@@ -90,8 +171,8 @@ apr_status_t h2_request_write_header(h2_request *req,
     
     if (name[0] == ':') {
         /* pseudo header, see ch. 8.1.2.3, always should come first */
-        if (req->to_h1) {
-            ap_log_perror(APLOG_MARK, APLOG_ERR, 0, req->pool,
+        if (!apr_is_empty_table(req->headers)) {
+            ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool,
                           APLOGNO(02917) 
                           "h2_request(%d): pseudo header after request start",
                           req->id);
@@ -100,25 +181,25 @@ apr_status_t h2_request_write_header(h2_request *req,
         
         if (H2_HEADER_METHOD_LEN == nlen
             && !strncmp(H2_HEADER_METHOD, name, nlen)) {
-            req->method = apr_pstrndup(req->pool, value, vlen);
+            req->method = apr_pstrndup(pool, value, vlen);
         }
         else if (H2_HEADER_SCHEME_LEN == nlen
                  && !strncmp(H2_HEADER_SCHEME, name, nlen)) {
-            req->scheme = apr_pstrndup(req->pool, value, vlen);
+            req->scheme = apr_pstrndup(pool, value, vlen);
         }
         else if (H2_HEADER_PATH_LEN == nlen
                  && !strncmp(H2_HEADER_PATH, name, nlen)) {
-            req->path = apr_pstrndup(req->pool, value, vlen);
+            req->path = apr_pstrndup(pool, value, vlen);
         }
         else if (H2_HEADER_AUTH_LEN == nlen
                  && !strncmp(H2_HEADER_AUTH, name, nlen)) {
-            req->authority = apr_pstrndup(req->pool, value, vlen);
+            req->authority = apr_pstrndup(pool, value, vlen);
         }
         else {
             char buffer[32];
             memset(buffer, 0, 32);
             strncpy(buffer, name, (nlen > 31)? 31 : nlen);
-            ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, req->pool,
+            ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, pool,
                           APLOGNO(02954) 
                           "h2_request(%d): ignoring unknown pseudo header %s",
                           req->id, buffer);
@@ -126,65 +207,59 @@ apr_status_t h2_request_write_header(h2_request *req,
     }
     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)
-{
-    return h2_to_h1_add_data(req->to_h1, data, len);
-}
-
-apr_status_t h2_request_end_headers(h2_request *req, struct h2_mplx *m,
-                                    h2_task *task, int eos)
+apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos)
 {
-    apr_status_t status;
+    if (req->eoh) {
+        return APR_EINVAL;
+    }
     
-    if (!req->to_h1) {
-        status = insert_request_line(req, m);
-        if (status != APR_SUCCESS) {
-            return status;
+    if (!req->seen_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);
     }
-    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;
-}
 
-apr_status_t h2_request_close(h2_request *req)
-{
-    return h2_to_h1_close(req->to_h1);
+    if (eos && req->chunked) {
+        /* We had chunking figured out, but the EOS is already there.
+         * unmark chunking and set a definitive content-length.
+         */
+        req->chunked = 0;
+        apr_table_setn(req->headers, "Content-Length", "0");
+    }
+    else if (req->chunked) {
+        /* We have not seen a content-length. We therefore must
+         * pass any request content in chunked form.
+         */
+        apr_table_mergen(req->headers, "Transfer-Encoding", "chunked");
+    }
+    
+    req->eoh = 1;
+    
+    return APR_SUCCESS;
 }
 
-static apr_status_t insert_request_line(h2_request *req, h2_mplx *m)
-{
-    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;
-}
+#define OPT_COPY(p, s)  ((s)? apr_pstrdup(p, s) : NULL)
 
-apr_status_t h2_request_flush(h2_request *req)
+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->method);
+    dst->authority      = OPT_COPY(p, src->method);
+    dst->path           = OPT_COPY(p, src->method);
+    dst->headers        = apr_table_clone(p, src->headers);
+    dst->content_length = src->content_length;
+    dst->chunked        = src->chunked;
+    dst->eoh            = src->eoh;
+    dst->seen_host      = src->seen_host;  
 }
 
index aa5e0bc3c0961ed4036c239fd545345c618b4540..5386848eec1a5625b12010b426186a9ac4ec5320 100644 (file)
@@ -19,9 +19,6 @@
 /* h2_request is the transformer of HTTP2 streams into HTTP/1.1 internal
  * format that will be fed to various httpd input filters to finally
  * become a request_rec to be handled by soemone.
- *
- * Ideally, we would make a request_rec without serializing the headers
- * we have only to make someone else parse them back.
  */
 struct h2_to_h1;
 struct h2_mplx;
@@ -30,38 +27,37 @@ 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_off_t content_length;
+    int chunked;
+    int eoh;
+    
+    int seen_host;
 };
 
-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);
+
 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_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_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_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..ab81b187ff7909093613b8ef1634589a21cfba57 100644 (file)
 #include "h2_private.h"
 #include "h2_h2.h"
 #include "h2_util.h"
+#include "h2_push.h"
 #include "h2_response.h"
 
-static h2_ngheader *make_ngheader(apr_pool_t *pool, const char *status,
-                                  apr_table_t *header);
+static void make_ngheader(apr_pool_t *pool, h2_response *to,
+                          apr_table_t *header);
 
 static int ignore_header(const char *name) 
 {
@@ -43,7 +44,7 @@ static int ignore_header(const char *name)
 
 h2_response *h2_response_create(int stream_id,
                                 int rst_error,
-                                const char *http_status,
+                                int http_status,
                                 apr_array_header_t *hlines,
                                 apr_pool_t *pool)
 {
@@ -56,7 +57,7 @@ h2_response *h2_response_create(int stream_id,
     
     response->stream_id = stream_id;
     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) {
@@ -112,17 +113,17 @@ h2_response *h2_response_rcreate(int stream_id, request_rec *r,
     }
     
     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;
 
-    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;
@@ -141,10 +142,10 @@ h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from)
 {
     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);
+        make_ngheader(pool, to, from->rheader);
     }
     return to;
 }
@@ -156,6 +157,7 @@ typedef struct {
     size_t offset;
     char *strbuf;
     apr_pool_t *pool;
+    apr_array_header_t *pushes;
 } nvctx_t;
 
 static int count_header(void *ctx, const char *key, const char *value)
@@ -171,8 +173,8 @@ static int count_header(void *ctx, const char *key, const char *value)
 #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
+                                    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)
@@ -205,28 +207,54 @@ static void addnv_lit_cs(nvctx_t *ctx, const char *key, size_t key_len,
     ctx->offset++;
 }
 
+static h2_push *h2_parse_preload(apr_pool_t *pool, const char *str)
+{
+    /* TODO: implement this */
+    return NULL;
+}
+
 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);
+        
+        if (apr_strnatcasecmp("link", key) 
+            && ap_strstr_c("preload", value)) {
+            /* Detect link headers with rel=preload to use as possible
+             * server push candidates. */
+            h2_push *push;
+            if ((push = h2_parse_preload(nvctx->pool, value))) {
+                if (!nvctx->pushes) {
+                    nvctx->pushes = apr_array_make(nvctx->pool, 5, 
+                                                   sizeof(h2_push*));
+                }
+                APR_ARRAY_PUSH(nvctx->pushes, h2_push*) = push;
+            }
+        }
     }
     return 1;
 }
 
-static h2_ngheader *make_ngheader(apr_pool_t *pool, const char *status,
-                                  apr_table_t *header)
+static void make_ngheader(apr_pool_t *pool, h2_response *to,
+                          apr_table_t *header)
 {
     size_t n;
     h2_ngheader *h;
     nvctx_t ctx;
+    char *status;
     
+    to->ngheader = NULL;
+    to->pushes   = NULL;
+    
+    status = apr_psprintf(pool, "%d", to->http_status);
     ctx.nv       = NULL;
     ctx.nvlen    = 1;
     ctx.nvstrlen = strlen(status) + 1;
     ctx.offset   = 0;
     ctx.strbuf   = NULL;
     ctx.pool     = pool;
+    ctx.pushes   = NULL;
     
     apr_table_do(count_header, &ctx, header, NULL);
     
@@ -240,9 +268,25 @@ static h2_ngheader *make_ngheader(apr_pool_t *pool, const char *status,
         NV_ADD_LIT_CS(&ctx, ":status", status);
         apr_table_do(add_header, &ctx, header, NULL);
         
-        h->nv = ctx.nv;
+        h->nv    = ctx.nv;
         h->nvlen = ctx.nvlen;
+        
+        to->ngheader = h;
+        to->pushes = ctx.pushes;
     }
-    return h;
 }
 
+int h2_response_push_count(h2_response *response)
+{
+    return response->pushes? response->pushes->nelts : 0;
+}
+
+h2_push *h2_response_get_push(h2_response *response, size_t i)
+{
+    if (response->pushes && i < response->pushes->nelts) {
+        return APR_ARRAY_IDX(response->pushes, i, h2_push*);
+    }
+    return NULL;
+}
+
+
index 64d68cc9c2295547d105aff4d8d98e20744286c3..e6d47f46694b585f6c642e82a600272194256147 100644 (file)
@@ -16,6 +16,8 @@
 #ifndef __mod_h2__h2_response__
 #define __mod_h2__h2_response__
 
+struct h2_push;
+
 /* h2_response is just the data belonging the the head of a HTTP response,
  * suitable prepared to be fed to nghttp2 for response submit. 
  */
@@ -24,18 +26,21 @@ typedef struct h2_ngheader {
     apr_size_t nvlen;
 } h2_ngheader;
 
+struct h2_push;
+
 typedef struct h2_response {
     int stream_id;
     int rst_error;
-    const char *status;
+    int http_status;
     apr_off_t content_length;
     apr_table_t *rheader;
     h2_ngheader *ngheader;
+    apr_array_header_t *pushes;
 } h2_response;
 
 h2_response *h2_response_create(int stream_id,
                                 int rst_error,
-                                const char *http_status,
+                                int http_status,
                                 apr_array_header_t *hlines,
                                 apr_pool_t *pool);
 
@@ -46,4 +51,20 @@ void h2_response_destroy(h2_response *response);
 
 h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from);
 
+/**
+ * Get the number of push proposals included with the response.
+ * @return number of push proposals in this response
+ */
+int h2_response_push_count(h2_response *response);
+
+/**
+ * Get the ith h2_push contained in this response.
+ * 
+ * @param response the response
+ * @param i the index of the push to get
+ * @return the ith h2_push or NULL if out of bounds
+ */
+struct h2_push *h2_response_get_push(h2_response *response, size_t i);
+
+
 #endif /* defined(__mod_h2__h2_response__) */
index 2e93a66cc518607051bb204e285343a941cf63f2..0d11097859875d7eb0902b3ed0665fe1bcc8a0b2 100644 (file)
@@ -29,6 +29,7 @@
 #include "h2_config.h"
 #include "h2_h2.h"
 #include "h2_mplx.h"
+#include "h2_push.h"
 #include "h2_response.h"
 #include "h2_stream.h"
 #include "h2_stream_set.h"
@@ -55,12 +56,12 @@ static int h2_session_status_from_apr_status(apr_status_t rv)
     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;
     }
     
     if (session->spare) {
@@ -71,11 +72,11 @@ static int stream_open(h2_session *session, int stream_id)
         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;
     }
     
@@ -83,7 +84,7 @@ static int stream_open(h2_session *session, int stream_id)
                   "h2_session: stream(%ld-%d): opened",
                   session->id, stream_id);
     
-    return 0;
+    return stream;
 }
 
 static apr_status_t h2_session_flush(h2_session *session) 
@@ -356,16 +357,12 @@ static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id,
 static int on_begin_headers_cb(nghttp2_session *ngh2,
                                const nghttp2_frame *frame, void *userp)
 {
+    h2_stream *s;
+    
     /* This starts a new stream. */
-    int rv;
     (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;
-    }
-    return NGHTTP2_ERR_CALLBACK_FAILURE;
+    s = h2_session_open_stream((h2_session *)userp, frame->hd.stream_id);
+    return s? 0 : NGHTTP2_ERR_CALLBACK_FAILURE;
 }
 
 static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame,
@@ -383,6 +380,7 @@ static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame,
     if (session->aborted) {
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
+    
     stream = h2_session_get_stream(session, frame->hd.stream_id);
     if (!stream) {
         ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
@@ -392,9 +390,9 @@ static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame,
         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;
     }
@@ -410,64 +408,45 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
                             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,
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
                   "h2_session(%ld): on_frame_rcv #%ld, type=%d", session->id,
                   (long)session->frames_received, frame->hd.type);
+
+    ++session->frames_received;
     switch (frame->hd.type) {
-        case NGHTTP2_HEADERS: {
-            int eos;
-            h2_stream * stream = h2_session_get_stream(session,
-                                                       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;
-                }
-                return 0;
+        case NGHTTP2_HEADERS:
+            stream = h2_session_get_stream(session, frame->hd.stream_id);
+            if (stream) {
+                int eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
+                status = stream_end_headers(session, stream, eos);
+            }
+            else {
+                status = APR_EINVAL;
             }
-
-            eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
-            status = stream_end_headers(session, stream, eos);
-
             break;
-        }
-        case NGHTTP2_DATA: {
-            h2_stream * stream = h2_session_get_stream(session,
-                                                       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);
+                if (eos) {
+                    status = h2_stream_close_input(stream);
                 }
-                return 0;
+            }
+            else {
+                status = APR_EINVAL;
             }
             break;
-        }
-        case NGHTTP2_PRIORITY: {
+        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,
@@ -475,15 +454,13 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
                           frame->priority.pri_spec.stream_id,
                           frame->priority.pri_spec.exclusive);
             break;
-        }
-        case NGHTTP2_WINDOW_UPDATE: {
+        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];
@@ -496,23 +473,10 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
             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_session_get_stream(session,
-                                                   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);
@@ -522,7 +486,6 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
         if (nghttp2_is_fatal(rv)) {
             return NGHTTP2_ERR_CALLBACK_FAILURE;
         }
-        return 0;
     }
     
     return 0;
@@ -911,8 +874,8 @@ apr_status_t h2_session_start(h2_session *session, int *rv)
         }
         
         /* 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", 
@@ -920,15 +883,7 @@ apr_status_t h2_session_start(h2_session *session, int *rv)
             return status;
         }
         
-        stream = h2_session_get_stream(session, 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;
         }
@@ -1011,8 +966,10 @@ static void update_window(void *ctx, int stream_id, apr_off_t bytes_read)
 
 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
@@ -1173,23 +1130,30 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream)
         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, stream->id, response->status);
+                      "h2_stream(%ld-%d): submitting response %d",
+                      session->id, stream->id, response->http_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));
+                                     
+        if (!rv 
+            && !stream->promised_on
+            && 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_TRACE1, 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, err);
     }
     
     stream->submitted = 1;
@@ -1205,11 +1169,62 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream)
     return status;
 }
 
+struct h2_stream *h2_session_push(h2_session *session, h2_push *push)
+{
+    apr_status_t status;
+    h2_stream *stream;
+    int nid;
+    
+    nid = nghttp2_submit_push_promise(session->ngh2, 0, push->initiating_id, 
+                                      push->promise->nv, push->promise->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_INFO, 0, session->c,
+                  "h2_stream(%ld-%d): promised new stream %d",
+                  session->id, push->initiating_id, nid);
+                  
+    stream = h2_session_open_stream(session, nid);
+    if (stream) {
+        h2_stream_set_h2_request(stream, push->req);
+        status = stream_end_headers(session, stream, 1);
+        if (status != APR_SUCCESS) {
+            ap_log_cerror(APLOG_MARK, APLOG_WARNING, 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_WARNING, 0, session->c,
+                      "h2_stream(%ld-%d): failed to create stream obj %d",
+                      session->id, push->initiating_id, nid);
+    }
+
+    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;
+}
+
 apr_status_t h2_session_stream_destroy(h2_session *session, h2_stream *stream)
 {
     apr_pool_t *pool = h2_stream_detach_pool(stream);
 
     h2_mplx_stream_done(session->mplx, stream->id, stream->rst_error);
+    if (session->last_stream == stream) {
+        session->last_stream = NULL;
+    }
     h2_stream_set_remove(session->streams, stream->id);
     h2_stream_destroy(stream);
     
@@ -1300,6 +1315,13 @@ static int frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
     }
 }
 
+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;
index b6a9beae5d36f3aa797c8da16489be2db06562ad..c110b8c2145b6cea3ea44d07c9f0f7023c46582d 100644 (file)
@@ -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;
@@ -72,6 +73,7 @@ struct h2_session {
     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 */
@@ -159,6 +161,21 @@ apr_status_t h2_session_handle_response(h2_session *session,
 /* Get the h2_stream for the given stream idenrtifier. */
 struct h2_stream *h2_session_get_stream(h2_session *session, int stream_id);
 
+/**
+ * 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
@@ -167,4 +184,15 @@ struct h2_stream *h2_session_get_stream(h2_session *session, int stream_id);
 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 stream the stream on which the push depends
+ * @param push the push to promise
+ * @return the new promised stream or NULL
+ */
+struct h2_stream *h2_session_push(h2_session *session, struct h2_push *push);
+
 #endif /* defined(__mod_h2__h2_session__) */
index 266a9e24fb81e8a7c08c220e51aa279a95d3e750..7ef28255feccd0ab7788eed09d38d77fa437bf3f 100644 (file)
@@ -25,7 +25,9 @@
 
 #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_util.h"
 
 
-static void set_state(h2_stream *stream, h2_stream_state_t state)
+static int state_transition[][7] = {
+    /*  ID OP RL RR CI CO CL */
+/*ID*/{  1, 0, 0, 0, 0, 0, 0 },
+/*OP*/{  1, 1, 0, 0, 0, 0, 0 },
+/*RL*/{  0, 0, 1, 0, 0, 0, 0 },
+/*RR*/{  0, 0, 0, 1, 0, 0, 0 },
+/*CI*/{  1, 1, 0, 0, 1, 0, 0 },
+/*CO*/{  1, 1, 0, 0, 0, 1, 0 },
+/*CL*/{  1, 1, 0, 0, 1, 1, 1 },
+};
+
+static int set_state(h2_stream *stream, h2_stream_state_t state)
 {
-    AP_DEBUG_ASSERT(stream);
-    if (stream->state != state) {
+    int allowed = state_transition[state][stream->state];
+    if (allowed) {
         stream->state = state;
+        return 1;
+    }
+    
+    ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c,
+                  "h2_stream(%ld-%d): invalid state transition from %d to %d", 
+                  stream->session->id, stream->id, stream->state, state);
+    return 0;
+}
+
+static int close_input(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;
+}
+
+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;
     }
 }
 
 h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session)
 {
     h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));
-    if (stream != NULL) {
-        stream->id = id;
-        stream->state = H2_STREAM_ST_IDLE;
-        stream->pool = pool;
-        stream->session = session;
-        stream->bbout = apr_brigade_create(stream->pool, 
+    stream->id        = id;
+    stream->state     = H2_STREAM_ST_IDLE;
+    stream->pool      = pool;
+    stream->session   = session;
+    return stream;
+}
+
+h2_stream *h2_stream_open(int id, apr_pool_t *pool, h2_session *session)
+{
+    h2_stream *stream = h2_stream_create(id, pool, session);
+    set_state(stream, H2_STREAM_ST_OPEN);
+    stream->request   = h2_request_create(id, pool);
+    stream->bbout     = apr_brigade_create(stream->pool, 
                                            stream->session->c->bucket_alloc);
-        stream->request = h2_request_create(id, pool, session->c->bucket_alloc);
-        
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
-                      "h2_stream(%ld-%d): created", session->id, stream->id);
-    }
+    
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+                  "h2_stream(%ld-%d): opened", session->id, stream->id);
     return stream;
 }
 
@@ -96,6 +189,8 @@ apr_pool_t *h2_stream_detach_pool(h2_stream *stream)
 void h2_stream_rst(h2_stream *stream, int error_code)
 {
     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);
@@ -105,6 +200,9 @@ 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)) {
+        return APR_ECONNRESET;
+    }
     
     stream->response = response;
     if (bb && !APR_BRIGADE_EMPTY(bb)) {
@@ -120,32 +218,14 @@ apr_status_t h2_stream_set_response(h2_stream *stream, h2_response *response,
         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,
+                      "h2_stream(%ld-%d): set_response(%d), len=%ld, eos=%d", 
+                      stream->session->id, stream->id, response->http_status,
                       (long)len, (int)eos);
     }
     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)
 {
     apr_status_t status;
     AP_DEBUG_ASSERT(stream);
@@ -153,27 +233,58 @@ apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r)
         return APR_ECONNRESET;
     }
     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;
 }
 
+void h2_stream_set_h2_request(h2_stream *stream, const h2_request *req)
+{
+    h2_request_copy(stream->pool, stream->request, req);
+}
+
+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 (!input_open(stream)) {
+        return APR_ECONNRESET;
+    }
+    return h2_request_add_header(stream->request, stream->pool,
+                                 name, nlen, value, vlen);
+}
+
 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) {
+    if (!output_open(stream)) {
         return APR_ECONNRESET;
     }
+    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_request_end_headers(stream->request, stream->pool, eos);
+    if (status == APR_SUCCESS) {
+        if (!eos) {
+            stream->bbin = apr_brigade_create(stream->pool, 
+                                              stream->session->c->bucket_alloc);
+        }
+        stream->input_remaining = stream->request->content_length;
+        
+        status = h2_mplx_process(stream->session->mplx, stream->id, 
+                                 stream->request, eos, cmp, ctx);
+    }
+    else {
+        h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
     }
+    
     ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c,
                   "h2_mplx(%ld-%d): start stream, task %s %s (%s)",
                   stream->session->id, stream->id,
@@ -183,56 +294,110 @@ apr_status_t h2_stream_schedule(h2_stream *stream, int eos,
     return status;
 }
 
-apr_status_t h2_stream_write_eos(h2_stream *stream)
+static apr_status_t h2_stream_input_flush(h2_stream *stream)
 {
-    AP_DEBUG_ASSERT(stream);
-    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;
+    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->mplx->id, stream->id);
+        }
     }
-    if (set_closed(stream)) {
-        return h2_request_close(stream->request);
+    return status;
+}
+
+static apr_status_t input_flush(apr_bucket_brigade *bb, void *ctx) 
+{
+    (void)bb;
+    return h2_stream_input_flush(ctx);
+}
+
+static apr_status_t input_add_data(h2_stream *stream,
+                                   const char *data, size_t len)
+{
+    apr_status_t status = APR_SUCCESS;
+
+    status = apr_brigade_write(stream->bbin, input_flush, stream, data, len);
+    if (status == APR_SUCCESS) {
+        status = h2_stream_input_flush(stream);
     }
-    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);
+    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;
+    if (close_input(stream) && stream->bbin) {
+        if (stream->request->chunked) {
+            status = input_add_data(stream, "0\r\n\r\n", 5);
+        }
+        
+        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);
+    return status;
 }
 
 apr_status_t h2_stream_write_data(h2_stream *stream,
                                   const char *data, size_t len)
 {
+    apr_status_t status;
+    
     AP_DEBUG_ASSERT(stream);
-    if (stream->rst_error) {
-        return APR_ECONNRESET;
+    if (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);
+
+    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 = apr_brigade_printf(stream->bbin, NULL, NULL,
+                                    "%lx\r\n", (unsigned long)len);
+        if (status == APR_SUCCESS) {
+            status = input_add_data(stream, data, len);
+            if (status == APR_SUCCESS) {
+                status = apr_brigade_puts(stream->bbin, NULL, NULL, "\r\n");
+            }
+        }
+        return status;
+    }
+    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;
+        }
+        return input_add_data(stream, data, len);
     }
-    return h2_request_write_data(stream->request, data, len);
 }
 
 apr_status_t h2_stream_prep_read(h2_stream *stream, 
@@ -356,13 +521,7 @@ int h2_stream_is_suspended(h2_stream *stream)
 
 int h2_stream_input_is_open(h2_stream *stream) 
 {
-    switch (stream->state) {
-        case H2_STREAM_ST_OPEN:
-        case H2_STREAM_ST_CLOSED_OUTPUT:
-            return 1;
-        default:
-            return 0;
-    }
+    return input_open(stream);
 }
 
 int h2_stream_needs_submit(h2_stream *stream)
@@ -378,3 +537,22 @@ int h2_stream_needs_submit(h2_stream *stream)
     }
 }
 
+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)) {
+        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, push);
+            if (!s) {
+                status = APR_ECONNRESET;
+                break;
+            }
+        }
+    }
+    return status;
+}
index a36733fa189cf5c7a2b2182e7db42b1950107de4..9484f649336180c3842ab0f12079b1fe06cad1d0 100644 (file)
 /**
  * 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,18 +50,22 @@ typedef struct h2_stream h2_stream;
 
 struct h2_stream {
     int id;                     /* http2 stream id */
+    int promised_on;            /* http2 stream this was a promise on, or 0 */
     h2_stream_state_t state;    /* http/2 state of this stream */
     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 req_eoh;                /* request HEADERs have been received */
     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 */
     
     apr_bucket_brigade *bbout;  /* output DATA */
     apr_off_t data_frames_sent; /* # of DATA frames sent out for this stream */
@@ -75,47 +74,211 @@ struct h2_stream {
 
 #define H2_STREAM_RST(s, def)    (s->rst_error? s->rst_error : (def))
 
+/**
+ * Create a stream in IDLE state.
+ * @param id      the stream identifier
+ * @param pool    the memory pool to use for this stream
+ * @param session the session this stream belongs to
+ * @return the newly created IDLE stream
+ */
 h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_session *session);
 
+/**
+ * Create a stream in OPEN state.
+ * @param id      the stream identifier
+ * @param pool    the memory pool to use for this stream
+ * @param session the session this stream belongs to
+ * @return the newly opened stream
+ */
+h2_stream *h2_stream_open(int id, apr_pool_t *pool, struct h2_session *session);
+
+/**
+ * Destroy any resources held by this stream. Will destroy memory pool
+ * if still owned by the stream.
+ *
+ * @param stream the stream to destroy
+ */
 apr_status_t h2_stream_destroy(h2_stream *stream);
 
+/**
+ * Removes stream from h2_session and destroys it.
+ *
+ * @param stream the stream to cleanup
+ */
 void h2_stream_cleanup(h2_stream *stream);
 
-void h2_stream_rst(h2_stream *streamm, int error_code);
-
+/**
+ * Detach the memory pool from the stream. Will prevent stream
+ * destruction to take the pool with it.
+ *
+ * @param stream the stream to detach the pool from
+ * @param the detached memmory pool or NULL if stream no longer has one
+ */
 apr_pool_t *h2_stream_detach_pool(h2_stream *stream);
 
-apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r);
 
-apr_status_t h2_stream_write_eos(h2_stream *stream);
+/**
+ * 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);
+
+/**
+ * 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, const struct h2_request *req);
 
-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) to the given stream.
+ *
+ * @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_schedule(h2_stream *stream, int eos,
-                                h2_stream_pri_cmp *cmp, void *ctx);
+/**
+ * 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);
+
+/**
+ * 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_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_off_t *plen, int *peos);
 
+/**
+ * Read a maximum number of bytes into the bucket brigade.
+ * 
+ * @param stream the stream to read from
+ * @param bb the brigade to append output to
+ * @param plen (in-/out) max. number of bytes to append and on return actual
+ *        number of bytes appended to brigade
+ * @param peos (out) != 0 iff end of stream has been reached while reading
+ * @return APR_SUCCESS if out information was computed successfully.
+ *         APR_EAGAIN if not data is available and end of stream has not been
+ *         reached yet.
+ */
 apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb, 
                                apr_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 17c75f08222b0aacf298d57c323d8688ca36b7fd..6b8832b4724751c87b8953b8ef37bf663e6c16c2 100644 (file)
@@ -32,10 +32,7 @@ struct h2_stream_set {
 
 static unsigned int stream_hash(const char *key, apr_ssize_t *klen)
 {
-    /* we use the "int stream_id" has key, which always odd from
-    * client and even from server. As long as we do not mix them
-    * in one set, snip off the lsb. */
-    return (unsigned int)(*((int*)key)) >> 1;
+    return (unsigned int)(*((int*)key));
 }
 
 h2_stream_set *h2_stream_set_create(apr_pool_t *pool, int max)
index f0f024a0dcc360fba5d8fb7174f06e3614ff8218..6995ba6693b69b71ec1cf43d1faf7aa913f6edfb 100644 (file)
@@ -38,6 +38,7 @@
 #include "h2_from_h1.h"
 #include "h2_h2.h"
 #include "h2_mplx.h"
+#include "h2_request.h"
 #include "h2_session.h"
 #include "h2_stream.h"
 #include "h2_task_input.h"
@@ -152,46 +153,30 @@ static int h2_task_process_conn(conn_rec* c)
 }
 
 
-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,
+                        conn_rec *c, 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;
     }
     
-    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->mplx = mplx;
-    
     task->c = c;
+
+    task->request = req;
+    task->input_eos = eos;    
     
-    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;
@@ -216,8 +201,7 @@ apr_status_t h2_task_do(h2_task *task, h2_worker *worker)
     if (status == APR_SUCCESS) {
         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);
+        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);
@@ -270,7 +254,7 @@ static request_rec *h2_task_create_request(h2_task *task)
     
     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, 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);
@@ -309,19 +293,19 @@ static request_rec *h2_task_create_request(h2_task *task)
     
     /* Time to populate r with the data we have. */
     r->request_time = apr_time_now();
-    r->method = task->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, task->request->path);
     r->protocol = (char*)"HTTP/2";
     r->proto_num = HTTP_VERSION(2, 0);
 
     r->the_request = apr_psprintf(r->pool, "%s %s %s", 
-                                  r->method, task->path, r->protocol);
+                                  r->method, task->request->path, r->protocol);
     
     /* update what we think the virtual host is based on the headers we've
      * now read. may update status.
index 1877a3920b5c56a6fe6748f0cc92ace0e9a449c2..d36a81308172948ae09ba7418daa541f376df949 100644 (file)
@@ -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,11 +50,7 @@ struct h2_task {
     int stream_id;
     struct h2_mplx *mplx;
     
-    const char *method;
-    const char *scheme;
-    const char *authority;
-    const char *path;
-    apr_table_t *headers;
+    const struct h2_request *request;
     int input_eos;
 
     int serialize_headers;
@@ -68,20 +65,12 @@ struct h2_task {
     struct apr_thread_cond_t *io;   /* used to wait for events on */
 };
 
-h2_task *h2_task_create(long session_id, int stream_id
+h2_task *h2_task_create(long session_id, const struct h2_request *req
                         apr_pool_t *pool, struct h2_mplx *mplx,
-                        conn_rec *c);
+                        conn_rec *c, 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);
 
 void h2_task_register_hooks(void);
index 1eac749f42a9fd6d6ab87ebd8c52ba701881bff5..86334ea6227d7f963657c0813d3b9e331f48b879 100644 (file)
@@ -23,6 +23,7 @@
 #include "h2_private.h"
 #include "h2_conn.h"
 #include "h2_mplx.h"
+#include "h2_request.h"
 #include "h2_session.h"
 #include "h2_stream.h"
 #include "h2_task_input.h"
@@ -53,11 +54,11 @@ h2_task_input *h2_task_input_create(h2_task *task, apr_pool_t *pool,
         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);
+                          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);
+                               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->task->input_eos) {
                 APR_BRIGADE_INSERT_TAIL(input->bb, apr_bucket_eos_create(bucket_alloc));
index 053e2d69e4ea6a5f43ee804796979524c245116b..06a5d7aafbbcbed813733348591f6f7d388271ea 100644 (file)
@@ -24,6 +24,7 @@
 #include "h2_private.h"
 #include "h2_conn.h"
 #include "h2_mplx.h"
+#include "h2_request.h"
 #include "h2_session.h"
 #include "h2_stream.h"
 #include "h2_from_h1.h"
 #include "h2_util.h"
 
 
-h2_task_output *h2_task_output_create(h2_task *task, 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->task = task;
         output->state = H2_TASK_OUT_INIT;
@@ -73,8 +72,9 @@ static apr_status_t open_if_needed(h2_task_output *output, ap_filter_t *f,
                 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->task->id, output->task->request->method, 
+                              output->task->request->authority, 
+                              output->task->request->path);
                 f->c->aborted = 1;
             }
             if (output->task->io) {
index 79cb6816c7aac31a422d16f2fe6828fd8cce3bae..a326c49096bdc7eb922cb69c840b2f4b1325a245 100644 (file)
@@ -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,
-                                      apr_bucket_alloc_t *bucket_alloc);
+h2_task_output *h2_task_output_create(struct h2_task *task, apr_pool_t *pool);
 
 void h2_task_output_destroy(h2_task_output *output);
 
diff --git a/modules/http2/h2_to_h1.c b/modules/http2/h2_to_h1.c
deleted file mode 100644 (file)
index 159fde3..0000000
+++ /dev/null
@@ -1,290 +0,0 @@
-/* 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 <httpd.h>
-#include <http_core.h>
-#include <http_log.h>
-#include <http_connection.h>
-
-#include "h2_private.h"
-#include "h2_mplx.h"
-#include "h2_response.h"
-#include "h2_task.h"
-#include "h2_to_h1.h"
-#include "h2_util.h"
-
-
-h2_to_h1 *h2_to_h1_create(int stream_id, apr_pool_t *pool, 
-                          apr_bucket_alloc_t *bucket_alloc, 
-                          const char *method, 
-                          const char *scheme, 
-                          const char *authority, 
-                          const char *path,
-                          struct h2_mplx *m)
-{
-    h2_to_h1 *to_h1;
-    if (!method) {
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
-                      APLOGNO(02943) 
-                      "h2_to_h1: header start but :method missing");
-        return NULL;
-    }
-    if (!path) {
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
-                      APLOGNO(02944) 
-                      "h2_to_h1: header start but :path missing");
-        return NULL;
-    }
-    
-    to_h1 = apr_pcalloc(pool, sizeof(h2_to_h1));
-    if (to_h1) {
-        to_h1->stream_id = stream_id;
-        to_h1->pool = pool;
-        to_h1->method = method;
-        to_h1->scheme = scheme;
-        to_h1->authority = authority;
-        to_h1->path = path;
-        to_h1->m = m;
-        to_h1->headers = apr_table_make(to_h1->pool, 10);
-        to_h1->bb = apr_brigade_create(pool, bucket_alloc);
-        to_h1->chunked = 0; /* until we see a content-type and no length */
-        to_h1->content_len = -1;
-    }
-    return to_h1;
-}
-
-void h2_to_h1_destroy(h2_to_h1 *to_h1)
-{
-    to_h1->bb = NULL;
-}
-
-apr_status_t h2_to_h1_add_header(h2_to_h1 *to_h1,
-                                 const char *name, size_t nlen,
-                                 const char *value, size_t vlen)
-{
-    char *hname, *hvalue;
-    if (H2_HD_MATCH_LIT("transfer-encoding", name, nlen)) {
-        if (!apr_strnatcasecmp("chunked", value)) {
-            /* This should never arrive here in a HTTP/2 request */
-            ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_BADARG, to_h1->m->c,
-                          APLOGNO(02945) 
-                          "h2_to_h1: 'transfer-encoding: chunked' received");
-            return APR_BADARG;
-        }
-    }
-    else if (H2_HD_MATCH_LIT("content-length", name, nlen)) {
-        char *end;
-        to_h1->content_len = apr_strtoi64(value, &end, 10);
-        if (value == end) {
-            ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, to_h1->m->c,
-                          APLOGNO(02959) 
-                          "h2_request(%d): content-length value not parsed: %s",
-                          to_h1->stream_id, value);
-            return APR_EINVAL;
-        }
-        to_h1->remain_len = to_h1->content_len;
-        to_h1->chunked = 0;
-    }
-    else if (H2_HD_MATCH_LIT("content-type", name, nlen)) {
-        /* If we see a content-type and have no length (yet),
-         * we need to chunk. */
-        to_h1->chunked = (to_h1->content_len == -1);
-    }
-    else if ((to_h1->seen_host && H2_HD_MATCH_LIT("host", name, nlen))
-             || 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("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(to_h1->headers, "cookie");
-        if (existing) {
-            char *nval;
-            
-            /* Cookie headers come separately in HTTP/2, but need
-             * to be merged by "; " (instead of default ", ")
-             */
-            hvalue = apr_pstrndup(to_h1->pool, value, vlen);
-            nval = apr_psprintf(to_h1->pool, "%s; %s", existing, hvalue);
-            apr_table_setn(to_h1->headers, "Cookie", nval);
-            return APR_SUCCESS;
-        }
-    }
-    else if (H2_HD_MATCH_LIT("host", name, nlen)) {
-        to_h1->seen_host = 1;
-    }
-    
-    hname = apr_pstrndup(to_h1->pool, name, nlen);
-    hvalue = apr_pstrndup(to_h1->pool, value, vlen);
-    h2_util_camel_case_header(hname, nlen);
-    apr_table_mergen(to_h1->headers, hname, hvalue);
-    
-    return APR_SUCCESS;
-}
-
-static int set_header(void *ctx, const char *key, const char *value)
-{
-    h2_to_h1 *to_h1 = (h2_to_h1*)ctx;
-    h2_to_h1_add_header(to_h1, key, strlen(key), value, strlen(value));
-    return 1;
-}
-
-apr_status_t h2_to_h1_add_headers(h2_to_h1 *to_h1, apr_table_t *headers)
-{
-    apr_table_do(set_header, to_h1, headers, NULL);
-    return APR_SUCCESS;
-}
-
-apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, int eos)
-{
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, to_h1->m->c,
-                  "h2_to_h1(%ld-%d): end headers", 
-                  to_h1->m->id, to_h1->stream_id);
-    
-    if (to_h1->eoh) {
-        return APR_EINVAL;
-    }
-    
-    if (!to_h1->seen_host) {
-        /* Need to add a "Host" header if not already there to
-         * make virtual hosts work correctly. */
-        if (!to_h1->authority) {
-            return APR_BADARG;
-        }
-        apr_table_set(to_h1->headers, "Host", to_h1->authority);
-    }
-
-    if (eos && to_h1->chunked) {
-        /* We had chunking figured out, but the EOS is already there.
-         * unmark chunking and set a definitive content-length.
-         */
-        to_h1->chunked = 0;
-        apr_table_setn(to_h1->headers, "Content-Length", "0");
-    }
-    else if (to_h1->chunked) {
-        /* We have not seen a content-length. We therefore must
-         * pass any request content in chunked form.
-         */
-        apr_table_mergen(to_h1->headers, "Transfer-Encoding", "chunked");
-    }
-    
-    to_h1->eoh = 1;
-    
-    return APR_SUCCESS;
-}
-
-static apr_status_t flush(apr_bucket_brigade *bb, void *ctx) 
-{
-    (void)bb;
-    return h2_to_h1_flush((h2_to_h1*)ctx);
-}
-
-static apr_status_t h2_to_h1_add_data_raw(h2_to_h1 *to_h1,
-                                          const char *data, size_t len)
-{
-    apr_status_t status = APR_SUCCESS;
-
-    if (to_h1->eos || !to_h1->eoh) {
-        return APR_EINVAL;
-    }
-    
-    status = apr_brigade_write(to_h1->bb, flush, to_h1, data, len);
-    if (status == APR_SUCCESS) {
-        status = h2_to_h1_flush(to_h1);
-    }
-    return status;
-}
-
-
-apr_status_t h2_to_h1_add_data(h2_to_h1 *to_h1,
-                               const char *data, size_t len)
-{
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, to_h1->m->c,
-                  "h2_to_h1(%ld-%d): add %ld data bytes", 
-                  to_h1->m->id, to_h1->stream_id, (long)len);
-    
-    if (to_h1->chunked) {
-        /* if input may have a body and we have not seen any
-         * content-length header, we need to chunk the input data.
-         */
-        apr_status_t status = apr_brigade_printf(to_h1->bb, NULL, NULL,
-                                                 "%lx\r\n", (unsigned long)len);
-        if (status == APR_SUCCESS) {
-            status = h2_to_h1_add_data_raw(to_h1, data, len);
-            if (status == APR_SUCCESS) {
-                status = apr_brigade_puts(to_h1->bb, NULL, NULL, "\r\n");
-            }
-        }
-        return status;
-    }
-    else {
-        to_h1->remain_len -= len;
-        if (to_h1->remain_len < 0) {
-            ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, to_h1->m->c,
-                          APLOGNO(02961) 
-                          "h2_to_h1(%ld-%d): got %ld more content bytes than announced "
-                          "in content-length header: %ld", 
-                          to_h1->m->id, to_h1->stream_id, 
-                          (long)to_h1->content_len, -(long)to_h1->remain_len);
-        }
-        return h2_to_h1_add_data_raw(to_h1, data, len);
-    }
-}
-
-apr_status_t h2_to_h1_flush(h2_to_h1 *to_h1)
-{
-    apr_status_t status = APR_SUCCESS;
-    if (!APR_BRIGADE_EMPTY(to_h1->bb)) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, to_h1->m->c,
-                      "h2_to_h1(%ld-%d): flush request bytes", 
-                      to_h1->m->id, to_h1->stream_id);
-        
-        status = h2_mplx_in_write(to_h1->m, to_h1->stream_id, to_h1->bb);
-        if (status != APR_SUCCESS) {
-            ap_log_cerror(APLOG_MARK, APLOG_ERR, status, to_h1->m->c,
-                          APLOGNO(02946) "h2_request(%d): pushing request data",
-                          to_h1->stream_id);
-        }
-    }
-    return status;
-}
-
-apr_status_t h2_to_h1_close(h2_to_h1 *to_h1)
-{
-    apr_status_t status = APR_SUCCESS;
-    if (!to_h1->eos) {
-        if (to_h1->chunked) {
-            status = h2_to_h1_add_data_raw(to_h1, "0\r\n\r\n", 5);
-        }
-        to_h1->eos = 1;
-        status = h2_to_h1_flush(to_h1);
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, to_h1->m->c,
-                      "h2_to_h1(%d): close", to_h1->stream_id);
-        
-        status = h2_mplx_in_close(to_h1->m, to_h1->stream_id);
-    }
-    return status;
-}
-
-
diff --git a/modules/http2/h2_to_h1.h b/modules/http2/h2_to_h1.h
deleted file mode 100644 (file)
index 6fc06fb..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-/* 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_to_h1__
-#define __mod_h2__h2_to_h1__
-
-struct h2_mplx;
-struct h2_task;
-typedef struct h2_to_h1 h2_to_h1;
-
-struct h2_to_h1 {
-    int stream_id;
-    apr_pool_t *pool;
-    h2_mplx *m;
-
-    const char *method;
-    const char *scheme;
-    const char *authority;
-    const char *path;
-    
-    int chunked;
-    int eoh;
-    int eos;
-    int flushed;
-    int seen_host;
-    
-    apr_off_t content_len;
-    apr_off_t remain_len;
-    apr_table_t *headers;
-    apr_bucket_brigade *bb;
-};
-
-/* Create a converter from a HTTP/2 request to a serialzation in
- * HTTP/1.1 format. The serialized data will be written onto the
- * given h2_mplx instance.
- */
-h2_to_h1 *h2_to_h1_create(int stream_id, apr_pool_t *pool, 
-                          apr_bucket_alloc_t *bucket_alloc, 
-                          const char *method, 
-                          const char *scheme, 
-                          const char *authority, 
-                          const char *path,
-                          struct h2_mplx *m);
-
-/* Destroy the converter and free resources. */
-void h2_to_h1_destroy(h2_to_h1 *to_h1);
-
-/* Add a header to the serialization. Only valid to call after start
- * and before end_headers.
- */
-apr_status_t h2_to_h1_add_header(h2_to_h1 *to_h1,
-                                 const char *name, size_t nlen,
-                                 const char *value, size_t vlen);
-
-apr_status_t h2_to_h1_add_headers(h2_to_h1 *to_h1, apr_table_t *headers);
-
-/** End the request headers.
- */
-apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, int eos);
-
-/* Add request body data.
- */
-apr_status_t h2_to_h1_add_data(h2_to_h1 *to_h1,
-                               const char *data, size_t len);
-
-/* Flush the converted data onto the h2_mplx instance.
- */
-apr_status_t h2_to_h1_flush(h2_to_h1 *to_h1);
-
-/* Close the request, flushed automatically.
- */
-apr_status_t h2_to_h1_close(h2_to_h1 *to_h1);
-
-#endif /* defined(__mod_h2__h2_to_h1__) */
index daada0f92bcc669cf0f8f46de74f10184f602d0b..98a431b3bf1b0706418f005a8571d68e21879e45 100644 (file)
@@ -20,7 +20,7 @@
  * @macro
  * Version number of the h2 module as c string
  */
-#define MOD_HTTP2_VERSION "1.0.4-DEV"
+#define MOD_HTTP2_VERSION "1.0.5-DEV"
 
 /**
  * @macro
@@ -28,7 +28,7 @@
  * release. This is a 24 bit number with 8 bits for major number, 8 bits
  * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
  */
-#define MOD_HTTP2_VERSION_NUM 0x010004
+#define MOD_HTTP2_VERSION_NUM 0x010005
 
 
 #endif /* mod_h2_h2_version_h */
index fbff711df77317b872a8e475987ee30505963a8b..12a3abfe98c096fb3203156ec4a542cc04c8d630 100644 (file)
@@ -149,6 +149,10 @@ SOURCE=./h2_mplx.c
 # 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 +193,6 @@ SOURCE=./h2_task_queue.c
 # 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