HTTPの303と307、POSTの後で結果を読むか、同じ要求を送るか

二つの桃色の立方体が別々の淡色の道に置かれ、左の到着先には透き通った緑色の板が立つ抽象的な構図。

フォームを送った後、画面が別のURLへ移ることがあります。同じ「移動」に見えても、送信した内容を移動先へもう一度送るのか、処理結果のページを読み取るのかで、サーバーが受け取る要求は変わります。HTTPの303と307は、この違いを考える手がかりです。

簡単にまとめると?

  • 303は、元の要求への間接的な回答を別の資源から取得する案内です。POST後に結果ページをGETする設計で使えます。
  • 307を自動追跡するとき、クライアントは元のメソッドを変更しません。POSTをGETへ置き換えるための応答ではありません。
  • 303に変えただけで二重登録が防げるわけではありません。表示の再取得と、処理そのものの重複対策を分けます。

Locationは行き先、ステータスコードは移り方

メソッドは、要求が何をしたいかを示します。GETは資源の表現を取得するために使い、POSTは送った内容を対象に処理してもらうために使います。リダイレクト応答に含まれるLocationは次の行き先を示しますが、URLだけでは、次の要求で何を送るかまでは説明できません。

たとえば教材サイトで、受講希望をPOST /applicationsへ送ったとします。次のURLが /applications/42 であっても、そこへ同じ申込内容を送るのか、受け付けた結果を読み出すのかは別の設計です。この二つを同じものとして扱うと、画面では正常に移動しているのに、サーバー側では予期しない処理が起きることがあります。

303で、処理の要求と結果の表示を分ける

RFC 9110の303 See Otherは、別の資源を通じて元の要求への間接的な回答を提供する意味を持ちます。HTTPでは、その資源をGETまたはHEADで取得できます。この記事では、POSTの後にGETで結果の本文を読む、一般的な使い方を例にします。303はPOSTだけに使えるコードではありません。

教材サイトが申込を受け付け、識別番号42を割り当てた後で、303とLocation: /applications/42を返すとします。クライアントはそのページをGETし、受付番号や入力内容を表示します。POSTで処理を依頼する段階と、GETで結果を見る段階が分かれます。取得先は、元のURLと同じ資源だと見なしてよい、という意味ではありません。

この流れでは、結果ページをもう一度読み出す操作はGETの繰り返しになります。ただし、その性質を生かすには、結果表示用のGETを、申込の追加登録を行う処理にしてはいけません。実装上の副作用を伴うGETを作っておいて、303だから安全だと説明することはできません。

307では、同じ種類の要求を別の場所へ送る

307 Temporary Redirectは一時的な移動を示し、自動で移動を追跡するときにメソッドを変えないことを求めます。元がPOSTなら、移動後もPOSTです。たとえば、まだ受け付けていない申込を別の受付口へ案内する設計なら、こちらの意味が合う場合があります。

このとき重要なのは、最初の受付口が実際に何をしたかです。すでに登録を確定した後で、同じ申込を登録する別の受付口へ307で案内すれば、移動先でも処理される可能性があります。307は「前のサーバーでは処理されていない」という証明ではありません。図では、307側の最初の受付口は保存せず、移動先だけが保存する条件を明示しています。

POST後の二つの設計例。303では最初に登録して結果ページをGETする。307では最初の受付口では登録せず、別の受付口へPOSTする。どちらも登録回数1となる例だが、登録を行う場所は異なる。
同じPOSTから始まる二つの説明用モデル。登録回数は図の実装条件から数えたもので、ステータスコード自体の重複防止能力を示す値ではない。

実際のクライアントでは、自動追跡を無効にしている場合や、送信元のストリームを再び読めず要求本文を再送できない場合もあります。307を返せば、すべてのクライアントが必ず同じ内容を送り直して成功する、とは扱えません。実装対象のクライアントと、再送可能な本文の条件を確認します。

302の歴史的な挙動と混同しない

302 Foundも一時的な移動を表しますが、歴史的な理由からPOSTをGETへ変えることが認められています。したがって、「一時移動だから302でPOSTも必ず維持できる」と考えると、クライアント間の違いが問題になります。メソッドを維持する意図を伝えるなら307、処理の結果を別の資源から取得してもらうなら303、というように、移動後の意味から選びます。

恒久的な移動でも同様の区別があり、308は自動追跡時にメソッドを変えないコードです。一時か恒久かという軸と、POSTをどう扱うかという軸を分けて読むと、コードの数字だけを暗記する必要が減ります。

二重登録の対策は、画面遷移より手前にも必要

303を使う設計でも、最初のPOSTが二度届く場合は残ります。送信ボタンを二回押した、通信が途切れて受付結果を受け取れなかった、といった場面です。最初のPOSTをサーバーが処理した後で応答が失われると、クライアント側の「失敗したように見える」と、サーバー側の「登録済み」が食い違います。

この問題には、同じ申込を識別する値と処理済みの記録を結び付け、重複を検出するなど、用途に応じた仕組みが必要です。図の303側で、GETを二回に増やしても登録回数が1なのは、GETが読み取りだけという条件があるからです。最初のPOSTを二回に増やし、重複検出を置かなければ、登録回数は2になります。303の有無だけでは変わりません。

移動先の安全性も別に確認します。外部から渡された任意のURLをそのままLocationへ入れる設計や、別のホストへ認証情報を引き継ぐ前提は、今回の単純なモデルには含めていません。移動を許す宛先、メソッド、送信本文、認証情報の扱いを、クライアントとサーバーの双方で確認します。

今回の比較はRFC 9110の意味と独自の要求列を整理したものです。ブラウザー各製品の実測、ネットワーク障害下の完全な再送制御、実際の登録サービスの保証を示すものではありません。「移動先で結果を読むのか、同じ処理を依頼するのか」を先に決めると、303と307を選ぶ理由が明確になります。

出典