「HTTPの境界線」を見誤るな:Content-Lengthが握るWeb通信の生死
ネットワークの現場で「なぜかレスポンスが途中で切れる」「ロードバランサーがエラーを吐く」といった怪奇現象に遭遇したことはないだろうか?
多くのエンジニアがHTTPを「リクエストを投げてレスポンスを待つだけの単純なやり取り」だと考えている。だが、その裏側では、OSやミドルウェアが必死になって「どこまでがこのパケットのメッセージなのか」を見極めようとしているんだ。
その判断基準の要(かなめ)となるのが、今回解説する `Content-Length` ヘッダーだ。これは単なる情報の付記ではない。ネットワークの境界を定義する、極めて重要な「国境線」なんだ。
—
1. なぜ「長さ」を宣言しなければならないのか?
HTTP/1.1において、TCPコネクションは再利用(Keep-Alive)されるのが前提だ。昔のHTTP/0.9のように「接続して、データを送って、切断する」という乱暴な手法では、毎回TCPの3ウェイ・ハンドシェイクが発生し、オーバーヘッドが大きすぎる。
しかし、コネクションを繋ぎっぱなしにすると、受信側のアプリケーションは「どこで一つのメッセージが終わり、どこからが次のメッセージの始まりなのか」を判別できなくなる。
ここで登場するのが `Content-Length` ヘッダーだ。
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 28 <-- 受信側はこのバイト数だけを読み込む
{"status": "success", "id": 1}
受信側(ブラウザやリバースプロキシ)は、このヘッダーを見て「28バイト読み込んだら、このメッセージは完了だ。次は次のリクエスト(あるいは応答)の処理に移ろう」と判断する。もしこの数値が間違っていれば、通信は即座に崩壊する。
---
2. 境界判定の不整合が招く「セキュリティの穴」
この `Content-Length` を巡る不整合は、しばしばセキュリティ事故の温床になる。特に厄介なのが、HTTP Request Smuggling(HTTPリクエスト・スマグリング)という攻撃手法だ。
例えば、フロントエンドのリバースプロキシ(Nginxなど)と、バックエンドのアプリケーションサーバーで「どこまでが1つのリクエストか」の解釈が異なると、攻撃者は悪意のあるリクエストを紛れ込ませることができる。
- ケースA: プロキシは `Content-Length` を優先して境界を判断する。
- ケースB: バックエンドは `Transfer-Encoding: chunked` を優先する。
この「解釈のズレ」を突かれると、あるユーザーのリクエストの断片が、次のユーザーのリクエストにくっついて処理されてしまう。これが、認証情報の漏洩や不正なコマンド実行に繋がるんだ。
—
3. 実務で確認する:cURLとPythonによる境界判定テスト
理論を理解したら、次は手を動かそう。まずは `curl` を使って、意図的に不正な `Content-Length` を送った時の挙動を観察してみるのが一番だ。
curlで意図的にヘッダーを操作する
Content-Lengthを実際のボディより短く指定してみる
curl -v -X POST http://localhost:8080/api \
-H “Content-Length: 5” \
-d “Hello, World!”
結果:サーバーは5バイト分だけ読み込み、残りの “World!” は無視するか、
次のリクエストの先頭として誤認する可能性がある。
Pythonで「正しくない長さ」をシミュレートする
API開発の際、動的にボディを生成するコードを書くこともあるだろう。その際、`Content-Length` を手動で計算するなら、必ずバイト数(`len(body.encode(‘utf-8’))`)で行うこと。文字数(len())で計算してマルチバイト文字を含めると、即座に整合性が崩れる。
import requests
不適切な長さの計算例(マルチバイト文字でバグる)
body = “こんにちは”
間違い: len(body) は 5 だが、UTF-8では 15バイトになる
headers = {‘Content-Length’: str(len(body))}
正しい計算方法
data = body.encode(‘utf-8’)
headers = {‘Content-Length’: str(len(data))}
response = requests.post(‘https://example.com/api’, data=data, headers=headers)
—
4. 現場のシニアとしてのアドバイス:トラブルシューティングの心得
もし君が現場で「レスポンスが途中で切れる」という相談を受けたら、まずは以下の3点を疑ってほしい。
1. Content-Length の不一致: バックエンドが動的に生成したメッセージのバイト数と、ヘッダーの値がズレていないか?
2. チャンク転送(Transfer-Encoding: chunked)との競合: `Content-Length` と `Transfer-Encoding` が同時に存在していないか?(RFC 7230では、`Transfer-Encoding` が優先される規定だ)
3. プロキシによる書き換え: フロントのロードバランサーやWAFが、レスポンスを加工する際に `Content-Length` を更新し忘れていないか?
特に、ロードバランサーの設定ミスで「ボディを圧縮したのにヘッダーの値を更新していない」というケースは、ベテランでも見落としがちな落とし穴だ。
最後に
HTTPは進化し、HTTP/2やHTTP/3ではフレーム構造によって境界判定がより堅牢になった。だが、依然としてレガシーなインフラや内部APIではHTTP/1.1が現役だ。
「パケットがどのように区切られているか」という想像力を持つこと。それが、ネットワークアーキテクトとして一流の仕事をするための第一歩だ。現場で困ったときは、まずはパケットキャプチャを開き、`Content-Length` と実際のボディのバイト数を1バイト単位で突き合わせてみてほしい。答えは必ずそこに書かれている。
コメント