HTTP/2 の概要

注: 以下の内容は、High Performance Browser Networking(O'Reilly 社、Ilya Grigorik)からの抜粋です。全文および関連コンテンツについては、hpbn.co を参照してください。

HTTP/2 によって、以前にアプリケーション内に組み込んだ多くの HTTP/1.1 の回避策を元に戻し、トランスポート層自体の懸念事項に対処できるようになり、アプリケーションを高速化、簡素化、堅牢化できます(これはまれな組み合わせです)。 さらにすばらしいことに、アプリケーションを最適化してパフォーマンス向上をするまったく新しいチャンスも開かれます。

HTTP/2 の主な目標は、レイテンシの短縮です。そのために、リクエストとレスポンスの完全な多重化、HTTP ヘッダー フィールドの効率的な圧縮によるプロトコル オーバーヘッドの最小化、リクエストの優先順位付けおよびサーバー プッシュのサポートを可能にします。これらの要件を実装するために、新しいフロー制御、エラー処理、アップグレードのしくみといったその他のプロトコル拡張が多数サポートされていますが、これらはすべてのウェブ デベロッパーが理解し、アプリケーションで活用すべきもっとも重要な機能です。

HTTP/2 によって、HTTP のアプリケーション セマンティクスが変更されることは一切ありません。HTTP のメソッド、ステータス コード、URI、ヘッダー フィールドなどの主要なコンセプトはすべてそのままです。 代わりに、HTTP/2 ではクライアント / サーバー間のデータ形式(フレーム)と転送方式が変更されます。どちらもプロセス全体を管理し、新しいフレーミング レイヤー内ではアプリケーションに対してあらゆる複雑さが意識されなくなります。 その結果、既存のアプリケーションはすべて、変更せずに配信できます。

  • HTTP/1.2 ではない理由*

HTTP Working Group によって設定されたパフォーマンス目標を達成するために、HTTP/2 では新しいバイナリ フレーミング レイヤーを導入しました。これは、以前の HTTP/1.x サーバー / クライアントとの後方互換性はありません。そのためメジャー プロトコル バージョンが HTTP/2 に上がりました。

とはいうものの、ロー TCP ソケットと連携して動作するウェブサーバー(またはカスタム クライアント)を実装していない限り、その違いを意識することはないでしょう。新しい低レベルのフレーミングは、クライアント / サーバーが代わりに実行します。 認識できる唯一の違いは、リクエストの優先順位付け、フロー制御、サーバー プッシュといったパフォーマンスおよび可用性の改善です。

SPDY および HTTP/2 の略歴

SPDY は Google が開発した試験的なプロトコルで、2009 年中頃に発表しました。このプロトコルの主な目標は、HTTP/1.1 の既知のパフォーマンス制限に対処し、ウェブページの読み込みレイテンシを短縮することです。大まかなプロジェクト目標は次のとおりです。

  • ページ読み込み時間(PLT)の 50% 短縮を目指す。
  • ウェブサイト作成者によるコンテンツ変更の必要性を回避する。
  • デプロイメントの複雑さを最小限に抑え、ネットワーク インフラの変更を回避する。
  • この新しいプロトコルをオープンソース コミュニティと協力して開発する。
  • 実際のパフォーマンス データを収集し、試験的なプロトコルを検証(または無効化)する。

注: 50% の PLT 改善を達成するため、SPDY は新しいバイナリ フレーミング レイヤーを導入して、リクエストとレスポンスの多重化、優先順位付け、ヘッダーの圧縮を可能にすることで、基盤となる TCP 接続の効率化を目指しています。プラットフォームのボトルネックとしてのレイテンシに関する記事をご覧ください。

最初の発表から間もなく、Mike Belshe と Roberto Peon(どちらも Google のソフトウェア エンジニア)が、新しい SPDY プロトコルの試験実装に関する最初の結果、ドキュメント、ソースコードを共有しました。

これまでのところ、ラボ環境でしか SPDY をテストしていません。最初の結果は非常に期待が持てる内容でした。シミュレートしたホームネットワーク接続で上位 25 のウェブサイトをダウンロードしたところ、パフォーマンスが大幅に向上し、ページの読み込みが最大 55% 高速化されました。(Chromium ブログ)

2012 年まで話を進めます。新しい試験的プロトコルが Chrome、Firefox、Opera でサポートされ、大規模なサイト(Google、Twitter、Facebook など)から小規模なサイトまで、インフラストラクチャ内で SPDY をデプロイするサイト数が急速に増加していきました。 業界での採用増加に伴い、SPDY は実質的にデファクト スタンダードになりました。

このトレンドを踏まえて、HTTP ワーキング グループ(HTTP-WG)は、SPDY から学んだことを取り入れ、さらに手を加えて改良し、正式な「HTTP/2」標準の提供に向けて新たな取り組みを開始しました。 新たな仕様の起案、HTTP/2 への提案の募集、ワーキング グループでの度重なる議論を経て、SPDY 仕様は新たな HTTP/2 プロトコルの出発点として採用されました。

その後数年間、SPDY と HTTP/2 は並行して共に進化を続けています。SPDY は、新しい機能や HTTP/2 標準の提案のテストに使用される試験ブランチとして機能しています。ドキュメント上では良さそうに見えても、実際にはうまく動作しないことや、その逆もあります。SPDY は HTTP/2 標準に含める前に各提案をテストおよび評価する道を提供しました。 最終的に、このプロセスは 3 年間にわたり、その間に 10 以上のドラフトが作成されました。

  • 2012 年 3 月:HTTP/2 への提案募集
  • 2012 年 11 月:HTTP/2 の最初のドラフト(SPDY をベースとする)
  • 2014 年 8 月:HTTP/2 ドラフト 17 および HPACK ドラフト 12 を公開
  • 2014 年 8 月:ワーキング グループが HTTP/2 の最終提案を募集
  • 2015 年 2 月:IESG が HTTP/2 および HPACK ドラフトを承認
  • 2015 年 5 月:RFC 7540(HTTP/2)および RFC 7541(HPACK)を公開

2015 年の初め、IESG が新しい HTTP/2 標準の公開に向けたレビューを実施し、承認しました。 その直後、Google Chrome チームは SPDY および TLS の NPN 拡張機能を廃止するスケジュールを発表しました。

HTTP/1.1 から HTTP/2 への主な変更点においては、パフォーマンスの向上を重視しています。一部の主な機能、たとえば多重化、ヘッダー圧縮、優先順位付け、プロトコル ネゴシエーションは、以前のオープンかつ非標準プロトコルである、SPDY で実施されていた処理から進化したものです。 Chrome は Chrome 6 以降から SPDY をサポートしていますが、そのメリットのほとんどは HTTP/2 に採用されたため、廃止するときがきました。 2016 年初めには Chrome でのサポートを終了し、ALPN を優先して TLS の NPN 拡張のサポートも同時に終了する予定です。

サーバー デベロッパーは、HTTP/2 および ALPN に移行することを強くお勧めします。Google は HTTP/2 につながるオープン規格プロセスに積極的に貢献しており、標準化と実装に向けた業界からの幅広いエンゲージメントを受けて、広く採用されることを期待しています。(Chromium Blog)

SPDY と HTTP/2 がともに進化の道を歩んだおかげで、サーバー、ブラウザ、サイトのデベロッパーは、実際に開発段階の新しいプロトコルを試すことができました。その結果、HTTP/2 標準は最初から徹底的にベストを尽くしてテストされています。HTTP/2 が IESG によって承認されるまでに、実稼働向けのクライアントおよびサーバーが実装され、徹底的なテストが何十回と繰り返されました。 実際、最終的なプロトコルの承認からわずか数週間後には、いくつかの人気のブラウザ(および多くのサイト)が完全な HTTP/2 のサポートをデプロイしたため、多くのユーザーがすでにそのメリットを享受していました。

デザインと技術的な目標

旧バージョンの HTTP プロトコルでは、意図的に実装が簡素化されるよう設計されていました。 HTTP/0.9 はワンライン プロトコルで、World Wide Web をブートストラップします。HTTP/1.0 では、HTTP/0.9 に対する人気の拡張機能が情報標準に文書化されました。HTTP/1.1 では正式な IETF 標準が導入されました。HTTP の略歴をご覧ください。このように、HTTP/0.9-1.x は、設定したとおりにそのまま配信されます。

HTTP は、インターネットでもっとも幅広く採用されたアプリケーション プロトコルの 1 つです。 残念ながら、実装の簡素化は、アプリケーションのパフォーマンス低下にもつながります。 HTTP/1.x クライアントは、並行処理とレイテンシの短縮のため、複数の接続を使用する必要があります。HTTP/1.x は、リクエストとレスポンスのヘッダーを圧縮しないため、不要なネットワーク トラフィックが発生します。HTTP/1.x では、効果的なリソース優先順位付けができないため、基盤となる TCP 接続の使用が最適化されません。

このような制限は致命的ではありませんが、ウェブ アプリケーションの範囲、複雑さ、日常生活における重要性が拡大し続ける中で、ウェブ デベロッパーとユーザーのどちらにとっても、このような制限が成長の妨げになります。このギャップを解決できるのが HTTP/2 です。

HTTP/2 ではネットワーク リソースの使用効率が高まり、ヘッダー フィールドの圧縮の導入により体感するレイテンシが短縮され、同じ接続で複数の並列交換を実行できます。特に、同じ接続でリクエストおよびレスポンス メッセージの混在が可能になり、HTTP ヘッダー フィールドの効率的なコーディングを使用できます。

また、リクエストの優先順位付けにより、重要度の高いリクエストを先に完了させ、パフォーマンスをさらに向上できます。このプロトコルでは、使用できる TCP 接続が減少しているため、HTTP/1.x よりもネットワーク親和性が高くなっています。 つまり、その他のフローとの競合が減り、接続時間が長期化するため、利用できるネットワーク容量の使用効率改善につながります。 最後に、HTTP/2 ではバイナリ メッセージ フレーミングの使用により、さらに効率良くメッセージを処理できます。(Hypertext Transfer Protocol バージョン 2、ドラフト 17)

注意点として、HTTP/2 は拡張であり、以前の HTTP に置き換わるものではありません。 HTTP のアプリケーション セマンティクスは同じで、提供される機能や主要なコンセプト、たとえば HTTP メソッド、ステータス コード、URI、ヘッダー フィールドなどに変更はありません。 これらの変更は明らかに HTTP/2 の作業範囲外です。 とはいえ、高水準 API が同じままでも、低水準の変更によってどのように、以前のプロトコルのパフォーマンス制限に対処しているのかを把握することが重要です。バイナリ フレーミング レイヤーとその機能について、これから簡単に説明します。

バイナリ フレーミング レイヤー

HTTP/2 のあらゆるパフォーマンス強化において中核をなすのは、新しいバイナリ フレーミング レイヤーです。このレイヤーは、クライアント / サーバー間における HTTP メッセージのカプセル化および転送方式を定めています。

HTTP/2 バイナリ フレーミング レイヤー

「レイヤー」とは、ソケット インターフェースと、アプリケーションに公開される高水準の HTTP API 間で、新しい最適化されたエンコーディングのしくみを導入するために、設計上選ばれたものを表しています。動詞、メソッド、ヘッダーなどの HTTP セマンティクスは影響を受けませんが、転送中のエンコーディング方法が異なります。改行で区切られたプレーンテキストの HTTP/1.x プロトコルとは異なり、HTTP/2 の通信はすべて、小さいメッセージとフレームに分割され、それぞれバイナリ形式でエンコーディングされます。

そのため、クライアントとサーバーの両方が新しいバイナリ エンコーディングのしくみを使用して、相互に理解できるようにする必要があります。HTTP/1.x クライアントは、HTTP/2 のみのサーバーを理解できず、その逆もあてはまります。 幸い、アプリケーションはこれらの変更を意識することなく平穏なままです。クライアントとサーバーのプラットフォームが、代わりに必要なフレーミング処理を実行してくれるからです。

ストリーム、メッセージ、フレーム

新しいバイナリ フレーミング方式の導入により、クライアント / サーバー間でデータの交換方式が変わります。 このプロセスを説明するにあたって、HTTP/2 の用語をまず紹介します。

  • ストリーム: 確立した接続におけるバイト単位のデータの双方向フロー。1 つ以上のメッセージを伝送する場合があります。
  • メッセージ: 論理的なリクエストまたはレスポンス メッセージにマッピングする、フレームの完全なシーケンス。
  • フレーム: HTTP/2 での通信の最小単位。それぞれフレーム ヘッダーが含まれ、少なくともフレームが属するストリームを識別します。

これらの用語の関係は、次のようにまとめることができます。

  • すべての通信は、任意の数の双方向ストリームを伝送できる 1 つの TCP 接続で実行されます。
  • 各ストリームには、双方向メッセージの伝送に使用する一意の識別子とオプションの優先順位情報が存在します。
  • 各メッセージは、リクエスト、レスポンスなどの論理 HTTP メッセージであり、1 つ以上のフレームで構成されます。
  • フレームは、HTTP ヘッダー、メッセージ ペイロードなど、特定のタイプのデータを伝送する通信の最小ユニットです。 さまざまなストリームのフレームが混在し、各フレームのヘッダーに組み込まれたストリーム識別子を使って再構築される場合があります。

HTTP/2 ストリーム、メッセージ、フレーム

つまり、HTTP/2 は HTTP プロトコル通信を分解して、バイナリ エンコーディングされたフレームを交換します。このフレームは、特定のストリームに属するメッセージにマッピングされ、1 つの TCP 接続内ですべて多重化されます。 これは、HTTP/2 プロトコルが提供するその他すべての機能とパフォーマンスの最適化を実現する基盤となります。

リクエストとレスポンスの多重化

HTTP/1.x では、クライアントがパフォーマンス向上のために複数の並列リクエストが必要である場合、複数の TCP 接続を使用する必要があります(複数の TCP 接続の使用に関するページをご覧ください)。 この動作は、1 つの接続につき、同時に 1 つのレスポンスしか配信(レスポンス キューに格納)できない、HTTP/1.x の配信モデルが直接影響しています。 さらに悪いことに、これによって、ヘッドオブライン ブロッキングが発生し、基盤となる TCP 接続の使用効率が低下します。

HTTP/2 の新しいバイナリ フレーミング レイヤーではこのような制限がなくなり、クライアントとサーバーが HTTP メッセージを個々のフレームに分割し、混在させ、送信先で再構築することで、リクエストとレスポンスのフル多重化が可能です。

共有接続内での HTTP/2 リクエストとレスポンスの多重化

スナップショットは、同じ接続内で伝送中の複数のストリームをキャプチャします。クライアントは、DATA フレーム(ストリーム 5)をサーバーに伝送し、サーバーは混在するフレームのシーケンスをストリーム 1 および 3 のクライアントに伝送します。その結果、3 つの並列ストリームが伝送中になります。

HTTP メッセージを個別のフレームに分解し、混在させ、送信先で再構築できる機能は、HTTP/2 のもっとも重要な拡張です。実際に、すべてのウェブ テクノロジーのスタック全体に多くのパフォーマンス上のメリットもたらし、その波及効果で次のことが可能になります。

  • リクエストをブロックすることなく、複数のリクエストを並列で混在させる。
  • レスポンスをブロックすることなく、複数のレスポンスを並列で混在させる。
  • 1 つの接続を使用して、複数のリクエストとレスポンスを並列で配信する。
  • 連結したファイル、image sprites、ドメイン共有など、不要な HTTP/1.x の回避策を削除する(HTTP/1.x の最適化を参照)。
  • 不要なレイテンシの短縮と、利用できるネットワーク容量の使用効率改善により、ページの読み込み時間を短縮する。
  • さらに…

HTTP/2 の新しいバイナリ フレーミング レイヤーは、HTTP/1.x で見つかったヘッドオブライン ブロッキングの問題を解決し、複数の接続がなくてもリクエストとレスポンスの並列処理および配信が可能です。 その結果、アプリケーションが高速化および簡素化され、デプロイのコストが下がります。

ストリームの優先順位

HTTP メッセージを多くの個別フレームに分割し、複数のストリームのフレームを多重化できる場合、クライアントとサーバーの両方でフレームを混在および配信する順序を考慮することが、パフォーマンス上重要になります。 これを実現するため、HTTP/2 標準では、各ストリームに関連する重みと依存関係を持たせています。

  • 各ストリームには、1~256 の整数の重みを割り当てることができます。
  • 各ストリームには、別のストリームに対して明示的な依存関係を指定できます。

ストリームの依存関係と重みを組み合わせて、クライアントで「優先順位ツリー」を構築および送信して、優先的に受信するレスポンスを指定できます。 次に、サーバーはこの情報を使用して、CPU、メモリ、その他リソースの割り当てと、さらにレスポンスのデータが使用可能になったら帯域幅の割り当てを制御することで、ストリームの処理の優先順位を付け、優先順位の高いレスポンスのクライアントへの配信を最適化します。

HTTP/2 ストリームの依存関係と重み

HTTP/2 内のストリームの依存関係は、親である別のストリームの一意の識別子を参照して宣言されます。ストリームに識別子がない場合、「ルート ストリーム」に依存していることになります。 ストリームの依存関係の宣言は、可能であれば、その依存関係よりも先に親ストリームを割り当てる必要があることを示します。 つまり、「レスポンス C の前にレスポンス D を処理して配信してください」ということです。

同じ親を共有するストリーム(つまり兄弟ストリーム)は、その重みに比例してリソースを割り当てる必要があります。 たとえば、ストリーム A が重み 12、その兄弟のストリーム B が重み 4 とします。各ストリームが受け取るリソースの比率を判断する手順は次のとおりです。

  1. すべての重みを合計します。4 + 12 = 16
  2. 各ストリームの重みを、重みの合計で除算します。A = 12/16, B = 4/16

したがって、ストリーム A はリソースの 3/4、ストリーム B はリソースの 1/4 を受け取ります。ストリーム B は、ストリーム A に割り当てられたリソースの 1/3 を受け取ることになります。上記の画像で、もう少し実践的な例をあげて説明します。 左から順番に説明します。

  1. ストリーム A も B も親の依存関係を指定しておらず、暗黙的に「ルート ストリーム」であることを示しています。A は重み 12、B は重み 4 です。したがって重みの比率に応じて、ストリーム B はストリーム A に割り当てられるリソースの 1/3 を受け取ります。
  2. ストリーム D はルート ストリームに依存し、C は D に依存します。したがって、D は C の前にすべてのリソースが割り当てられます。重みは考慮されません。C の依存関係のほうが優先的に伝えられるからです。
  3. ストリーム D は C より先にすべてのリソースが割り当てられます。C は A および B より先にすべてのリソースが割り当てられます。ストリーム B は ストリーム A に割り当てられるリソースの 1/3 を受け取ります。
  4. ストリーム D は E および C よりも先にすべてのリソースを割り当てられます。E と C は、A と B より先に同量のリソースを受け取ります。A と B はそれぞれの重みの比例してリソースを受け取ります。

上記の例で示すとおり、ストリームの依存関係と重みの組み合わせで、リソースの優先順位を表現できます。さまざまな依存関係や重みを持つ多数のリソースが存在するため、これはブラウジングのパフォーマンスを向上するために重要な機能です。 さらに HTTP/2 プロトコルでは、クライアントがいつでもプリファレンスを更新でき、ブラウザでさらに最適化できるというメリットもあります。 つまり、ユーザーのインタラクションやその他のサインに応じて、依存関係の変更や重みの再割り当てを行えます。

注: ストリームの依存関係と重みは、要件ではなく転送のプリファレンスを表します。したがって、特定の処理や転送順序は保証されません。 したがってクライアントは、ストリームの優先順位付けを使用して、サーバーに特定の順序でストリームを処理するよう強制することはできません。 これは直感的でないように思えるかもしれませんが、実際には望ましい動作です。 優先順位が高いリソースがブロックされている場合、サーバーがそれよりも優先順位の低いリソースを処理するのを阻害したくはないでしょう。

オリジンごとに 1 つの接続

新しいバイナリ フレーミングのしくみが導入されたことで、HTTP/2 でストリームを並列に多重化する場合、複数の TCP 接続は不要になりました。各ストリームを多くのフレームに分割し、そのフレームを混在させて優先順位を付けることができます。 その結果、すべての HTTP/2 接続が永続的になり、オリジンごとに必要な接続は 1 つだけになりました。これはパフォーマンス上、数多くのメリットをもたらします。

SPDY と HTTP/2 のどちらも、目玉となる機能は、適切な輻輳制御が可能な 1 つのチャンネルで任意の多重化が可能な点です。 これは非常に重要で、さらに驚くほどうまく動作します。 これを示す優れた指標の 1 つとして、1 つの HTTP トランザクションだけを伝送する接続を作成します(すると、このトランザクションがすべてのオーバーヘッドを引き受けます)。HTTP/1 では、アクティブな接続の 74% は、トランザクションを 1 つだけ伝送しており、永続的な接続は期待するほど役立っていません。HTTP/2 では、この数値が 25% まで下がります。これは、オーバーヘッドの大幅な削減につながります。(HTTP/2 is Live in Firefox、Patrick McManus)

ほとんどの HTTP 転送は短時間で突発的に起こりますが、TCP は長時間接続と一括データ転送に最適化されています。 同じ接続を再利用することで、HTTP/2 は各 TCP 接続の使用効率を高め、プロトコルの全体的なオーバーヘッドも大幅に削減します。 さらに、使用する接続数が減ることで、接続パス全体(つまり、クライアント、中間、オリジン サーバー)にわたって、メモリと処理のフットプリントも削減されます。 これにより運用コスト全体が削減され、ネットワークの使用率と容量が改善されます。 その結果、HTTP/2 への移行はネットワーク レイテンシを短縮するだけでなく、スループットの向上と運用コストの削減にもなります。

注: 接続数の削減は特に、HTTPS デプロイメントのパフォーマンスを改善するために重要な機能です。これにより、コストのかかる TLS ハンドシェイクが減少し、セッションの再利用が改善され、クライアントとサーバーで必要になる全体的なリソースも削減できます。

フロー制御

フロー制御は、センダーからレシーバーに望ましくない量のデータや処理できないデータを送信して負荷をかけることを防ぐしくみです。レシーバーの負荷が高くビジー状態だったり、または特定のストリームに所定のリソースを割り当てたいだけの場合もあります。 たとえば、クライアントが大容量の動画ストリームを高い優先順位でリクエストしても、ユーザーが動画を一時停止すると、クライアントは一時停止してサーバーからの配信を絞り込み、不要なデータの取得とバッファリングを回避しなければなりません。 または、プロキシ サーバーで高速のダウンストリーム接続と低速のアップストリーム接続がある場合、リソースの使用状況を制御するため、同様に、アップストリームの速度に合わせてダウンストリームのデータ配信速度を制限したい場合などがあります。

上記の要件で、TCP フロー制御を思い出しましたか?実質的にこの問題と同じですから、思い出されたことでしょう(フロー制御をご覧ください)。 ただし、HTTP/2 ストリームは 1 つの TCP 接続内で多重化されるため、TCP フロー制御では精度が不足しています。また、個々のストリーム配信を規制するために必要な、アプリケーションレベルの API が提供されていません。 この問題に対処するため、HTTP/2 はシンプルな一連の構成要素を提供しており、クライアントとサーバーで独自のストリームレベルおよび接続レベルのフロー制御が可能になります。

  • フロー制御には指向性があります。各レシーバーは、各ストリームおよび接続全体に必要な任意のウィンドウ サイズを設定できます。
  • フロー制御はクレジット方式です。各レシーバーは最初の接続とストリーム フロー制御ウィンドウ(バイト単位)をアドバタイズします。これは、センダーが DATA フレームを発行すると削減され、レシーバーによって送信された WINDOW_UPDATE フレームによって増加します。
  • フロー制御を無効にすることはできません。HTTP/2 接続はクライアントとサーバーが SETTINGS フレームをやり取りすることで確立され、双方向でフロー制御ウィンドウのサイズを設定します。 フロー制御ウィンドウのデフォルト値は 65,535 バイトに設定されますが、レシーバーは最大ウィンドウ サイズ(2^31-1 バイト)を設定でき、データを受信すると WINDOW_UPDATE フレームを送信して管理できます。
  • フロー制御はエンドツーエンドではなく、ホップバイホップです。そのため、独自の条件とヒューリスティックに基づいて、中間でフロー制御を使用してリソースの使用を制御したり、リソース割り当てのしくみを実装したりできます。

HTTP/2 では、フロー制御の実装には特定のアルゴリズムを指定していません。代わりに、シンプルな構成要素を提供し、クライアントとサーバーに実装を任せて、リソースの使用と割り当てを制限する独自の戦略を実装したり、実際のパフォーマンスや体感パフォーマンスを改善する新しい配信機能の実装に使用できます(ウェブ アプリケーションの速度、パフォーマンス、人の知覚)に関する記事をご覧ください。

たとえば、アプリケーション層のフロー制御では、ブラウザが特定のリソース部分だけを取得したり、フロー制御ウィンドウをゼロに下げることで取得を保留したり、後で再開したりできます。 つまり、ブラウザは画像のプレビューや最初のスキャンを取得して、それを表示したり、その他の優先度の高い取得を続行したり、より重要なリソースの読み込みが完了したら取得を再開したりできます。

サーバー プッシュ

HTTP/2 のもう 1 つの強力な新機能は、1 つのクライアント リクエストに対して複数のレスポンスを送信できるサーバーの機能です。 つまり、元のリクエストに対するレスポンスに加えて、サーバーは追加のリソースをクライアントにプッシュできます(図 12-5)。クライアントが各リソースを明示的にリクエストする必要はありません。

サーバーはリソースをプッシュするための新しいストリーム(Promise)を開始

注: HTTP/2 は、厳格なリクエスト / レスポンスのセマンティクスから離れて、1 対多のプッシュ ワークフローをサーバーから開始できるため、ブラウザの内部および外部で、新しいインタラクションの可能性が開けています。 これは、プロトコルについて、またその使用場所と使用方法を検討するにあたって、重要かつ長期的な影響があります。

なぜブラウザでこのようなしくみが必要なのでしょうか。一般的なウェブ アプリケーションは何十ものリソースで構成され、サーバーによって提供されたドキュメントをクライアントが調べることで、そのリソースを検出します。 それなら、余分なレイテンシを短縮し、前もって関連するリソースをサーバーがプッシュできるようにしてはどうでしょうか。 サーバーはすでに、クライアントがどのリソースを必要とするかを把握しています。それがサーバー プッシュです。

実際のところ、データ URI(リソースのインライン化を参照)を介して CSS、JavaScript、その他のアセットをインライン化している場合、すでにサーバー プッシュを実践済みということになります。 手動でリソースをドキュメントにインライン化すると、クライアントはリソースをリクエストする待ち時間がなくなり、事実上、リソースをクライアントにプッシュしていることになります。 HTTP/2 を使用すると、同じ結果を達成できるうえに、さらにパフォーマンス上のメリットがあります。 プッシュ リソースは次のように処理されます。

  • クライアントによってキャッシュされる
  • 別のページで再利用される
  • その他のリソースと多重化される
  • サーバーによって優先順位付けされる
  • クライアントによって拒否される

PUSH_PROMISE 101

すべてのサーバー プッシュ ストリームは、PUSH_PROMISE フレームを使用して開始されます。これは、サーバーが、記述されているリソースをクライアントにプッシュする意図があるというシグナルで、プッシュされたリソースをリクエストするレスポンス データの前に配信する必要があります。 この配信順序が重要です。クライアントは、サーバーがプッシュしようとしているリソースを把握し、リソースのリクエストが重複しないようにする必要があります。 この要件を満たすシンプルな戦略は、親のレスポンス(DATA フレーム)の前に、すべての PUSH_PROMISE フレームを送信することです。これには、約束したリソースの HTTP ヘッダーだけが含まれます。

クライアントが PUSH_PROMISE フレームを受け取ると、必要に応じて、そのストリームを拒否することができます(RST_STREAM フレームを使用)。 (リソースがすでにキャッシュに存在するなどの理由で、拒否することがあります。) これは、HTTP/1.x からの重要な改善です。 一方、リソースのインライン化の使用は、HTTP/1.x では人気の高い「最適化」方法でしたが、これは「強制プッシュ」と同等です。クライアントはインライン化されたリソースを個別に拒否、キャンセル、または処理することはできません。

HTTP/2 では、クライアントはサーバー プッシュの使用方法を完全に制御できます。クライアントは同時にプッシュされるストリームの数を制限したり、最初のフロー制御ウィンドウを調整して、ストリームを最初にオープンしたときにプッシュされるデータ量を制御したり、サーバープッシュを完全に無効化したりできます。 これらのプリファレンスは、HTTP/2 接続の開始時点で SETTINGS フレームを介して伝えられ、いつでも更新できます。

プッシュされた各リソースはストリームで、インライン化されたリソースと異なり、クライアントによって個別に多重化、優先順位付け、処理が可能です。 唯一のセキュリティ上の制限は、ブラウザによって適用されるとき、プッシュされたリソースは同じオリジンのポリシーに従う必要があります。サーバーには、提供されたコンテンツに対する権限が必要です。

ヘッダー圧縮

各 HTTP 転送には、転送されるリソースとそのプロパティを説明する一連のヘッダーが付加されています。 HTTP/1.x では、このメタデータは常にプレーン テキストで送信され、場所を問わず転送 1 回あたりに追加されるオーバーヘッドが 500~800 バイトで、HTTP Cookie が使用されている場合はキロバイト以上にのぼることもあります (プロトコル オーバーヘッドの測定と制御をご覧ください)。 このオーバーヘッドを削減してパフォーマンスを改善するため、2 つのシンプルかつ強力なテクニックを使用する HPACK 圧縮形式を使用して、HTTP/2 ではリクエストとレスポンスのヘッダー メタデータを圧縮します。

  1. これにより、転送されるヘッダー フィールドを静的ハフマン符号化を使用してエンコードし、個々の転送サイズを削減できます。
  2. クライアントとサーバーの両方で、以前に検出したヘッダー フィールドのインデックス化したリストのメンテナンスと更新が必要です(つまり、共有の圧縮コンテキストを確立します)。これは、以前に転送した値を効率的にエンコードするためのリファレンスとして使用されます。

ハフマン符号化を使用すると、転送時に個々の値を圧縮できます。また、以前に転送した値をインデックス化したリストを使用すると、インデックス値を転送することで、重複する値をエンコードできます。これは、効率的な検索とヘッダー全体のキーと値の再構築に使用できる場合があります。

HPACK:HTTP/2 のヘッダー圧縮

さらに最適化を進めるため、HPACK 圧縮コンテキストは静的テーブルと動的テーブルで構成されています。静的テーブルは仕様で定義され、すべての接続で使用する共通の HTTP ヘッダー フィールド(有効なヘッダー名など)のリストを提供します。動的テーブルは最初は空で、特定の接続でやり取りされた値に基づいて更新されます。 その結果、今まで検出されていない値には静的ハフマン符号化を使用し、いずれか一方ですでに静的テーブルまたは動的テーブルに存在する値には、値のインデックスを代わりに使用することで、各リクエストのサイズは削減されます。

注: HTTP/2 のリクエストおよびレスポンス ヘッダー フィールドの定義に変更はありませんが、若干の例外があります。すべてのヘッダー フィールド名が小文字になり、リクエストの行が個々の :method、:scheme、:authority、path pseudo-header フィールドになりました。

HPACK のセキュリティとパフォーマンス

HTTP/2 および SPDY の以前のバージョンでは、zlib とカスタムのディクショナリを使用して、すべての HTTP ヘッダーを圧縮していました。 これにより、転送されるヘッダー データのサイズが 85~88% 削減され、ページの読み込みレイテンシが大幅に改善されました。

低帯域幅の DSL リンクでは、アップロード リンクがわずか 375 Kbps であるため、特にリクエスト ヘッダーの圧縮は、特定のサイト(多数のリソース リクエストを発行しているサイト)で、ページの読み込み時間の大幅な短縮につながりました。ヘッダー圧縮だけで、ページの読み込み時間が 45~1,142 ミリ秒短縮されることがわかりました。(SPDY のホワイトペーパー、chromium.org)

ただし、2012 年の夏、"CRIME" セキュリティ攻撃が TLS および SPDY 圧縮アルゴリズムに対して公開されました。この攻撃により、セッションのハイジャックが起こる可能性があります。 そのため、zlib 圧縮アルゴリズムが HPACK に置き換えられました。これは特に、検出されたセキュリティの問題への対処、正しく実装するための効率とシンプルさ、そしてもちろん、HTTP ヘッダー メタデータの大幅な圧縮を可能にするために設計されています。

HPACK 圧縮アルゴリズムの完全な詳細については、https://tools.ietf.org/html/draft-ietf-httpbis-header-compression をご覧ください。

参考資料: