[nginx] HTTP/2: push additional request headers (closes #1478).

Ruslan Ermilov ru at nginx.com
Thu Feb 15 14:55:01 UTC 2018


details:   http://hg.nginx.org/nginx/rev/3d2b0b02bd3d
branches:  
changeset: 7207:3d2b0b02bd3d
user:      Ruslan Ermilov <ru at nginx.com>
date:      Thu Feb 15 17:51:32 2018 +0300
description:
HTTP/2: push additional request headers (closes #1478).

The Accept-Encoding, Accept-Language, and User-Agent header fields
are now copied from the original request to pushed requests.

diffstat:

 auto/modules                            |    1 +
 src/http/v2/ngx_http_v2.c               |  162 ++++++++++++++++++++-----------
 src/http/v2/ngx_http_v2.h               |    5 +-
 src/http/v2/ngx_http_v2_filter_module.c |  155 ++++++++++++++++++++++-------
 4 files changed, 222 insertions(+), 101 deletions(-)

diffs (560 lines):

diff -r 33edea74bd58 -r 3d2b0b02bd3d auto/modules
--- a/auto/modules	Thu Feb 15 17:51:26 2018 +0300
+++ b/auto/modules	Thu Feb 15 17:51:32 2018 +0300
@@ -420,6 +420,7 @@ if [ $HTTP = YES ]; then
 
     if [ $HTTP_V2 = YES ]; then
         have=NGX_HTTP_V2 . auto/have
+        have=NGX_HTTP_HEADERS . auto/have
 
         ngx_module_name=ngx_http_v2_module
         ngx_module_incs=src/http/v2
diff -r 33edea74bd58 -r 3d2b0b02bd3d src/http/v2/ngx_http_v2.c
--- a/src/http/v2/ngx_http_v2.c	Thu Feb 15 17:51:26 2018 +0300
+++ b/src/http/v2/ngx_http_v2.c	Thu Feb 15 17:51:32 2018 +0300
@@ -11,6 +11,14 @@
 #include <ngx_http_v2_module.h>
 
 
+typedef struct {
+    ngx_str_t           name;
+    ngx_uint_t          offset;
+    ngx_uint_t          hash;
+    ngx_http_header_t  *hh;
+} ngx_http_v2_parse_header_t;
+
+
 /* errors */
 #define NGX_HTTP_V2_NO_ERROR                     0x0
 #define NGX_HTTP_V2_PROTOCOL_ERROR               0x1
@@ -156,6 +164,8 @@ static ngx_int_t ngx_http_v2_parse_schem
     ngx_str_t *value);
 static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r,
     ngx_str_t *value);
+static ngx_int_t ngx_http_v2_parse_header(ngx_http_request_t *r,
+    ngx_http_v2_parse_header_t *header, ngx_str_t *value);
 static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r);
 static ngx_int_t ngx_http_v2_cookie(ngx_http_request_t *r,
     ngx_http_v2_header_t *header);
@@ -201,6 +211,23 @@ static ngx_http_v2_handler_pt ngx_http_v
     (sizeof(ngx_http_v2_frame_states) / sizeof(ngx_http_v2_handler_pt))
 
 
