野ログはノロキュアMaxHeart
2007-12-17
_ map.resourcesとApacheでページキャッシュが共存できない件とその解決法
RailsはRestfulな方向に進化していて、最近は
map.resourcesというメソッドを使いリソースに対するルーティングを一気に定義したりできます。
map.resources :targets
とroutesに書いたとき、定義されるルーティングは
targets GET /targets {:action=>"index", :controller=>"targets"}
formatted_targets GET /targets.:format {:action=>"index", :controller=>"targets"}
POST /targets {:action=>"create", :controller=>"targets"}
POST /targets.:format {:action=>"create", :controller=>"targets"}
new_target GET /targets/new {:action=>"new", :controller=>"targets"}
formatted_new_target GET /targets/new.:format {:action=>"new", :controller=>"targets"}
edit_target GET /targets/:id/edit {:action=>"edit", :controller=>"targets"}
formatted_edit_target GET /targets/:id/edit.:format {:action=>"edit", :controller=>"targets"}
target GET /targets/:id {:action=>"show", :controller=>"targets"}
formatted_target GET /targets/:id.:format {:action=>"show", :controller=>"targets"}
PUT /targets/:id {:action=>"update", :controller=>"targets"}
PUT /targets/:id.:format {:action=>"update", :controller=>"targets"}
DELETE /targets/:id {:action=>"destroy", :controller=>"targets"}
DELETE /targets/:id.:format {:action=>"destroy", :controller=>"targets"}
となります。
現在PUTやDELETEを実装しているブラウザはほぼないので,実際は_methodというパラメータにメソッド名を入れて渡している感じになります。
これにより,
/targets/:id
に対してGETでアクセスした場合はshow,PUTでアクセスした場合はupdate,DELETEでアクセスした場合はdestroyというようになることを擬似的に実現しています。
この時に問題になるのがページキャッシュ。
ページキャッシュは一度目にアクセスを行われた時に、指定されたパスにファイルがある場合そのファイルを返す。ということによって高速なアクセスを実現させています。
Apacheの場合標準では.htaccessに対して
RewriteRule ^$ index.html [QSA]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
というような記述があります。
一度/targets/1にアクセスがあった場合/targets/1.htmlを生成し、二度目からはRailsにアクセスを通すことなく、Apacheが/targets/1.htmlを返却します。
しかし、map.resourcesを利用した場合、見る場合、更新する場合、削除する場合全て同じ/targets/1にアクセスします。
現状のページキャッシュでは消したいとき、更新したいときもページキャッシュが存在する場合は、そのファイルを返してしまい、意図した通りにできないのではないか…
と考えました。
apacheとmongrelでそれぞれチェックしてみたところ、
mongrelでは意図した通りの動くものの、apacheではやはり.htaccessの指定通りキャッシュを返します
(当たり前ですが…)
現状mongrelをAPとして使っている構成が多いとは思いますが、たいていの場合、フロントにApacheを置いてmod_proxy_balancerでアクセスをmongrelに振り分けていると思います。
その場合フロントのApacheに静的ファイルを返させるような設定にしている場合がほとんどかと思われます。
そのような場合destroyやupdateに対してもページキャッシュを返してしまい、意図した通り動いてくれません。
なので.htaccessを変更して意図した通りに動くように変更しましょう。
RewriteRule ^$ index.html [QSA]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f [OR]
RewriteCond %{REQUEST_METHOD} !GET
RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
私は上記のように変更を行いました。
GET以外のメソッドでアクセスされた時は、ページキャッシュを利用しないよう設定しました。
これで意図したように動作するはずです。
.htaccessの中身はもう少しいろいろ考えたい…
僕は普段はこれプラスページキャッシュは/cache/以下に全て保存するようにしてます。
そうすると
rm -rf ./public/cache/
とかでキャッシュ全部消せて楽。
svnから除外する設定も楽だし。
補足
onkブログに書いてある件だと多分無理です。
ページキャッシュはクエリーストリングを消したhtmlファイルを基本生成します。
あとうまく生かせる方法あったとしても上手いこと使えるヘルパー作らないと面倒な予感。
(もし勘違いした話してたらごめんなさい)
editが動かない。
ちょっと理由まで調査してないのですが、
上記の設定をしてもページキャッシュが存在するときeditが動きません。
(上記のをしなくても動きません。)
/targets/1/edit
が
/targets/1.html/edit
にリライトされてしまい、問題が発生します。
僕の設定ではうまくいってるので、原因解決には一切なってないですけど、僕の設定を紹介しておきます。
environment.rbで
config.action_controller.page_cache_directory = RAILS_ROOT+"/public/cache/"
と書いておく。
.htaccessは
RewriteRule ^$ cache/index.html [QSA]
RewriteRule ^([^.]+)$ cache/$1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f [OR]
RewriteCond %{REQUEST_METHOD} !GET
RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
こんな感じで。
解決したみたいです
原因はMultiViewみたいですねー。
考えてみるとたしかに!
MultiViewを切るのもいいですが、ページキャッシュを保存するディレクトリを別の場所にしてしまう方をおススメします。
なんでこんなくだらないことに必死かって?
CGIしか使えないレンタルサーバでRails使うにはページキャッシュが必須だからです!!←結論