Ruby学習記 - CGIのリダイレクト
今日はCGIを使ったリダイレクトに挑戦。
掲示板にて(POSTメソッドを使って)記事を投稿した後に、 F5キーを押してページをリロードすると 同じ記事が二重投稿されることがある。 (PukiWikiも同じ仕様になっていた。そのため画面上に「リロード」というリンクをわざわざ用意しているんだろう)
リロードによる二重投稿を防ぐためには、POSTされた後にいったん画面を表示し、すぐに別の画面に移動する方法がある。 (tDiaryやBitChannelはこの方法を使っているみたい) これに似た方法としては、POSTへの応答として Location: ヘッダと302ステータスを使うやり方がある。 試しにこのやり方を試してみた。
#!/usr/bin/env ruby require 'cgi' cgi = CGI.new base_url = "http://#{ENV['SERVER_NAME']}#{ENV['REQUEST_URI']}".gsub(/\?.*$/, '') if cgi.params['cmd'][0] == 'redirect' print cgi.header({'status' => '302 Found', 'Location' => base_url }) else cgi.out { cgi.params['cmd'][0].to_s + %Q(<form action="#{base_url}" method="post"> <input name="cmd" type="text"><input type="submit"></form>) } end
テキストボックスに redirect と入力して Submit すると、 サーバからは302のステータスコードが返される。
HTTP/1.1 302 Found Date: Tue, 27 Jul 2004 06:37:57 GMT Server: Apache/1.3.29 (Cygwin) Content-Length: 236 Location: http://localhost/index.cgi Keep-Alive: timeout=15, max=100 Connection: Keep-Alive Content-Type: text/html
これを見て、Webブラウザは Location: ヘッダに書かれたURLへ 再度接続する。このときWebブラウザはPOSTではなくGETを使う。 結果として表示されたページは、GETで取得したものなので、 F5による再読み込みをしてもPOSTのデータが二重に送られることはない。
ちなみにHTTPの規格上では、こういう場合のステータスコードは 303 See Otherを使うのがよさそう。 (HTTPの仕様上は、302の場合はリダイレクト後も同じメソッド(POST)を使うことと定められている。) だけど、303に対応しているブラウザ(エージェント)が限定されていることと、 302でもリダイレクト後はほとんどの実装がGETを使うことから、 302を使うのが無難みたい。 (wgetも303を理解できなかった)
っていうのが、おおむね今日学んだこと。 学んだばかりだから、ウソを書いているかも。
追記 (28日)
書き忘れていたこと。
- 302を返した場合に、仕様どおりにリダイレクト先でも POST を使うユーザエージェントがいたら無限ループに陥りそう。
- 302を返すときに、HTTPヘッダ(ステータスコードやLocationフィールド)だけでなくボディ(HTMLの本文)を送ることができる。tDiaryにようにHTMLにはRefreshを使ったコードを埋め込んでもいいかも。
HTTP/1.1 302 Found Date: Wed, 28 Jul 2004 02:00:47 GMT Server: Apache/1.3.29 (Cygwin) Content-Length: 232 Location: http://example.com/redirect.cgi Connection: close Content-Type: text/html <html> <head> <meta http-equiv="refresh" content="1;url=http://example.com/redirect.cgi"> <title>moving...</title> </head> <body>Wait or <a href="http://example.com/redirect.cgi">Click here!</a></body> </html> Connection closed by foreign host.