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

Alessandro Ghedini alessandro at ghedini.me
Mon Feb 12 15:30:21 UTC 2018


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.

E.g.

  daemon off;
  worker_processes  1;
  master_process off;

  events {
    worker_connections  1024;
  }

  http {
    access_log /dev/stdout;
    error_log  logs/error.log debug;

    server {
        listen       4433 ssl http2;

        server_name  localhost;

        ssl_certificate      cert.crt;
        ssl_certificate_key  cert.key;

        http2_max_concurrent_pushes   200;
        http2_push_preload            on;


        gzip on;
        gzip_types text/plain;
        gzip_min_length 1;

        location /hello {
            http2_push /a;

            root   html;
        }

        location /a {
            return 200 "a";
        }
    }
  }

 % nghttp https://127.0.0.1:4433/hello -vn  
[  0.001] Connected
[WARNING] Certificate verification failed: Hostname mismatch
The negotiated protocol: h2
[  0.003] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
          (niv=2)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[  0.003] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
          (dep_stream_id=0, weight=201, exclusive=0)
[  0.003] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
          (dep_stream_id=0, weight=101, exclusive=0)
[  0.003] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
          (dep_stream_id=0, weight=1, exclusive=0)
[  0.003] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
          (dep_stream_id=7, weight=1, exclusive=0)
[  0.003] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
          (dep_stream_id=3, weight=1, exclusive=0)
[  0.003] send HEADERS frame <length=44, flags=0x25, stream_id=13>
          ; END_STREAM | END_HEADERS | PRIORITY
          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)
          ; Open new stream
          :method: GET
          :path: /hello
          :scheme: https
          :authority: 127.0.0.1:4433
          accept: */*
          accept-encoding: gzip, deflate
          user-agent: nghttp2/1.29.0
[  0.004] recv SETTINGS frame <length=18, flags=0x00, stream_id=0>
          (niv=3)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):128]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65536]
          [SETTINGS_MAX_FRAME_SIZE(0x05):16777215]
[  0.004] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=2147418112)
[  0.004] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  0.004] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  0.004] recv (stream_id=13) :method: GET
[  0.004] recv (stream_id=13) :path: /a
[  0.004] recv (stream_id=13) :authority: 127.0.0.1:4433
[  0.004] recv (stream_id=13) :scheme: https
[  0.004] recv (stream_id=13) accept-encoding: gzip, deflate
[  0.004] recv (stream_id=13) user-agent: nghttp2/1.29.0
[  0.004] recv PUSH_PROMISE frame <length=47, flags=0x04, stream_id=13>
          ; END_HEADERS
          (padlen=0, promised_stream_id=2)
[  0.004] recv (stream_id=13) :status: 200
[  0.004] recv (stream_id=13) server: nginx/1.13.9
[  0.004] recv (stream_id=13) date: Mon, 12 Feb 2018 15:18:39 GMT
[  0.004] recv (stream_id=13) content-type: text/plain
[  0.004] recv (stream_id=13) last-modified: Thu, 08 Feb 2018 15:43:05 GMT
[  0.004] recv (stream_id=13) etag: W/"5a7c7009-1f5"
[  0.004] recv (stream_id=13) content-encoding: gzip
[  0.004] recv HEADERS frame <length=105, flags=0x04, stream_id=13>
          ; END_HEADERS
          (padlen=0)
          ; First response header
[  0.004] recv DATA frame <length=32, flags=0x01, stream_id=13>
          ; END_STREAM
[  0.004] recv (stream_id=2) :status: 200
[  0.004] recv (stream_id=2) server: nginx/1.13.9
[  0.004] recv (stream_id=2) date: Mon, 12 Feb 2018 15:18:39 GMT
[  0.004] recv (stream_id=2) content-type: text/plain
[  0.004] recv (stream_id=2) content-length: 1
[  0.004] recv HEADERS frame <length=48, flags=0x04, stream_id=2>
          ; END_HEADERS
          (padlen=0)
          ; First push response header
[  0.004] recv DATA frame <length=1, flags=0x01, stream_id=2>
          ; END_STREAM
[  0.004] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
          (last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[])

Notice how the response of the pushed request doesn't actually get compressed.
Additionally from the access logs I see:

  127.0.0.1 - - [12/Feb/2018:15:18:39 +0000] "GET /hello HTTP/2.0" 200 32 "-" "nghttp2/1.29.0"
  127.0.0.1 - - [12/Feb/2018:15:18:39 +0000] "GET /a HTTP/2.0" 200 1 "-" "-"

The second one is the pushed request, notice how it doesn't have a user agent
unlike the first request (which is the one coming from the client directly).

With my patch (the one with the custom config directive, but this mostly applies
to the other one as well) however I get this:

 % nghttp https://127.0.0.1:4433/hello -vn
[  0.002] Connected
[WARNING] Certificate verification failed: Hostname mismatch
The negotiated protocol: h2
[  0.004] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
          (niv=2)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[  0.004] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
          (dep_stream_id=0, weight=201, exclusive=0)
[  0.004] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
          (dep_stream_id=0, weight=101, exclusive=0)
[  0.004] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
          (dep_stream_id=0, weight=1, exclusive=0)
[  0.004] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
          (dep_stream_id=7, weight=1, exclusive=0)
[  0.004] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
          (dep_stream_id=3, weight=1, exclusive=0)
[  0.004] send HEADERS frame <length=44, flags=0x25, stream_id=13>
          ; END_STREAM | END_HEADERS | PRIORITY
          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)
          ; Open new stream
          :method: GET
          :path: /hello
          :scheme: https
          :authority: 127.0.0.1:4433
          accept: */*
          accept-encoding: gzip, deflate
          user-agent: nghttp2/1.29.0
[  0.004] recv SETTINGS frame <length=18, flags=0x00, stream_id=0>
          (niv=3)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):128]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65536]
          [SETTINGS_MAX_FRAME_SIZE(0x05):16777215]
[  0.004] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=2147418112)
[  0.004] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  0.005] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  0.005] recv (stream_id=13) :method: GET
[  0.005] recv (stream_id=13) :path: /a
[  0.005] recv (stream_id=13) :authority: 127.0.0.1:4433
[  0.005] recv (stream_id=13) :scheme: https
[  0.005] recv (stream_id=13) accept-encoding: gzip, deflate
[  0.005] recv (stream_id=13) user-agent: nghttp2/1.29.0
[  0.005] recv PUSH_PROMISE frame <length=67, flags=0x04, stream_id=13>
          ; END_HEADERS
          (padlen=0, promised_stream_id=2)
[  0.005] recv (stream_id=13) :status: 200
[  0.005] recv (stream_id=13) server: nginx/1.13.9
[  0.005] recv (stream_id=13) date: Mon, 12 Feb 2018 15:27:14 GMT
[  0.005] recv (stream_id=13) content-type: text/plain
[  0.005] recv (stream_id=13) last-modified: Thu, 08 Feb 2018 15:43:05 GMT
[  0.005] recv (stream_id=13) etag: W/"5a7c7009-1f5"
[  0.005] recv (stream_id=13) content-encoding: gzip
[  0.005] recv HEADERS frame <length=105, flags=0x04, stream_id=13>
          ; END_HEADERS
          (padlen=0)
          ; First response header
[  0.005] recv DATA frame <length=32, flags=0x01, stream_id=13>
          ; END_STREAM
[  0.006] recv (stream_id=2) :status: 200
[  0.006] recv (stream_id=2) server: nginx/1.13.9
[  0.006] recv (stream_id=2) date: Mon, 12 Feb 2018 15:27:14 GMT
[  0.006] recv (stream_id=2) content-type: text/plain
[  0.006] recv (stream_id=2) content-encoding: gzip
[  0.006] recv HEADERS frame <length=62, flags=0x04, stream_id=2>
          ; END_HEADERS
          (padlen=0)
          ; First push response header
[  0.006] recv DATA frame <length=21, flags=0x01, stream_id=2>
          ; END_STREAM
[  0.006] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
          (last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[])

The second response is compressed and the logs look like this:

  127.0.0.1 - - [12/Feb/2018:15:27:14 +0000] "GET /hello HTTP/2.0" 200 32 "-" "nghttp2/1.29.0"
  127.0.0.1 - - [12/Feb/2018:15:27:14 +0000] "GET /a HTTP/2.0" 200 21 "-" "nghttp2/1.29.0"

Cheers


More information about the nginx-devel mailing list