この記事について
今さらだが、個人的に HTTP/2 の概要をまとめたもの。
HTTP/2 に至る背景
HTTP/1.x のコネクション管理
HTTP/1.x では、トランスポートのコネクション(通常はTCPコネクション)をどう使用/制御するか、HTTP メッセージ(リクエストとレスポンス)をどう送受信するかについて、3つのモデルがある。
- Short-lived
- HTTP リクエストを送信するたびに新しいコネクションを作成し、レスポンスを受信するとコネクションをクローズする
- HTTPの初期から存在するモデル (HTTP/1.0 のデフォルト)
- パフォーマンスやリソース消費に問題あり
- Persistent (Keep-Alive)
- コネクションはしばらく開いたままにして、いくつかのリクエストで再利用する。いわゆる Keep-Alive。
- HTTP/1.1 で導入 (HTTP/1.1 のデフォルト)
- 1つのリクエストへレスポンスが返らないと、次のリクエストを送れない (HOLが生じる)
- Pipeline
- 1つのコネクション上で連続してリクエストを送信できる(でも、レスポンスはリクエストされた順に従い返される)
- HTTP/1.1 で導入
- 結局、HOL の問題は残る
- 実装が難しく不具合のあるサーバーやプロキシも多いことから、ほとんどのブラウザでデフォルトで無効化されてる
これらの課題に対して、ブラウザは複数のTCPコネクションを開き、並列でリクエストを送ることで対処する(同時コネクション数は、1つのドメインに対して最大で6つ)。
さらに、コネクション数を増やしたい場合は、サーバー上のリソースを複数のドメインに分散して配置するという方法がある(ドメインシャーディング)。
しかし、サーバーのリソース消費・負荷増大とさらに、コンテンツ提供者が頑張らなければいけないという問題がある。
(参考)
HTTP/1.x の問題
Webサイトのコンテンツ数増加やサイズ増大に伴い、1つのページを表示するのに大量のリクエスト送信が必要になり、上記のコネクション管理とリクエスト送信の問題によるパフォーマンスの問題が顕著になってきた。
また他にも HTTP ヘッダーのサイズが増大してきたが(特に Cookie やアクセストークンが含まれる場合)、圧縮せずそのままテキストとして送受信するため不要なネットワークトラフィックが発生するという問題も生じてきた。
HTTP/2
目標
HTTP/1.1のパフォーマンス問題に対処して、ウェブページの読み込みレイテンシを短縮すること。Google の HTTP/2 の概要によると、HTTP/2 の元となった試験的プロトコル SPDY の目標は以下。
- ページ読み込み時間(PLT)の 50% 短縮を目指す。
- ウェブサイト作成者によるコンテンツ変更の必要性を回避する。
- デプロイメントの複雑さを最小限に抑え、ネットワーク インフラの変更を回避する。
- この新しいプロトコルをオープンソース コミュニティと協力して開発する。
- 実際のパフォーマンス データを収集し、試験的なプロトコルを検証(または無効化)する。
なお、HTTP/2 は IETF にて SPDY を元に改良を加え標準化したもの。
HTTP/2 のコア技術
コアとなる技術は フレーム と ストリーム。
- HTTP メッセージ(リクエスト/レスポンス)をテキストではなくバイナリで送受信するための「フレーム」と呼ばれる構造と、それを処理するフレームレイヤー
- 1つのTCPコネクション上に「ストリーム」と呼ばれる仮想的な経路を複数作成できるようになり、リクエスト/レスポンスのフレームを並列で処理する。
他にも、ヘッダー圧縮、サーバープッシュ、優先度付けなどの機能があるが順次説明。
フレーム
HTTP/2 では、1つの HTTP メッセージを複数のフレームというバイナリ構造に分解して送受信する。そして、受信側は受け取ったフレーム群をメッセージとして再構築する。後で述べるように、異なるメッセージのフレームを混在させて並行に送受信可能であり、このフレームの分解/混在/再構築が HTTP/2 で一番大事な機能。
それら処理を行うのがフレームレイヤーで Socket インターフェースとアプリケーションレイヤーの間に位置づけられる。
(HTTP/2 の概要から引用)
フレームは、HTTP/2 での通信の最小単位になり、送信するデータ種別や目的に応じて以下の種類が定義されている。
名称 | タイプ | 役割 |
---|---|---|
DATA | 0 | メッセージのボディを送信 |
HEADERS | 1 | メッセージのヘッダーを送信 |
PRIORITY | 2 | ストリームの優先度を指定 |
RST_STREAM | 3 | ストリームの終了要求 |
SETTINGS | 4 | コネクションの設定とそのACK |
PUSH_PROMISE | 5 | サーバプッシュのための事前通知 |
PING | 6 | アイドル状態のコネクションの確認 |
GOAWAY | 7 | コネクションの終了 |
WINDOW_UPDATE | 8 | フロー制御 |
CONTINUATION | 9 | フレームの継続送信 |
例えば、以下は GET リクエストを送る HEADERS フレームの例(Wireshark のキャプチャ)。
ストリーム
HTTP/1.x ではサーバーに対して複数の TCP コネクションを張って並行にメッセージを送受信していたが、HTTP/2 では1のドメイン(正確に言うとオリジン)に対しては1つの TCP コネクションしか張らない。
この1つの TCP コネクション上で、複数のフレームを同時に送受信するための仮想的な経路をストリームと呼ぶ。ストリームはクライアントとサーバーの双方向でデータ転送が可能。
次の特徴がある。
- クライアントとサーバーのどちからからもストリームを開始できる
- ストリームは整数のIDを割り当てて識別する
- クライアントは奇数、サーバーは偶数のIDでストリームを開始する(IDの重複を防ぐため)
- ただし、ID 0 はコネクションを制御するための特殊なストリームとして使われる
- フレームのヘッダーにはストリームIDが格納されている
- ストリームは同時に複数開始可能(上限は設定できる)
- ストリームはそれぞれ独立している(他のストリームに影響を与えない)
なお、先に添付した Wireshark の画面では、ストリームID 1 で通信している(Stream Identifier: 1
となっている個所)。
ストリームと異なるリクエストのフレームが多重化され送受信されるイメージは以下のような感じ。
(HTTP/2 の概要から引用)
ストリームの優先度
クライアント側はストリームを開始する際に、依存関係(他のどのストリームに依存するか)と重みを指定することにより、サーバーにストリームを処理する際の優先度を伝えることができる。これにより、サーバーはストリームの処理に必要なリソースの割り当てを変更できる。
ロジックについてはストリームの優先順位に分かりやすく説明されてる。
ただし、この優先度は強制力はなく、サーバー側で指定した通りに処理される保証は無い。
フロー制御
ストリームごとに、受信側が受信可能なデータ量を制御する仕組み。
TCP でもフロー制御は実装されているが HTTP/2 では TCP コネクションが1つのため、それだとストリーム単位でのフロー制御ができない。ストリーム単位のフロー制御は、例えば以下の目的で使用する。
- 特定のストリームがリソースを専有することを防ぐ
- 大容量ファイルのダウンロードや動画を見てる場合など
- Proxy サーバーにて、ダウンストリームとアップストリームの回線速度が大きく異なる場合に、低速な方に合わせて通信速度を制御したい
HTTP/2 のフロー制御には次の特徴がある。
- ウィンドウサイズの更新は、WINDOW_UPDATE フレームを用いて行う
- フロー制御は DATA フレームにのみ適用される
- ホップ間で行われる (End-to-Endではない)
サーバープッシュ
クライアントからの1つのリクエストに対して、サーバーから複数のレスポンスを返せる機能。例えば、HTML に対して GET リクエストが来た際、サーバーは、続いてその HTML に記述されているリソース(JavaScript, CSSなど)がリクエストされることを想定できる。
そういった場合に、サーバーが想定したリソースを予めクライアントに送る仕組み。余分な通信を削減してレイテンシーを短縮する。
次の特徴がある。
- サーバープッシュは PUSH_PROMISE フレームで事前通知してから開始される
- もしクライアント側でリソースがキャッシュ済みなどの理由でサーバープッシュが不要な場合は、それを拒否できる
ヘッダー圧縮
HTTP/2 では、HPACK という方式でリクエストとレスポンスのヘッダーを圧縮する。HPACK は HTTP/2 のために作られた圧縮方式。
なお、ヘッダーは HEADERS フレームで送信されるが、HTTP のメソッドやパス、レスポンスのステータスコードなども HEADERS フレームで送信される。
ヘッダー圧縮には次の特徴がある。
- 1度送信したヘッダーは基本的に再送信する必要は無く、差分のヘッダーだけを送信する
- ハフマン符号化を用いて、転送サイズを削減する
Appendix
HTTP の歴史 概要
-
HTTP/0.9 (1991年)
- 初版
- メソッドはGETだけ。レスポンスは単にドキュメントの内容を返すだけで、レスポンスコードの規定もない。
- ワンライン プロトコル
-
HTTP/1.0 (1996年)
- RFC 1945 で規定。
- メソッドに POST が追加
- レスポンスにヘッダーが付くようになり、ステータスコードが返るようになった
- コネクション管理は short-lived
-
HTTP/1.1 (1997年)
- RFC 2068 で規定。(その後、RFC 7230~7235で規定)
- 1.0から大幅に機能が追加される
- HOST ヘッダが追加され、バーチャルホストがサポートされた
- 新しく2つのコネクション管理モデル(persistent, pipeline)が導入された
(参考) HTTP の進化