+static ngx_http_v2_parse_header_t  ngx_http_v2_parse_headers[] = {
+    { ngx_string("host"),
+      offsetof(ngx_http_headers_in_t, host), 0, NULL },
+
+    { ngx_string("accept-encoding"),
+      offsetof(ngx_http_headers_in_t, accept_encoding), 0, NULL },
+
+    { ngx_string("accept-language"),
+      offsetof(ngx_http_headers_in_t, accept_language), 0, NULL },
+
+    { ngx_string("user-agent"),
+      offsetof(ngx_http_headers_in_t, user_agent), 0, NULL },
+
+    { ngx_null_string, 0, 0, NULL }
+};
+
+
 void
 ngx_http_v2_init(ngx_event_t *rev)
 {
@@ -2514,21 +2541,25 @@ ngx_http_v2_parse_int(ngx_http_v2_connec
 }
 
 
-ngx_int_t
-ngx_http_v2_push_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t depend,
-    size_t request_length, ngx_str_t *path, ngx_str_t *authority)
+ngx_http_v2_stream_t *
+ngx_http_v2_push_stream(ngx_http_v2_stream_t *parent, ngx_str_t *path)
 {
-    ngx_int_t              rc;
-    ngx_str_t              value;
-    ngx_connection_t      *fc;
-    ngx_http_request_t    *r;
-    ngx_http_v2_node_t    *node;
-    ngx_http_v2_stream_t  *stream;
+    ngx_int_t                     rc;
+    ngx_str_t                     value;
+    ngx_table_elt_t             **h;
+    ngx_connection_t             *fc;
+    ngx_http_request_t           *r;
+    ngx_http_v2_node_t           *node;
+    ngx_http_v2_stream_t         *stream;
+    ngx_http_v2_connection_t     *h2c;
+    ngx_http_v2_parse_header_t   *header;
+
+    h2c = parent->connection;
 
     node = ngx_http_v2_get_node_by_id(h2c, h2c->last_push, 1);
 
     if (node == NULL) {
-        return NGX_ERROR;
+        return NULL;
     }
 
     if (node->parent) {
@@ -2538,19 +2569,17 @@ ngx_http_v2_push_stream(ngx_http_v2_conn
 
     stream = ngx_http_v2_create_stream(h2c, 1);
     if (stream == NULL) {
-        return NGX_ERROR;
+        return NULL;
     }
 
     stream->pool = ngx_create_pool(1024, h2c->connection->log);
     if (stream->pool == NULL) {
-        return NGX_ERROR;
+        return NULL;
     }
 
     r = stream->request;
     fc = r->connection;
 
-    r->request_length = request_length;
-
     stream->in_closed = 1;
     stream->node = node;
 
@@ -2559,10 +2588,10 @@ ngx_http_v2_push_stream(ngx_http_v2_conn
     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
                    "http2 push stream sid:%ui "
                    "depends on %ui excl:0 weight:16",
-                   h2c->last_push, depend);
+                   h2c->last_push, parent->node->id);
 
     node->weight = NGX_HTTP_V2_DEFAULT_WEIGHT;
-    ngx_http_v2_set_dependency(h2c, node, depend, 0);
+    ngx_http_v2_set_dependency(h2c, node, parent->node->id, 0);
 
     r->method_name = ngx_http_core_get_method;
     r->method = NGX_HTTP_GET;
@@ -2579,51 +2608,64 @@ ngx_http_v2_push_stream(ngx_http_v2_conn
         r->schema_end = r->schema_start + 4;
     }
 
-    value.len = authority->len;
-
-    value.data = ngx_pstrdup(stream->pool, authority);
+    value.data = ngx_pstrdup(stream->pool, path);
     if (value.data == NULL) {
-        return NGX_ERROR;
-    }
-
-    rc = ngx_http_v2_parse_authority(r, &value);
-
-    if (rc != NGX_OK) {
-        goto error;
+        return NULL;
     }
 
     value.len = path->len;
 
-    value.data = ngx_pstrdup(stream->pool, path);
-    if (value.data == NULL) {
-        return NGX_ERROR;
-    }
-
     rc = ngx_http_v2_parse_path(r, &value);
 
     if (rc != NGX_OK) {
         goto error;
     }
 
+    for (header = ngx_http_v2_parse_headers; header->name.len; header++) {
+        h = (ngx_table_elt_t **)
+                ((char *) &parent->request->headers_in + header->offset);
+
+        if (*h == NULL) {
+            continue;
+        }
+
+        value.len = (*h)->value.len;
+
+        value.data = ngx_pnalloc(stream->pool, value.len + 1);
+        if (value.data == NULL) {
+            return NULL;
+        }
+
+        ngx_memcpy(value.data, (*h)->value.data, value.len);
+        value.data[value.len] = '\0';
+
+        rc = ngx_http_v2_parse_header(r, header, &value);
+
+        if (rc != NGX_OK) {
+            goto error;
+        }
+    }
+
     fc->write->handler = ngx_http_v2_run_request_handler;
     ngx_post_event(fc->write, &ngx_posted_events);
 
-    return NGX_OK;
+    return stream;
 
 error:
 
     if (rc == NGX_ABORT) {
-        return NGX_ERROR;
+        /* header handler has already finalized request */
+        return NULL;
     }
 
     if (rc == NGX_DECLINED) {
         ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
-        return NGX_ERROR;
+        return NULL;
     }
 
     (void) ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR);
 
-    return NGX_ERROR;
+    return NULL;
 }
 
 
