Rack middleware: Adding cache headers 2008-03-22

I'm playing with a small web based RSS reader, and one of the things it does is cache a lot of data to reduce the impact on the sites I follow feeds from. However I realized this a couple of days ago I'd completely forgotten to set cache headers, and I kept hammering my own site.

That wouldn't do, and since my app is using Rack to interface to the webserver, there's a simple solution: Write a tiny Rack middleware class, just like I described in "Rewriting content types with Rack".

The great thing is that because of Rack this class can be used to add cache headers for anything from pure Rack based apps to apps using Rails, Merb, or any number of other Ruby frameworks that has Rack adapters. If you haven't looked into Rack already, do.

I added the following in a file named "cachesettings.rb":

    class CacheSettings
      def initialize app, pat
        @app = app
        @pat = pat
      def call env
        res = @app.call(env)
        path = env["REQUEST_PATH"]
        @pat.each do |pattern,data|
          if path =~ pattern
            res[1]["Cache-Control"] = data[:cache_control] if data.has_key?(:cache_control)
            res[1]["Expires"] = (Time.now + data[:expires]).utc.rfc2822 if data.has_key?(:expires)
            return res

It's pretty straightforward:

I then added it to my config.ru (the Rack config file for my app) like this:

    require 'cachesettings'
    use CacheSettings, {
      /\/static\// =>
         { :cache_control => "max-age=86400, public",
          :expires => 86400
    use Rack::Static, :urls => ["/static"]

That's all there's too it - it can add cache headers to any arbitrary results. It should be obvious how to extend this to add any arbitrary headers too, or rewrite existing headers.

Useful exercises for the reader: Extend it to allow adding a Last-Modified header or Etag's.

blog comments powered by Disqus