Rack::SteadyETag
Important: This gem has been deprecated. We instead recommend reconfiguring your Rails app so two requests to the same resource produce the same HTML for a given user.
Rack::SteadyETag
is a Rack middleware that generates the same default ETag
for responses that only differ in XOR-masked CSRF tokens or CSP nonces.
By default Rails uses Rack::ETag
to generate ETag
headers by hashing the response body. In theory this would enable caching for multiple requests to the same resource. However, since most Rails application layouts insert randomly rotating CSRF tokens and CSP nonces into the HTML, two requests for the same content and user will never produce the same response bytes. This means Rack::ETag
will never send the same ETag twice, causing responses to never hit a cache.
Rack::SteadyETag
is a drop-in replacement for Rack::ETag
. It excludes random content (like CSRF tokens) from the generated ETag, causing two requests for the same content to usually carry the same ETag.
What is ignored
Rack::SteadyTag
ignores the following patterns from the ETag
hash:
<meta name="csrf-token" value="random" ...>
<meta name="csp-nonce" value="random" ...>
<input name="authenticity_token" value="random" ...>
<script nonce="random" ...> <!-- only the [nonce] attribute -->
You can add your own patterns:
Rack::SteadyETag::STRIP_PATTERNS << /<meta name="XSRF-TOKEN" value="[^"]+">/
You can also push lambda for arbitrary transformations:
Rack::SteadyETag::STRIP_PATTERNS << -> { |text| text.gsub(/<meta name="XSRF-TOKEN" value="[^"]+">/, '') }
Transformations are only applied for the ETag
hash. The response body will not be changed.
What responses are processed
This middleware will process responses that match all of the following:
- Responses with a HTTP status of 200 or 201.
- Responses with a
Content-Type
oftext/html
orapplication/xhtml+xml
. - Responses with a body.
Responses should also have an UTF-8 encoding (not checked by the middleware).
This middleware can also add a default Cache-Control
header for responses it didn't process. This is passed as an argument during middleware initialization (see Installation below).
Covered edge cases
- Different
ETags
are generated when the same content is accessed with different Rack sessions. - Different
ETags
are generated when a Rails controller manually rotates the CSRF token. -
ETags
are only generated when the response isCache-Control: private
(this is a default in Rails). - No
ETag
is generated when the response already has anETag
header. - No
ETag
is generated when the response already has anLast-Modified
header.
Installation in Rails
Add this line to your application's Gemfile:
gem 'rack-steady_etag'
And then execute:
bundle install
Make an initializer config/initializer/etags.rb
:
Rails.application.config.middleware.swap Rack::ETag, Rack::SteadyETag, 'no-cache'
The 'no-cache'
argument is the default Cache-Control
for responses that cannot be digested. While it may feel surprising that the middleware changes the Cache-Control
header in such a case, the Rails default middleware stack configures the same behavior.
Development
- After checking out the repo, run
bin/setup
to install dependencies. - Run
bundle exec rspec
to run the tests. - Run
BUNDLE_GEMFILE=Gemfile.rack1 bundle exec rspec
to run tests for old Rack 1. - You can also run
bin/console
for an interactive prompt that will allow you to experiment. - To release a new version, update the version number in
version.rb
, and then runbundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the.gem
file to rubygems.org.
Credits
This library is based on Rack::ETag
, created by the Rack Core Team and Rack contributors.
Additional changes by Henning Koch from makandra.
Limitations
- No streaming support. This will be broken until at least Rack 3. This is not a use case of mine.