【実務・中級編】HTTP/1.1のExpect: 100-continueヘッダーの挙動と最適化 – HTTPプロトコル・通信規格実践ガイド

現場のエンジニアに捧ぐ:「Expect: 100-continue」という名の慎重な握手

ネットワークのトラブルシューティングをしていると、たまに「なぜかリクエストが妙に遅い」「特定の状況下でだけパケットが数秒間沈黙する」といった現象に遭遇する。ログを追うと、サーバー側で処理が詰まっているわけでもなく、単にクライアントが何かを「待っている」。

その犯人が、HTTP/1.1でひっそりと、しかし重要な役割を担うヘッダー `Expect: 100-continue` であることは意外と多い。今日は、この一見地味な機能が何を意図し、なぜ時に「現場のボトルネック」になるのかを、プロトコルの深淵から紐解いていこう。

—

1. なぜ「100-continue」が必要なのか?

想像してほしい。あなたが数ギガバイトにも及ぶ巨大なファイルを、認証が必要なAPIにPOSTしようとしているとしよう。

もし、送信を開始した瞬間にサーバーから「認証エラー」や「リクエストボディが大きすぎる」といった拒絶応答が返ってきたらどうなるか? クライアントは貴重な帯域を消費し、無駄なTCPセグメントを投げ続けた挙句、結局エラーを受け取る。これはリソースの浪費だ。

そこで登場するのが `Expect: 100-continue` だ。このヘッダーを付けてリクエストを送ると、クライアントはこう宣言する。

「これから大きなデータを送るけれど、先にヘッダーだけ見て受け入れる準備があるか教えてくれないか?」

サーバーが「OKだ、送ってくれ」となれば `100 Continue` を返し、クライアントはそこで初めてボディの送信を開始する。これが、効率的な通信のための「握手」というわけだ。

—

2. 通信シーケンスのリアル

この挙動をパケットキャプチャで追うと、以下のようになる。

1. Client: `POST /upload`
`Expect: 100-continue`
`Content-Length: 1073741824`
(ボディはまだ送信しない)
2. Server: `HTTP/1.1 100 Continue`
(サーバーが「準備よし」と応答)
3. Client: (ここで初めて実データを送信)
4. Server: `HTTP/1.1 200 OK`

このフローの美しい点は、サーバーが拒絶すべき(4xxエラーなど)場合、ステップ2の段階で即座に応答を返せることだ。これにより、クライアントは無駄なデータ送信を回避できる。

—

3. 実務上の罠:待機時間という名の落とし穴

しかし、現場でこの仕様を扱う際は注意が必要だ。RFC 7231では、サーバーが100-continueを受信した際、クライアントに何らかの応答を返す必要があるとされているが、「サーバーが即座に反応しない場合」のクライアントの挙動が厄介なのだ。

多くのクライアントライブラリは、`Expect: 100-continue` を送信した後、サーバーからの応答をデフォルトで「一定時間(例:1〜2秒)」待つ。もしその間に `100 Continue` が返ってこなければ、痺れを切らして「もういいや、勝手に送っちゃえ」とばかりにボディの送信を開始する。

ここがトラブルの温床だ。 高負荷なサーバーやネットワークの遅延により、このタイマーと実際の通信が競合し、意図しないリクエストの停滞や再送が発生することがある。

—

4. 実装の現場:コードで見る挙動

Python (requestsライブラリの場合)

requestsはデフォルトでは `Expect: 100-continue` を送らないことが多いが、明示的に付与するとこうなる。

import requests

url = “https://api.example.com/upload”
headers = {
“Expect”: “100-continue”,
“Content-Type”: “application/octet-stream”
}

大きなデータをダミーで作成
data = b”0″ 1024 1024

注意: requestsはデフォルトで100-continueを自動制御しない場合がある
低レイヤーを叩く必要がある場合は、httplib2やurllib3で細かく制御する
response = requests.post(url, data=data, headers=headers)
print(f”Status: {response.status_code}”)

curl でデバッグする

開発中に挙動を確認したいなら、`curl` の `-v` オプションが一番の近道だ。

-v: 詳細出力
-H: ヘッダー追加
-T: ファイル送信
curl -v -H “Expect: 100-continue” -T large_file.bin https://api.example.com/upload

ここで「`< HTTP/1.1 100 Continue`」が返ってくるまでの時間を計測し、もしそれが遅いようなら、サーバー側の前段にいるリバースプロキシ(Nginxなど)の設定を見直す必要がある。

Nginx の設定例

Nginxはデフォルトでこの挙動をサポートしているが、明示的に制御することも可能だ。

nginx.conf
server {
# クライアントからのExpectヘッダーを無視して即座に処理させたい場合などに調整
# ただし、基本的にはデフォルトの ‘on’ で問題ない
proxy_set_header Expect “100-continue”;

# 必要に応じてタイムアウトを調整
client_body_timeout 10s;
}

—

5. 最後に:エンジニアへのアドバイス

「仕組みを知る」ということは、単にRFCを読むことではない。「その仕組みが、異常系においてどう振る舞うかを想像すること」だ。

もしあなたがAPIを設計しているなら、以下のことを自問してほしい。

  • このAPIは、100-continueを待たせるほど巨大なボディを扱うか?
  • サーバーのレスポンスが遅延した際、クライアント側のタイムアウト設定は適切か?
  • ロードバランサーがこのヘッダーを適切に透過(または処理)できているか?

ネットワークエンジニアとして言えるのは、「魔法の解決策はない」ということ。ただ、正しくプロトコルの挙動を理解し、パケットの流れを可視化できていれば、どんなボトルネックも必ず特定できる。

今日の知識が、あなたの次なるデバッグの助けになることを願っている。現場からは以上だ。

コメント

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