conditional use of page cache in Rails

As discussed in my previous post, I worked out a way to use Page caching for the ‘public’ view of my Rails app, while rendering the same pages through Rails if the user is logged in. I implemented my idea today, so here’s a bit more technical detail.

First, I add a cookie that indicates the user is logged in:

#in login code...
cookies[:logged_in] = 'yes'
 
#in logout code...
cookies.delete :logged_in

The actual user details are kept in the session as before, this is a separate flag.

Then the web server needs to check the cookie when deciding whether to use the page cache. In Apache, this means adding a condition to the rewrite rule in .htaccess, so the block looks like this:

RewriteRule ^$ index.html [QSA]
RewriteCond %{HTTP_COOKIE} !^.*logged_in=yes.*$
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]

(I’ve added the line with ‘HTTP_COOKIE’ in it – a RewriteCond applies to the RewriteRule below it.)

Update: see my followup article for some discussion of problems and solutions with HTTP caching.

For development, I implemented the same behaviour in Webrick. I copied webrick_server.rb into my app’s lib directory so I could edit it, and added the cookie check in handle_file:

def handle_file(req, res) #:nodoc:
begin
req = req.dup
path = req.path.dup
 
if path != '/' && (%r{(^|/)[^./]+$} =~ path)
if(req.cookies.find{|c| c.name == "logged_in" && c.value == 'yes'})
return false
end
 
# Add .html if the last path piece has no . in it
path << '.html'
end
path.gsub!('+', ' ') # Unescape + since FileHandler doesn't do so.
 
req.instance_variable_set(:@path_info, path) # Set the modified path...
 
@file_handler.send(:service, req, res)
return true
rescue HTTPStatus::PartialContent, HTTPStatus::NotModified => err
res.set_error(err)
return true
rescue => err
return false
end
end

These are the web servers I’m using, so I haven’t looked at how it would be implemented on any others.

I wrote my own cache setup method in my application.rb:

def self.caches_public_page(*actions)
return unless perform_caching
actions.each do |action|
class_eval "after_filter { |c| c.cache_page if !c.loggedin? && c.action_name == '#{action}' }"
end
end

This is basically the same as caches_page, but with a check so it only caches if there isn’t a logged in user. Other than using this the caching is set up and expired in the usual kind of way.

Update 2: this code uses a loggedin? method which you may need to define: see my next followup article for some discussion of how to do that.

3 Comments

  1. Posted October 24, 2006 at 1:34 am | Permalink
  2. anthony
    Posted May 10, 2008 at 5:48 am | Permalink

    wow, how did you get TripAdvisor photos into your page??

    This caching solution is very elegant.

  3. Edward
    Posted May 10, 2008 at 7:07 am | Permalink

    I work for TripAdvisor now, and so heard about the photo widget which is in ‘soft launch’.

3 Trackbacks

  1. […] The Junction » conditional use of page cache in Rails (tags: rails caching ruby ror performance rubyonrails) […]

  2. […] The Junction » conditional use of page cache in Rails […]

  3. […] does not work properly when switched over to Passenger though. The problem is that my caching setup relies on a custom rewrite rule, but Passenger disables use of mod_rewrite for web applications it […]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*