@@ -3436,41 +3478,45 @@ ngx_http_v2_parse_scheme(ngx_http_reques
 static ngx_int_t
 ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value)
 {
+    return ngx_http_v2_parse_header(r, &ngx_http_v2_parse_headers[0], value);
+}
+
+
+static ngx_int_t
+ngx_http_v2_parse_header(ngx_http_request_t *r,
+    ngx_http_v2_parse_header_t *header, ngx_str_t *value)
+{
     ngx_table_elt_t            *h;
-    ngx_http_header_t          *hh;
     ngx_http_core_main_conf_t  *cmcf;
 
-    static ngx_str_t host = ngx_string("host");
-
     h = ngx_list_push(&r->headers_in.headers);
     if (h == NULL) {
         return NGX_ERROR;
     }
 
-    h->hash = ngx_hash_key(host.data, host.len);
-
-    h->key.len = host.len;
-    h->key.data = host.data;
+    h->key.len = header->name.len;
+    h->key.data = header->name.data;
+    h->lowcase_key = header->name.data;
+
+    if (header->hh == NULL) {
+        header->hash = ngx_hash_key(header->name.data, header->name.len);
+
+        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+        header->hh = ngx_hash_find(&cmcf->headers_in_hash, header->hash,
+                                   h->lowcase_key, h->key.len);
+        if (header->hh == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    h->hash = header->hash;
 
     h->value.len = value->len;
     h->value.data = value->data;
 
-    h->lowcase_key = host.data;
-
-    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
-
-    hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
-                       h->lowcase_key, h->key.len);
-
-    if (hh == NULL) {
-        return NGX_ERROR;
-    }
-
-    if (hh->handler(r, h, hh->offset) != NGX_OK) {
-        /*
-         * request has been finalized already
-         * in ngx_http_process_host()
-         */
+    if (header->hh->handler(r, h, header->hh->offset) != NGX_OK) {
+        /* header handler has already finalized request */
         return NGX_ABORT;
     }
 
diff -r 33edea74bd58 -r 3d2b0b02bd3d src/http/v2/ngx_http_v2.h
--- a/src/http/v2/ngx_http_v2.h	Thu Feb 15 17:51:26 2018 +0300
+++ b/src/http/v2/ngx_http_v2.h	Thu Feb 15 17:51:32 2018 +0300
@@ -283,9 +283,8 @@ void ngx_http_v2_init(ngx_event_t *rev);
 ngx_int_t ngx_http_v2_read_request_body(ngx_http_request_t *r);
 ngx_int_t ngx_http_v2_read_unbuffered_request_body(ngx_http_request_t *r);
 
-ngx_int_t ngx_http_v2_push_stream(ngx_http_v2_connection_t *h2c,
-    ngx_uint_t depend, size_t request_length, ngx_str_t *path,
-    ngx_str_t *authority);
+ngx_http_v2_stream_t *ngx_http_v2_push_stream(ngx_http_v2_stream_t *parent,
+    ngx_str_t *path);
 
 void ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc);
 
diff -r 33edea74bd58 -r 3d2b0b02bd3d src/http/v2/ngx_http_v2_filter_module.c
--- a/src/http/v2/ngx_http_v2_filter_module.c	Thu Feb 15 17:51:26 2018 +0300
+++ b/src/http/v2/ngx_http_v2_filter_module.c	Thu Feb 15 17:51:32 2018 +0300
@@ -50,20 +50,48 @@
 #define NGX_HTTP_V2_STATUS_404_INDEX      13
 #define NGX_HTTP_V2_STATUS_500_INDEX      14
 
+#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16
+#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17
 #define NGX_HTTP_V2_CONTENT_LENGTH_INDEX  28
 #define NGX_HTTP_V2_CONTENT_TYPE_INDEX    31
 #define NGX_HTTP_V2_DATE_INDEX            33
 #define NGX_HTTP_V2_LAST_MODIFIED_INDEX   44
 #define NGX_HTTP_V2_LOCATION_INDEX        46
 #define NGX_HTTP_V2_SERVER_INDEX          54
+#define NGX_HTTP_V2_USER_AGENT_INDEX      58
 #define NGX_HTTP_V2_VARY_INDEX            59
 
 #define NGX_HTTP_V2_NO_TRAILERS           (ngx_http_v2_out_frame_t *) -1
 
 
+typedef struct {
+    ngx_str_t      name;
+    u_char         index;
+    ngx_uint_t     offset;
+} ngx_http_v2_push_header_t;
+
+
+static ngx_http_v2_push_header_t  ngx_http_v2_push_headers[] = {
+    { ngx_string(":authority"), NGX_HTTP_V2_AUTHORITY_INDEX,
+      offsetof(ngx_http_headers_in_t, host) },
+
+    { ngx_string("accept-encoding"), NGX_HTTP_V2_ACCEPT_ENCODING_INDEX,
+      offsetof(ngx_http_headers_in_t, accept_encoding) },
+
+    { ngx_string("accept-language"), NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX,
+      offsetof(ngx_http_headers_in_t, accept_language) },
+
+    { ngx_string("user-agent"), NGX_HTTP_V2_USER_AGENT_INDEX,
+      offsetof(ngx_http_headers_in_t, user_agent) },
+};
+
+#define NGX_HTTP_V2_PUSH_HEADERS                                              \
+    (sizeof(ngx_http_v2_push_headers) / sizeof(ngx_http_v2_push_header_t))
+
+
 static ngx_int_t ngx_http_v2_push_resources(ngx_http_request_t *r);
 static ngx_int_t ngx_http_v2_push_resource(ngx_http_request_t *r,
-    ngx_str_t *path, ngx_str_t *authority);
+    ngx_str_t *path, ngx_str_t *binary);
 
 static u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len,
     u_char *tmp, ngx_uint_t lower);
