2013-09-09 4 views
8

Что лучше всего сказать рельсам использовать weak instead of strong ETAGs при использовании методов fresh_when и stale??Слабые ETAG в Rails?

Причина, по которой я спрашиваю, это то, что nginx (correctly) removes strong ETAG headers from responses when on-the-fly gzipping is enabled.

+0

Механизм ETag поддерживает как сильную проверку, так и слабую проверку. Они отличаются наличием начального «W /» в идентификаторе ETag, поэтому мы можем проверить, что при получении этого –

+0

@RajarshiDas Итак, как вы это делаете? В моем контроллере у меня есть 'fresh_when etag: @ my_model', который генерирует (сильный) ETag на основе метода cache_key моделей. Ответ просто «fresh_when etag:« W/#{@my_model.cache_key} »? – KaptajnKold

+1

'etag_matches? (Etag)' для проверки etag или 'fresh_when (« W/# {@ my_model.cache_key} »), как вы сказали –

ответ

6

Я взял код из ответа @ GROSSER и превратили его в Gem:

Вы можете просто добавить это к вашему Gemfile:

gem 'rails_weak_etags' 

И он будет установлен в ваше промежуточное программное обеспечение до Rack::ConditionalGet:

> bundle exec rake middleware 
.... 
use RailsWeakEtags::Middleware 
use Rack::ConditionalGet 
use Rack::ETag 
.... 

Тогда все электронные теги, созданные рельсами, либо с помощью Rack :: ETag, либо с явными e-тегами будут преобразованы в слабые. Используя исправленную версию или версию> 1.7.3 nginx, вы сможете использовать электронные теги и сжатие gzip.

RACK 1.6 defaults etags to weak - этот драгоценный камень больше не помогает при обновлении.

+0

Я до сих пор не вижу, как это исправлено, поскольку Rack никогда не удаляет часть «W /», а Rails не знает, как ее обрабатывать. Похоже, они добавили это в Rails 5! (Https://github.com/johnnaegle/rails_weak_etags/issues/1) –

3

промежуточного слоя:

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 

плюс добавить промежуточного

Rails.application.config.middleware.insert_before(Rack::ETag, WeakEtagMiddleware) 

плюс блок тестирует

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 

плюс интеграционные тесты

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 
+0

Это работало, чтобы добавить слабые etags, но nginx по-прежнему не поддерживает слабые электронные теги (апрель 2014 года). Возможно, в 1.7.x говорится, что один из авторов: forum.nginx.org/read.php?29,249380,249423#msg-249423) –

+0

nginx patch: https://github.com/boxen/puppet-nginx/pull/26 – grosser

+2

nginx 1.7.3 ищет слабую поддержку электронных тегов: http://nginx.org/en/CHANGES –

1

Вот альтернатива, которая позволяет избежать внесения каких-либо изменений в сервере приложений. Эта директива преобразует все etags, возвращаемые вашим приложением, в слабые etags, прежде чем они удаляются из ответа. Поместите его внутри своей конфигурации nginx:

location/{ 
    add_header ETag "W/$sent_http_ETAG"; 
} 

Я проверил, что это работает с nginx 1.7.6.