キャッシュの多言語対応 のバックアップ差分(No.1)

更新


  • 追加された行はこの色です。
  • 削除された行はこの色です。
[[ソフトウェア/rails/言語ネゴシエーション]]

#contents

* ページキャッシュの多言語対応 [#z6cc2a4b]

まずは rails そのままで、ページキャッシュ機能をONにしてみる。

 LANG:console
 $ jed config/environments/development.rb
  config.action_controller.perform_caching = true
 $ jed app/controllers/test_controller.rb
  class TestController < ApplicationController
    caches_page :index
  end
 $ (restart script/server)
 $ ls public/test*
  ls: cannot access public/test*: そのようなファイルやディレクトリはありません
 $ wgetc http://localhost:3000/test
  English: Test   OK
  
  
 $ ls public/test*
  public/test.html
 $ cat public/test.html
  English: Test   OK
  
  
 $ wgetc http://localhost:3000/test.en
  English: Test   OK
  
  
 $ ls public/test*
  public/test.html  public/test.en

test.html というファイルができてしまうと、
次回からは言語設定にかかわらず常にこれが表示されてしまうため、うまくいかない。

それに、test.en というキャッシュファイルもいけてない。

期待するのは、test.html.en や test.html.ja というファイルができること。

ページキャッシュは ActionController::Caching::Pages が作成するので、
このときファイル名に言語名やフォーマット名を正しく付ければよい。

ファイル名に付ける言語指定を、path として指定された物にするか
実際に使ったテンプレートの言語にするか。
提供できない言語は何度訪れても提供できないので、
path として指定された物を付けるのが正しそう。

ちなみに cache_page が暗黙に呼び出される時、
ActionController::Caching::Pages.cache_page には引数が与えられないので、
キャッシュファイル名は request.path から作る事になる。

 LANG:ruby(linenumber)
 module ActionController::Caching::Pages
   def cache_page(content = nil, options = nil)
     return unless perform_caching && caching_allowed
 
     path = case options
       when Hash
         url_for(options.merge(
             :only_path => true, 
             :skip_relative_url_root => true, 
             :format => params[:format],
             :rails_language => request.accepts_languages.first))
       when String
         options
       else
         p= request.path.split('.')
         p.pop if ActionController::AbstractRequest.acceptable_language?(p.last)
         p[0]+= self.class.page_cache_extension if p.count==1
         p << request.accepts_languages.first
         p.join('.')
     end
 
     self.class.cache_page(content || response.body, path)
   end
 end

** テスト [#t5c0d140]

 LANG:console
 $ (restart script/server)
 $ rm -r public/test*
 $ wgetc http://localhost:3000/test
  English: Test   OK
 
 $ ls public/test*
  public/test.html.en
 $ wgetc http://localhost:3000/test.en
  English: Test   OK
 
 $ ls public/test*
  public/test.html.en
 $ wgetc http://localhost:3000/test.ja
  日本語:テスト ばっちり
 
 $ ls public/test*
  public/test.html.en  public/test.html.ja
 $ wgetc http://localhost:3000/test
  日本語:テスト ばっちり
 
 $ ls public/test*
  public/test.html.en  public/test.html.ja
 $ wgetc http://localhost:3000/test/index
  日本語:テスト ばっちり
 
 $ ls public/test*
  public/test.html.en  public/test.html.ja
  
  public/test:
  index.html.ja
 $ wgetc http://localhost:3000/test/index/0
  日本語:テスト ばっちり
 
 $ ls public/test* public/test*/* 
  public/test.html.en  public/test.html.ja  public/test/index.html.ja
  
  public/test:
  index  index.html.ja
  
  public/test/index:
  0.html.ja
 $ wgetc http://localhost:3000/test/index/0.html
  日本語:テスト ばっちり
 
 $ ls public/test* public/test*/* 
  public/test.html.en  public/test.html.ja  public/test/index.html.ja
  
  public/test:
  index  index.html.ja
  
  public/test/index:
  0.html.ja
 $ wgetc http://localhost:3000/test/index/0.html.ja
  日本語:テスト ばっちり
 
 $ ls public/test* public/test*/* 
  public/test.html.en  public/test.html.ja  public/test/index.html.ja
  
  public/test:
  index  index.html.ja
  
  public/test/index:
  0.html.ja

* ページキャッシュの多言語化に対応させるための apache の設定 [#t2a0cd95]

まずは rails を apache から cgi 経由で production 環境で動くように設定する。

 LANG:console
 $ cp db/development.sqlite3 db/production.sqlite3
 $ ln -s (negotiation app path)/public/ /var/www/negotiation
 $ wgetc http://localhost/negotiation | html2text
  ...
  
  ****** Welcome aboard ******
  ***** You&rsquo;re riding Ruby on Rails! *****
  **** About_your_application&rsquo;s_environment ****
  ...
  
 $ cat > public/.htaccess
  SetEnv RAILS_RELATIVE_URL_ROOT /negotiation
  SetEnv RAILS_ENV production
  SetEnv RAILS_ACCEPTABLE_LANGUAGES ja|en
  
  RewriteEngine On
  RewriteRule ^$ index.html [QSA]
  RewriteRule ^([^.]+)$ $1.html [QSA]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
  ^D
 $ rm -r public/test*
 $ ls db
  development.sqlite3
 $ wgetc http://localhost/negotiation/test
  日本語: テスト ばっちり
  
 $ ls db
  development.sqlite3  production.sqlite3
 $ ls public/test*
  public/test.html.ja
 $ wgetc http://localhost/negotiation/test.en
  English: Test   OK
  
 $ ls public/test*
  public/test.html.en  public/test.html.ja

DRY の原則になるべく従いたいので、

  SetEnv RAILS_ACCEPTABLE_LANGUAGES ja|en

をここに書いて、routes.rb での設定を無効化している。

とはいえ、後に見るように ja en を書かなければならないところは
どうしても他に出てくる。RoR の外側なので仕方のないところ。

以下ページキャッシュのテスト。

apache2 は mod_rewrite, mod_negotiation, mod_cgi, mod_env, mod_setenvif 
が有効になっている。

また、デバッグ用に apache の設定で RewriteLogLevel を 2 とかに設定しておく。~
後で必ず 0 に戻す事。

 LANG:console
 $ rm -r public/test*
 $ wgetc http://localhost/negotiation/test.ja
  日本語: テスト ばっちり
  
 $ ls public/test*
  public/test.html.ja
 $ wgetc http://localhost/negotiation/test
  日本語: テスト ばっちり
  
 $ tail /var/log/apache2/rewrite.log
  [perdir (DOCROOT)/negotiation/] pass through (DOCROOT)/negotiation/test.html.ja
 $ wgetc http://localhost/negotiation/test.ja
  日本語: テスト ばっちり
  
 $ tail /var/log/apache2/rewrite.log
  [perdir (DOCROOT)/negotiation/] pass through (DOCROOT)/negotiation/dispatch.cgi
 $ wget -O- --header="Accept-Language: en" http://localhost/negotiation/test
  エラー 406: Not Acceptable
 $ wgetc http://localhost/negotiation/test.en
  English: Test   OK
  
 $ ls public/test*
  public/test.html.en  public/test.html.ja
 $ wgetc http://localhost/negotiation/test
  日本語: テスト ばっちり
  
 $ tail /var/log/apache2/rewrite.log
  [perdir (DOCROOT)/negotiation/] pass through (DOCROOT)/negotiation/test.html.ja
 $ wget -qO- --header="Accept-Language: en" http://localhost/negotiation/test
  English: Test   OK
  
 $ wget -qO- --header="Accept-Language: ja" http://localhost/negotiation/test
  日本語: テスト ばっちり
  

何点か問題が指摘できる。

** test.ja 形式の呼び出しにキャッシュが効かない [#q96ec24a]

まず、public/test.html.ja が作成された後も http://localhost/negotiation/test.ja
の読み込みがキャッシュファイルではなく cgi 呼び出しに rewrite されている。

これは、
 LANG:Shell(linenumber)
 RewriteCond %{HTTP_ACCEPT} html
 RewriteCond %{SCRIPT_FILENAME} !\.html\.
 RewriteCond %{SCRIPT_FILENAME} (.*)\.([a-z][a-z])$
 RewriteCond %1.html.%2 -f
 RewriteRule (.*)\.([a-z][a-z])$ $1.html.$2 [L]

というルールを追加してやる事でキャッシュを読ませる事ができる。

このルールは、

+ ACCEPT ヘッダに text/html が含まれていて
+ ファイル名が .html. を含まず
+ ファイル名が .ja や .en のように、2文字の拡張子で終わっていて
+ .ja などの前に .html を追加すると、同名のファイルが見つかる
+ のであれば、.html を追加して、test.en のような名前を test.html.en に書き換える

という意味になる。

** 406 エラーの回避 [#n6841768]

次に、public/test.html.ja があって public/test.html.en が無い
状況で wget -O- --header="Accept-Language: en" http://localhost/negotiation/test すると
406 エラーになる。

これは、RewriteCond %{REQUEST_FILENAME} !-f が public/test.html.ja 
に対して成立しないにもかかわらず、実際には言語指定が折り合わないため、
変装するデータが見つからないという状況のようだ。

まずエラーになるのを避けるため、

 LanguagePriority ja en
 ForceLanguagePriority Prefer Fallback

としてやることで、望みの言語が見つからない場合には ja, en の順に探して、
見つかった物を返すようにした。

** キャッシュミスを検出 [#he27a9f0]

上記だけだと英語を要求したのに日本語の答えが返って来る。

正しい完全な動作とは言えないので、

 LANG:shell(linenumber)
 RewriteCond %{HTTP:Accept-Language} !$2
 RewriteCond %{HTTP:Accept-Language} ([a-z][a-z])
 RewriteRule (.*\.html\.)([a-z][a-z]) $1%1

を追加した。

この意味は、test.html.ja を {test.html.}{ja} の形に分けたとき、
後ろの {ja} が Accept-Language に含まれていなければ、
Accept-Language から望みの言語 ([a-z][a-z]) を取り出して、
test.html. の後ろに付ける。

ただこれだと、es や fr を求めてくる閲覧者に対しては
常にキャッシュが効かないという状況が生じる。

また、Accept-Language を En とか JA とか、
大文字を混ぜて指定されるとやはりキャッシュが効かない。

ここの記述が無くても、テストの最後の部分で確かめたように、
public/test.html.en と public/test.html.ja とが両方あれば
Accept-Language が正常に働くので、そのページを表示した最初の一人だけが
被害を被り、test.en の形で言語を指定すればちゃんと読める。

また、以下のように、一度 cookie で言語指定してしまえば、
次からはキャッシュミスをしっかり検出可能。

という事で、記述が無くてもそれほどの害はない。

パフォーマンスが問題になるようであれば
この3行は省略した方が良い。

** 言語指定を記憶する [#c3196e2a]

もう一点、wgetc http://localhost/negotiation/test.en の後の
wgetc http://localhost/negotiation/test 呼び出しで
日本語が表示されてしまっている部分に不具合がある。

これは、直前に選択した言語を apache が覚えていないせい(あたりまえ)。

 SetEnvIf Cookie "rails_language=([a-z][a-z])" prefer-language=$1

とすることで、cookie の値に応じて apache に言語を選ばせることができる。

cookie の値は信用できないので、[a-z][a-z] にマッチする時に限って
値を採用する。

** 言語指定のある時にキャッシュミスを検出 [#v76d4482]

 LANG:shell(linenumber)
 RewriteCond %{ENV:prefer-language} ^[a-z][a-z]$
 RewriteCond %{ENV:prefer-language} !=$2
 RewriteRule (.*\.html\.)([a-z][a-z]) $1%{ENV:prefer-language} [L]

とすることで、cookie に値があるときは他の言語のキャッシュがあっても
望みのファイルが無ければ dispatch.cgi を呼ぶようにした。

詳しく説明すると:~
test.html.ja が存在して test.html.en が存在しない時、~
prefer-language の設定にかかわらず、
test への呼び出しは apache のネゴシエーションで test.html.ja になってしまう。
これを検出して test.html.en に書き直すため、

+ ファイル名が (.*\.html\.)([a-z][a-z]) の形で、
+ prefer-language に正しそうな値が入っているにも関わらず
+ ファイル名に付いている言語指定が prefer-language に含まれていなければ
+ 言語指定を prefer-language の物に置き換える

というルールを追加している。

当然 test.html.en は存在しないので、結果的に dispatch.cgi に
rewrite されて test.html.en が作られる事になる。

cookie が存在するとき、Accept-Language の指定は無視したいので、
RewriteRule に [L] を付けておく。

** .htaccess の最終形 [#h8d761ca]

最終的に、public/.htaccess の内容は
 # rails setting
 SetEnv RAILS_RELATIVE_URL_ROOT /negotiation
 SetEnv RAILS_ENV production
 SetEnv RAILS_ACCEPTABLE_LANGUAGES ja|en
 
 # save preferential language in cookie
 SetEnvIf Cookie "rails_language=([a-z][a-z])" prefer-language=$1
 
 # avoid error due to cache miss
 LanguagePriority ja en
 ForceLanguagePriority Prefer Fallback
 
 # rewrite start
 RewriteEngine On
 
 # default format of ".html"
 RewriteCond %{HTTP_ACCEPT} html
 RewriteCond %{SCRIPT_FILENAME} !\.html\.
 RewriteCond %{SCRIPT_FILENAME} (.*)\.([a-z][a-z])$
 RewriteCond %1.html.%2 -f
 RewriteRule (.*)\.([a-z][a-z])$ $1.html.$2 [L]
 
 # rails' default rules
 RewriteRule ^$ index.html [QSA]
 RewriteRule ^([^.]+)$ $1.html [QSA]
 RewriteCond %{REQUEST_FILENAME} !-f
 # change this to adapt to your server
 RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
 
 # detect cache miss for prefer-language
 RewriteCond %{ENV:prefer-language} ^[a-z][a-z]$
 RewriteCond %{ENV:prefer-language} !=$2
 RewriteRule (.*\.html\.)([a-z][a-z]) $1%{ENV:prefer-language} [L]
 
 # detect cache miss for accept-language
 # omit this section if performance matters
 RewriteCond %{HTTP:Accept-Language} !$2 [NC]
 RewriteCond %{HTTP:Accept-Language} ([a-z][a-z])
 RewriteRule (.*\.html\.)([a-z][a-z]) $1%1

となった。

まさに黒魔術。

** Mongrel を使うのであれば [#y50f2fe4]

dispatch.cgi ではなく Mongrel に渡すのであれば

 RewriteRule ^(.*)$ dispatch.cgi [QSA,L]

の部分を

 RewriteRule ^/(.*)$ balancer://cluster%{REQUEST_URI} [P,QSA,L]

にすればよい?

これは冒頭の pdf の記述のままなのだけれど、、、未確認。~
何か動かなさそう。

** テスト [#f34fa374]

 LANG:console
 $ rm public/test*
 $ wget -qO- --header="Accept: text/html" --header="Accept-Language: en" http://localhost/negotiation/test
  English: Test   OK
 
 $ ls public/test*
  public/test.html.en
 $ wget -qO- --header="Accept: text/html" --header="Accept-Language: en" http://localhost/negotiation/test
  English: Test   OK
 
 $ tail /var/log/apache2/rewrite.log
  initial URL equal rewritten URL: (DOCROOT)/negotiation/test.html.en
 $ wget -qO- --header="Accept: text/html" --header="Accept-Language: ja" http://localhost/negotiation/test
  日本語: テスト ばっちり
  
 $ ls public/test*
  public/test.html.en  public/test.html.ja
 $ wget -qO- --header="Accept: text/html" --header="Accept-Language: ja" http://localhost/negotiation/test
  日本語: テスト ばっちり
  
 $ tail /var/log/apache2/rewrite.log
  initial URL equal rewritten URL: (DOCROOT)/negotiation/test.html.ja
 $ wget -qO- --header="Accept: text/html" --header="Accept-Language: en" http://localhost/negotiation/test
 $ tail /var/log/apache2/rewrite.log
  initial URL equal rewritten URL: (DOCROOT)/negotiation/test.html.en
 $ rm public/test*
 $ wget -qO- --header="Accept: text/html" http://localhost/negotiation/test.ja
  日本語: テスト ばっちり
  
 $ ls public/test*
  public/test.html.ja
 $ wget -qO- --header="Accept: text/html" http://localhost/negotiation/test.ja
  日本語: テスト ばっちり
  
 $ tail /var/log/apache2/rewrite.log
  initial URL equal rewritten URL: (DOCROOT)/negotiation/test.html.ja
 $ wget -qO- --header="Accept: text/html" http://localhost/negotiation/test.en
  English: Test   OK
 
 $ ls public/test*
  public/test.html.en  public/test.html.ja
 $ wgetc --header="Accept: text/html" http://localhost/negotiation/test.en
  English: Test   OK
 
 $ cat tmp/wget.cookie 
  # HTTP cookie file.
  # Edit at your own risk.
  
  dora.bk.tsukuba.ac.jp   FALSE   /       FALSE   0       _negotiation_session    BAh7BiIKZm...
  dora.bk.tsukuba.ac.jp   FALSE   /       FALSE   0       rails_language  en
 $ rm public/test.html.en
 $ wgetc --header="Accept: text/html" http://localhost/negotiation/test
  English: Test   OK
  
 $ ls public/test*
  public/test.html.en  public/test.html.ja

* アクションキャッシュ [#gd9d14bc]

まずは rails そのままで、アクションキャッシュ機能をONにしてみる。

 LANG:console
 $ jed app/controllers/test_controller.rb
  class TestController < ApplicationController
  #  caches_page :index
    caches_action :index
    def index
    end
  end
 $ rm public/test.*
 $ (restart script/server)
 $ wgetc http://localhost:3000/test.en
  404: Not Found
 $ tail log/production.log
  Processing TestController#index to html (for 127.0.1.1 at 2009-06-08 21:56:33) [GET]
    Parameters: {"rails_language"=>"en"}
  Cookie set: rails_language=en; path=/
  
  
  ActionController::RoutingError (No route matches {:action=>"index", :format=>"en"}):
      /vendor/rails/actionpack/lib/action_controller/routing/route_set.rb:370:in `generate'
      /vendor/rails/actionpack/lib/action_controller/url_rewriter.rb:208:in `rewrite_path'
      /vendor/rails/actionpack/lib/action_controller/url_rewriter.rb:187:in `rewrite_url'
      /vendor/rails/actionpack/lib/action_controller/url_rewriter.rb:165:in `rewrite'
      /vendor/rails/actionpack/lib/action_controller/base.rb:626:in `url_for'
      /vendor/rails/actionpack/lib/action_controller/caching/actions.rb:144:in `initialize'

キャッシュ内容を保存する際に使うキーが生成できずにエラーになっている。

このあたりの処理は、ActionController::Caching::Actions::ActionCachePath 
で行われている。

 LANG:ruby(linenumber)
 module ActionController::Caching::Actions
   class ActionCachePath
     attr_reader :language
 
     def initialize(controller, options = {}, infer_extension=true)
       if infer_extension and options.is_a? Hash
         request_extension = extract_extension(controller.request)
         options = controller.params.merge(
                   options.reverse_merge(
                       :format => request_extension, 
                       :rails_language => controller.request.accepts_languages.first))
       end
       path = controller.url_for(options).split('://').last
       if infer_extension
         @extension = request_extension
         add_extension!(path, @extension)
       end
       @path = URI.unescape(path)
     end
 
   private
 
     def add_extension!(path, extension)
       if extension
         p= path.split('.')
         p.insert(-2, extension) unless path =~ /\b#{Regexp.escape(extension)}\b/
         p.join('.')
       end
     end
     
     def extract_extension(request)
       p= request.path.split('.')
       # drop file name
       p.shift
       # drop language
       p.pop if !p.empty? && ActionController::AbstractRequest.acceptable_language?(p.last)
       extension = p.join('.')
 
       # If there's no extension in the path, check request.format
       extension = request.cache_format if extension==""
 
       extension
     end
   end
 end

8〜11行目の部分、元のコードでは controller.params とマージせずに
うまく行っているのだけど、このコードでは controller.params とマージしないと
action や id の指定が無視されるようだったので、対症療法として入れてある。

後で要チェック

request.accepts_languages.first の言語テンプレートファイルが存在しない時、
キャッシュキーには request.accepts_languages.first で示される言語指定が
付くにもかかわらず、中身は提供可能な言語で表示された内容になる。
注意が必要ではあるが、恐らくこれは正しい動作。

** テスト [#n1f7d9d6]

 LANG:console
 $ (restart script/server)
 $ wgetc http://localhost:3000/test
  日本語:テスト ばっちり
  
 $ less log/development.log
  Cached fragment miss: views/localhost:3000/test.ja (0.1ms)
 $ wgetc http://localhost:3000/test
  日本語:テスト ばっちり
  
 $ less log/development.log
  Cached fragment hit: views/localhost:3000/test.ja (0.1ms)
 $ wgetc http://localhost:3000/test.ja
  日本語:テスト ばっちり
  
 $ less log/development.log
  Cached fragment hit: views/localhost:3000/test.ja (0.1ms)
 $ wgetc http://localhost:3000/test.en
  English: Test   OK
  
 $ less log/development.log
  Cached fragment miss: views/localhost:3000/test.en (0.1ms)
 $ wgetc http://localhost:3000/test.en
  English: Test   OK
  
 $ less log/development.log
  Cached fragment hit: views/localhost:3000/test.en (0.1ms)
 $ wgetc http://localhost:3000/test
  English: Test   OK
  
 $ less log/development.log
  Cached fragment hit: views/localhost:3000/test.en (0.1ms)
 $ wgetc http://localhost:3000/test/index
  English: Test   OK
  
 $ less log/development.log
  Cached fragment hit: views/localhost:3000/test.en (0.1ms)
 $ wgetc http://localhost:3000/test/index/0
  English: Test   OK
  
 $ less log/development.log
  Cached fragment miss: views/localhost:3000/test/index/0.en (0.1ms)
 $ wgetc http://localhost:3000/test/index/0.html
  English: Test   OK
  
 $ less log/development.log
  Cached fragment hit: views/localhost:3000/test/index/0.en (0.1ms)

http://localhost:3000/test/index.en へのアクセスに対して~
views/localhost:3000/test.en が、~
http://localhost:3000/test/index/0.html.en へのアクセスに対して~
views/localhost:3000/test/index/0.en が、~
それぞれ引かれていることに注意。

url_for で変換し直している効果が現れている。

routes の記述順によっては逆に冗長な記述への変換(/test を /test/index へ、など)
が起こってしまう場合があるので、routes では省略形をより上の方に書くのが良いようだ。

* フラグメントキャッシュ [#p06455c8]

まずは例によって rails そのままで、フラグメントキャッシュ機能を使ってみる。

 LANG:console
 $ echo "<% cache do %>Test   <%= render :partial=>'ok' %><% end %>" > app/views/test/index.html.en.erb
 $ echo "<% cache do %>テスト <%= render :partial=>'ok' %><% end %>" > app/views/test/index.html.ja.erb
 $ jed app/controllers/test_controller.rb
  class TestController < ApplicationController
  #  caches_page :index
  #  caches_action :index
    def index
    end
  end
 $ (restart script/server)
 $ wgetc -qO- http://localhost:3000/test
  English: Test   OK
 
 $ less log/development.log
  Cached fragment miss: views/localhost:3000/test (0.1ms)

やっぱりうまく行っていない。

ここは、views/localhost:3000/test.en となってほしいところ。

これは ActiveSupport::Cache のテリトリーらしい。

 LANG:ruby(linenumber)
 module ActionController
   module Caching
     module Fragments
       def fragment_cache_key(key)
         ActiveSupport::Cache.expand_cache_key(
               key.is_a?(Hash) ? 
                   url_for(key.reverse_merge(
                            :rails_language=>request.accepts_languages.first)
                       ).split("://").last :
                   key, 
               :views)
       end
     end
  end
 end

** テスト [#le18b1e9]

 LANG:console
 $ (restart script/server)
 $ wgetc http://localhost:3000/test
  English: Test   OK
  
 $ less log/development.log
  Cached fragment miss: views/localhost:3000/test.en (0.1ms)
 $ wgetc http://localhost:3000/test
  English: Test   OK
  
 $ less log/development.log
  Cached fragment hit: views/localhost:3000/test.en (0.1ms)
 $ wgetc http://localhost:3000/test.en
  English: Test   OK
  
 $ less log/development.log
  Cached fragment hit: views/localhost:3000/test.en (0.1ms)
 $ echo "<% cache :param1=>:test do %>Test   <%= render :partial=>'ok' %><% end %>" > app/views/test/index.html.en.erb
 $ wgetc http://localhost:3000/test
  English: Test   OK
  
 $ less log/development.log
  Cached fragment miss: views/localhost:3000/test.en?param1=test (0.1ms)

うまく行っているようだ。



Counter: 6087 (from 2010/06/03), today: 4, yesterday: 0