[PATCH] HTTP/2: added support for setting custom push request headers

Alessandro Ghedini alessandro at ghedini.me
Tue Feb 13 11:58:40 UTC 2018


On Mon, Feb 12, 2018 at 03:30:21PM +0000, Alessandro Ghedini wrote:
> On Mon, Feb 12, 2018 at 05:11:55PM +0300, Ruslan Ermilov wrote:
> > On Mon, Feb 12, 2018 at 12:35:13PM +0000, Alessandro Ghedini wrote:
> > > # HG changeset patch
> > > # User Alessandro Ghedini <alessandro at ghedini.me>
> > > # Date 1518438578 0
> > > #      Mon Feb 12 12:29:38 2018 +0000
> > > # Branch http2-push-header
> > > # Node ID 4eb0c9e8da0bc52065578e4ee78df1833617ac35
> > > # Parent  a49af443656f2b65ca5de9d8cad5594f44e18ff7
> > > HTTP/2: added support for setting custom push request headers.
> > > 
> > > This implementa the http2_push_header configuration directive that makes
> > > it possible to set custom request headers for pushed requests.
> > > 
> > > Complex values are evaluated in the context of the original request,
> > > so it's possible to copy its headers into pushed requests.
> > > 
> > > Example usage:
> > > 
> > >     http2_push_header User-Agent $http_user_agent;
> > 
> > If I'm not mistaken, both the original patch and this one
> > do not send the copied headers in the PUSH_PROMISE frame.
> > 
> > Given the https://trac.nginx.org/nginx/ticket/1478, and some
> > research of how different implementations behave, I prepared
> > the following patch:
> 
> Also, your patch doesn't seem to work completely. You don't to copy the request
> headers in the request's headers_in, which means they don't get processed by
> NGINX, if the pushed resource is handled by NGINX itself.

If it's of any help, I merged your patch and mine into one, which copies the
headers (excluding Accept) into PUSH_PROMISE and r->headers_in like my original
patch did, as well as HPACK encode them into PUSH_PROMISE instead of writing
them as literal strings, as your patch did. This fixes the problems I mentioned
in my previous email.

# HG changeset patch
# User Alessandro Ghedini <alessandro at ghedini.me>
# Date 1518522249 0
#      Tue Feb 13 11:44:09 2018 +0000
# Branch push-headers
# Node ID bfd3019a0f63cf00364d44bab74b7864808abbd7
# Parent  8b0553239592f5d0fd419e5116b9d343838685cf
HTTP/2: push additional headers (ticket #1478).

When pushing requests, copy and push Accept-Encoding, Accept-Language,
and User-Agent header fields from the original request.

Includes changes made by Ruslan Ermilov.

diff -r 8b0553239592 -r bfd3019a0f63 src/http/v2/ngx_http_v2.c
--- a/src/http/v2/ngx_http_v2.c	Fri Feb 09 23:20:08 2018 +0300
+++ b/src/http/v2/ngx_http_v2.c	Tue Feb 13 11:44:09 2018 +0000
@@ -156,6 +156,8 @@
     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_copy_header(ngx_http_request_t *r,
+    ngx_table_elt_t *hdr);
 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);
@@ -2516,7 +2518,8 @@
 
 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)
+    size_t request_length, ngx_str_t *path, ngx_str_t *authority,
+    ngx_http_headers_in_t *headers_in)
 {
     ngx_int_t              rc;
     ngx_str_t              value;
@@ -2605,6 +2608,28 @@
         goto error;
     }
 
+    rc = ngx_http_v2_copy_header(r, headers_in->user_agent);
+
+    if (rc != NGX_OK) {
+        goto error;
+    }
+
+#if (NGX_HTTP_GZIP)
+    rc = ngx_http_v2_copy_header(r, headers_in->accept_encoding);
+
+    if (rc != NGX_OK) {
+        goto error;
+    }
+#endif
+
+#if (NGX_HTTP_HEADERS)
+    rc = ngx_http_v2_copy_header(r, headers_in->accept_language);
+
+    if (rc != NGX_OK) {
+        goto error;
+    }
+#endif
+
     fc->write->handler = ngx_http_v2_run_request_handler;
     ngx_post_event(fc->write, &ngx_posted_events);
 
@@ -3479,6 +3504,63 @@
 
 
 static ngx_int_t
