ETag 是 HTTP 协议的一部分,可以用来检测客户端的缓存是否仍然有效。不少网站都实现了对 ETag 的支持,在 HTTP 响应头中加入当前传送内容的 ETag。以 heroku.com 为例:

$ curl -I www.heroku.com
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 17 Feb 2012 17:36:44 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Etag: "f74bb78aa48d36e6a0b2072a131b20b9"
Cache-Control: public, max-age=300
Content-Length: 15481

可以看到响应头中给出了请求内容的 ETag 为 f74…b9。接下来我们再次向服务器发起请求,并通过 If-None-Match 字段告之服务器我们已经获得过一份 ETag 为 f74…b9 的内容。服务器收到请求并生成页面后,如果发现页面的 ETag 不变,就会返回 304,声明缓存有效:

$ curl -I -H 'If-None-Match: "f74bb78aa48d36e6a0b2072a131b20b9"' www.heroku.com
HTTP/1.1 304 Not Modified
Server: nginx
Date: Fri, 17 Feb 2012 17:37:03 GMT
Connection: keep-alive
Last-Modified: Fri, 17 Feb 2012 17:34:00 GMT
Cache-Control: public, max-age=300
ETag: "f74bb78aa48d36e6a0b2072a131b20b9"

浏览器在访问网页时,记录下上一次服务器返回的 ETag,在下一次访问该网页时发送这个 ETag,这样就能有效的利用本地缓存,减少不必要的网络传输了。

Nginx 和 Apache 都很好的支持了 ETag 功能,它们会为静态资源计算 Hash,并将它作为 ETag 返回给客户端。 而对于 CPU 负载较高的应用,还有一个可以优化的空间。Web 服务器在处理客户端请求时,通常的过程是从数据库读取所需数据,交给模版引擎生成 HTML,计算该页面的 ETag,再回复客户端。事实上所需的数据读取完成后,就足以判断缓存是否有效了。

Rails 早在版本 2.2 就加入了 stale? 方法方便开放人员自定义 ETag。以一个博客文章页面为例,页面内容由文章和评论内容生成,如果这两份内容都没有变化,就可以认为客户端缓存依然有效了:

def show
  @article = Article.find(params[:id])
  @comments = @article.comments.all
  if stale?(:etag => [@article, @comments])
    respond_to do |format|
      # ...
    end
  end
end

除此之外,还可以通过 Last Modified 字段控制缓存有效性,Rails 也同样提供了很方便的支持。但是ETag 方法控制缓存更为精确,而且在服务器时间变化时用 Last Modified 容易出错,因此 ETag 往往是更被推荐的方案。