【テクニカル・上級編】HTTP/1.1におけるExpect: 100-continueの挙動 – HTTPプロトコル・通信規格実践ガイド

「Expect: 100-continue」の深淵:巨大なペイロードを投げる前に、我々が知るべきプロトコルの作法

ネットワークの最前線で戦うエンジニアなら、一度は「巨大なリクエストボディの送信」に頭を抱えたことがあるはずだ。クライアントが数GBのバイナリをアップロードしようとした矢先、サーバー側で認証エラーや容量超過が発覚し、貴重な帯域とRTTが霧散する。

HTTP/1.1の標準仕様(RFC 7231)にひっそりと存在する`Expect: 100-continue`は、この悲劇を防ぐための「同意を求める握手」である。しかし、このプロトコル上の礼儀作法は、現代の複雑なインフラスタックにおいて、時としてパフォーマンスの劇薬となる。今日は、このハンドシェイクの裏側をパケットレベルで解剖してみよう。

—

1. 100-continueのパケット挙動:期待と現実

クライアントが`Expect: 100-continue`ヘッダーを付けてリクエストを送信する際、それは「まだボディは送らない。サーバーが受け入れる準備ができているか確認したい」という信号だ。

通信フローの理想

1. Client -> Server: Headerのみ(`Expect: 100-continue`含む)を送信。
2. Server -> Client: サーバーが要件を満たせば `100 Continue` を即座に返す。
3. Client -> Server: 待機していたリクエストボディを流し込む。

ここで重要なのは、「クライアントはサーバーからのレスポンスを待つ」という点だ。もしサーバーの反応が遅ければ、クライアントは数ミリ秒〜数百ミリ秒の「無駄な待機時間」を強いられる。

実務上の罠:タイムアウトの設計

多くのクライアントライブラリ(cURLや各種言語のHTTPクライアント)には、`Expect`ヘッダーのレスポンスを待つための専用タイムアウト設定が存在する。例えば、cURLのデフォルト設定はしばしば「即時」または「短い閾値」に設定されているが、サーバー側で重い認証処理(JWTの検証やDBクエリ)が走っている場合、このタイムアウトが先に発火し、本来成功するはずだったアップロードが失敗するケースが多発する。

—

2. インフラ・トランスポート層からの最適化

`Expect: 100-continue`を有効活用しつつ、パフォーマンスを最大化するには、トランスポート層のチューニングが不可欠だ。

TCPバッファとウィンドウサイズ

サーバーが `100 Continue` を送るタイミングは、アプリケーション層の処理に依存する。もしサーバーがレスポンスを返す前にTCPの受信バッファが溢れれば、TCPウィンドウサイズはゼロになり、通信はスタックする。
Linuxカーネルで大容量のアップロードを捌くなら、`net.ipv4.tcp_rmem`や`net.core.rmem_max`を適切に広げ、高トラフィック環境では `BBR` 混雑制御アルゴリズムの適用を検討すべきだ。

TCPバッファの動的調整を最適化し、スループットを最大化する例
sysctl -w net.ipv4.tcp_rmem=”4096 87380 16777216″
sysctl -w net.ipv4.tcp_congestion_control=bbr

TLSハンドシェイクとの共存

TLS 1.3が普及した現在、`100-continue`の握手はTLSの暗号化トンネル内で完結する。特筆すべきは、Early Data (0-RTT)との相性だ。0-RTTと`Expect: 100-continue`を併用する場合、リプレイ攻撃のリスクが跳ね上がる。セキュリティ専門家として忠告するが、状態を変更するリクエスト(POST等)に0-RTTを安易に許可してはならない。

—

3. 実践:Nginxでのハンドリングとタイムアウト制御

現場で最も多いトラブルは「サーバーの反応待ちによるレスポンス遅延」だ。Nginxをリバースプロキシとして運用する場合、`client_body_buffer_size`と`proxy_read_timeout`のバランスを調整する必要がある。

Nginxの設定例
server {
# ボディをメモリに展開するサイズを調整(大きすぎるとメモリ圧迫、小さすぎるとディスクI/O発生)
client_body_buffer_size 128k;

# 100-continueのレスポンス自体に時間がかかる場合のタイムアウト設定
# アプリケーションサーバーのバックエンド応答速度に合わせて調整せよ
proxy_read_timeout 60s;

# 巨大なリクエストボディの制限
client_max_body_size 1G;
}

—

4. セキュリティ:Dos攻撃の踏み台にさせない

`Expect: 100-continue`を悪用したDos攻撃があることを忘れてはならない。攻撃者が「ヘッダーだけを大量に送りつけ、ボディを送らずに接続を維持する」ことで、サーバーの同時接続数(`worker_connections`)を枯渇させる手法だ。

この防御策として、以下の対策を徹底してほしい:

  • `client_header_timeout`の適正化: リクエストヘッダーがすべて届くまでの時間を短くする(例: 10秒〜20秒)。
  • `keepalive_timeout`の厳格化: 接続のライフサイクルを短く抑え、ゾンビコネクションを排除する。

—

結びに:プロトコルは「会話」である

`Expect: 100-continue`は、単なる仕様上のフラグではない。それは、クライアントとサーバーが「これから大きな荷物を運ぶが、準備はいいか?」という確認を行う、ネットワーク上の会話そのものだ。

この会話をいかにスムーズに行い、いかに早く「はい、受け入れます」と言えるか。それがインフラアーキテクトの腕の見せ所だ。パケットがどう流れ、どこで待たされ、カーネルがどうメモリを割り当てているか。その一挙手一投足に意識を向けることで、あなたのシステムはより堅牢で、より速いものへと進化するだろう。

さあ、次のパケットキャプチャで、この握手が正しく行われているかを確認してみてほしい。そこには、教科書には書かれていない「現場の鼓動」が流れているはずだ。

コメント

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