«前の日記(2004-07-25 (日)) 最新 次の日記(2004-07-27 (火))»  

まちゅダイアリー


2004-07-26 (月)

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.
Tags: Ruby