Weak ETAGs in Rails?

前端 未结 4 1584
你的背包
你的背包 2020-12-16 03:20

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

相关标签:
4条回答
  • 2020-12-16 03:41

    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.

    0 讨论(0)
  • 2020-12-16 03:43

    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.

    0 讨论(0)
  • 2020-12-16 03:50

    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
    0 讨论(0)
  • 2020-12-16 03:56

    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
    
    0 讨论(0)
提交回复
热议问题