処理完了後のリダイレクトのHTTPのステータスコードに「302」と「303」どちらを使うべきかという話

こんにちは、CTOの山岡(@hiroy.kotori.styleもしくは@hiro_y)です。

Webアプリケーションを作成する際、最近だとJavaScriptでAPIにアクセス、その結果を受け取って画面の表示を変えたり、場合によっては別のURLに移動するように作ることが多いかもしれません。Next.jsやRemix、SvelteKit等のフロントエンド由来のフレームワークを用いれば簡単に実装もできてしまいます。

しかし全てのWebアプリケーションがそうした実装を必要としているわけではありませんし、以前から運用されているWebアプリケーションはもっと素朴な作りをしているのではないでしょうか。サーバーサイドでHTMLをレンダリングし、フォームから値がPOSTで送信され、サーバーサイドで処理を行う、というような。

そうしたAPIを返すのではないサーバーサイド実装において、POSTで値を受け取り、処理(データベースに値を保存したり、いろいろ)を実行した後は(そのまま結果を表すHTMLをレンダリングしたりせずに)どこか別のページにリダイレクト処理を行うのがお約束です。

さて、そのリダイレクト処理でHTTPのステータスコードには何が使われているでしょうか。

HTTPのステータスコードにはたくさん種類がありますが、リダイレクトに関わるのは300番台です。そのうち、恒久的でない一時的なリダイレクトを示すのは次の3つ。

おそらく一番使われているのが「302」ではないでしょうか。Webアプリケーションフレームワークのリダイレクト機能を使う場合、ステータスコードを明示的に指定しない場合「302」でリダイレクトされる実装が多いと思います。

3つのステータスコードのうち、HTTP/1.0のころから定義されているのは「302」のみで、「303」「307」はHTTP/1.1で新しく追加されたステータスコードです。そして実はHTTP/1.0では「302」は「Moved Temporarily」という名前(reason-phrase)でした。

本来、HTTP/1.1で「302」はHTTPのメソッドを変更してはいけないリダイレクト(例えばPOSTのリダイレクトはPOSTのまま)として定義される予定でした。しかし当時、多くのブラウザ等の実装はそうなっておらず、POSTからGETへのリダイレクトに「302」が使われている状態。それを変更してしまうと大きな影響があるので、「302」は「Found」というふんわりした名前になり、大きな変更は加えられませんでした(仕様でも歴史的経緯について触れられています。また、メソッドの変更も結局許容されることになりました)。

Note: For historical reasons, a user agent MAY change the request method from POST to GET for the subsequent request. If this behavior is undesired, the 307 (Temporary Redirect) status code can be used instead.

そして代わりに「307」を新しく作って「Temporary Redirect」を名乗らせました。「307」を使う場合、HTTPのメソッドを変更してはいけません(MUST NOT)。例えば、POSTのリダイレクトはそのままPOSTを用いる必要があります(API実装の一時的な移転等で使えるのかも…?)。

また、それとは別に「303」も作られました。こちらは主にPOSTの結果ページへのリダイレクトをGETで実施するために使われます。一時的なリダイレクトを示すステータスコードが「302」しかなかったHTTP/1.0の状態から、目的別のステータスコード「307」「303」がHTTP/1.1で新しく追加された(派生した)感じですね。

というわけで結論としては、POSTの処理が終わってからリダイレクトを行うのであればHTTP/1.1以降の場合「303」を使うのが目的にかないそうです。今どきHTTP/1.0しか使えないクライアントもそうそうないはずなので。

しかし実際は「302」が多く使われている現状があります。正直なところ「302」を使っていてもあまりデメリットはありません。どうしてもHTTPのメソッドを明示的にコントロールする必要がある場合は「303」を使いましょう。しかし大部分の場合、「302」をそのまま使い続けても問題はないので、無理に変更する必要はありません(PUTやDELETEの場合、ブラウザによっては「302」だとリダイレクトしません。しかしHTMLのフォーム(form要素)からはGETかPOSTしか送れないので、問題になりません)。

なんとも締まらない結論になってしまいましたが、新しくWebアプリケーションを実装するとき等に思い出してもらえたらと思います。

参考: Ruby on Railsに置ける実装の議論:
Use 303 for redirects instead of 302? · Issue #4144 · rails/rails