1

现在的Web应用越来越复杂,体验越来越好。相应的,资源文件也越来越大,如果能让客户端在资源没更新的情况下,直接取用缓存的数据,那么不仅资源加载的更快,服务器压力更小,也为绿色地球做出了一份贡献。

程序员向来是追求最优,在设法缓存静态资源以后,大家又找到了一些方法来缓存动态生成的内容。看来程序员们都是环境保护主义者(笑。

从HTTP/1.0-HTTP/1.1,一共产生了3种控制缓存的方法:

  • Expires(响应头,HTTP/1.0)
  • Cache-Control(响应头,HTTP/1.1)

    - Last-Modified(响应头),If-Modified-Since(请求头)
    - Etag(响应头),If-None-Match(请求头)
    

以上就是HTTP控制缓存的方法。

除了这些,在第二个还有If-Unmodified-Since,第三个还有If-Match。但这两个响应头不是为了控制缓存,而是为了确认修改的资源的一致性。之后会介绍。

HTTP/1.0

Expires

Expires: <http-date>

其中 <http-date> 的格式如下:

Date: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
Example: Fri, 03 Nov 2017 03:22:39 GMT

当设置了Expires后,客户端在此日期之前都不会去请求服务器,而是直接从缓存中取。
不过这样也有一个问题:如果完全不去请求服务器的话,在资源过期之前如果资源有更新,客户端还是使用着之前的资源。所以就产生了下面更加高级的缓存控制方法(第三、第四项方法)。

不过还有一个方法能解决这个问题:将资源的Expires设置为一个足够长的时间,而资源名为资源的Hash值,这样资源更新了以后,也使用的是不同的名称。那么就不存在上面的问题了。

HTTP/1.1

Cache-Control

Expires只能设置具体的日期,这样的话,如果客户端与服务器的时间不一致,会导致缓存时间不正确,还有可能直接导致缓存失效。

HTTP/1.1的Cache-Control配合If-Modified-Since或If-None-Match则完美的解决了这个问题。

Cache-Control比较常用的指令如下:

  • 可缓存性

    • public: 响应可被任意缓存
    • private: 响应只能被客户端缓存,不能被中间节点(代理、CDN等)缓存
    • no-cache: 中间节点必须向原始服务器去验证缓存有效性。如果本身是原始服务器,则向自己确认
    • only-if-cached: 中间节点直接使用已存在的缓存来确认有效性,不向原始服务器确认。若本身是原始服务器,则此指令没有效果。
  • 过期控制

    • max-age=<seconds>: 指定相对于请求时间的过期时间,以秒为单位。
  • 再验证

    • must-revalidation: 缓存过期后必须向服务器确认缓存是否有效
  • 其他

    • no-store: 忽略缓存的存在,直接向服务器请求最新的资源。

上面列表中向服务器确认缓存是否有效的技术手段就是下文介绍的 Last-Modified/If-Modified-SinceETag/If-None-Match

Last-Modified / If-Modified-Since

Last-Modified: <http-date>

Last-Modified头是服务器告诉客户端此资源的最后修改时间,客户端则会将资源和这个时间都储存起来。之后,根据Cache-Control的指令,如果需要同服务器确认资源的有效性的时候则会将这个时间放在If-Modified-Since头中,供服务器进行比较,是返回304还是200。

If-Modified-Since头只会在GET和HEAD请求被附加进请求头。

If-Unmodified-Since
If-Unmodified-Since: <http-date>

一般来说在PUT, POST等方法使用,表示该次请求更新的资源的日期为<http-date>,如果服务器检测到现存资源的日期不为<http-date>(即已经被其他的方式更新了),则会失败并返回412。

ETag / If-None-Match

ETag: W/"<etag_value>"
ETag: "<etag_value>"
If-None-Match: "<etag_value>"[, "<etag_value>"...]
If-None-Match: W/"<weak_etag_value>"[, "<weak_etag_value>"]

如果将上面的 Last-Modified / If-Modified-Since 应用在动态的数据上的话,基本上没有可行性。那么就有了此方式的缓存。

在第一次请求的时候,服务器会附带ETag头。ETag的值是根据响应的内容来进行生成的,一般来说是内容(+其他一些标识的)哈希值。之后,根据Cache-Control的指令,如果需要同服务器确认资源的有效性的时候则会将这个etag_value放在If-None-Match头中,供服务器进行比较,是返回304还是200。

在ETag的值前如果有W/则表示这是一个弱Etag。弱ETag相等意味着这两个内容语义上是相等的,强ETag相等则表示内容每一个字节都相等。

举个弱ETag的例子:返回的数据中带有log信息,而两次返回的数据是相同的,log不同。

还有一种语法:If-None-Match: *,基本上用在PUT, POST等上,用于上传。表示要求上传的文件在服务器上不存在。

If-Match
If-Match: "<etag_value>"[, "<etag_value>"...]
If-Match: W/"<weak_etag_value>"[, "<weak_etag_value>"]

对于GET, HEAD等请求,如果资源的ETag匹配上If-Match的值,则返回资源,否则返回412。
对于PUT, POST等请求,如果现有资源的Etag匹配上If-Match的值,那么进行写操作,否则失败返回412。

----- 记得点赞 -----


JasonKidd
3.3k 声望527 粉丝