问题
What is the best way to tell rails to use weak instead of strong ETAGs when using methods fresh_when
and stale?
?
The reason I ask is that nginx (correctly) removes strong ETAG headers from responses when on-the-fly gzipping is enabled.
回答1:
I took the code from @grosser's answer and turned it into a Gem:
- https://rubygems.org/gems/rails_weak_etags
- https://github.com/johnnaegle/rails_weak_etags
You can just add this to your gemfile:
gem 'rails_weak_etags'
And it will be installed into your middleware before Rack::ConditionalGet
:
> bundle exec rake middleware
....
use RailsWeakEtags::Middleware
use Rack::ConditionalGet
use Rack::ETag
....
Then all the e-tags generated by rails, either with Rack::ETag or with explicit e-tags will be converted to weak. Using a patched, or version > 1.7.3 of nginx, will then let you use e-tags and gzip compression.
RACK 1.6 defaults etags to weak - this gem is no longer helpful if you upgrade.
回答2:
middleware:
class WeakEtagMiddleware
def initialize(app)
@app = app
end
def call(env)
# make request etags "strong"
etag = env['HTTP_IF_NONE_MATCH']
if etag && etag =~ /^W\//
env['HTTP_IF_NONE_MATCH'] = etag[2..-1]
end
status, headers, body = @app.call(env)
# make response etags "weak"
etag = headers['ETag']
if etag && etag !~ /^W\//
headers['ETag'] = "W/#{etag}"
end
[status, headers, body]
end
end
plus add middleware
Rails.application.config.middleware.insert_before(Rack::ETag, WeakEtagMiddleware)
plus unit tests
context WeakEtagMiddleware do
let(:backend) { Rack::ConditionalGet.new(Rack::ETag.new(lambda { |env| [env["status"] || 200, {}, ["XXX"]] })) }
let(:app) { WeakEtagMiddleware.new(backend) }
let(:expected_digest_1) { "bc9189406be84ec297464a514221406d" }
let(:env) { {"REQUEST_METHOD" => "GET"} }
should "converts etags to weak" do
status, headers, body = app.call(env)
assert_equal %{W/"#{expected_digest_1}"}, headers["ETag"]
assert_equal status, 200
end
should "not add etags to responses without etag" do
status, headers, body = app.call(env.merge("status" => 400))
refute headers["ETag"]
assert_equal status, 400
end
should "recognize weak ETags" do
status, headers, body = app.call(env.merge("HTTP_IF_NONE_MATCH" => %{W/"#{expected_digest_1}"}))
assert_equal status, 304
end
should "not recognize invalid ETags" do
status, headers, body = app.call(env.merge("HTTP_IF_NONE_MATCH" => %{W/"something-not-fresh"}))
assert_equal status, 200
end
end
plus integration tests
require_relative "../helpers/test_helper"
class WeakEtagsTest < ActionController::IntegrationTest
class TestController < ActionController::Base
def auto
render :text => "XXX"
end
def fresh
if stale? :etag => "YYY"
render :text => "XXX"
end
end
end
additional_routes do
get '/test/weak_etags/:action', :controller => 'weak_etags_test/test'
end
fixtures :accounts, :users
context "weak etags" do
let(:expected_digest_1) { "bc9189406be84ec297464a514221406d" }
let(:expected_digest_2) { "fd7c5c4fdaa97163ee4ba8842baa537a" }
should "auto adds weak etags" do
get "/test/weak_etags/auto"
assert_equal "XXX", @response.body
assert_equal %{W/"#{expected_digest_1}"}, @response.headers["ETag"]
end
should "adds weak etags through fresh_when" do
get "/test/weak_etags/fresh"
assert_equal "XXX", @response.body
assert_equal %{W/"#{expected_digest_2}"}, @response.headers["ETag"]
end
should "recognize auto-added ETags" do
get "/test/weak_etags/auto", {}, {"HTTP_IF_NONE_MATCH" => %{W/"#{expected_digest_1}"}}
assert_response :not_modified
end
should "recognize fresh ETags" do
get "/test/weak_etags/fresh", {}, {"HTTP_IF_NONE_MATCH" => %{W/"#{expected_digest_2}"}}
assert_response :not_modified
end
end
end
回答3:
It looks like Rack::ETag will use weak-etags in the future:
- https://github.com/rack/rack/issues/681 https://github.com/rack/rack/commit/12528d4567d8e6c1c7e9422fee6cd8b43c4389bf
回答4:
Here's an alternative that avoids making any changes in your application server. This directive converts all etags returned by your application to weak etags before they get stripped from the response. Put it inside your inside your nginx config:
location / {
add_header ETag "W/$sent_http_ETAG";
}
I've checked that this works with nginx 1.7.6.
来源:https://stackoverflow.com/questions/18693718/weak-etags-in-rails