PerformancePaintTiming API(First Paint Timing API)とは

昨今、「Navigation Timing API」といったクライアント側でのページの処理時間を取得する機能が充実してきております。


W3CのWeb Performance WGでは、Navigation Timing、User Timing、Resource TimingそれぞれLevel2, Level3が改訂作業が行われております。(ドラフト状況)


また、昨年末頃より「PerformancePaintTiming」APIの議論が行われているように感じます。簡単な仕様はgithubのwicg organization以下に公開されております ( https://github.com/WICG/paint-timing )。これは、より多くの情報を取得できるようにする一環で、実際の描画時間が重要度が高いためのようだ。


数年前に、ふろしきさんが「Webページ遷移時間のパフォーマンス「First Paint」を計測する方法」で書かれている通り、window.chrome.loadTimes()より、firstPaintがとれましたが、より現代的な標準APIが検討されております。


PerformancePaintTiming

リポジトリの使い方とサンプルを見るのがわかりやすいかと思います。
PerformanceObserverから、firstPaintエントリより最初の描画タイミングが取得できます。

var observer = new PerformanceObserver(function(list) {
  var perfEntries = list.getEntries();
  for (var i = 0; i < perfEntries.length; i++) {
     // Process entries
     // report back for analytics and monitoring
     // ...
  }
});

// register observer for long task notifications
observer.observe({entryTypes: ["firstPaint"]});



(https://github.com/WICG/paint-timing#examples

  • first-paint: ナビゲーション後のcontaining blockを最初に描画した時間(背景を除く)
  • first-contentful-paint: 最初の、テキスト、画像、キャンバス、SVGを描画した時間

Improving accuracy of FP / FCP

Chromeでは、内部的にFPとFCPの時間を取得しており stable版で情報収集をしている。
https://docs.google.com/document/d/1XVP9jaZT7acQtK5O6vO-mIz31i5iB-pZn1S41_YP6p0/edit#


その中で、さらに細かくどこまでの時間をFP, FCPに含めるべきなのかというレポートを出している。どこまで仕様化できるかはわからないが、今後共WebPerf WGで議論が続けられる模様である。

OpenSSLのTLS1.3対応 喋れる

追記 20170120


NginxでTLS1.3 動いた(OpenSSL)
後日Nginxでも動いたので、上記記事に記載


セキュリティとパフォーマンスが向上したTLS1.3の登場が待ち望まれております。


標準化としては、現在IETFTLSワーキンググループではWGラストコールという段階に入っており、2月頃にはIESGに提出され標準化は最終段階となる見込みになっております(結構そっから議論があったりはするんですが)。


すでに、実装としてはChrome, Firefoxといったブラウザもオプションで有効化に出来る他、BoringSSL, picotls, nssといった実装も出てきており、相互通信テストなども行われております。


昨年行われたIETF97時点ですが、下記のような実装で通信テストに成功しています。

https://www.ietf.org/proceedings/97/slides/slides-97-tls-tls-13-00.pdf


さて、OpenSSLはと思うところですが、同じくIETF97時点ですが、OpenSSLの状況が報告されております。すべての作業はgithub上でプルリクが出されており、次のリリースで出るとのことでした。
https://www.ietf.org/proceedings/97/slides/slides-97-tls-openssl-update-00.pdf


TLS1.3の中身の話は、kazuhoさんやkazuさんの資料がわかりやすいかと思います

とりあえず、現状で試してみる

OpenSSLはまだまだ開発中で動かないかもと思いながらも、試してみる
./config時にenable-tls1_3を与える

$ git clone https://github.com/openssl/openssl.git
$ cd ./openssl
$ git log --oneline |head -n1
ef3f621 Fix man3 reference to CRYPTO_secure_used

$ ./config enable-tls1_3
$ make


既存実装のリストは下記にあるので、公開URLのものから幾つか繋いで見る
https://github.com/tlswg/tls13-spec/wiki/Implementations

クライアントで試す

他のバージョンと同様 --tls1_3を与える

$ LD_LIBRARY_PATH=./ ./apps/openssl s_client -connect franziskuskiefer.de:9913 --tls1_3
...
New, TLSv1.3, Cipher is TLS13-AES-128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : TLS13-AES-128-GCM-SHA256
    Session-ID:
    Session-ID-ctx:
    Master-Key: AC496ABF3FE5EF1C197CA5B825746CD58A05E86494288C82057604F819C2B757
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1484672332
    Timeout   : 7200 (sec)
    Verify return code: 20 (unable to get local issuer certificate)
    Extended master secret: no
---
GET / HTTP/1.1
host:test

HTTP/1.1 200 OK
Date: Tue, 17 Jan 2017 16:58:56 GMT
Server: Apache
Upgrade: h2
Connection: Upgrade, close
Last-Modified: Fri, 03 Jun 2016 08:50:46 GMT
ETag: "d5f-5345bd194edaf"
Accept-Ranges: bytes
Content-Length: 3423
Content-Type: text/html; charset=UTF-8

<!DOCTYPE html>
...


他のは繋がらなかったものの、つながるサーバもあった。
(どこが悪いのかまだちゃんと見てない...

サーバ側で試す

Chrome canaryと通信させる

$ LD_LIBRARY_PATH=./ ./apps/openssl s_server -cert ../server.crt -key ../server.key  -tls1_3
...
-----BEGIN SSL SESSION PARAMETERS-----
MGUCAQECAgMEBAITAQQgMuuXEJUnlwWHomt0g/L89WPXiudXU2AHGFd6NdSKBPYE
IIKAszcJj6hsNaEPwtZz6t/IskTrQB5QnBvCCV9YjxZ+oQYCBFh+TwiiBAICHCCk
BgQEAQAAAA==
-----END SSL SESSION PARAMETERS-----
Shared ciphers:TLS13-AES-128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA:AES256-SHA
Signature Algorithms: ECDSA+SHA256:RSA+SHA256:RSA+SHA256:ECDSA+SHA384:RSA+SHA384:RSA+SHA384:RSA+SHA512:RSA+SHA512:RSA+SHA1
Shared Signature Algorithms: ECDSA+SHA256:RSA+SHA256:RSA+SHA256:ECDSA+SHA384:RSA+SHA384:RSA+SHA384:RSA+SHA512:RSA+SHA512:RSA+SHA1
Supported Elliptic Groups: 0x9A9A:X25519:P-256:P-384
Shared Elliptic groups: X25519:P-256:P-384
CIPHER is TLS13-AES-128-GCM-SHA256
Secure Renegotiation IS NOT supported
GET / HTTP/1.1
Host: 192.168.0.179:4433
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2984.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: ja,en-US;q=0.8,en;q=0.6

なんか、怪しいけど、HTTPリクエストが来てることは確認できた。

2016年振り返り

早いもので2016年も終わりですね。


今年は、毎月コンスタントにブログ記事を書けたもの、記事数39と去年よりも少なくなってしまいました。internet-draft絡みも9本でした。


ただ反面、OSSにPR投げてマージされたり、英語でissue投げてみたり、Nginxのモジュール書いてみたり出来たのは良かったなと思いました。


昨年同様 IETFにも行けましたし、来年も行けるように頑張ります。
来年も、微力ながら誰かのお役に立てるように精進していければと思います。


今年もありがとうございました。

varnishtestのHTTP/2対応 アップデート

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


今年の頭に書いた「varnishtestがHTTP2対応して超便利!」で紹介しましたvarnishtestのHTTP/2対応ですが、シンタックスの一部変更で当時のサンプルが使えなくなっていたのでアップデートについて軽く書きたいと思います。


当時は開発者様の個人リポジトリで開発が進められている状況でしたが、varnish5でmasterにマージされております。
また、varnish5で本体側もhttp2対応が進められているため、ようやくvarnish本体を経由するようなテストもかけるようになりました。ただし本体側のhttp2はまだまだバグがあるようで実際に使う場合は慎重になったほうが良いようです。


varnishtest自体については前回の記事を見ていただくか、いわなちゃんさんの記事がわかりやすいかと思います

サンプル

HTTP/2のサーバとクライアントでボディ付きの通信をする例になります。
https://github.com/varnishcache/varnish-cache/blob/master/bin/varnishtest/tests/a02002.vtc

varnishtest "Simple request with body"

server s1 {
	stream 1 {
		rxreq
		txresp -body "bob"
	} -run
} -start

client c1 -connect ${s1_sock} {
	stream 1 {
		txreq
		rxresp
		expect resp.bodylen == 3
	} -run
} -run
  • 以前は「 h2client」のように明示的にh2対応のクライアントと書いていましたが、stream句を使うことで各エンドポイントはHTTP/2通信に切り替わるようになりましt(http1からのアップグレードの手順も記述できるようになっています)
  • HTTP/2で要求される疑似ヘッダ(scheme, method, authority)などが明示的に書く必要がなくなりました
その他新機能

コネクションレベル・ストリームレベルのウィンドウサイズのテストが記述できるようになりました
https://github.com/varnishcache/varnish-cache/blob/master/bin/varnishtest/tests/a02023.vtc


ウィンドウサイズのテストにおいて、SETTINGSフレームで指定される初期ウィンドウサイズを考慮するようになりました
https://github.com/varnishcache/varnish-cache/blob/master/bin/varnishtest/tests/a02010.vtc



ストリームの依存関係・ウェイトのテストが記述できるようになりました
https://github.com/varnishcache/varnish-cache/blob/master/bin/varnishtest/tests/a02015.vtc



サーバプッシュのテストが記述できるようになりました
https://github.com/varnishcache/varnish-cache/blob/master/bin/varnishtest/tests/a02017.vtc

varnishを間に挟む場合のテスト

varnish本体がh2(h2cのみ)に対応しているため、varnishtestもクライアント・(バックエンドとしての)サーバの間にvarnishを挟む形のテスト例も追加されています。


https://github.com/varnishcache/varnish-cache/blob/master/bin/varnishtest/tests/t02000.vtc

server s1 {
	rxreq
	expect req.http.host == foo.bar
	txresp -bodylen 10
} -start

varnish v1 -vcl+backend {} -start

varnish v1 -cliok "param.set feature +http2"
varnish v1 -cliok "param.set debug +syncvsl"

client c1 {
	stream 1 {
		txprio -weight 10 -stream 0
	} -run
	stream 3 {
		txprio -weight 10 -stream 0
	} -run
	stream 5 {
		txprio -weight 10 -stream 2
	} -run
	stream 7 {
		txreq -hdr :authority foo.bar
		rxresp
		expect resp.status == 200
	} -start
	stream 0 {
		txping -data "_-__-_-_"
		rxping
		expect ping.ack == "true"
		expect ping.data == "_-__-_-_"
	} -run
	stream 7 -wait
} -run

この例では、バックエンドサーバはhttp/1を使用して、クライアント側からはh2で接続するテストになっています。
varnishのパラメータでhttp2を有効にする必要はありますが、ココらへんは記述方式的にはhttp2独自ではないので通常通り動くという感じになるかと思います。

おまけ

テスト例として、別のクライアント実装と通信する例もあります
https://github.com/varnishcache/varnish-cache/blob/master/bin/varnishtest/tests/a02022.vtc

feature cmd "nghttp --version | grep -q 'nghttp2/[1-9]'"

server s1 {
	rxreq
	upgrade

	stream 3 { rxprio } -run
	stream 5 { rxprio } -run
	stream 7 { rxprio } -run
	stream 9 { rxprio } -run
	stream 11 { rxprio } -run

	stream 1 {
		rxprio
		txresp
	} -start

} -start


shell { nghttp http://${s1_addr}:${s1_port} -nu }


varnishtestはvarnish以外でも使用できます。IP指定も出来ますし、今回はありませんが複数クライアントで同時にリクエストを送らせたりも出来ます。
工夫次第では色々な使い方ができるかと思います!!

103 EarlyHints (RFC 8297) を送信する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を先に送信すれば今後必要になるリソースをより早くクライアントに知らせることができます。



前回の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のサポート、ほかモジュールとの併用検証など進めていければと思います。

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

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してないサーバを混在させないようにすることが推奨されている