Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rack:Cache plays poorly with Rails asset pipeline #80

Open
iangreenleaf opened this issue Feb 26, 2013 · 3 comments
Open

Rack:Cache plays poorly with Rails asset pipeline #80

iangreenleaf opened this issue Feb 26, 2013 · 3 comments

Comments

@iangreenleaf
Copy link

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?

@rtomayko
Copy link
Owner

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.

@iangreenleaf
Copy link
Author

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

@chengguangnan
Copy link

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
    })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants