307 と 302 の違いは?POST が変わるかどうかで使い分ける
「一時的なリダイレクトには 302 を使う」と覚えている方は多いでしょう。実際、長年にわたり、一時的な転送といえば 302 が定番でした。そこへ、同じく一時的な転送を表す 307 が登場したことで、「302 と 307 は何が違うのか」「どちらを使えばよいのか」という疑問が生まれました。
答えは単純です。通常のページ表示(GET)なら、どちらを使っても利用者から見た動きは同じです。違いが現れるのは、フォーム送信や API 呼び出しのように、POST・PUT・DELETE といったメソッドを使う通信だけです。
GET の場合
307 と 302 は、利用者から見て同じ動きになります。
POST の場合
302 は GET に変わることがあり、307 は変わりません。
根拠
RFC 9110 の 15.4.3(302)と 15.4.8(307)です。
検索エンジン
Google は 307 を 302 と同じ扱いにしています。
「どちらでも同じ」と思い込んだときのリスク
GET だけを相手にしていると、302 と 307 の差は目に見えません。そのため「どちらでも同じ」という認識のまま運用が進み、ある日、書き込みを伴う通信で不具合が出ます。典型的な例を挙げます。
- API の移行で、更新リクエストが失われます。 旧 API の URL に 302 を設定して新しい URL へ案内したところ、クライアントが POST を GET に切り替え、更新が実行されずに一覧の取得だけが走ってしまいます。エラーにはならないため、気づくまでに時間がかかります。
- フォームの送信先を切り替えたら、入力内容が消えます。 送信先を別の URL へ一時的に振り向けた際、本文が捨てられ、移動先には空の画面が表示されます。
- 同じ設定でも、クライアントによって動きが違います。 302 では POST を保つクライアントと GET に切り替えるクライアントが混在しうるため、環境ごとに結果が変わり、再現しにくい不具合になります。
どれも、「一時的な転送」という大きな目的は果たしているのに、メソッドの扱いだけが食い違うことで起こる問題です。しかも、エラー画面が出ないことが多く、発見が遅れがちです。
302 が抱えていた曖昧さ
この食い違いの原因は、302 の仕様にあります。
HTTP/1.0 の時代の仕様では、302 は「一時的に別の場所にある」ことを示し、クライアントはメソッドを変えてはならない、と読める定義でした。ところが、広く使われていたブラウザーの実装は、302 を受け取ると POST を GET に切り替えて移動先へ向かう動作をとっていました。
1999 年 6 月に公開された RFC 2616(HTTP/1.1)は、この状況を注記として明記しています。以前の仕様ではメソッドの変更を禁じていたものの、既存のユーザーエージェントの多くは 302 を 303(See Other)のように扱っている、という内容です。実装の側が先に進んでしまったため、仕様のほうが追認するしかなかったのです。
そこで RFC 2616 は、サーバーが期待する動作を曖昧にしないために、新しい番号を 2 つ追加しました。メソッドを GET に切り替えさせたい場合の 303、メソッドを保たせたい場合の 307 です。302 は、現実の挙動と矛盾しないように残されたまま、「どう動くか」の幅を持った番号になりました。
現在の RFC 9110 も、この経緯を引き継いでいます。302 については、歴史的な理由から、ユーザーエージェントは POST を GET に変えてもよい(MAY)と書き、メソッドを変えたくない場合は 307 を使えると案内しています。307 については、自動で転送するときにメソッドを変更してはならない(MUST NOT)と、はっきり述べています。
307 が解決したこと
307 の価値は、一言でいえば「意図の明確化」です。
302 Found
- 一時的な移動を表します。
- POST を GET に変えることが、仕様上許されています。
- 実際の動作は、クライアントの実装に左右されます。
307 Temporary Redirect
- 一時的な移動を表します。
- 自動転送でメソッドを変えることは、仕様上許されません。
- 本文も保たれ、動作が仕様どおりに統一されます。
サーバー側の開発者は、番号を選ぶだけで、クライアントに期待する動きを伝えられるようになりました。「GET に切り替えてよい(むしろ切り替えてほしい)」なら 302 や 303、「メソッドを保ってほしい」なら 307、という具合です。
ブラウザーの実際の動作
Web ブラウザーの fetch の手順を定めた WHATWG の Fetch Standard では、リダイレクトを受け取ったときの扱いが次のように書かれています。
- 状態コードが 301 または 302 で、要求メソッドが POST の場合は、メソッドを GET に変え、本文を捨てます。
- 状態コードが 303 で、要求メソッドが GET・HEAD 以外の場合も、同様にメソッドを GET に変え、本文を捨てます。
- それ以外(307 と 308 を含む)では、メソッドと本文を変えません。
つまり、現在のブラウザーが従う手順では、302 の POST は GET に変わり、307 の POST は POST のまま保たれます。この違いは、実装のばらつきではなく、標準の手順として決まっています。
同じ POST で比べる
違いを、同じ要求で並べてみます。
clientserver
clientserver
302 のほうは、最終的に 200 が返るため、正常に動作しているように見えます。しかし、実際には登録が行われていません。307 であれば、移動先でも POST が実行され、登録まで完了します。
設計上の使い分け
では、実際にどちらを選ぶべきでしょうか。基準は次のとおりです。
| 状況 | 選ぶ番号 | 理由 |
|---|---|---|
| 通常のページ(GET)を一時的に別の URL へ案内する | 302 または 307 | どちらでも結果は同じ |
| POST・PUT・DELETE などのメソッドを保ちたい | 307 | メソッドと本文が保たれる |
| POST の後に、結果ページを GET で表示させたい | 303 | GET への切り替えが仕様で決まっている |
| 古いソフトウェアが混在する可能性が高い | 302 | 307 を理解できない環境でも、一般的な転送として動作する |
迷ったら、メソッドを使う通信かどうかで判断してください。使うなら 307、使わないなら、どちらでも構いません。新しくサーバーを設定するのであれば、意図を明示できる 307 を選ぶ理由はあっても、避ける理由はほとんどありません。
検索エンジンとキャッシュの観点
検索エンジンの扱い
Google 検索セントラルのクロールに関する文書は、307 と 308 を、それぞれ 302 と 301 に相当するものとして説明しています。302 は、リダイレクト先を正規 URL とみなす弱いシグナル、301 は強いシグナルです。したがって、検索結果への影響という点では、307 と 302 を入れ替えても、大きな差は生じません。詳しくは検索エンジンとの関係で解説します。
キャッシュの扱い
RFC 9110 では、302 と 307 のどちらも、特に指定がなければ保存されない応答として扱われます。Cache-Control や Expires で保存期間を明示すれば、保存の対象になります。この点でも、両者に差はありません。
API を公開している側の視点
Web サイトの利用者はブラウザーですが、API の利用者はプログラムです。プログラムの HTTP クライアントは、ブラウザーと違って、転送を自動で追うかどうかを設定で選べるものが少なくありません。転送を追わない設定のクライアントは、307 を受け取った時点で処理を止めます。
API の提供者としては、転送を返す場合に、次の 2 点を押さえておくと利用者の混乱を防げます。
- メソッドを保ちたいときは 307 か 308 を返す。 302 では、クライアントによって POST が GET に変わるため、利用者が気づかないうちに更新が失われるおそれがあります。
- 転送が起きること自体を、API の説明に書く。 転送の有無と、使う番号が分かれば、利用者は自分のクライアントを正しく設定できます。
307 が使われている身近な例
意識していなくても、307 は身近な場所で使われています。代表的なものが、ブラウザーによる HSTS の処理です。HSTS の対象サイトに http でアクセスしようとすると、ブラウザーは通信を始める前に、URL を https に書き換えます。Chromium のソースコードを見ると、この書き換えを表現する応答として 302 ではなく 307 が選ばれており、その理由はコメントで「メソッドを保つことで、POST 要求が正しく動くようにするため」と説明されています。
HSTS による書き換えが 302 のように POST を GET へ切り替えてしまっては、http で送ろうとしたフォームの内容が失われてしまいます。メソッドを確実に保つ必要がある場面で 307 が採用されている、という事実は、この番号の存在意義をよく表しています。詳しくは307 Internal Redirect と HSTSで説明しています。
curl で動きの違いを確かめる
違いは、手元で再現して確かめられます。curl は -L を付けると転送を自動で追いかけます。curl のマニュアルによれば、POST で要求したとき、応答が 301・302・303 の場合は次の要求を GET で送り、それ以外の 3xx(307 や 308 を含む)の場合は元のメソッドを保ちます。
# 302 が返る URL に POST して、転送を追う(移動先には GET で届く)
curl -L -X POST -d 'name=pen' https://example.com/old-302
# 307 が返る URL に POST して、転送を追う(移動先にも POST で届く)
curl -L -X POST -d 'name=pen' https://example.com/old-307
なお、curl には --post301、--post302、--post303 というオプションもあります。これらを付けると、対応する番号でも POST を GET に変えずに送り続けます。マニュアルでは、ブラウザーが通常は GET に変換するため curl も既定でそれに合わせている、と説明されています。つまり、ツールの設定しだいで 302 の動きは変えられる一方で、307 はそうした設定に頼らず、仕様どおりに動きます。環境による動作のばらつきを減らしたいなら、サーバー側で 307 を返すほうが確実です。
切り替えるときのチェックリスト
すでに 302 で運用している転送を 307 に切り替える場合は、次の点を確認してください。
- メソッドを使う通信があるか。 アクセスログで POST・PUT・DELETE が該当の URL に届いているかを調べます。GET しかなければ、切り替えなくても動作は変わりません。
- 移動先がメソッドを受け付けるか。 307 では移動先にも POST が送られます。移動先が POST を受け付けない設定だと、405 Method Not Allowed のようなエラーになります。302 のときは GET に変わっていたため気づかなかった、という問題が表に出ることがあります。
- 本文を再送しても安全か。 307 は本文も再送します。大きなファイルのアップロードでは、転送のたびに通信量が倍になります。可能であれば、転送を介さず最初から正しい URL に送る設計が理想です。
- 意図しない二重処理が起きないか。 最初の URL でも何らかの処理を実行してから 307 を返す実装だと、同じ内容が二度処理されます。転送を返す URL では、処理をせずに案内だけを返すのが原則です。
どれも、「メソッドが保たれる」という 307 の長所の裏返しとして現れる注意点です。確認しておけば、切り替えは難しくありません。
結局、どう覚えればよいか
302 と 307 の関係は、次の 3 行にまとめられます。
- どちらも「一時的な移動」を表します。
- 違いは、POST などのメソッドを保つかどうかだけです。
- メソッドを保ちたいなら 307、GET に切り替えたいなら 303、どちらでもよいなら好みで選びます。
「302 のほうが有名だから」という理由で選んでいた方は、これを機に、通信の性質に合わせて選び直してみてはいかがでしょうか。全体像は301・302・303・307・308 の違いに、サーバーでの具体的な設定はnginx、Apache、PHP の各解説にまとめています。