@@ -685,16 +713,17 @@ ngx_http_v2_push_resources(ngx_http_requ
 {
     u_char                     *start, *end, *last;
     ngx_int_t                   rc;
-    ngx_str_t                   path, authority;
+    ngx_str_t                   path;
     ngx_uint_t                  i, push;
     ngx_table_elt_t           **h;
     ngx_http_v2_loc_conf_t     *h2lcf;
     ngx_http_complex_value_t   *pushes;
+    ngx_str_t                   binary[NGX_HTTP_V2_PUSH_HEADERS];
 
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "http2 push resources");
 
-    ngx_str_null(&authority);
+    ngx_memzero(binary, NGX_HTTP_V2_PUSH_HEADERS * sizeof(ngx_str_t));
 
     h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module);
 
@@ -715,7 +744,7 @@ ngx_http_v2_push_resources(ngx_http_requ
                 continue;
             }
 
-            rc = ngx_http_v2_push_resource(r, &path, &authority);
+            rc = ngx_http_v2_push_resource(r, &path, binary);
 
             if (rc == NGX_ERROR) {
                 return NGX_ERROR;
@@ -880,7 +909,7 @@ ngx_http_v2_push_resources(ngx_http_requ
         if (push && path.len
             && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/'))
         {
-            rc = ngx_http_v2_push_resource(r, &path, &authority);
+            rc = ngx_http_v2_push_resource(r, &path, binary);
 
             if (rc == NGX_ERROR) {
                 return NGX_ERROR;
@@ -905,15 +934,18 @@ ngx_http_v2_push_resources(ngx_http_requ
 
 static ngx_int_t
 ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
-    ngx_str_t *authority)
+    ngx_str_t *binary)
 {
-    u_char                    *start, *pos, *tmp;
-    size_t                     len;
-    ngx_table_elt_t           *host;
-    ngx_connection_t          *fc;
-    ngx_http_v2_stream_t      *stream;
-    ngx_http_v2_out_frame_t   *frame;
-    ngx_http_v2_connection_t  *h2c;
+    u_char                      *start, *pos, *tmp;
+    size_t                       len;
+    ngx_str_t                   *value;
+    ngx_uint_t                   i;
+    ngx_table_elt_t            **h;
+    ngx_connection_t            *fc;
+    ngx_http_v2_stream_t        *stream;
+    ngx_http_v2_out_frame_t     *frame;
+    ngx_http_v2_connection_t    *h2c;
+    ngx_http_v2_push_header_t   *ph;
 
     fc = r->connection;
 
@@ -944,42 +976,70 @@ ngx_http_v2_push_resource(ngx_http_reque
         return NGX_DECLINED;
     }
 
-    host = r->headers_in.host;
-
-    if (host == NULL) {
+    if (r->headers_in.host == NULL) {
         return NGX_ABORT;
     }
 
-    if (authority->len == 0) {
-
-        len = 1 + NGX_HTTP_V2_INT_OCTETS + host->value.len;
+    ph = ngx_http_v2_push_headers;
+
+    if (binary[0].len) {
+        tmp = ngx_palloc(r->pool, path->len);
+        if (tmp == NULL) {
+            return NGX_ERROR;
+        }
+
+    } else {
+        len = path->len;
+
+        for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
+            h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset);
+
+            if (*h) {
+                len = ngx_max(len, (*h)->value.len);
+            }
+        }
 
         tmp = ngx_palloc(r->pool, len);
-        pos = ngx_pnalloc(r->pool, len);
-
-        if (pos == NULL || tmp == NULL) {
+        if (tmp == NULL) {
             return NGX_ERROR;
         }
 
-        authority->data = pos;
-
-        *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX);
-        pos = ngx_http_v2_write_value(pos, host->value.data, host->value.len,
-                                      tmp);
-
-        authority->len = pos - authority->data;
+        for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
+            h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset);
+
+            if (*h == NULL) {
+                continue;
+            }
+
+            value = &(*h)->value;
+
+            len = 1 + NGX_HTTP_V2_INT_OCTETS + value->len;
+
+            pos = ngx_pnalloc(r->pool, len);
+            if (pos == NULL) {
+                return NGX_ERROR;
+            }
+
+            binary[i].data = pos;
+
+            *pos++ = ngx_http_v2_inc_indexed(ph[i].index);
+            pos = ngx_http_v2_write_value(pos, value->data, value->len, tmp);
+
+            binary[i].len = pos - binary[i].data;
+        }
     }
 
     len = (h2c->table_update ? 1 : 0)
           + 1
           + 1 + NGX_HTTP_V2_INT_OCTETS + path->len
-          + authority->len
           + 1;
 
-    tmp = ngx_palloc(r->pool, len);
+    for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
+        len += binary[i].len;
+    }
+
     pos = ngx_pnalloc(r->pool, len);
-
-    if (pos == NULL || tmp == NULL) {
+    if (pos == NULL) {
         return NGX_ERROR;
     }
 
@@ -1003,11 +1063,6 @@ ngx_http_v2_push_resource(ngx_http_reque
     *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX);
     pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp);
 
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
-                   "http2 push header: \":authority: %V\"", &host->value);
-
-    pos = ngx_cpymem(pos, authority->data, authority->len);
-
 #if (NGX_HTTP_SSL)
     if (fc->ssl) {
         ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
@@ -1022,6 +1077,20 @@ ngx_http_v2_push_resource(ngx_http_reque
         *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX);
     }
 
+    for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
+        h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset);
+
+        if (*h == NULL) {
+            continue;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 push header: \"%V: %V\"",
+                       &ph[i].name, &(*h)->value);
+
+        pos = ngx_cpymem(pos, binary[i].data, binary[i].len);
+    }
+
     frame = ngx_http_v2_create_push_frame(r, start, pos);
     if (frame == NULL) {
         return NGX_ERROR;
@@ -1031,8 +1100,14 @@ ngx_http_v2_push_resource(ngx_http_reque
 
     stream->queued++;
 
-    return ngx_http_v2_push_stream(h2c, stream->node->id, pos - start,
-                                   path, &host->value);
+    stream = ngx_http_v2_push_stream(stream, path);
+
+    if (stream) {
+        stream->request->request_length = pos - start;
+        return NGX_OK;
+    }
+
+    return NGX_ERROR;
 }
 
 


More information about the nginx-devel mailing list