at posts/single.html

POST 後の振る舞い

PHPSPOT開発日誌より。 POST でデータを送った後は、ステータスコード 302 を返して GET させたほうがいいという話。

掲示板などで、フォームに対して送信した後は、そのまま画面のHTMLを返すのではなく、書き込み処理等を行った後、Location等で別の位置にリダイレクトするのが吉、という記事。ごく当たり前のことなんですが、慣れでついそのまま画面HTMLを返していた人も多いのではないでしょうか?(PHPSPOT開発日誌より引用)

jetwindの日記 でも指摘されているけど、 Location の値には絶対パスURLを書かないといけない。詳しくは、Studying HTTP - HTTP Header Fieldsが参考になる。 知っている人には常識だけど、こういうのは誤った情報が広まりがちなので(間違っていても分かりやすく書かれている情報のほうが普及する)、怖いな。

(2006/02/24追記) 絶対 URL について指摘されていたのは、jetwindの日記ではなくblackhaltの日記の誤りでした。 僕の確認不足で jetwind さんにご迷惑をおかけしました。申し訳ありません。 また、絶対パスではなく絶対URLの誤りでした。(絶対パスだと、「/」から始まるパスのことです)こちらも訂正します。 (追記ここまで)

ついでだけど、ステータスコード 302 の本来の挙動も知っておくといいかも。 以下、Studying HTTP - HTTP Status Codeより引用。

もし 302 ステータスコードが GET や HEAD 以外のリクエストのレスポンスとして受信されたら、リクエストが発行された時点の条件から変わっているかもしれないため、ユーザエージェントはユーザに確認されなければ、リクエストを自動的にリダイレクトしてはならない。

つまり、 POST を送った後に 302 を受信した場合は、本来であればリダイレクト先の URL でも GET じゃなくて POST を使わないといけないということ。 まぁ、 GET を使うクライアントがほとんどらしいので、実用的には 302 を返していればいいんだろうけど。

昔の日記にそういうのを書いたな…と思っていたら、やっぱりあった。

ちなみにHTTPの規格上では、こういう場合のステータスコードは 303 See Otherを使うのがよさそう。(HTTPの仕様上は、302の場合はリダイレクト後も同じメソッド(POST)を使うことと定められている。)だけど、303に対応しているブラウザ(エージェント)が限定されていることと、 302でもリダイレクト後はほとんどの実装がGETを使うことから、 302を使うのが無難みたい。(wgetも303を理解できなかった)

追記 (2005/02/24)

オレンジニュースで、「POSTでデータを送った後は、ステータスコード302を返してGETさせたほうがいい」という名前でリンクされたので補足。 リンク先を見てもらえれば分かると思うけど、 POST 後は常に 302 を返したほうがいいというわけじゃない。

  • この手法は「リロード」による二重投稿を防ぎたい場合に使う。主に掲示板など。ただし、完全に二重投稿を防ぎたいのであれば、セッションIDによる二重投稿チェックが別に必要。
  • 二重投稿されても問題がなく、POST されたデータの内容によって返すコンテンツの内容が変わるような場合は、この手法は使わない方がいい。(ちとうまい例が思いつかない→ 日記末尾に追記しました)

ちなみに、「ASP.NET 1.1 までは Postback→Response.Redirect というのをほぼ強制される」らしい。 Rails の scaffold で作ったコントローラも、同じようにリダイレクトした。

class PagesController < ApplicationController
  def create
    @page = Page.new(params[:page])
    if @page.save
      flash[:notice] = 'Page was successfully created.'
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
  end

  def list
    @page_pages, @pages = paginate :pages, :per_page => 10
  end
end

以下、 HTTP の送受信内容。余計な箇所は削除している。

# 1-1. POST でデータ送信 (create)
POST http://example.com/pages/create HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

# 1-2. 一覧画面 (list) へリダイレクト
HTTP/1.x 302 Moved Temporarily
Location: http://example.com/pages/list

# 2-1. GET で一覧画面 (list) を要求
GET http://example.com/pages/list HTTP/1.1
Host: example.com
Referer: http://example.com/pages/new

# 2-2. 一覧画面を応答
HTTP/1.x 200 OK
Content-Length: 583
Content-Type: text/html; charset=UTF-8

※ この後にリロード (F5) すると 2-1, 2-2 が繰り返される

さらに追記 (2005/02/24)

POST 後になんでもリダイレクトはおかしいと思う」にて、POST 後にリダイレクトしないほうがいい例を書いてくれている。

アンケートフォームなどで、完了時に受付番号を発行するなどの場合などが思いつきます。この場合、受付番号はあくまで POST された内容に対応する結果なので、別のリクエストに分割してしまうのは不自然でかつ手間が増えるだけの結果になります。

この例はいいかも。感謝。

関連する日記