Apache mod_http2 で 103 EarlyHints (RFC 8297) を試す

これは、http2 Advent Calendar 2016の5日目の記事です。



20161208 今回はtoy serverを使用しましたが、103 EarlyHintsを送信できるNginxモジュールを書きました
103 EarlyHintsを送信するNginxモジュール書いた


103 Early Hints

103 Early Hintsは、@kazuho氏によって提案されている仕様です。すでに Individual-Draftが提出されています。
https://tools.ietf.org/html/draft-kazuho-early-hints-status-code-00


IETF97でも仕様についての議論が行われており、その際のスライドを見ると分かりやすいかと思います
https://www.ietf.org/proceedings/97/slides/slides-97-httpbis-sessb-early-hints-00.pdf


簡単に言うと、HTTPレスポンスは一般的にコンテンツの生成が終わってからステータスコードが決定します。そのため、特定のHTTPレスポンスヘッダをまず返したい!ということは出来ません。


そこで、informationalステータスコードである100番代の "103" を使用することでHTTPレスポンスヘッダをコンテンツの生成が終わる前にクライアントに通知することが出来ます。一般的に馴染みのない 100番代のステータスコードですが、少々特殊でこのHTTPレスポンスのみ連続で送信する事ができます。但し、正しく実装されていないクライアント・サーバもあるので注意が必要であり、仕様としては議論が続くところかと思います。


IETF97の発表資料の書かれている通り、ユースケースとしてLinkヘッダでPreloadを先行してレスポンスすることで、クライアントは早いタイミングでそのリソースを取得しようとします。
また、プロキシがバックエンドWebサーバより103を受け取った場合、クライアントとHTTP/2で通信していれば サーバプッシュを使用しリソースをプッシュすることもできます。


すでに、h2oやnghttp2で対応されているようです

Apache mod_http2 で試す

mod_http2(nghttp2)でざっと試す

  • Ubuntu 16.04
  • Apache2.4 (Revision 1772437)
  • nghttp2 (commit 85ba33c08f46)
  • Openssl 1.0.2g


特殊なことはないが、nghttp2をインストールしてから、Apache2.4をsvnからチェックアウトしビルドする。その後、http2の有効化およびリバースプロキシの設定を入れる。

# https://github.com/nghttp2/nghttp2 にそって、インストールしておく
sudo apt-get install subversion build-essential autoconf libxml2 libxml2-dev libtool libtool-bin libpcre3-dev

svn checkout http://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x httpd-2.4.x
cd ./httpd-2.4.x/
./buildconf
./configure  --enable-http2 --with-libxml2

make
sudo make install
構成


バックエンドWebサーバとして、toy serverを準備しておく。こいつが、103を返す

proxy

ざっと、ココらへんの設定を入れる。(フロントはhttp2)

#h2の設定
Protocols h2 http/1.1
ProtocolsHonorOrder On
#H2EarlyHints on

#proxy
ProxyStatus On
ProxyPreserveHost On
ProxyPass / balancer://test
<Proxy balancer://test>
        BalancerMember http://127.0.0.1:8080 loadfactor=10
</Proxy>
バックエンド

status 103を返すバックエンドサーバを簡易的に準備。
Linkヘッダでpreloadを指定します。nghttp2は103のpreloadヘッダを解釈し、HTTP/2 Server Pushをしてくれます。

vagrant@vagrant:~$ cat ./res
HTTP/1.1 103
Link: </hoge.css>;rel=preload

HTTP/1.1 200 OK
Date: Sun, 04 Dec 2016 00:00:00 GMT

helloworld


vagrant@vagrant:~$ while true; do ( cat ./res ) | nc -l 8080 >/dev/null; [ $? != 0 ] && break; done &
試してみる

nghttp2で接続を試みる

vagrant@vagrant:~$ nghttp https://localhost -vn --no-dep|lv |grep -i frame
...
[  0.011] send HEADERS frame <length=33, flags=0x05, stream_id=1>
[  0.013] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
[  0.015] recv PUSH_PROMISE frame <length=53, flags=0x04, stream_id=1>
[  0.015] recv HEADERS frame <length=57, flags=0x04, stream_id=1>
[  0.016] recv DATA frame <length=14, flags=0x01, stream_id=1>
[  0.016] recv HEADERS frame <length=55, flags=0x04, stream_id=2>
[  0.016] recv DATA frame <length=528, flags=0x01, stream_id=2>
[  0.016] send GOAWAY frame <length=8, flags=0x00, stream_id=0>

ちゃんと、103のLinkヘッダを読んで、Proxy側からPUSH_PROMISEが送信されていることが確認できる。
(今回のtoy serverは103と200を同時に返しているの意味は薄いが)


また、mod_http2の設定で「H2EarlyHints」をon にするとクライアント側に103が伝達される

vagrant@vagrant:~$ nghttp https://localhost -vn --no-dep|lv |grep -i -e frame  -e status
...
[  0.013] send HEADERS frame <length=33, flags=0x05, stream_id=1>
[  0.014] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
[  0.017] recv PUSH_PROMISE frame <length=53, flags=0x04, stream_id=1>
[  0.017] recv (stream_id=1) :status: 103
[  0.017] recv HEADERS frame <length=41, flags=0x04, stream_id=1>
[  0.017] recv (stream_id=1) :status: 200
[  0.017] recv HEADERS frame <length=57, flags=0x04, stream_id=1>
[  0.017] recv DATA frame <length=14, flags=0x01, stream_id=1>
...