なぜ「大きな荷物」を運ぶ前に確認が必要なのか? — HTTP/1.1 `Expect: 100-continue` の真実
ネットワークエンジニアの端くれとして、これまで数え切れないほどの「謎のタイムアウト」や「不可解なパケットロス」と対峙してきた。その中でも、Web APIのパフォーマンスチューニングや大規模なデータ転送の現場で、意外と軽視されがち、あるいは誤解されがちなのが `Expect: 100-continue` という仕組みだ。
教科書には「サーバーにボディを送る前に許可を取るためのもの」とさらりと書いてあるが、現場ではこの「事前確認」が命取りになることもあれば、救世主になることもある。今日は、このプロトコル上の「握手」について、パケットの裏側を覗きながら深掘りしていこう。
—
1. なぜこの仕組みが必要なのか?(ユースケース)
想像してほしい。あなたが数GBの巨大なファイルをAPI経由でアップロードしようとしている。クライアントは何も考えずにPOSTリクエストのボディを投げ始めた。しかし、サーバー側は「認証エラー」や「リクエストサイズ制限オーバー」で、その数秒後には接続を切断する。
…なんという時間の無駄だろうか。
`Expect: 100-continue` は、この「無駄なデータ転送」を防ぐための紳士協定だ。クライアントはまず「これから大きい荷物を送るけど、受け取れる?」とヘッダーだけで問いかけ、サーバーが「ああ、いいよ(100 Continue)」と答えてから初めてボディを送信する。このフローにより、不要な帯域消費とサーバーリソースの浪費を劇的に抑えられる。
—
2. 通信シーケンスのリアル
この挙動をパケットキャプチャで見ると、非常に理にかなった動きをしていることがわかる。
1. クライアント: `Expect: 100-continue` ヘッダーを含むリクエストを送信。
2. サーバー:
- 許可する場合: `HTTP/1.1 100 Continue` を返す。
- 拒否する場合: `4xx` や `5xx` ステータスコードを即座に返す。
3. クライアント: サーバーから `100 Continue` が来たら、ボディの送信を開始する。
注意すべきポイント:タイムアウトの罠
仕様上、クライアントは `100 Continue` を永遠に待つわけではない。サーバーからの応答が遅いと、クライアントは「このサーバーは100 Continueを知らない(あるいは反応が遅い)」と判断し、待機を打ち切ってボディの送信を強行する実装が多い。この「待機時間」のチューニングが、高負荷時の安定性に大きく関わってくる。
—
3. 実践:コードで確認する「Expect」の挙動
理論だけでは現場は回らない。実際にどう動くのか、`curl` と `Python` で確認しよう。
curl での検証
`curl` はデフォルトでこの挙動をサポートしている。`-v` オプションでヘッダーを見ると、サーバーとのやり取りが手に取るようにわかる。
巨大なデータを送るシミュレーション
-v でHTTPヘッダーのやり取りを確認する
curl -v -X POST -H “Expect: 100-continue” \
-d “巨大なデータ本体…” \
http://example.com/api/upload
実行結果を見ると、`> Expect: 100-continue` を送信した後、サーバーから `< HTTP/1.1 100 Continue` が返ってきてから、その後に ` We are completely uploaded and fine` と続くプロセスが確認できるはずだ。
Python (requests) でのハンドリング
実は `requests` ライブラリは、デフォルトで `Expect: 100-continue` をよしなに処理してくれる。だが、独自のAPIクライアントを書く際は、あえてこのヘッダーを明示的に外す必要がある場合もある。
import requests
url = “http://example.com/api/upload”
data = b”…” 1024 1024 # 巨大なデータ
必要に応じてExpectヘッダーを制御する
headers = {
“Expect”: “100-continue”
}
サーバー側がこのヘッダーに対応していない場合、
余計な待機時間が発生するため、あえて空にするケースもある
headers = {“Expect”: “”}
response = requests.post(url, data=data, headers=headers)
print(f”ステータスコード: {response.status_code}”)
—
4. インフラエンジニアが知っておくべきトラブルシューティング
最後に、現場で泣きを見ないためのTipsを置いておく。
- L7ロードバランサーの挙動:
AWSのALBやNginxなどのプロキシを介している場合、`Expect: 100-continue` が正しくプロキシされるか確認が必要だ。特に古いミドルウェアや特定の設定では、100 Continueを適切に転送せず、クライアントとサーバーの間でデッドロックのような待機が発生することがある。
- 「とりあえず外す」という選択肢:
もしAPIのレスポンスが妙に遅いと感じたら、一度 `Expect` ヘッダーを空にしてみることを勧める。特に、サーバー側が `100-continue` の処理を省略して即座にボディを読み込みたい設計の場合、このヘッダーが逆にオーバーヘッド(余計なRTT)になるからだ。
- デバッグ手順:
1. `tcpdump` または `Wireshark` でクライアント・サーバー間のパケットを確認する。
2. `100 Continue` が返ってきた直後にデータの転送が始まっているか(TCPセグメントのシーケンス番号を見て)確認する。
3. もしタイムアウトが発生しているなら、サーバー側の `Expect` 処理のロジックを見直す。
—
まとめ
`Expect: 100-continue` は、ネットワークの古典的な「礼儀作法」だ。しかし、現代の高速なAPI環境においては、その礼儀が時に「待ち時間」という名の足かせになることもある。
プロトコルの仕様を知った上で、「使うべき場所」と「外すべき場所」を見極める。それこそが、エンジニアとしての経験値であり、真のインフラアーキテクトに求められるセンスだ。次にAPIのレイテンシで悩んだときは、ぜひこの「握手」のプロセスを思い出してほしい。
コメント