最近和朋友交流的时候,发现有人误以为 ETags 机制是直接通过服务器内存中已保存的信息进行匹配,不需要 Rails 再次生成。其实该结论在 ruby-china 一篇经典帖子 (评论更经典) 中已有结论:总结 web 应用中常用的各种 cache
Etags 是一种 Web 缓存验证机制,并且允许客户端进行缓存协商,能够更加高效的利用客户端的缓存。
如图,假设我们即将访问一个博客,并请求该博客的列表页面:
那么,使用 ETag 机制来匹配服务器上的内容有什么好处呢?好处就是 Rails 将不发送生成的页面内容,这样响应体将变的更小从而使得其网络中的传输速度更快。浏览器通过加载自身缓存中的内容,使得网站刷新更快,体验更好。
在 Rails 中,已经默认使用 ETag 机制,不需要额外操作,以下代码将自动使用 Rails 的默认 ETag 缓存机制
class PostsController < ApplicationController
def show
@post = Post. find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: @post }
end
end
def edit
@post = Post.find(params[:id])
end
end
那么 Rails 是如何生成 ETag 信息的呢? 首先,Rails 生成响应内容,并根据生成的响应内容生成 MD5 散列的 ETag,代码类似:
headers['ETag'] = Digest::MD5.hexdigest(body)
通过每次生成的响应内容来生成 ETag 并不能高效的利用服务器,因为这样服务器将耗时调用数据库和渲染模板文件。 如何避免呢? 方法就是通过 Rails 的 helper 方法 fresh_when 和 stale?来实现。 示例代码:
class PostsController < ApplicationController
def show
@post = Post. find(params[:id])
if stale? @post
respond_to do |format|
format.html # show.html.erb
format.json { render json: @post }
end
end
end
def edit
@post = Post.find(params[:id])
fresh_when @post
end
end
那么代码中 rails 是如何生成 ETags 呢? Rails 中 helper 方法 stale?和 fresh_when 实现原理如下:
headers['ETag'] = Digest::MD5.hexdigest(@post.cache_key)
cache_key
是结合了model_name/model.id-model.updated_at
。对于 Post 模型来说,cache_key
形式将是:post/123-201312121212
若你有特定的响应处理(如下面代码中的 show 动作),请使用 stale?方法;若你没有特定的响应处理,例如你不需要使用 respond_to 或调用 render 方法(如下面代码中的 edit 和 recent 动作),请使用 fresh_when。
假如你缓存是基于 current_user 或 current_customer,可以通过传入 Hash 格式的参数来生成自己的 ETag。 示例代码如下:
class PostsController < ApplicationController
def show
@post = Post. find(params[:id])
if stale? @post, current_user_id: current_user.id
respond_to do |format|
format.html # show.html.erb
format.json { render json: @post }
end
end
end
def edit
@post = Post.find(params[:id])
fresh_when @post, current_user_id: current_user.id
end
def recent
@post = Post.find(params[:id])
fresh_when @post, current_user_id: current_user.id
end
end
聪明的你一定发现,以上一些代码中有些代码冗余,我们要 DRY!这里就是 Rails4 引入的 ETags 声明特性发挥威力的时候了。 Rails 3 和 Rails 4 都默认使用 ETags 机制处理浏览器缓存。但 Rails 4 添加了声明式 ETags 特性,该特性允许你在控制器中添加全局的 Etag 信息。 示例代码:
class PostsController < ApplicationController
etag { current_user.id }
def show
@post = Post. find(params[:id])
if stale? @post
respond_to do |format|
format.html # show.html.erb
format.json { render json: @post }
end
end
end
def edit
@post = Post.find(params[:id])
fresh_when @post
end
def recent
@post = Post.find(params[:id])
fresh_when @post
end
end
你可以生成多个 ETags:
class PostsController < ApplicationController
etag { current_user.id }
etag { current_customer.id }
# ...
end
你也能够设置 ETags 的生成条件:
class PostsController < ApplicationController
etag do
{ current_user_id: current_user.id } if %w(show edit).include? params[:action]
end
end
:only
,:if
参数选项