HTTP/2
HTTP/2 ・ えいちてぃーてぃーぴーつー
1本のTCP接続に複数のやり取りを多重化し、HTTP/1.1の待ち行列問題を解いた改訂版。
✦ 30秒で試す — 航海ブラウザは同じ相手に、わざわざ6本も回線を張っていた遅い6個以外の14個が、早くそろうのはどっち?予想してみる概要
HTTP/2 は、Web の土台である HTTP を約15年ぶりに大改訂したプロトコルで、2015年に標準化されました。意味論(メソッドやステータスコード、ヘッダの意味)は HTTP/1.1 のまま変えず、「メッセージをどう運ぶか」という転送の仕組みだけを根本から作り直したのが特徴です。アプリケーション開発者から見ると同じ HTTP に見えるのに、裏側の運び方が別物になっている、と言えます。
最大の変更は「多重化(マルチプレクシング)」です。HTTP/1.1 では1本の接続の上で1度に1つのやり取りしかできませんでしたが、HTTP/2 では1本の TCP 接続の中に「ストリーム」と呼ばれる論理的な通り道を何本も作り、多数のリクエストとレスポンスを同時並行で流せます。現在では主要ブラウザと大半の大手サイトが対応しており、HTTPS でアクセスするサイトの多くは気づかないうちに HTTP/2 で通信しています。
なぜ生まれたか
HTTP/1.1 の基本は「1つの接続で、リクエストを送り、レスポンスを待ち、返ってきたら次を送る」という逐次の往復です。ところが Web ページはどんどんリッチになり、1ページの表示に画像・CSS・JavaScript など数十から百以上のリソースを取得するのが当たり前になりました。逐次処理では、先頭のレスポンスが遅いと後続がすべて待たされます。これが HTTP レベルの「Head-of-Line(HOL)ブロッキング」、いわば隊列先頭の閉塞です。
ブラウザは対策として1ドメインあたり6本程度の接続を並列に張りますが、それでも足りず、開発者側は「画像サーバを img1 と img2 のサブドメインに分けて接続数の上限を稼ぐ」(ドメインシャーディング)、「小さい画像を1枚に結合する」(CSSスプライト)、「多数のJSファイルをバンドラで1つに固める」といった苦肉の策を積み上げていました。いずれもプロトコルの制約を回避するためだけの複雑さです。2009年に Google が実験プロトコル SPDY を発表して「1本の接続に多重化すれば速くなる」ことを実証し、それを土台に IETF が標準化したのが HTTP/2(RFC 7540、2015年。現在の仕様は RFC 9113)です。
詳細
バイナリフレーミングとストリーム
HTTP/1.1 のメッセージは人間が読めるテキストでしたが、HTTP/2 は同じ中身を「フレーム」というバイナリの小片に分解して運びます。ヘッダは HEADERS フレーム、本文は DATA フレームになり、各フレームには所属するストリームの番号が付きます。受信側は番号を頼りにフレームを組み立て直すので、複数のリクエスト・レスポンスのフレームを1本の接続上で自由に混ぜて送れます。これが多重化の正体です。クライアントが始めるストリームには奇数の番号(1, 3, 5…)が振られ、1本の接続の中で同時に開いておけるストリームの数は、互いに SETTINGS フレームで上限を伝え合います(100 前後にすることが多い)。テキストの解析より高速で曖昧さがないことも、バイナリ化の利点です。
遅い応答が1つあるとき、何が起きるか
違いがはっきり出るのは、応答の遅いリソースが混ざったときです。HTTP/1.1 の接続は、応答が返ってくるまで次のリクエストを送れません(パイプライン化という「応答を待たずに次々送る」拡張も仕様にはありましたが、応答は送った順に返す決まりなので先頭が遅いと結局詰まり、中継機器の不具合も多くて、主要ブラウザは有効にしませんでした)。そのため、遅い応答は接続を1本握ったまま離しません。ブラウザは同じ相手に6本前後の接続を張り、空いている接続に次のリクエストを回すので、1本が握られても残りが肩代わりします。けれども遅い応答が6本すべてを握ると、後ろのリクエストは送ることすらできずに待たされます。
HTTP/2 では、遅い応答はそのストリームが遅れるだけです。サーバは用意のできたストリームのフレームから順に回線へ載せるので、ほかの応答は遅いストリームを追い越して届きます。
下の部品で確かめてください。上の棒は20個のリソースのサーバでの処理時間です。棒をつかんで上へ引っぱると、その応答が遅くなり、2つの方式で同じページを同時に読み込み直します。遅い棒を1本、3本、6本と増やしていくと、HTTP/1.1 のほうだけが崩れていく様子が見えます。
上の棒は、20 個のリソースそれぞれのサーバの処理時間です。まず棒 1 をつかんで上へ引っぱり、遅い応答を1つ作ってください。離すと、2つの方式で同時に読み込みが始まります。
ヘッダ圧縮 HPACK と優先度
HTTP のヘッダは Cookie や User-Agent など毎回ほぼ同じ内容が繰り返し送られ、リクエスト数が多いほど無駄が積み上がります。HTTP/2 は HPACK という専用の圧縮方式を導入し、送信済みのヘッダを送受信の双方が「辞書」として記憶して、2回目以降は短い番号で参照します。これにより数百バイトのヘッダが数バイトに縮むこともあります。gzip のような汎用圧縮を使わない専用設計なのは、圧縮と秘密情報の混在を突く CRIME という攻撃への対策でもあります。
また、多重化したことで「どのストリームに帯域を優先して割り当てるか」という新しい問題が生まれ、優先度(プライオリティ)の仕組みが用意されました。ブラウザレンダリングを止めてしまう CSS を画像より先に、といった制御が意図されましたが、ストリームの依存関係を木で表す仕組みは複雑すぎて実装のばらつきが大きく、RFC 9113 で非推奨になりました。いまは緊急度(urgency)と逐次かどうかをヘッダで伝える、よりシンプルな Extensible Priorities(RFC 9218)が使われています。同じく鳴り物入りだった「サーバプッシュ」(リクエストされる前にサーバがリソースを送りつける機能)も、キャッシュとの相性の悪さから効果が出しにくく、主要ブラウザでは削除されました。
残った限界 — TCPレベルのHOLブロッキング
HTTP/2 は HTTP レベルの待ち行列を解消しましたが、その下の TCP には「バイト列を順番どおりに届ける」という性質があります。1本の TCP に全ストリームを載せたため、パケットが1つ失われると、TCP が再送を待つ間、そのパケットと無関係なストリームのデータまで全部が足止めされます。つまり隊列閉塞が HTTP 層から TCP 層に移っただけ、という側面が残ったのです。パケットロスの多いモバイル回線では HTTP/1.1 の複数接続より遅くなるケースさえ報告されました。この限界が、TCP そのものを置き換える HTTP/3とQUIC の開発動機になります。
実務への影響 — 最適化の常識が変わった
HTTP/2 の普及は開発のベストプラクティスも変えました。接続数の節約が目的だったドメインシャーディングは、むしろ接続を分断してヘッダ圧縮や輻輳制御の効率を下げるため不要(むしろ有害)になりました。ファイル結合も「多数の小さいリクエストが安くなった」ことで必須ではなくなりましたが、圧縮効率やモジュール解決のオーバーヘッドの観点からバンドラ自体は今も使われており、その意義が「リクエスト数削減」から「変換と最適化」へ変質した、と捉えるのが正確です。
なお、仕様上 HTTP/2 は平文でも動きますが、主要ブラウザはすべて TLS 上でのみ対応しているため、事実上 HTTPS が前提です。導入自体はリバースプロキシや CDN の設定で有効化するだけのことが多く、アプリケーションコードの変更はほぼ不要です。
試問
画面の上段の棒は、20個の部品それぞれのサーバでの処理時間です。棒をつかんで上へ引っぱると、その部品の返事が遅くなり(金の棒)、指を離すと HTTP/1.1(接続6本、1本では返事が来るまで次を送れない)と HTTP/2(接続1本に混ぜて流す)で同じページを同時に読み込みます。
上の棒は、20 個のリソースそれぞれのサーバの処理時間です。まず棒 1 をつかんで上へ引っぱり、遅い応答を1つ作ってください。離すと、2つの方式で同時に読み込みが始まります。
