【実務・中級編】HTTP/2におけるCONTINUATIONフレームの役割 – HTTPプロトコル・通信規格実践ガイド

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)` の関係性を追うのがトラブルシューティングの定石。

ネットワークプロトコルは、私たちが普段意識しないレイヤーで、こうした緻密なバトンタッチを幾重にも行っている。この仕組みを解解し、パケットの呼吸を感じ取れるようになれば、君も立派なネットワーク・インフラのスペシャリストだ。

さあ、次のパケット解析の旅に出ようか。

コメント

タイトルとURLをコピーしました