【実務・中級編】HTTP/1.1のExpect: 100-continueのフロー – HTTPプロトコル・通信規格実践ガイド

なぜ「大きな荷物」を運ぶ前に確認が必要なのか? — 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のレイテンシで悩んだときは、ぜひこの「握手」のプロセスを思い出してほしい。

コメント

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