11 May 2015

《http权威指南》读书笔记之缓存篇

今天休假一天,下午在家补充营养。

对于缓存这个东西,一直是半知不解,平时开发中接触的不多,所以也没太过深入的去了解。

正好今天有空,把《HTTP权威指南》的第七章缓存部分好好读了一遍,记录下来,供自己以后翻阅。


### 正文开始 ———-

好了不废话,关于什么是缓存,大家应该都有所了解。简单来说,就是为了节约带宽,加快网页的浏览速度,浏览器将服务器请求的文档储存一份副本在本地(这里讲的是私人缓存,其实还有公有缓存,这里不做讨论)。

(注:只有GET请求可以被缓存,而POST请求不能)

那么该如何判断使用本地缓存,还是向服务器拉取最新的文件?如何判断本地缓存是否过期?这些问题都是这一章主要讲的内容。

缓存使用中的三种状态

  1. 缓存命中–无任何请求发送,直接获取本地缓存

  2. 缓存再验证命中(revalidation)–发送请求至服务器,如果本地缓存仍是最新版本,则获取本地缓存(304 Not Modified)

  3. 缓存未命中–发送请求至原服务器获取文件

缓存使用中的三种状态

图1 缓存使用中的三种状态

那么我就从这三种状态出发,分别介绍HTTP协议中是如何规范这三种情况的。

1. 缓存命中

由图1a可以看出,在缓存命中的情况下,客户端无需发送任何请求,可直接使用本地缓存。

控制判断缓存是否生效(不发送请求直接使用本地副本)的是Cache-Control(HTTP/1.0中的Pragma)和Expires这两个首部。

Cache-Control

一般来说,Cache-Control又可以称为试用时间,它是一个相对时间,单位为秒。

如果Cache-Control由响应头从服务器发来,并指定了max-age(如mag-age:4000),指的是自客户端从服务器获取这份文档这一刻开始,4000秒内,客户端无需发送请求至服务器验证文档的新旧程度,可直接使用本地副本。

另外,可以通过html中的meta标签来指定该html文档的过期时间,但是不会影响除该html文档以外的文件(如JS,CSS,图片等)。

如:

<meta http-equiv="cache-control" content="max-age: 4000">

Expires

Expires就是一个真正的过期时间了,它是一个绝对时间。但是由于各服务器的始终可能不一致,或者不正确,所以这个时间并不靠谱,故书中不推荐使用。

(注意:有些服务器会回送一个Expires: 0,但这种语法是非法的,可能对某些软件带来问题。)

同样的,Expires可以由客户端接收的服务器响应头中设置,也可以通过html中的meta标签设置。

<!--注意:时间必须是GMT格式-->
<meta http-equiv="expires" content="Thu, 01 Dec 1994 16:00:00 GMT">

同时设置Cache-Control与Expires,Expires会被Cache-control覆盖。

2.缓存再验证命中(revalidation)

如果文件不在Cache-control/Expires的有效范围内,则该文档是需要再验证的(need revalidate)。

再验证的意思是,客户端必须再发送一个请求给服务器,并通过一定规则去验证当前本地副本是否最新的。

如果当前文档仍然是最新的(书中叫新鲜的),则服务器发送一个简单的304 Not Modified响应,告诉客户端放心使用本地副本,并可以更新本地副本的最新时间/版本信息。

控制再验证规则的是Last-Modified(If-Modified-since)以及ETag(If-None-match)头。

Last-Modified(If-Modified-since)

顾名思义,Last-Modified头表明该文件的最后修改日期,它是随响应头传来的。

当文档不满足直接使用本地缓存规则,并需要再验证的时候,如果它发送过来时的响应头中带有Last-Modified,则再验证文件时,需要带上一个If-Modified-Since的请求头。 它的意思也很明显,就是询问服务器,这个文件在这个日期以后有无被修改。

如果在该日期以后,文件被修改过,服务器会返回一个200 OK并将整个文档发送回来。否则,服务器仅会返回一个304 Not Modified,告诉客户端本地缓存仍然有效,可以直接使用,并更新本地文件的最后修改时间。

