[TurboGears] redirect関数の引数にurl関数の戻り値を与えてはならない。

環境

この記事の内容は、TurboGears 1.0.1で確認しました。

redirect

TurboGearsではリダイレクトするのにturbogears.controllers.redirect関数を使用します。

    redirect("/foo/")

一方、URLの先頭に設定ファイルのserver.webpathの値を加えるturbogears.controllers.url関数があります。

    path = url("/foo/")

server.webpathが"/bar"である場合、上のpath変数の値は"/bar/foo/"になります。

ここで、このurl関数の戻り値をredirect関数に与えてはいけません。すなわち、

    redirect(url("/foo/"))

とするのは誤りです。このようにした場合、server.webpathの設定が"/bar"であった場合、リダイレクト先が"/bar/bar/foo/"になります。

というのも、redirect関数内でurl関数を呼び出しているからです。recirect関数は、以下のように実装されています。

def redirect(redirect_path, redirect_params=None, **kw):
    """
    Redirect (via cherrypy.HTTPRedirect).
    Raises the exception instead of returning it, this to allow
    users to both call it as a function or to raise it as an exeption.
    """
    raise cherrypy.HTTPRedirect(
                    url(tgpath=redirect_path, tgparams=redirect_params, **kw))

したがって、web.serverpathを設定している場合でも、リダイレクトするときは、

    redirect("/foo/")

と、コンテキスト内のパスだけ指定すればよいです。

所感

すぐに原因は分かりましたが、これに引っかかりました。ビューの内部では、

tg.url("/foo/")

としてweb.serverpathを設定する必要があるのだから、リダイレクトも同じだろうとずっと思っていました。判明したのは実働環境に移してからで、そのときには既に多くの箇所でこの間違いをしていました。結局、

# ruby

require "fileutils"
require "find"

Find.find(".") do |path|
  next if /\.py\Z/ !~ path

  orig = "#{path}.orig"
  if !FileTest.file?(orig)
    FileUtils.cp(path, orig, :preserve => true)
  end

  tmp = "#{path}.tmp"
  open(tmp, "w") do |dest|
    open(path) do |src|
      src.each do |line|
        while m = line.match(/\A(.*)redirect\(url\("(.*)"\)\)(.*)\Z/)
          line = m[1] + "redirect(\"#{m[2]}\")" + m[3]
        end
        dest.write(line)
      end
    end
  end
  FileUtils.mv(tmp, path)
end

# vim: tabstop=2 shiftwidth=2 expandtab

というRubyスクリプトを書いてまとめて変換しました。