HTTP/2の裏側で何が起きているのか?CONTINUATIONフレームが担う「巨大ヘッダー」の寿命と実務的デバッグ手法
こんにちは。ネットワークの深淵を覗き続けて幾星霜、数々の不可解なパケットロスやレイヤー7の泥沼トラブルを潜り抜けてきたシニアアーキテクトの私だ。
Web APIの設計や、大規模インフラのパフォーマンスチューニングに携わる君たちなら、HTTP/2がもたらしたパラダイムシフト——TCPのヘッド・オブ・ライン・ブロック問題を華麗に回避する「マルチプレクシング」や、バイナリフレームによる効率的な通信の恩恵を日々感じていることだろう。
しかし、パケットキャプチャ(Wiresharkなど)を広げ、TCPストリームの奥底をじっと凝視したことがあるだろうか?
「なぜ、このリクエストは綺麗に分割されているんだ?」
「あれ、この見慣れない `CONTINUATION` というフレームは何だ?」
今日のテーマは、まさにその `CONTINUATION` フレームだ。教科書にはサラリとしか書かれていない、しかし実務の現場ではOAuthの肥大化したトークンや、複雑なCookieの氾濫によって確実に牙を剥く、HTTP/2ヘッダー分割の核心に迫ろう。
—
1. なぜ `CONTINUATION` フレームが必要なのか?(RFC 7540の現実)
HTTP/2の基本単位は「フレーム」だ。リクエストやレスポンスのメタデータ(ヘッダー)は `HEADERS` フレームという器に詰め込まれて流れていく。ここで一つの物理的な制約にぶつかる。
「一つの `HEADERS` フレームに収まりきらないほど、ヘッダーが巨大化したらどうするか?」
HTTP/2(RFC 7540)では、悪意ある巨大なフレームによるDoS攻撃を防ぐため、デフォルトで各実装が受け入れ可能なフレームの最大ペイロードサイズ(`SETTINGS_MAX_FRAME_SIZE`)を定めている。その下限は 16,384オクテット(16KB) だ。
現代のWebアプリケーションを見渡してみよう。
- 膨大なカスタム `X-` ヘッダー
- OpenID ConnectやSAMLでやり取りされる、数キロバイトに及ぶJWT(JSON Web Token)
- 無秩序に肥大化したCookieの山
これらを足し合わせていくと、16KBの壁など軽々と突破してしまう。ここで登場するのが `CONTINUATION` フレーム(フレームタイプ:`0x9`) だ。
データの流れ:HEADERSからCONTINUATIONへ
HTTP/2では、1つの論理的なヘッダーブロックを分割して送信することを許している。通信のフローとしては以下のような形になる。
1. `HEADERS` フレーム(または `PUSH_PROMISE`)の送信。
- ただし、この時点ではまだヘッダーブロックが完結していないため、フラグに `END_HEADERS`(0x4)を立てない。
2. `CONTINUATION` フレームの送信。
- 収まりきらなかった残りのヘッダーフラグメントをここに詰める。
- まだ足りなければ、さらに複数の `CONTINUATION` フレームをチェイン(連続)させる。
3. 最後に送信される `CONTINUATION` フレームには、`END_HEADERS` フラグを立てて、ヘッダーブロックの終端を明示する。
> ⚠️ 現場の鉄則(アーキテクトからの警告)
> `HEADERS` から始まった一連のヘッダー送信シーケンスが完結する(`END_HEADERS` を検知する)までの間、同じストリーム上で他のフレーム(例えば `DATA` フレームなど)を挟み込むことは絶対に許されない。もし途中に別フレームが割り込んだ場合、受信側は接続エラー(`PROTOCOL_ERROR`)としてコネクションを切断(RST_STREAM)しなければならない仕様になっている。
—
2. パラメーターとフラグの構造解析
Wireshark等でパケットを覗いた際に確認すべき、`CONTINUATION` フレームの構造とフラグを整理しておこう。
フレームヘッダーの構造(9オクテット)
- Length (24 bits): ペイロード(ヘッダーフラグメント)のサイズ。
- Type (8 bits): `0x9` (`CONTINUATION` を示す)
- Flags (8 bits): 後述するフラグ。
- Reserved (1 bit) + Stream Identifier (31 bits): どのストリームに属するかの識別子。
定義されているフラグ
`CONTINUATION` フレームで使用できるフラグは非常にシンプルだ。
| フラグ名 | 16進数 | 意味・役割 |
| :— | :— | :— |
| END_HEADERS | `0x4` | このフレームが、一連のヘッダーブロックの最後のフレームであることを示す。これが立つことで、受信側はHPACKデコーダを起動し、ヘッダーの確定処理を行う。 |
※ `PADDED` などのフラグは親である `HEADERS` フレーム側に依存することが多く、`CONTINUATION` 自体は主に `END_HEADERS` の有無が命綱となる。
—
3. 実務での検証:Python / curl でCONTINUATIONを観測する
机上の空論はここまでにして、実際に巨大なヘッダーを送りつけて、`CONTINUATION` が生成される瞬間をシミュレートしてみよう。
以下のPython(`httpx` または `hyper-h2` ライブラリベース)を用いて、あえて巨大なカスタムヘッダーを持つリクエストを構築するコードを見てほしい。
import httpx
16KBを超える巨大なカスタムヘッダーを生成する(ダミーのJWTやCookieを想定)
HTTP/2のデフォルトフレームサイズ上限(16KB)を超える長さをわざと作る
huge_token = “A” 18000
headers = {
“user-agent”: “Architect-Debug-Client/1.0”,
“x-massive-auth-payload”: huge_token
}
HTTP/2を強制してリクエストを送信するスクリプト
※接続先はローカルのnghttp2やテスト用HTTP/2サーバーを想定
url = “https://http2.golang.org/reqinfo”
print(“— 巨大ヘッダーを持つHTTP/2リクエストを送信します —“)
try:
with httpx.Client(http2=True) as client:
response = client.get(url, headers=headers)
print(f”レスポンスステータス: {response.status_code}”)
print(f”サーバー側が受け取った情報:\n{response.text[:500]}…”)
except Exception as e:
print(f”通信エラーが発生しました: {e}”)
💡 デバッグの現場から:curlを使う場合
手元の環境でパケットをキャプチャしながら試すなら、`curl` の `–http2` オプションと `-v`(verbose)または `–trace-ascii` を組み合わせるのが一番手っ取り早い。
巨大なヘッダーを付与してHTTP/2でリクエストを投げる
curl –http2 -v https://http2.golang.org/reqinfo \
-H “X-Custom-Data: $(python3 -c ‘print(“B” 20000)’)”
この通信を Wireshark でキャプチャしてみると、`HEADERS` フレームの直後に、同じ Stream ID を持った `CONTINUATION` フレームがミリ秒単位の隙間なく連続して流れていく美しい(あるいは恐ろしい)バイナリの波を確認できるはずだ。
—
4. インフラ・API設計におけるトラブルシューティングと対策
最後に、現場でこの `CONTINUATION` フレームが引き起こす「実際の障害」と、その対策についてシニアの視点から伝授しよう。
トラブル事例:「HTTP/2 CONTINUATION DoS 脆弱性(CVE-2019-9516)」
数年前に業界を震撼させた脆弱性を覚えているだろうか?
悪意あるクライアントが、`END_HEADERS` フラグを一切立てずに、無限に小さな `CONTINUATION` フレームを送り続けるという攻撃手法だ。サーバー側は、ヘッダーブロックが完了するまでメモリ上にデータを保持し続けなければならない仕様だったため、これによって容易にメモリリーク(OOMKilled)を引き起こし、Webサーバーがダウンした。
- 対策: 現在の主要なWebサーバー(Nginx, Apache, Envoy, Node.js等)やロードバランサーは、CONTINUATIONフレームの連続回数や合計サイズに厳格な制限(リミット)を設けている。もしインフラ層(リバースプロキシ)のチューニングを行う際は、デフォルトのセキュリティしきい値を勝手に緩めないこと。
API設計上のTips
もし君が設計しているWeb APIで、毎回数キロバイトにも及ぶ巨大なリクエストヘッダー(巨大な認証トークンや署名)が飛び交っているのであれば、それはプロトコルの仕組み云々の前に設計のアンチパターンだ。
1. Cookieやカスタムヘッダーのダイエット:
JWTは本当にそのサイズが必要か?ステートフルなセッションIDに切り替え、実態はRedisなどのインフラ側で保持するアーキテクチャへの移行を検討すべきだ。
2. プロキシ/WAFの制限に注意:
AWS ALBやCloudflareなどのクラウド型WAF/LBを挟む場合、あまりにもヘッダーが肥大化して `CONTINUATION` が多発すると、エッジ側で「HTTP Header Too Large (431)」や「502 Bad Gateway」として弾かれるケースがある。
—
まとめ
- `CONTINUATION` フレームは、16KBを超える巨大なヘッダーブロックを分割・安全に運ぶためのHTTP/2の裏方の主役である。
- `HEADERS` から `CONTINUATION` のチェインが完了する(`END_HEADERS` が現れる)までは、同一ストリームに他フレームの割り込みは一切許されない。
- 現場のデバッグでは、Wireshark等のパケットアナライザで `Type: CONTINUATION (9)` と `Flags: 0x04 (END_HEADERS)` の関係性を追うのがトラブルシューティングの定石。
ネットワークプロトコルは、私たちが普段意識しないレイヤーで、こうした緻密なバトンタッチを幾重にも行っている。この仕組みを解解し、パケットの呼吸を感じ取れるようになれば、君も立派なネットワーク・インフラのスペシャリストだ。
さあ、次のパケット解析の旅に出ようか。
コメント