Rails Rails / Nginx 与 Weak Etag

ylt · 2014年12月15日 · 最后由 Guest 回复于 2014年12月17日 · 5256 次阅读
本帖已被管理员设置为精华贴

Ruby-china 上有几篇讨论 Etag 的帖子,比如

要了解 Etag 的使用,推荐先看看上面的文章和讨论。但是我好像没有看到过介绍 rails 中使用 Weak Etag 的。

在讨论 Weak Etag 前,先解释一下 rails 自带的中间件 Rack::ETag 与自己写 fresh_when/is_stale 的区别。使用 Rack::ETag 能够节省网络传输时间,并不节省服务器端的计算量;而使用 fresh_when 则能节省页面渲染+网络传输时间,复杂一些的 Rails 的页面渲染一般需要 10ms 以上,这是 fresh_when 相比 Rack::ETag 能够节省下来的。我的做法是重要的页面自己使用 fresh_when 计算 Etag,其它统统让 Rack::ETag 处理。

但是用 Rails 生成 Etag 配合 Nginx 有个坑,就是使用 Nginx 的压缩的时候,Nginx 会简单的把 Etag 去掉,理由是压缩后的内容变化了,Etag 无效了。Quakewang 的帖子中提到了这个问题,并给出了两种解决办法:1.用 Rack 中间件进行 gzip 压缩,2.修改 nginx 源代码。其实这两种方案都不能让人满意。这个坑就引出了本文的主角 Weak Etag,对 Etag 详细的说明见下面的链接:

http://en.wikipedia.org/wiki/HTTP_ETag

简单来说,Weak Etag 和 (strong) ETag 的区别在于:Etag 保证文件的内容是完全相同的,而 Weak Etag 只保证文件的内容是语义上相同的。区分 ETag 和 Weak Etag 对一些 byte by byte 的操作有影响,比如断点续传等。

Weak Etag 就是在 Etag 前面增加了两个字符 W/,比如:

"123456789" -- A strong ETag validator W/"123456789" -- A weak ETag validator

从 Nginx1.7.3 版本开始,其 gzip 模块能正确的处理 Weak Etag 了。源代码在下面两个文件中: src/http/modules/ngx_http_gzip_filter_module.c src/http/ngx_http_core_module.c

如果 response 中带 Weak Etag,那么 Nginx 的 gzip 模块不处理;如果 response 中带 strong Etag,那么 Nginx 会把它转化为 Weak Etag。

所以如果使用了 Nginx1.7.3 以后版本,让 rails 应用支持 etag 很简单。让 rails 生成的 Etag 成为 Weak Etag 即可,代码如下:

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

也可以使用 gem 'rails_weak_etags', 代码拷贝自 http://stackoverflow.com/questions/18693718/weak-etags-in-rails

此外,Rack::ETag 在 1.6.0.beta2 以后,默认也生成 Weak Etag。 https://github.com/rack/rack/commit/12528d4567d8e6c1c7e9422fee6cd8b43c4389bf 如果你使用的 Rails 版本足够新,那么不需要做任何事情就能正确处理 Etag 了。我目前还是使用的 Rack 1.5 的版本,所以还是需要上面的解决方案。

匿名 #2 2014年12月17日

:thumbsup:

3 楼 已删除
需要 登录 后方可回复, 如果你还没有账号请 注册新账号