问题
I'm using CloudFlare CDN on my Rails 3.1 application. Cloudflare is a CDN that works at the DNS level. On the first hit to a static asset, CloudFlare loads it from your app then caches it in their CDN. Future requests for that asset load from the CDN instead of your app.
The problem I'm having is that if you set controller caching to true:
config.action_controller.perform_caching = true
it enables the Rack::Cache middleware. Since Rails sets a default cache control setting for static assets, those assets get written to the Rails.cache store. As a result my cache store (in my case redis) is being filled up with static assets with the url as the hash key.
Unfortunately, I can't turn off the static asset cache control headers without affecting how Cloudflare and my users' browsers cache the assets. I can't turn off controller caching or I lose page/action/fragment caching. Same result if I delete the Rack::Cache middleware.
Does anyone have any other ideas?
Update: I've opened a ticket on GitHub here.
回答1:
The original poster wanted to prevent static assets from getting into the general Rails cache, which led them to want to disable the Rack::Cache. Rather than doing this, the better solution is to configure Rack::Cache to use a separate cache than the general Rails cache.
Rack::Cache should be configured differently for entity storage vs meta storage. Rack::Cache has two different storage areas: meta and entity stores. The metastore keeps high level information about each cache entry including HTTP request and response headers. This area stores small chunks of data that is accessed at a high frequency. The entitystore caches the response body content which can be a relatively large amount of data though it is accessed less frequently than the metastore.
The below configuration caches the metastore info in memcached but the actual body of the assets to the file system.
Using memcached gem:
config.action_dispatch.rack_cache = {
:metastore => 'memcached://localhost:11211/meta',
:entitystore => 'file:tmp/cache/rack/body',
:allow_reload => false
}
Using dalli gem
config.action_dispatch.rack_cache = {
:metastore => Dalli::Client.new,
:entitystore => 'file:tmp/cache/rack/body',
:allow_reload => false
}
By the way this configuration is the recommendation for Heroku: https://devcenter.heroku.com/articles/rack-cache-memcached-static-assets-rails31
回答2:
After a lot of experimentation, I've ended up doing this in my config/application.rb:
if !Rails.env.development? && !Rails.env.test?
config.middleware.insert_before Rack::Cache, Rack::Static, urls: [config.assets.prefix], root: 'public'
end
What this does is add a Rack::Static rack middleware before requests to Rack::Cache. The Rack::Static middleware serves up urls with a matching prefix to a root directory. Here I'm giving config.assets.prefix as my url prefix which defaults to '/assets.' I'm setting the root to the 'public' directory.
Requests for this path:
/assets/jquery-e8da439bbc8fd345e34ac57c6a216318.min.js
should find it in this file:
public/assets/jquery-e8da439bbc8fd345e34ac57c6a216318.min.js
This should serve any assets directly out of the public/assets directory instead of hitting Rails::Cache at all, which will prevent it from storing the assets in the Rails cache_store. This will only work if you run the 'rake assets:precompile' in production, otherwise there will be no precompiled assets in 'public/assets'.
回答3:
You can turn off caching of asset pipeline files while leaving other caching in place with:
config.assets.cache_store = :null_store
That should keep Sprockets from caching anything.
回答4:
Another way to solve the same problem and this issue is to use the ActionDispatch::Static middleware instead of Rack::Static like this:
if !Rails.env.development? && !Rails.env.test?
config.middleware.insert_before Rack::Cache, ::ActionDispatch::Static, 'public', config.static_cache_control
end
What's the difference between Rack::Static and ActionDispatch::Static you ask?
Rack::Static takes an array of url prefixes to check against the request url. So in our case, it will only check for files if the request path begins with '/assets'.
ActionDispatch::Static will check for the existence of the file in 'public' on every GET/HEAD request, regardless of path.
Rack::Static doesn't check for the file first, it calls Rack::File.new on the file, so if it doesn't exist it will return a 404, it will not pass the request down the middleware chain.
If ActionDispatch::Static doesn't find the file in its path, it'll continue down the rack middleware chain (the rest of the Rails stack).
In the end, whatever ActionDispatch::Static doesn't find in 'public' it'll just pass on down to the Rails stack. So Rails will end up serving the assets that ActionDispatch::Static can't find. This solves my issue of assets not being found by Rack::Cache, but it's also more resource intensive since every request will trigger a file check.
来源:https://stackoverflow.com/questions/6962896/how-do-i-prevent-rails-3-1-from-caching-static-assets-to-rails-cache