+ngx_http_v2_copy_header(ngx_http_request_t *r, ngx_table_elt_t *hdr)
+{
+    ngx_table_elt_t            *h;
+    ngx_http_header_t          *hh;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    if (hdr == NULL) {
+        return NGX_OK;
+    }
+
+    h = ngx_list_push(&r->headers_in.headers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    h->hash = hdr->hash;
+
+    h->key.len = hdr->key.len;
+
+    h->key.data = ngx_pnalloc(r->stream->pool, h->key.len + 1);
+    if (h->key.data == NULL) {
+        h->hash = 0;
+        return NGX_ERROR;
+    }
+
+    (void) ngx_cpystrn(h->key.data, hdr->key.data, h->key.len + 1);
+
+    h->value.len = hdr->value.len;
+
+    h->value.data = ngx_pnalloc(r->stream->pool, h->value.len + 1);
+    if (h->key.data == NULL) {
+        h->hash = 0;
+        return NGX_ERROR;
+    }
+
+    (void) ngx_cpystrn(h->value.data, hdr->value.data, h->value.len + 1);
+
+    h->lowcase_key = h->key.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) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
 ngx_http_v2_construct_request_line(ngx_http_request_t *r)
 {
     u_char  *p;
diff -r 8b0553239592 -r bfd3019a0f63 src/http/v2/ngx_http_v2.h
--- a/src/http/v2/ngx_http_v2.h	Fri Feb 09 23:20:08 2018 +0300
+++ b/src/http/v2/ngx_http_v2.h	Tue Feb 13 11:44:09 2018 +0000
@@ -285,7 +285,7 @@
 
 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_str_t *authority, ngx_http_headers_in_t *headers_in);
 
 void ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc);
 
diff -r 8b0553239592 -r bfd3019a0f63 src/http/v2/ngx_http_v2_filter_module.c
--- a/src/http/v2/ngx_http_v2_filter_module.c	Fri Feb 09 23:20:08 2018 +0300
+++ b/src/http/v2/ngx_http_v2_filter_module.c	Tue Feb 13 11:44:09 2018 +0000
@@ -50,20 +50,37 @@
 #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                             authority;
+#if (NGX_HTTP_GZIP)
+    ngx_str_t                             accept_encoding;
+#endif
+#if (NGX_HTTP_HEADERS)
+    ngx_str_t                             accept_language;
+#endif
+    ngx_str_t                             user_agent;
+} ngx_http_v2_push_ctx_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_http_v2_push_ctx_t *ctx);
+static ngx_int_t ngx_http_v2_indexed_header_encode(ngx_http_request_t *r,
+    u_char index, ngx_str_t *value, ngx_str_t *header);
 
 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 +702,17 @@
 {
     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_v2_push_ctx_t      ctx;
     ngx_http_complex_value_t   *pushes;
 
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "http2 push resources");
 
-    ngx_str_null(&authority);
+    ngx_memzero(&ctx, sizeof(ngx_http_v2_push_ctx_t));
 
     h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module);
 
@@ -715,7 +733,7 @@
                 continue;
             }
 
-            rc = ngx_http_v2_push_resource(r, &path, &authority);
+            rc = ngx_http_v2_push_resource(r, &path, &ctx);
 
             if (rc == NGX_ERROR) {
                 return NGX_ERROR;
@@ -880,7 +898,7 @@
         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, &ctx);
 
             if (rc == NGX_ERROR) {
                 return NGX_ERROR;
@@ -905,11 +923,17 @@
 
 static ngx_int_t
 ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
-    ngx_str_t *authority)
+    ngx_http_v2_push_ctx_t *ctx)
 {
     u_char                    *start, *pos, *tmp;
     size_t                     len;
-    ngx_table_elt_t           *host;
+    ngx_table_elt_t           *host, *user_agent;
+#if (NGX_HTTP_GZIP)
+    ngx_table_elt_t           *accept_encoding;
+#endif
+#if (NGX_HTTP_HEADERS)
+    ngx_table_elt_t           *accept_language;
+#endif
     ngx_connection_t          *fc;
     ngx_http_v2_stream_t      *stream;
     ngx_http_v2_out_frame_t   *frame;
@@ -950,31 +974,69 @@
         return NGX_ABORT;
     }
 
-    if (authority->len == 0) {
-
-        len = 1 + NGX_HTTP_V2_INT_OCTETS + host->value.len;
-
-        tmp = ngx_palloc(r->pool, len);
-        pos = ngx_pnalloc(r->pool, len);
-
-        if (pos == NULL || tmp == NULL) {
+#if (NGX_HTTP_GZIP)
+    accept_encoding = r->headers_in.accept_encoding;
+#endif
+
+#if (NGX_HTTP_HEADERS)
+    accept_language = r->headers_in.accept_language;
+#endif
+
+    user_agent = r->headers_in.user_agent;
+
+    if (ctx->authority.len == 0) {
+
+        if (ngx_http_v2_indexed_header_encode(r, NGX_HTTP_V2_AUTHORITY_INDEX,
+                                              &host->value, &ctx->authority)
+            != NGX_OK)
+        {
             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;
+#if (NGX_HTTP_GZIP)
+        if (accept_encoding
+            && ngx_http_v2_indexed_header_encode(r,
+                   NGX_HTTP_V2_ACCEPT_ENCODING_INDEX,
+                   &accept_encoding->value, &ctx->accept_encoding)
+               != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+#endif
+
+#if (NGX_HTTP_HEADERS)
+        if (accept_language
+            && ngx_http_v2_indexed_header_encode(r,
+                   NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX,
+                   &accept_language->value, &ctx->accept_language)
+               != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+#endif
+
+        if (user_agent
+            && ngx_http_v2_indexed_header_encode(r,
+                   NGX_HTTP_V2_USER_AGENT_INDEX,
+                   &user_agent->value, &ctx->user_agent)
+               != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
     }
 
     len = (h2c->table_update ? 1 : 0)
           + 1
           + 1 + NGX_HTTP_V2_INT_OCTETS + path->len
-          + authority->len
-          + 1;
+          + ctx->authority.len
+          + 1
+#if (NGX_HTTP_GZIP)
+          + ctx->accept_encoding.len
+#endif
+#if (NGX_HTTP_HEADERS)
+          + ctx->accept_language.len
+#endif
+          + ctx->user_agent.len;
 
     tmp = ngx_palloc(r->pool, len);
     pos = ngx_pnalloc(r->pool, len);
@@ -1006,7 +1068,7 @@
     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);
+    pos = ngx_cpymem(pos, ctx->authority.data, ctx->authority.len);
 
 #if (NGX_HTTP_SSL)
     if (fc->ssl) {
@@ -1022,6 +1084,36 @@
         *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX);
     }
 
+#if (NGX_HTTP_GZIP)
+    if (accept_encoding) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 push header: \"accept-encoding: %V\"",
+                       &accept_encoding->value);
+
+        pos = ngx_cpymem(pos, ctx->accept_encoding.data,
+                         ctx->accept_encoding.len);
+    }
+#endif
+
+#if (NGX_HTTP_HEADERS)
+    if (accept_language) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 push header: \"accept-language: %V\"",
+                       &accept_language->value);
+
+        pos = ngx_cpymem(pos, ctx->accept_language.data,
+                         ctx->accept_language.len);
+    }
+#endif
+
+    if (user_agent) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 push header: \"user-agent: %V\"",
+                       &user_agent->value);
+
+        pos = ngx_cpymem(pos, ctx->user_agent.data, ctx->user_agent.len);
+    }
+
     frame = ngx_http_v2_create_push_frame(r, start, pos);
     if (frame == NULL) {
         return NGX_ERROR;
@@ -1032,7 +1124,34 @@
     stream->queued++;
 
     return ngx_http_v2_push_stream(h2c, stream->node->id, pos - start,
-                                   path, &host->value);
+                                   path, &host->value, &r->headers_in);
+}
+
+
+static ngx_int_t
+ngx_http_v2_indexed_header_encode(ngx_http_request_t *r, u_char index,
+    ngx_str_t *value, ngx_str_t *header)
+{
+    size_t   len;
+    u_char  *pos, *tmp;
+
+    len = 1 + NGX_HTTP_V2_INT_OCTETS + value->len;
+
+    tmp = ngx_palloc(r->pool, len);
+    pos = ngx_pnalloc(r->pool, len);
+
+    if (pos == NULL || tmp == NULL) {
+        return NGX_ERROR;
+    }
+
+    header->data = pos;
+
+    *pos++ = ngx_http_v2_inc_indexed(index);
+    pos = ngx_http_v2_write_value(pos, value->data, value->len, tmp);
+
+    header->len = pos - header->data;
+
+    return NGX_OK;
 }
 
 




More information about the nginx-devel mailing list