HTTP Expect: 100-continue の仕組み: 本文を送る前にサーバーの応答を待つルール

RFC 9110 と RFC 9112 をもとに、Expect: 100-continue の流れ、クライアント・オリジンサーバー・プロキシが守るべきルール、417 Expectation Failed の意味、早期拒否後の接続の扱い方を整理します。

httpexpect100-continuerfc9110http-1.1

数 GB のファイルを PUT でアップロードしたのに、全部送り終えてから 401(Unauthorized)や 405(Method Not Allowed)が返ってきたら、送ったデータはすべて無駄になります。Expect: 100-continue はこの無駄を減らすための仕組みです。クライアントはまずヘッダーを送り、サーバーが 100 Continue で「本文を送ってよい」と答えるまで本文の送信を待つことができます。

この記事の根拠は、現行の HTTP セマンティクス標準である RFC 9110(2022 年 6 月)と、HTTP/1.1 のメッセージと接続のルールを定める RFC 9112(2022 年 6 月)だけです。特定のクライアントライブラリやサーバー製品が実際にどれだけ待つか、デフォルト値が何かは扱いません。こうした動作は標準では決まっておらず、各プロジェクトの公式ドキュメントで確認する必要があります。

リクエストの流れ

RFC 9110 の 10.1.1 節にある例のリクエストは、次のように始まります。

PUT /somewhere/fun HTTP/1.1
Host: origin.example.com
Content-Type: video/h264
Content-Length: 1234567890987
Expect: 100-continue

RFC によると、このリクエストを受け取ったオリジンサーバーは、クライアントが不要なデータを送り始める前に 401 や 405 などのエラーをすぐに返せます。受け入れるつもりなら中間応答の 100 (Continue) を送り、クライアントはその応答を破棄して本文の送信を続けます。本文を受け取って処理したあと、サーバーは最終応答を送ります。

つまり分かれ道は二つです。

  1. ヘッダーだけで結果が決まる場合: サーバーはすぐに最終ステータスコード(例: 401、405)を送ります。
  2. 本文が必要な場合: サーバーはすぐに 100 Continue を送り、本文を受け取ってから最終応答を送ります。

RFC 9110 は、この方式で効率が上がりうる場面として、データが非常に大きいときと、クライアントがエラーを予想しているときを挙げています。例として挙げているのは、認証情報をまだ検証されていない状態で、状態を変更するメソッドを初めて送る場合です。

Expect フィールドそのもの

  • Expect のフィールド値は大文字と小文字を区別しません。
  • RFC 9110 が定義している期待(expectation)は 100-continue だけで、定義済みのパラメーターもありません。
  • 文法はリスト形式(#expectation)です。RFC 9110 付録 B.3(RFC 7231 からの変更点)には、RFC 2616 との互換性のためにリスト形式の文法を復活させたと書かれています。
  • 100-continue 以外の値が含まれていれば、サーバーは 417(Expectation Failed)で応答してもかまいません(MAY)。

クライアントが守るべきルール

RFC 9110 の 10.1.1 節にあるクライアント側の要件です。

  • 本文(content)のないリクエストに 100-continue を付けてはいけません(MUST NOT)。
  • 本文を送る前に 100 Continue を待つつもりなら、必ず Expect: 100-continue を送らなければなりません(MUST)。
  • 100-continue を送っても、決まった時間だけ待つ義務はありません。応答を受け取っていなくても本文を送り始めてかまいません(MAY)。
  • HTTP/1.0 の仲介者は 100 Continue を転送できないため、無期限に待つべきではありません(SHOULD NOT)。
  • 100-continue を含むリクエストに 417 が返ってきたら、期待なしで同じリクエストを送り直すべきです(SHOULD)。RFC は、417 は応答経路のどこか(例: HTTP/1.0 サーバー)が期待をサポートしていないことを示すだけだと説明しています。

ここで大事なのは、待ち時間を標準が数値で決めていないことです。「どれだけ待ってから送ってしまうか」は実装に任されているので、クライアントごとに動作が違うことがあります。

RFC 9112 の 9.5 節は、本文を送っている最中の振る舞いも勧めています。本文を送信中のクライアントは、エラー応答が来ないか接続を監視するべきです(SHOULD)。サーバーが本文を受け取るつもりがなく接続を閉じるという応答が見えたら、すぐに送信をやめ、自分の側の接続を閉じるべきです(SHOULD)。

オリジンサーバーが守るべきルール

HTTP/1.1 以降のリクエストで、メソッド、ターゲット URI、ヘッダーセクションがすべて届き、その中に 100-continue と本文が続くことを示す情報があるとします。このときオリジンサーバーは次のどちらかを送らなければなりません(MUST)。

  • メソッド、ターゲット URI、ヘッダーだけで判断できるなら、すぐに最終ステータスコード
  • そうでなければ、すぐに 100 Continue

さらに、100 Continue を送る前に本文を待ってはいけません(MUST NOT)。筆者の見方では、このルールによって、クライアントは 100 を待ち、サーバーは本文を待って、互いに止まってしまう状況を防げます。

そのほかのサーバー側の要件は次のとおりです。

  • HTTP/1.0 リクエストに含まれる 100-continue は無視しなければなりません(MUST)。1xx 応答そのものも HTTP/1.0 クライアントに送ってはいけません(RFC 9110 15.2 節、MUST NOT)。
  • 本文の一部または全部をすでに受け取っている場合や、メッセージのフレーミング上本文がない場合は、100 Continue を省略してもかまいません(MAY)。
  • 100 Continue を送ったなら、本文を受け取って処理したあとに必ず最終ステータスコードを送らなければなりません(MUST)。接続が途中で切れた場合は例外です。
  • 本文を読み終える前に最終ステータスコードを送る場合は、接続を閉じるのか本文を読み続けるのかを示すべきです(SHOULD)。

早期拒否のあとの接続の扱い

最後の項目は RFC 9112 と合わせて読む必要があります。RFC 9112 の 9.3 節によると、サーバーはリクエスト本文を最後まで読むか、応答を送ったあとに接続を閉じなければなりません(MUST)。そうしないと、持続的接続に残った本文データが次のリクエストとして誤って解釈されます。

接続を閉じる意思は Connection: close で示します(RFC 9112 9.6 節)。ただしサーバーが TCP 接続をすぐに切断すると、クライアントがまだ送っているデータのせいでサーバーの TCP スタックがリセットを送り、そのリセットがクライアントのまだ読んでいない応答を消してしまうことがあります。そのため RFC 9112 は、サーバーは通常段階的に接続を閉じると説明しています。まず書き込み方向だけを閉じ(ハーフクローズ)、クライアントが閉じるか、最後の応答が届いたとある程度確信できるまで読み続け、それから完全に閉じるという方法です。

プロキシが守るべきルール

同じ条件のリクエストを受け取ったプロキシは、次のどちらかをしなければなりません(MUST)。

  • メソッド、ターゲット URI、ヘッダーだけで判断できるなら、すぐに最終ステータスコードを送る。
  • そうでなければ、リクエストラインとヘッダーセクションを次のインバウンドサーバーへ転送する。

次のインバウンドサーバーが HTTP/1.0 しかサポートしていないと判断する根拠(設定や過去のやり取り)があれば、プロキシが自分で 100 Continue を生成して送ってもかまいません(MAY)。

1xx 応答は原則として転送しなければなりません(MUST)。例外は、プロキシ自身が 1xx を要求した場合です。たとえばプロキシがリクエストを転送する際に Expect: 100-continue を自分で追加したなら、それに対する 100 Continue はクライアント側へ転送しなくてもかまいません(RFC 9110 15.2 節)。

417 の定義もこの構造と関係します。RFC 9110 の 15.5.18 節は 417 を「Expect の期待を、インバウンドサーバーの少なくとも一つが満たせなかった」という意味で定義しています。つまり 417 を返したのがオリジンサーバーとは限りません。

1xx 応答を扱うときに知っておくこと

  • 1xx 応答はヘッダーセクションの終わりで終了します。本文やトレーラーを持つことはできません(RFC 9110 15.2 節、RFC 9112 6.3 節)。
  • クライアントは、最終応答の前に 1xx が一つ以上届いたら、予期していなくても解析できなければなりません(MUST)。ユーザーエージェントは予期しない 1xx を無視してもかまいません(MAY)。
  • Expect: 100-continue を送っていないのに受け取った 100 Continue は、そのまま破棄すればかまいません(RFC 9110 15.2.1 節)。
  • HTTP/1.1 にはリクエスト識別子がないため、クライアントは受け取った応答を、まだ最終(1xx 以外の)応答を受け取っていない最も古いリクエストに対応付けなければなりません(RFC 9112 9.2 節、MUST)。100 応答を最終応答として扱ってしまうと、同じ接続のその後の応答が別のリクエストに対応付けられるおそれがあります。
  • Upgrade と Expect: 100-continue が両方付いたリクエストでは、サーバーは 101 (Switching Protocols) より先に 100 Continue を送らなければなりません(RFC 9110 7.8 節、MUST)。

チェックリスト

以下は上のルールから筆者が導いた提案で、RFC の文言そのものではありません。

  • クライアント: 100 Continue を待つ時間に上限を設け、時間が過ぎたら本文を送る動作になっているか確認してください。417 を受け取ったら Expect なしで一度送り直す経路があるかも確認してください。
  • クライアント: 本文のないリクエスト(例: 本文のない GET)に Expect を付けていないか確認してください。
  • オリジンサーバー: 認証、認可、メソッドの許可、サイズ制限など、ヘッダーだけで判断できるチェックは本文を読む前に行ってください。そうすることで、標準が求める「すぐに最終応答か、すぐに 100」を守れます。
  • オリジンサーバー: 本文を読む前に拒否するなら、Connection: close を送って段階的に閉じるか、残りの本文を最後まで読んで捨ててください。どちらもしないと、残ったデータが次のリクエストとして解釈されるおそれがあります。
  • プロキシとゲートウェイ: 1xx を転送しているか、自分で Expect を追加する場合はその 100 応答をどう扱うかを設定で確認してください。

範囲と限界

  • この記事は RFC 9110 と RFC 9112 のルールだけを扱います。RFC 9110 は要件を「HTTP/1.1 (or later)」のリクエストを基準に書いていますが、HTTP/2 と HTTP/3 でメッセージがどうフレーミングされるかはそれぞれの RFC で定められており、この記事では確認していません。
  • 特定の HTTP クライアント、サーバー、プロキシのデフォルトの待ち時間や、Expect を自動で付けるかどうかは製品ごとに異なり、この記事では扱いません。

参考資料

  • RFC 9110, HTTP Semantics(STD 97、2022 年 6 月): 7.8 節 Upgrade、10.1.1 節 Expect、15.2 節 Informational 1xx、15.2.1 節 100 Continue、15.5.18 節 417 Expectation Failed、付録 B.3。https://www.rfc-editor.org/rfc/rfc9110.txt
  • RFC 9112, HTTP/1.1(STD 99、2022 年 6 月): 6.3 節 Message Body Length、9.2 節 Associating a Response to a Request、9.3 節 Persistence、9.5 節 Failures and Timeouts、9.6 節 Tear-down。https://www.rfc-editor.org/rfc/rfc9112.txt

ブログへ