Rails中的弱ETAG?

时间:2013-09-09 07:51:56

标签: ruby-on-rails http nginx etag

在使用方法fresh_whenstale?时告诉rails使用weak instead of strong ETAGs的最佳方法是什么?

我问的原因是nginx (correctly) removes strong ETAG headers from responses when on-the-fly gzipping is enabled

4 个答案:

答案 0 :(得分:6)

我从@ grosser的答案中取出代码并将其变成了宝石:

您可以将它添加到您的gemfile:

gem 'rails_weak_etags'

它将在Rack::ConditionalGet之前安装到您的中间件中:

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

然后,使用Rack :: ETag或带有显式电子标签的rails生成的所有电子标签都将转换为弱标签。使用修补或版本> 1.7.3 of nginx,然后让你使用电子标签和gzip压缩。

RACK 1.6默认etags为弱 - 如果升级,此gem不再有用。

答案 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

答案 2 :(得分:1)

答案 3 :(得分:1)

这是一种可以避免在应用程序服务器中进行任何更改的替代方法。该指令将应用程序返回的所有etags转换为弱etags,然后才从响应中删除它们。把它放在你的nginx配置里面:

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

我已经检查过这适用于nginx 1.7.6。