(注意,有些Web服务器会将If-Modified-Since左右字符串作为比较,而不是当作时间比较。)

ETag(If-None-match)

有了If-Modified-Since仍不足够,因为:

  • 有些文档可能被周期性的重写,但实际内容数据是一样的。
  • 有些文档可能被修改了,但所做修改并不重要,不需要让世界范围内的缓存都重装数据
  • 有些服务器无法准确的判断页面的最后修改日期
  • 有些服务器提供的文档会在亚秒间隙变化,对他们来说,以一秒为粒度的修改日期可能就不够用了

为了解决以上问题,HTTP允许用于使用被称为实体标签ETag的版本标识进行比较文件是否最新。

(ETag仅能通过设置Web服务器来控制,Apache,Nginx等)

ETag可以理解为一个名字,标签,指纹等,为一个字符串,可在服务器上设定规则,当文档改变时,改变ETag。

当文档的响应头中带有ETag时,再验证时,就会发送一个If-None-match=“ETAG的值”给服务器。

比如一个文件的ETag=v2.6,如果服务器上的Etag仍为v2.6,则返回304。

如果ETag已改变,如v3.0,则返回200 Ok 并返回新的ETag值。

如果同时设置了ETag与Last-modified,则二者在再验证中都将被使用。

(注意:HTTP 响应头中不包含 Last-Modified/Etag,也不包含 Cache-Control/Expires 的请求无法被缓存)

控制缓存的能力

由优先级递减的顺序,服务器可以附加这些首部到响应头中:

  • Cache-Control: no-store
  • Cache-Control: no-cache
  • Cache-Control: must-revalidate
  • Cache-Control: max-age
  • Expires日期
  • 不附加过期时间,让缓存确定自己的过期日期

no-store

禁止缓存对响应进行复制(副本至本地),响应向客户端发送no-store后,将文档删除。

no-cache

实际上标识了no-cache的响应是可以储存在本地的,只是在再验证以前,不能将其提供给客户端(必须再验证)。

关于Pragma: no-cache

为了兼容HTTP/1.0+, 除了为了兼容HTTP/1.0以外,所有HTTP/1.1应用都应该使用Cache-control: no-cache。

Cache-control:max-age

自服务器传文档起,认为缓存有效而不发送请求的秒数。

可以设置不要缓存(max-age: 0),而全部需要再验证。

Expires

绝对过期时间,不推荐使用,由于服务器时钟可能不同步,或者不正确,绝对时间并不靠谱。

客户端可以控制缓存的使用规则

每个浏览器的具体规则可能不尽相同,下图是一个参考的例子:

浏览器控制缓存

通过上图我们可以看到,当用户在按 F5 进行刷新的时候,会忽略 Expires/Cache-Control 的设置,而进行文件再验证,而 Last-Modified/Etag 还是有效的,服务器会根据情况判断返回 304 还是 200;

而当用户使用 Ctrl+F5 进行强制刷新的时候,只是所有的缓存机制都将失效,重新从服务器拉去资源。

总结

对缓存起作用的主要是这4个头:

  • Cache-Control(Pragma)
  • Expires
  • Last-Modifed(If-Modifed-Since)
  • ETag(If-None-Match)

Cache-Control(Pragma)与Expires设置了浏览器可以不发送请求而直接使用本地副本的规则。

其中Cache-Control是相对时间,Expires是绝对时间。同时设置时Cache-Control优先级高于Expires(如果设置了Pragma也将被覆盖)。

Cache-Control(Pragma)与Expires可在html中由meta标签设置,仅对该html文档本身生效。

If-Modifed-Since与If-None-Match是当Cache-Control/Expires失效时,需要向服务器发送再验证请求时用到的。

由最初获取该文档时响应头中是否带有Last-Modifed/ETag而决定是否使用这两种再验证方法。

以上四个头不带,则不会进行缓存。

全文完。

Posted in 2015-05-11 17:18