今回はHTTP/2の仕組みの一つであるServer Pushという技術についてご紹介したいとおもいます。HTTP/2 Server Pushとはその名の通りサーバーサイド側からコンテンツをクライアントにプッシュする仕組みです。このような仕組みをうまく使うことによって効率的にWeb高速化をおこなうことができます。
HTTP/2 Server Pushとは?
HTTP/2はHTTP1.1の次の規格として効率がよい通信が行える仕組みのことで、Server PushはHTTP/2の機能の一つとして動きます。※HTTP/2について詳しくはこちらの記事にまとめております。
Server PushはGoogleによって最初に提案されました。Server Pushを利用するとサーバーは特定のリソースをクライアントにまとめてプッシュできるため、リクエストが到着しなくてもブラウザのキャッシュで将来のレスポンスを準備することができます。
HTTP(s)のやりとり
Server Pushの仕組みをご紹介する前に、まずはHTTPの通信がどのようにされているかおさらいしてみましょう。
1:アクセスする側からWebサーバーにリクエストを送信
2:サーバーがGETリクエストを受け取り対象コンテンツを返却
実際はTCP接続やらSSLネゴシエーションやらありますが、あくまでHTTPの簡単なやりとりのみにとどめておきます。
HTTP通信は情報が欲しいユーザーが欲しいというアクションを起こして初めて情報が取得できるのがHTTP通信です。このように、Webサイトへのアクセスは、常にリクエストとレスポンスのパターンで構成されています。ユーザーがリクエストをリモートサーバーに送信するとサーバーは少し遅れて要求されたコンテンツを返却します。このごくごく一般的なやりとりはブラウザでHTMLの解析をおこない追加リソースへのリクエストを送信するまで待たされてしまうという欠点があります。
近年のWebサイトやサービスによっては、こんな情報はいかがですか?という提案する内容やSNSの通知など自ら情報を取得するアクションを起こさなくても、サービス側から通知する方式も多くなってきました。このようなアクションがPUSHアクションです。
HTTP/2 Server Pushのやりとり
さて、HTTP/2 Server PushはSNSからの通知のように、情報を受け取る側がなにもアクションをすること無くコンテンツをサーバーからPushする仕組みを意味します。しかしこれらはブラウザとWebサーバーとのやりとりの中のお話で、どこを見たいのかという初めのアクションは当然クライアントから行なわなければいけないところがSNSなどの通知とは異なる点です。
Server Pushは予めこのコンテンツをリクトエスされた時、レスポンスするタイミングでJS1とCSS1を一緒にレスポンスするというように追加リソースをユーザーのリクエスト無しにサーバー側からクライアントに送信することができます。たとえば、1つのHTMLとCSSで構成されたページがあった場合、Server Pushを利用すると最初のHTMLのレスポンスと一緒にCSSがレスポンスされ、クライアントからのGETリクストは一回だけでよくなるのです。
PUSH_PROMISE
HTTP/2 Server PushはPUSH_PROMISEと呼ばれるメカニズムが実行されサーバーはデータをプッシュするときにPUSH_PROMISEフレームを作成します。PUSH_PROMISEフレームヘッダーでは、フロー情報と応答情報が要求に関連付けられており、サーバー側のHTMLリクエストにはストリームIDに関連するPUSH_PROMISEフレームが含まれます。これらはヘッダー情報により、クライアントにどのリソースを送信するか制御することができますが、Pushされるリソースをレスポンスデータの前に配信する必要があります。
よって、まずクライアントは、要求されたデータを受信する前にPUSH_PROMISEフレームを取得します。それ以外の場合、PUSH_PROMISEフレームは、すでに利用可能なデータに対する不要な要求を送信します。クライアントはプッシュされたデータをキャッシュに保存し、異なるページ間の遷移で再利用できますがコンテンツが不要な場合、必要に応じてRST_STREAMフレームで拒否するか、プッシュされるストリームの数を制限できます。これがHTTP/2のServerPush最大の特徴といえます。
HTTP/1.1の頃のインライン化は全リソース強制プッシュと同じためこのような個別制御ができないことが欠点でした。
HTTP/2フレームタイプ
0x0 | DATA | リクエスト・レスポンスボディ |
0x1 | HEADERS | 非圧縮または圧縮されたHTTPヘッダー |
0x2 | PRIORITY | ストリーム優先度変更 |
0x3 | RST_STREAM | ストリーム終了通知 |
0x4 | SETTINGS | 接続に関する設定 |
0x5 | PUSH_PROMISE | リソースのプッシュ通知 |
0x6 | PING | 接続状況確認 |
0x7 | GOAWAY | 接続終了通知 |
0x8 | WINDOW_UPDATE | フロー制御ウィンドウの更新 |
0x9 | CONTINUATION | HEADERS フレーム・PUSH_PROMISE フレームのデータ |
Server Pushの実装と要件
Server Pushを実装するためにはWebサーバー側で条件をみたしていなければいけません。対応条件としては主に以下の通りです。
- HTTP/2に対応している
- SSL通信である※ブラウザの制約
- Server Push対応である
まずServer PushはHTTP/2の機能です。HTTP/2はHTTPでも動きますがブラウザ側の対応がHTTPS必須となっているためSSL通信が必須となります。次に、Server Pushであるという命令を解釈できる必要があります。HTTP/2対応だからといって全てServer Pushが対応しているというわけではありません。
HTTP/2 Server Push対応Webサーバー
mod_http2でデフォルトで有効になっています。H2Push機能(オン/オフ)により、このサーバー/仮想ホストのすべてのリソースでスイッチをオフにできます。以下の用にLinkヘッダーを追加することも可能ですし、.htaccess等や逆にPHP等のプログラム側で設定してもOKです。
Header add Link "</css/test.css>;rel=preload" as=style
Nginxは以前までモジュールをコンパイルして有効化する必要がありました。2019年最新バージョンではパッケージで入れてもHTTP/2は有効となります。コンフィグで、http2_push_preload on;
とすることにより、リンクヘッダを識別することが可です。
HTTP/2 Server Push対応クライアント
HTTP/2 Server Pushは対応クライアントでなければ利用することができませんが、2019年現在では主要ブラウザは対応しているため何ら問題はありません。
Pushするコンテンツの指定
さて、Webサーバーが要件を全て満たしていた場合、どのコンテンツをPushするのかという指定は主にWebサーバーのコンフィグに直接記載する場合と、Linkヘッダ内の記述をみて判断する二通りがあります。
ApacheやNginxなどの主要Webサーバーは2019年現在Linkヘッダを解釈してServer Pushしてくれるようになりました。よって、とりあえずServer Pushを試してみたい場合は、レスポンスヘッダに一緒にPushしてほしいコンテンツをLinkヘッダを使って以下の用にコンテンツパスをフルパスで書いてあげると実現できます。
Linkヘッダの指定方法
Linkヘッダではオプションという位置づけではなく、むしろ適切に設定するいくつかのパラメーターがあります。たとえば、as=styleはブラウザにPushするコンテンツタイプを通知します。気をつける点は必ずコンテンツタイプを指定するas=を付けることです。これらを付与しないと、ブラウザはコンテンツを二回ダウンロードしてしまう可能性があります。※今回はCSSですのでas=styleとしていますが、他にもコンテンツタイプによって色々指定可能です。
なお、ApacheのHTTP / 2モジュールは、H2PushResourceディレクティブを使用してリソースのプッシュを開始することもできます。このメソッドはLinkヘッダーメソッドが使用される場合よりも早くプッシュを開始できることがApacheのドキュメントに記載されています。よって、ファイル名がきまっておりPushするコンテンツが明確な場合はスタティックにこのようなディレクティブを利用してもよいですが、利便性を考慮して今回はLinkヘッダーによるPush制御で話を進めています。
複数コンテンツをPushする場合
Linkヘッダーでは複数のコンテンツをPushする事も可能です。複数のリソースをプッシュする場合は、各プッシュディレクティブをカンマで区切ります。
Link: </css/test.css>; rel=preload; as=style, </js/test.js>; rel=preload; as=script, </img/test.png>; rel=preload; as=image
また、Pushディレクティブと他のリソースヒントを混在させるためには、Pushディレクティブとpreconnectリソースヒントを利用します。
Link: </css/test.css>; rel=preload; as=style, <https://fonts.gstatic.com>; rel=preconnect
※その他、詳しいリンクヘッダーについてはこちら
HTTP/2 Server Pushの確認方法
対応しているWebサーバーであればLinkヘッダを用いて簡単にコンテンツをPushできることが分かりました。次は本当にリソースがPushされているか確認してみましょう。
Chromeブラウザを使ったServer Pushの確認方法
一番簡単な方法はChromeブラウザの開発者ツールを利用して、ネットワークタブのイニシエーター項目にPUSH / index:1のような名称が確認できるかどうかです。
また、ネットワークタブのウォーターフォールでコンテンツにカーソルを合わせるとServer Pushされた詳細が確認できます。
NGHTTPを使ったServer Pushの確認方法
エンジニアとして手っ取り早いのがNGHTTPというツールをつかってリクエストを行うことです。
nghttp -ans https://www.redbox.ne.jp
id responseEnd requestStart prcess code size request path
1 +47.15ms +1.06ms 47.15ms 200 17.8K /
2 +58.52ms * +50.15ms 8.37ms 200 126K /fae39.css
4 +58.51ms * +50.10ms 8.41ms 200 33.5K /jquery.js
PUSHされたコンテンツはrequestStart列にアスタリスクがマークされるためすごくわかりやすいですが、NGHTTP自体の導入にコンパイル作業が必要になるため若干敷居が高いです。
HTTP/2 Server Push パフォーマンス計測方法
さて、HTTP/2 Server Pushは本当にWebパフォーマンスという観点において有効なのでしょうか。Web表示速度は何事も計測から始まります。理論ではなく計測してみないと本当の所どうなのか分かりませんよね。そこで以下のようないろいろなパターンでどの程度差がでるのか計測してみました。ポイントとなるのはなんといってもすべてのリソースをPushする場合とそうでない場合、HTTP1.1のインラインCSSとの速度比較でしょう。
計測パターン
- HTTP/2(No Server Push)
これは純粋にHTTP/2をつかった配信のみ行ないます。
- HTT/2 Server Push(only CSS)
Server Pushを使いますがPushするコンテンツはCSSのみに限定します。
- HTTP/2 ALL PUSH
とにかく全てのコンテンツをServer Pushにて配信します。
最小RTTで全てのコンテンツが配信されることになります。
- HTTP/1.1(none inline)
これは従来のHTTP/1.1を使った配信で、リクエストを削減するためのインライン化等は
おこなわない状態です。
- HTTP/1.1 (Only CSS inline)
こちらはCSSのみインライン化してリクエスト数を削減します。
- HTTP/1.1 ALL inline
とにかくあらゆるコンテンツをインライン化してみます。リクエスト数をHTTP/1.1環境で最小にします。※SVGなどもBase64エンコードでインライン化すると通常より大きなコンテンツサイズになるため膨大なリクエスト数出ない限りBase64で埋め込むことはお薦め出来ません。
テストのシナリオ
本テストは全てのパターン以下の条件でテストを行なうことにします。
- テストページはテスト結果の平均を取るためそれぞれ10回テストされます。
- ページ内のリンクはクロールせず、テストページのみ対象とする
- ネットワーク要件:25mm RTT ダウンロード速度:5Mbps アップロード速度: 1Mbps
- Chromeを使ったシミュレーション
テスト結果(First Paint Time)
これは、ブラウザに最初に表示されるまでの時間を計測したものです。ロード時間とは異なりユーザーが体感上早いと認識できるポイントでもあります。
HTTP/1.1の何も対策をしていない環境は一番遅く次にHTTP/2で何も対策していない環境が遅いという結果になりました。よってHTTP/2にすれば早くなるという表現は好きではありませんが本状況では結果的にHTTP/1.1より若干早くなっていることがわかります。
次にCSSだけServer Pushを利用した環境はPUSHしない環境より約6パーセント早くなります。そして注目して頂きたいのが、全てのコンテンツをPushしてしまうと、CSSだけPushする時より遅くなってしまっているという点です。このようにServer Pushは闇雲になんでもPushすれば速くなるというわけではありません。適度にServer Pushを利用すると、インライン化したHTTP/1.1と比べても速い速度で表示されることがわかりました。
また、DOMのロード時間についてもFirstPaintTimeとほぼ同様の結果が得られ、インライン化されていないものはロード時間が長く、一番DOMのロード時間が短いのはHTTP/2 Server PushでCSSのみPushするパターンでした。
HTTP/2 Server Pushのメリット・デメリット
Server Pushの良い所は最初のHTMLをサーバーがレスポンスするとき所定の追加リソースも一緒にブラウザウにPushできる点に尽きます。最初のHTMLレスポンス時にレンダリングに必要なリソースを素早くブラウザに渡す事ができるため、結果レンダリング時間が短縮されます。
そしてこのようなRTTを減らす目的以外にも様々なメリットがあります。たとえば、HTTP/1.1で良く用いられたHTML内に直接CSSやJavaScriptをインライン化する方法や、データURLスキームを利用してバイナリデータを埋込む方法などかつてのHTTP/1.1で高速化トリックとして用いられてきた手法は、追加CSSなどのリソースを直ぐに読み込むことができるためServer Pushと同様にHTMLを取得した瞬間からスタイルを適用できます。しかし、インライン化の問題点はインライン化されたコンテンツを効率よくキャッシュまたは制御を個別に出来ないことです。追加リソースがキャッシュされれば本来のリクエストを行う事無くサーバーへのリクエストも行われないため、本来の望ましい形といえるでしょう。
Pushするコンテンツはほどほどに
逆に全てのコンテンツをServer Push化すれば実質少ないRTTでコンテンツの受け渡しがいっぺんに出来るため早くなるのではないか、と思われるかもしれません。しかし、検証の通り全てのコンテンツをPushすることは逆にスピードの悪化につながります。Server Pushはユーザー体験が良くなるCSSなどのリソースに絞った方法がベストでしょう。どうしても大きなコンテンツをPushしたい場合は、Apache等にあるH2PushPriorityや優先度をつけられるH2PushDiarySizeを適用して、同じ接続の重複Pushを回避しましょう。そしてこれはコンテンツによるとおもいますが、不用意にユーザーがまだクリックしていないページのリソースPushも避けるべきです。
CDNでHTTP/2 Server Pushをする
CDNでもHTTP/2 Server Pushを利用することが出来ます。CDNがServer Pushに対応すると嬉しいことは、なんといってもオリジンサーバーであるWebサーバー側は無加工で対応できるということでしょう。通常Webサーバー側でLinkヘッダを解釈させServer Pushするためにはミドルウェアの設定変更やバージョンUP、HTTP2の対応などを行なわなければいけません。CDN側で対応しているとWebサーバーはHTTPSの設定すら行なわなくて良くなります。
CDNは中間キャッシュとしてオリジンサーバーから受け取ったHTMLヘッダにLinkが含まれている時、そのリソースがキャッシュにある場合はキャッシュをPushしキャッシュに無い場合はオリジンから取得後キャッシュしてPushします。
HTTP/2 Server Pushのまとめ
HTTP/2 Server Pushは従来からリクエストを削減するために利用されていた外部ファイルのインライン化と比べてもレンダリングが速くなることがわかりました。インライン化は少なからずフロントのコーディングが必要ですが、サーバーサイドの技術を利用することによりコンテンツに手を加えること無くユーザー体験を向上させることができます。
ただし、全てのコンテンツをPushすると逆にネットワークコストがかかって遅くなる場合もあります。そのため、Pushするコンテンツは見た目の体感を速くでき、且つファイルとして軽いCSS等にとどめておくことが重要で、Webサイトの最適解を導きだし正しいコンテンツをPushすることが重要です。
どのような技術にもいえることですが、ServerPushをONにすれば必ずしもOK!というわけではありません。whats-the-benefit-of-server-pushには以下のように記載されています。
What’s the benefit of Server Push?
When a browser requests a page, the server sends the HTML in the response, and then needs to wait for the browser to parse the HTML and issue requests for all of the embedded assets before it can start sending the JavaScript, images and CSS.
Server Push potentially allows the server to avoid this round trip of delay by “pushing” the responses it thinks the client will need into its cache.
However, Pushing responses is not “magical” – if used incorrectly, it can harm performance. Correct use of Server Push is an ongoing area of experimentation and research
つまり、HTTP/2もServerPushも魔法ではありません。Webサイトが無数にあるようにユーザーの行動も無数のパターンがあります。つまり適用する予定のWebサイトに合わせてそのWebサイトの最適を探し調整する必要があることを忘れてはいけません。