RFC 9114: HTTP/3
HTTP/2が解けなかったTCPのヘッドオブラインブロッキングをHTTP/3がQUIC上でどう解消したかをRFC 9114で精読し、ストリーム対応付け・QPACK・Alt-Svc発見・0-RTTの注意点まで一気に把握できる。
- RFC 9114はHTTPの意味論をQUIC(RFC 9000)へ載せたHTTP/3を定義する。QUICはストリームごとに再送するため、TCPで1損失が全ストリームを止める転送層の頭詰まりを除く。
- 各要求・応答を1本のQUIC双方向ストリームで運ぶ。再送とフロー制御はQUIC、優先度は別RFCへ移し、ヘッダ圧縮はHPACKからQPACKへ置き換えた。
- h3はAlt-SvcやHTTPS RRで発見し、TLS 1.3内蔵のため平文版はない。ただしQPACKの動的テーブルはHTTP層で頭詰まりを起こし得るため、問題は消えず場所が移る。
このRFCが決めたこと
横にスクロール
RFC 9114(2022年6月)は、HTTP/3を定義した文書だ。メソッドやステータスコードの意味論はRFC 9110: HTTPセマンティクスに委ねたうえで、その意味論をRFC 9000: QUICというトランスポートの上でどう運ぶかを定める。意味論を別文書へ切り出す構図はRFC 9113: HTTP/2とまったく同じで、違うのは土台がTCPではなくQUICになった点だけだ。
なぜQUICへ移る必要があったのか。HTTP/2は1本のTCP接続に複数のストリームを多重化し、アプリケーション層の順番待ちは解消した。しかしTCPは全ストリームを1本の順序保証されたバイト列として運ぶため、パケットが1つ失われると、無関係なストリームのデータまで再送を待って止まる。これがトランスポート層のヘッドオブラインブロッキングであり、QUICが各ストリームに独立した順序空間を与えることで解消される(その仕組みはQUIC側の担当なので、詳細はRFC 9000の解説に譲る)。
その結果、RFC 9114自身は驚くほど薄い。多重化・フロー制御・損失回復・暗号化はすべてQUICが持つため、9114が定義するのは「HTTPをQUICのストリームへどう対応付けるか」という写像に絞られる。9113が多重化やフロー制御を自前で作り込んだのとは対照的だ。
GETの意味も404の意味もHTTP/1.1と変わらない。さらにHTTP/3では転送機構の多くもQUICの持ち物になったため、9114は意味論と転送の両側から仕事を引き算した、薄い写像仕様になっている。
要点の精読
リクエストは双方向ストリーム1本に対応する
HTTP/3では、クライアントが開くQUICの双方向ストリーム1本が1組のリクエストとレスポンスに対応する(リクエストストリームと呼ぶ)。最初のリクエストはストリーム0、以降は4、8と続く。応答は同じストリームで返るため、リクエストとの対応付けが自然に取れる。
ここで効いてくるのが、フレームからストリームIDが消えたことだ。HTTP/2はすべてのフレームヘッダに31ビットのストリームIDを持たせて多重化を実現していたが、HTTP/3ではどのQUICストリームに載っているかで所属が決まるので、フレーム側にIDを書く必要がない。並列性を絞りすぎないよう、サーバーは同時に少なくとも100本のリクエストストリームを許可することが推奨される。
制御用やQPACK用には、方向が一方向のユニディレクショナルストリームを使う。先頭に付く型で用途を区別し、制御ストリーム(0x00)、プッシュストリーム(0x01)、QPACKエンコーダ(0x02)、QPACKデコーダ(0x03)に分かれる。SETTINGSやGOAWAYといった接続全体の制御フレームは、この制御ストリームの上を流れる。
フレーム層はHTTP/2に似て、中身はQUICへ委譲された
HTTP/3のフレームは、型(可変長整数)と長さ(可変長整数)とペイロードだけの単純な構造だ。HTTP/2にあった9バイト固定ヘッダのうち、フラグとストリームIDが不要になり消えている。ストリーム終端はQUICが扱うのでEND_STREAMフラグも要らない。
HTTP/2 フレーム : | Length(24) | Type(8) | Flags(8) | R | Stream ID(31) | Payload |
HTTP/3 フレーム : | Type (i) | Length (i) | Payload | ← Flags も Stream ID も無い
定義されるフレームはDATA(0x00)、HEADERS(0x01)、CANCEL_PUSH(0x03)、SETTINGS(0x04)、PUSH_PROMISE(0x05)、GOAWAY(0x07)、MAX_PUSH_ID(0x0d)だ。名前はHTTP/2に似ているが、これは別のフレーミングレイヤーであり、意味も完全には一致しない。HTTP/2が自前で持っていた機能がQUICにもある場合、HTTP/3は再実装せず委譲する。その結果、いくつかのHTTP/2フレームはHTTP/3に存在しない。
| HTTP/2フレーム | HTTP/3での扱い |
|---|---|
| PRIORITY (0x02) | 廃止。HTTP/3に優先度を伝える手段はなく、RFC 9218へ分離された |
| RST_STREAM (0x03) | 廃止。ストリームの中断はQUICのRESET_STREAM/STOP_SENDINGが担う。空いた0x03はCANCEL_PUSHに転用 |
| PING (0x06) | 廃止。QUIC自身が同等の機能を持つ |
| WINDOW_UPDATE (0x08) | 廃止。フロー制御はQUICが提供する |
| CONTINUATION (0x09) | 廃止。HEADERSを分割せず1フレームに収められる |
消えたフレームの型番号は将来のためにHTTP/2と互換の位置で予約されており、HTTP/2由来のこれらの型を受け取ったらH3_FRAME_UNEXPECTEDという接続エラーになる。優先度についてはPRIORITYフレームを廃したうえで、urgency(0〜7)とincrementalだけの単純な方式がRFC 9218のPriorityヘッダとPRIORITY_UPDATEフレームとして別建てされた。9113で優先度ツリーが非推奨になった流れの延長線上にある。
QPACK:頭詰まりは消えず、HTTPレイヤーへ移った
ヘッダ圧縮はHPACKからQPACK(RFC 9204、2022年6月)へ置き換わった。理由は明快で、HPACKは送受信で動的テーブルを同期し続けるステートフルな方式であり、フレームが送った順に届くことを前提にしていた。QUICはストリーム間の順序を保証しないため、この前提が崩れる。
QPACKは動的テーブルへの変更を専用のエンコーダストリーム1本に集約し、そこだけは順序が保たれるようにした。各フィールドセクションはその時点のテーブル状態を参照するだけで、テーブルを書き換えない。静的テーブルもHPACKの61エントリからHTTP実務に合わせた99エントリへ増えている。
問題は、動的テーブルを参照した結果だ。あるヘッダがまだ届いていない動的テーブルのエントリを参照していると、そのストリームは必要なエントリが届くまでデコードできない。これが「ブロックされたストリーム」であり、SETTINGS_QPACK_BLOCKED_STREAMS(既定値0)がその上限を定める。エンコーダは確認応答済みのエントリだけを参照する、あるいは静的テーブルとリテラルだけで組み立てるという保守的な戦略で、ブロックを完全に避けることもできる。ただし圧縮効率は下がる。
QUICが解消したのはトランスポート層の頭詰まりだ。一方でQPACKが動的テーブルを攻めて参照すると、HTTPレイヤーで新たな頭詰まりが生じうる。HTTP/3で頭詰まりが完全に消えたと考えるのは誤りで、残余のリスクをどこに置くかがQPACK設定と符号化戦略の勘所になる。
h3の発見:平文h3は無く、Alt-SvcやHTTPS RRで知る
HTTP/3はいきなり話し始められない。クライアントはまずHTTP/1.1や/2で接続し、そこでh3が使えると教わってから移る。教える手段の一つがAlt-Svc(RFC 7838)で、レスポンスに Alt-Svc: h3=":50781" のように書けば、同じホストのUDP 50781番でh3が使えると伝わる。TLSハンドシェイクのALPNで合意するトークンは h3 だ。
Alt-Svcは一度TCPで接続してから知る仕組みなので、初回接続の往復が一度は要る。これを前倒しするのが後発のHTTPS RR(RFC 9460、2023年11月)というDNSレコードで、名前解決の段階でh3対応やポートを知れるため、初回からh3で入れる。これはRFC 9114自身が定めた仕組みではなく、9114(2022年6月)より後に標準化された補完的なDNS機構である点に注意したい。
平文のh3は存在しない。http スキームのURIへHTTP/3で直接アクセスする方法はなく、HTTP/1.1のUpgradeでh3へ移る仕組みも用意されていない。QUICのバージョン1はTLS 1.3(RFC 8446)を必須とするため、暗号化は選択肢ではなく前提になっている。UDPが遮断されて接続できない場合は、クライアントはTCPベースのHTTP/2以下へフォールバックする(各版の位置付けはHTTPのバージョンを参照)。
DNSでh3を先に知る仕組みは魅力的だが、RFC 9114が規定するのはAlt-Svcまでだ。HTTPS RR(RFC 9460)は独立した後発の標準であり、「9114がDNS発見を定めた」と誤解しないこと。
0-RTTのリプレイとサーバープッシュ
QUICの0-RTTを使うと、再接続時に最初のパケットへリクエストを同乗させて往復を1つ省ける。ただしこのデータは攻撃者が盗聴して複製・再送できるため、RFC 9114は0-RTT利用時にRFC 8470(Using Early Data in HTTP)のアンチリプレイ対策を必須とする。載せてよいのは再生されても害のない安全なメソッドに限られる。加えてクライアントは、前回サーバーから受け取ったSETTINGSを覚えておき、0-RTT中はその値に従わなければならない。
サーバープッシュも仕様には残っている。ただしHTTP/3ではクライアントがMAX_PUSH_IDフレームを送るまでサーバーは一切プッシュできず、既定では1件も許可されない。しかもサーバーはブラウザのキャッシュ状態を知らないという根本問題はHTTP/2と同じで、Chromeが2022年にHTTP/2プッシュ対応を打ち切った経緯と同様に、HTTP/3プッシュも実務ではほとんど使われていない。
つまずきやすい点
まず「QUICにしたからヘッドオブラインブロッキングは消えた」と単純化しないこと。消えたのはトランスポート層の頭詰まりで、QPACKが動的テーブルを攻めれば同種の待ちがHTTPレイヤーに残る。次に、優先度をHTTP/2の依存ツリー前提で設計しないこと。HTTP/3に優先度シグナルはなく、正解はRFC 9218のurgency/incrementalだ。そして平文h3やUpgradeでの移行は存在しない。QUICはTLS 1.3必須で、UDPが通らなければTCPへ戻るのが唯一の逃げ道になる。最後にサーバープッシュは、有効化してもMAX_PUSH_IDをクライアントが送らなければ動かず、実運用では死んでいるに等しいと見ておくのが安全だ。
まとめ
RFC 9114は、RFC 9110の意味論をQUICの上へ写し取り、HTTP/2が自前で抱えていた多重化・フロー制御・優先度・ストリーム中断をトランスポートへ委譲した、薄いが要となる写像仕様だ。双方向ストリームへの対応付け、フラグとストリームIDを削ったフレーム、QPACK、Alt-Svcによる発見という部品を押さえれば、HTTP/2との差分と「頭詰まりの移動」という本質が見通せる。他のRFC解説はRFC精読から辿れる。
RFC精読の記事ガイド
RFC 9114: HTTP/3を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
RFC
比較で見る軸
難易度: advanced / カテゴリ: RFC精読 / タグ数: 5
導入後に効く点
各要求・応答を1本のQUIC双方向ストリームで運ぶ。再送とフロー制御はQUIC、優先度は別RFCへ移し、ヘッダ圧縮はHPACKからQPACKへ置き換えた。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- RFC精読
- タグ数
- 5
判断チェックリスト
- 自社の用途が「RFC / HTTP」に近いか確認する。
- 強みである「RFC 9114はHTTPの意味論をQUIC(RFC 9000)へ載せたHTTP/3を定義する。QUICはストリームごとに再送するため、TCPで1損失が全ストリームを止める転送層の頭詰まりを除く。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。