rack-cache icon indicating copy to clipboard operation
rack-cache copied to clipboard

Rack:Cache plays poorly with Rails asset pipeline

Open iangreenleaf opened this issue 11 years ago • 3 comments

This issue isn't strictly a bug in Rack::Cache, but it's a problem that can very easily come up when using Rack::Cache with Rails. Not sure what the best solution is, but thought I'd raise the issue and see what you thought.

It's very easy to recreate the problem:

  1. Create a new Rails app. Use the asset pipeline. Put something into one of your assets.

  2. Add Rack::Cache as directed in config/application.yml:

       config.middleware.use(Rack::Cache)
    
  3. Start your Rails server in development mode.

  4. Load a page, receive asset.

  5. In a fresh cache (i.e. a new Chrome incognito window), load the same page a second time. This time the asset request wrongly returns a 304 and empty body, thus the asset is not received.

Here's why it happens (to the best of my understanding):

The Rails middleware stack includes Rack::ConditionalGet, and this resides higher on the stack than Rack::Cache (which is added near the end). ConditionalGet sees the request for the asset and forwards it along.

Rack::Cache receives the request (because of the asset pipeline, asset requests travel the whole stack in development). It needs to check if its cached version is fresh, so it adds HTTP_IF_MODIFIED_SINCE and HTTP_IF_NONE_MATCH headers to the request env and forwards it along.

Rails agrees that yeah, that's fresh, so Rack::Cache correctly builds a 200 response using its cached copy. It passes things back up the stack to Rack::ConditionalGet, which now looks at the env, sees that the caching headers are set (because Rack::Cache set them earlier), and thinks those are browser headers. So it decides to return a 304 to the poor browser, who never indicated that it had anything cached.

Here's how I fixed / worked around the issue:

config.middleware.insert_before(Rack::ConditionalGet, Rack::Cache)

If Rack::Cache gets to do its business first, everything goes according to plan, and the two middlewares play much nicer together.

So... is this Rack::Cache's fault for dirtying up @env? Or Rack::ConditionalGet's fault for looking at the env at a weird time and assuming it came from the browser? Or is this just a documentation issue?

iangreenleaf avatar Feb 26 '13 00:02 iangreenleaf

Hmm, teah Rack::Cache should definitely come before Rack::ConditionalGet in the middleware stack IMO. I thought it was like this originally. I wonder if something changed there.

rtomayko avatar Feb 26 '13 01:02 rtomayko

I've been testing this issue on Rails 3.2, BTW.

iangreenleaf avatar Feb 26 '13 02:02 iangreenleaf

Thanks for this explanation. I'm having the same problem for a while and finally it bothered me enough to do a search here.

For future visitors, if you need to pass the config, the syntax is:

    config.middleware.insert_before(Rack::ConditionalGet, Rack::Cache, {
      metastore: "memcached://localhost:11211/meta",
      entitystore: "file:tmp/cache/rack/body",
      verbose: true,
      allow_reload: true,
      allow_revalidate: true
    })

chengguangnan avatar Jul 11 '15 07:07 chengguangnan