Rails ETag 应用

使用 ETag 验证缓存

etag.png-27.9kB
来源:Browser Cache: How ETags Works in Rails 3 and Rails 4
可以把 ETag 看成是响应的 token,客户端在下一个同样的请求中将其发送给服务器。服务器通过 token 来判断资源是否进行修改。若未修改,则返回 304 Not Modified,使客户端不需再去下载与缓存中已有的完全相同的字节。

fresh_when

在 Rails 中,我们可以通过 stalefresh_when 方法来显式地使用 ETag。

1
2
3
4
5
6
7
8
9
10
class ProductsController < ApplicationController

# This will automatically send back a :not_modified if the request is fresh,
# and will render the default template (product.*) if it's stale.

def show
@product = Product.find(params[:id])
fresh_when last_modified: @product.published_at.utc, etag: @product
end
end

上例中通过 `@product.published_at.utc` 来判断资源是否过期。

默认开启的 ETag

最近才发现不用 fresh_when 返回的请求也会自带 ETag,查了下原来 Rails 框架默认使用 Rack::ETag middleware,会自动给无 ETag 的 response 根据其 body 添加上 ETag。

相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/rack/etag.rb', line 24

def call(env)
status, headers, body = @app.call(env)

if etag_status?(status) && etag_body?(body) && !skip_caching?(headers)
original_body = body
digest, new_body = digest_body(body)
body = Rack::BodyProxy.new(new_body) do
original_body.close if original_body.respond_to?(:close)
end
headers[ETAG_STRING] = %(W/"#{digest}") if digest
end

unless headers[CACHE_CONTROL]
if digest
headers[CACHE_CONTROL] = @cache_control if @cache_control
else
headers[CACHE_CONTROL] = @no_cache_control if @no_cache_control
end
end

[status, headers, body]
end

那这种默认的 ETag 和 fresh_when 有什么区别呢?
主要区别是判定缓存命中的时机。fresh_when 通过指定的 last_modified 来判定是否命中,若命中则不需要进行渲染。而 默认的 ETag 是根据 response body 生成的,必须先进行模板文件的渲染。也就是说,fresh_when 的效率更高。但同样的,使用 fresh_when 必须准确地指定 last_modified 来判定缓存是否失效。

参考