Ruby学習記 - CGIのリダイレクト
2004-07-26
今日は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.