このページの目次
HTTP/2
HTTP/2 は1つのTCP接続で複数の並列ストリームを処理する——フレーム多重化、HPACKヘッダー圧縮、サーバープッシュ。HTTP層でのヘッドオブラインブロックは解消されたが、TCP層へ移行した(パケット損失が全ストリームをブロック)。これがHTTP/3がQUICへ移行した理由である。
概要
HTTP/2(RFC 7540、2015年)はHTTPのセマンティクス(メソッド、ステータスコード、ヘッダー)を変更せず、トランスポート層を完全に書き換えた:テキストからバイナリフレームへ、1つのTCP接続からマルチプレクストストリームへ、ヘッダーの冗長性からHPACK圧縮へ。目標はHTTP/1.1のパフォーマンスボトルネック——ヘッドオブラインブロックとヘッダーの肥大化——を解決することだった。実装効果は顕著だが、TCP層には依然としてヘッドオブラインブロックが存在する(パケット損失が全ストリームをブロックするため)、この問題を解決するためにHTTP/3はQUICへ移行した。Server Pushは利用率が低いためChromeで削除された。
HTTP/2の目標
HTTP/1.1のパフォーマンスボトルネックを解決する:
- ヘッドオブラインブロック:1つのリクエストが遅い → 同じ接続上の全リクエストが待機する
- ヘッダーの冗長性:各リクエストで大きなヘッダー(Cookie、User-Agent、Accept)が繰り返される
- 接続数:ブラウザは並列化のために6〜8個のTCP接続を開く(リソースの浪費 + 非決定的な順序付け)
HTTP/2:マルチプレクシング + ヘッダー圧縮 + サーバープッシュ → 1つのTCP接続で全リクエストを効率的に処理。
バイナリフレーム
HTTP/2はバイナリプロトコルである(テキストではない):
フレーム形式(9バイトのヘッダー + 可変ペイロード):
Length(3バイト):ペイロードの長さ ≤ 2^14(16384、デフォルト)または 2^24-1(最大フレームサイズ)
Type(1バイト):DATA(0x0)、HEADERS(0x1)、PRIORITY(0x2)、RST_STREAM(0x3)、
SETTINGS(0x4)、PUSH_PROMISE(0x5)、PING(0x6)、GOAWAY(0x7)、
WINDOW_UPDATE(0x8)、CONTINUATION(0x9)
Flags(1バイト):タイプに依存
Reserved(1ビット)+ ストリーム識別子(31ビット):どのストリームか
DATAフレーム:
フラグ:END_STREAM(0x1)、PADDED(0x8)
HEADERSフレーム:
ペイロード:HPACK圧縮されたヘッダーブロック断片
フラグ:END_STREAM(0x1)、END_HEADERS(0x4)、PADDED(0x8)、PRIORITY(0x20)
SETTINGSフレーム(接続レベル):
ストリーム0でのみ送信:
SETTINGS_HEADER_TABLE_SIZE (0x1):HPACK動的テーブルの最大サイズ(デフォルト4096)
SETTINGS_ENABLE_PUSH (0x2):0=サーバープッシュ無効化(ほとんどのサーバーが実施)
SETTINGS_MAX_CONCURRENT_STREAMS (0x3):最大並列ストリーム数(推奨≥100)
SETTINGS_INITIAL_WINDOW_SIZE (0x4):初期フローコントロールウィンドウ(65535)
SETTINGS_MAX_FRAME_SIZE (0x5):最大フレームサイズ(16384-16777215)
SETTINGS_MAX_HEADER_LIST_SIZE (0x6):最大ヘッダーリストサイズ(バイト)
ストリームマルチプレクシング
1つのTCP接続 → 複数のストリーム(クライアント開始:奇数、サーバー開始:偶数):
ストリーム0:制御フレームのみ(SETTINGS、GOAWAY)
ストリーム1:GET /index.html
ストリーム3:GET /style.css ← 並列!ストリーム1の完了を待たない
ストリーム5:GET /script.js ← 並列
各ストリーム:
- 独立したフローコントロール(ストリームごとのWINDOW_UPDATE)
- ストリーム1でパケット損失 → TCP再送信 → ストリーム1が停止
→ 他のストリームへの影響:理論的にはなし(フレームレベルでのマルチプレクシング)
→ 実際:TCPパケット損失が全ストリームをブロック(これがHTTP/3が解決しようとしているTCP HOLブロック!)
HPACK
HPACK = 静的テーブル(61エントリ)+ 動的テーブル(接続ごと)+ Huffman符号化
静的テーブル(不変):
インデックス1::authority
インデックス2::method GET
インデックス3::method POST
インデックス4::path /
インデックス5::path /index.html
インデックス6::scheme http
インデックス7::scheme https
インデックス8::status 200
インデックス9::status 204
...
インデックス61:www-authenticate
動的テーブル(FIFO、最大サイズ = SETTINGS_HEADER_TABLE_SIZE):
各HEADERSレスポンス終了後 → 動的テーブルに格納(圧縮可能な場合)
→ 後のHEADERSリクエスト → インデックスのみ参照 → 極めて節約
gzip圧縮との比較(gzipを使わない理由):
- CRIME攻撃:gzipはヘッダーブロック全体を圧縮 → 圧縮率がCookieの内容を漏洩
- HPACK:ヘッダーごとの圧縮 → ヘッダー間での圧縮なし → 安全
サーバープッシュ(廃止済み)
HTTP/2 サーバープッシュ:
クライアント:GET /index.html
サーバー:index.htmlを返す + PUSH_PROMISE(style.css)+ push style.cssデータ
→ クライアントがstyle.cssを事前に受信 → ブラウザの解析 + GETを待たない
廃止された理由:
- キャッシュ問題:クライアントにstyle.cssが既に存在する可能性あり(キャッシュ)→ サーバープッシュは帯域幅の無駄
- 実際の利用率が低い:Chromeの統計ではプッシュされたアセットの<2%しか使用されていない
- 複雑さ:サーバーはクライアントのキャッシュ状態を知らない
Chrome 106以降、サーバープッシュは削除された
参考
- RFC: 7540, 7541, 9113(改訂版)
- ツール:
curl --http2 -v、nghttp2、Chromechrome://net-internals/#http2
キーワード: HTTP/2, マルチプレクシング, HPACK, バイナリフレーム, SETTINGS, ストリーム, フローコントロール, サーバープッシュ