Hatena::ブログ(Diary)

あすのかぜ Twitter

2016-12-08

103 EarlyHintsを送信するNginxモジュール書いた

これは、nginx Advent Calendar 2016の8日目の記事です。


103 Early Hints

先日の「Apache mod_http2 で 103 EarlyHints を試す」という記事でも書きましたが。軽くおさらい。


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レスポンスは送信開始できません(1行目がステータスコードのため)。


103ステータスコードのレスポンスは本来のHTTPレスポンスに先んじて送信できるので、先行してHTTPレスポンスヘッダを送信することができます。たとえばLinkヘッダでPreloadを先に送信すれば今後必要になるリソースをより早くクライアントに知らせることができます。


f:id:ASnoKaze:20161207234258p:image:w280


前回の103ステータスコードを用いたmod_http2の実験では、103ステータスコードを返すサーバはモックサーバを立てましたが今回はNginxモジュールを書いてみました。(試験実装なので怪しいとは思います)


ngx_http_early_hints

試験実装ですがngx_http_early_hints モジュールとして、すでに公開済になります。

https://github.com/flano-yuki/ngx_http_early_hints


以下のように、locationディレクティブに add_early_header を宣言すると、マッチする際に103ステータスのレスポンスを返すようになります。

location /103.html {
    add_early_header "Link" "</main.css>;rel=preload";
}

実際にHTTPリクエストを送信してみると以下のように103スレータスのレスポンスがまず送信されます。

(NGX_HTTP_ACCESS_PHASEでフックしてるので、コンテンツの生成より早いタイミングで送信されます)

vagrant@vagrant:~$ telnet localhost 80
Connected to localhost.
Escape character is '^]'.
GET /103.html HTTP/1.1
host:localhost
HTTP/1.1 103 Early Hints
Link: </main.css>;rel=preload

HTTP/1.1 200 OK
Server: nginx/1.11.6
Date: Wed, 07 Dec 2016 13:29:12 GMT
Content-Type: text/html
Content-Length: 22
Last-Modified: Wed, 07 Dec 2016 13:28:44 GMT
Connection: keep-alive
ETag: "58480e8c-16"
Accept-Ranges: bytes

This is main contents


あとがき

Nginxモジュールを初めて書くので、お作法がだいぶわからなかったのですが、下記サイトが非常に参考になりました。ありがとうございます。


実用に足るかはわかりませんが、エラーハンドリング、HTTP/2のサポート、複数add_early_headerのサポート、ほかモジュールとの併用検証など進めていければと思います。

2016-12-05

Apache mod_http2 で 103 EarlyHints を試す

これは、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

構成

f:id:ASnoKaze:20161205001516p:image

バックエンド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で接続を試みる

f:id:ASnoKaze:20161205001618p:image:w480

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>
...

2016-12-01

ntpdのLeap Smearingを有効にし、うるう秒を24時間かけて調整する

うるう秒を挿入せず、1秒分を長い時間で少しずつ適応することでソフトウェアなどのバグを回避する Leap Smearingは ntpdでも使用できる。


4.2.8.p3 と 4.3.47以降でサポートしているが、defaultでは無効になっているので自身でビルドする必要がある。


ubuntu 16.04で試す

wget http://www.eecis.udel.edu/~ntp/ntp_spool/ntp4/ntp-4.2/ntp-4.2.8p9.tar.gz
tar zxvf ./ntp-4.2.8p9.tar.gz
cd ./ntp-4.2.8p9/
./configure --enable-leap-smear
make

動作確認(既存のntpdが起動してれば事前に切っておく)

ntpq -c rv で leapsmearinterval が表示される

$ echo 'leapsmearinterval 86400' | sudo tee -a /etc/ntp.conf
$ sudo ./ntpd/ntpd

$ ntpq -c rv
associd=0 status=0618 leap_none, sync_ntp, 1 event, no_sys_peer,
version="ntpd 4.2.8p9@1.3265-o Thu Dec  1 13:54:30 UTC 2016 (1)",
processor="x86_64", system="Linux/4.4.0-31-generic", leap=00, stratum=3,
precision=-23, rootdelay=8.972, rootdisp=292.929, refid=59.106.180.168,
reftime=dbeab436.6e302275  Thu, Dec  1 2016 14:37:42.430,
clock=dbeab49a.75ac45a4  Thu, Dec  1 2016 14:39:22.459, peer=58337, tc=6,
mintc=3, offset=-86.392728, frequency=-125.660, sys_jitter=0.000000,
clk_jitter=28.609, clk_wander=2.963, leapsmearinterval=86400,
leapsmearoffset=0.000

おまけ

現在IETFで「Network Time Protocol Best Current Practices」として、NTPのベスト・プラクティスが書かれている。


section 4.6.1でLeap Smearingについて書かれている。パブリックサーバとして使用しないように勧められている他、現在のサーバ構成を把握しSmearingしてるサーバとSmearingしてないサーバを混在させないようにすることが推奨されている