2009-12-11

Page caching per request domain with Rails and nginx

At work we run a number of different sites of the same codebase, serving content based on the request's domain. we recently found some problems with page caching when different sites shared common urls, so domain1.com/somepage and domain2.com/somepage would end up wrongly serving the same content, randomly switching based on which was requested first after the cached version expired.

This was solved with a little modification to Rails and our nginx config.

Firstly we chucked this guy in lib/cache_page_with_domain_name.rb

require 'action_controller'
module ActionController::Caching::Pages
def cache_page_with_domain_name(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]))
when String
options
else
request.path
end
#prefix the request domain so caches between sites dont interfere with each other
path = "/#{request.domain}#{path}"

self.class.cache_page(content || response.body, path)
end

alias_method_chain :cache_page, :domain_name
end


this makes the rails cache_page method write out cache files into public/#{request.domain}/ instead of just public/

next we added some config for nginx to make it look in these paths for these pages

nginx.conf

# serve any existing file in site specific cache dirs
if (-f $document_root/$host$uri) {
rewrite (.*) /$host$1
break;
}

# serve any standard Rails page cache file with .html extension in site specific cache dirs
if (-f $document_root/$host$uri) {
rewrite (.*) /$host$1.html
break;
}


I should add a note that you'll also have to modify whichever method you use to sweep stale caches to clear out the "public/#{domain}" folders, otherwise your cached pages wont update between deploys