原文: http://cncc.bingj.com/cache.aspx?q=max-age expires Last-Modified&d=4997458151473641&mkt=en-US&setlang=en-US&w=obLC-DXYkHDglJJNfr5xl1yTc4LW9a_o
本文着重论述了关于 HTTP 缓存头部的重要信息,以及有关的 CDN 行为。假如你正在寻找现代 web 中 HTTP cache headers 的深入信息,这里有你所需的一切。
HTTP 缓存头部
主要通过新鲜度和有效性,来决定对资源的缓存。如果一个有效的资源没有改变过,很少会再次发送完整的资源,此时缓存中的一个新鲜的副本是立即可用的。假设没有校验器(如 ETag/Last-modified 头部),并且缺少明确的新鲜度信息的话,那就要经常(但并不总是)被认定为无法缓存了。现在把焦点转移到需要被考虑到的几种 HTTP 头部上:
- Cache-control
- 每个资源都可以通过 Cache-Control HTTP 头部来定义其缓存策略
- Cache-Control 指令控制了谁、在什么条件下来缓存响应,以及缓存多久
不需要服务器通信的请求被认为是最佳的请求:使用响应的本地副本,既可以消除网络延迟,又能避免数据传输带来的网络负载。HTTP 规范允许服务器发送多个不同的 Cache-Control 指令,用以控制身处诸如CDN的中间人缓存中,浏览器如何(以及何时)缓存个别的响应:
代码语言:javascript复制Cache-control: private, max-age=0, no-cache
这些设置项被称为响应指令,如下所示:
Public vs. Private
被标记为 `public` 的响应,即便在其关联了一个 HTTP 认证或其 HTTP 响应状态码通常不可缓存的情况下,也都可以被缓存。在大多数情况下,标记 `public` 不是必要的,因为明确的缓存信息(如 `max-age`)已经表示响应是可缓存的。
反之,被标记为`private`的响应,可以被(浏览器)缓存,但是这样的响应是典型地面向单个用户的,因此不能被中间人缓存(比如含有用户私人信息的 HTML 页面可以被用户的浏览器缓存而非 CDN)。
No-cache 和 No-store
`no-cache`表示在检查过服务器响应是否已经改变之前,返回的响应不能被用于随后向同一 URL 的请求。如果一个适当的 Etag (校验记号) 作为一个结果出现,`no-cache`就会引发一次往返动作以试图校验已缓存过的响应。如果资源未改变过,缓存就会避免下载行为。换句话说,web 浏览器可能会缓存资源,但不得不在每次请求都检查资源是否已改变(未改变则返回 304)。
与之相反的是,`no-store`更简单些。这样说是因为它禁止浏览器和所有中间人从返回的响应中缓存任何版本的资源,例如响应中包含隐私/个人的信息或银行数据等。用户每次请求这个资源都是请求服务器,每次资源都要被下载。
max-age
`max-age` 指令表示获取到的资源被允许重复使用的最长时间,以秒为单位(从请求发起的时刻算起)。举例来说,`max-age=90`指示了资源在之后的 90 秒钟可被重用(保存在浏览器缓存中)。
s-maxage
`s-`代表`shard cache`。这个指令是明确针对中间人缓存中的 CDN 的。当这个指令出现在头部中时,会覆盖掉 `max-age` 和 `expires` 的设置。
`Cache-Control` 是作为 HTTP/1.1 标准的一部分定义的,用来接替之前的头部信息(如 `expires`)来规定响应的缓存策略。 `Cache-Control` 被所有现代浏览器支持,实乃我们必备的。
2. Pragma
旧的 `pragma` 头部承担了许多工作,其中大部分都有了更新的实现。我们一般将cache-control: no-cache
视为pragma: no-cache
的更新一些的实现。在整体的理解上了解这个指令是有必要的,但今后不会再为其定义新的 HTTP 指令了。
3. Expires
几年前,这是指定资源有效期的主要途径。Expires 只是个基本的 date-time 时间戳,对那些游走在不规范地带的用户代理来说,还是相当管用的;而对于现代系统,则是优先使用cache-control
头部,设置max-age
和s-maxage
等。为了兼容的目的,在此设置匹配的值是个好的做法;同样重要的是确保日期的格式正确,不然会被认为过期。
Expires: Sun, 03 May 2015 23:02:37 GMT
为了避免破坏规范,不要设置超过一年的日期值。(译注:规范中的说明为 -- To mark a response as "never expires," an origin server sends an Expires date approximately one year from the time the response is sent. HTTP/1.1 servers SHOULD NOT send Expires dates more than one year in the future. )
4. Validators
ETag
这种在 HTTP/1.1 中定义的有效性 token:
- 通过服务器的 `ETag` HTTP头部传达
- 确保资源更新的有效性,也就是说,如果资源没改变,就不会发生数据传输
这个例子将展示其作用:在首次获取一个资源 90 秒后,发起一次新的浏览器请求(完全一样的资源);浏览器寻找本地缓存,当找到上一次缓存的请求后并发现其过期时,就会从服务器请求完整的内容 -- 问题是,如果资源没有改变过,在其已存在于 CDN 缓存的情况下,绝无理由再下载一遍。
有效性 token 正是用来解决此类问题。边缘服务器(edge server, 译注:如专门负责缓存、防火墙、负载均衡等的第一层服务器;其后是提供web服务的第二层和提供数据库的第三层)创建并返回特制的 token,存放在 ETag 头部域中,作为既有文件的指纹信息,一般用一个 hash 值来表示。客户端不需要知道该 token 如何产生,只肖在随后的请求中携带之便可。如果 token 相同,也就意味着资源没变,这样一来也就跳过了重新下载。
web 浏览器自动提供了“If-None-Match” 请求头,用来包含 ETag token;服务器将根据此 token 比对缓存中的当前资源。如果缓存中的资源未发生变化,浏览器将收到一个 `304 Not Modified` 的响应,有效性续期 90 秒。特别是因为不用重新下载资源,带宽和时间都被节省了。
web 开发者如何从重新生效中获益?
浏览器为 web 开发者承担了大多数工作。比如,浏览器自动检测到前一次指定的有效性 token ,将其附加到随后的请求中,并按需基于服务器响应更新缓存的时间戳。web 开发者因此只需要确保服务器提供所需的 ETag token 就行了。
Last-Modified
`Last-Modified` 头部作为一个常见的校验器,指示了文件最后一次改变的时间。可以将其视为一个 HTTP/1.0 时代遗留的校验器。当缓存保存了一个包含此头部的资源时,可以利用其查询服务器资源是否已超时(从资源上次被使用时)。相应的请求头部为 `If-Modified-Since`。
一个 HTTP/1.1 的源服务器应该同时发送 ETag 和 Last-Modified。更多细节可以在 RFC2616 规范中找到。
一个例子:
代码语言:javascript复制HTTP/1.1 200 OK
Server: keycdn-engine
Date: Mon, 27 Apr 2015 18:54:37 GMT
Content-Type: text/css
Content-Length: 44660
Connection: keep-alive
Vary: Accept-Encoding
Last-Modified: Mon, 08 Dec 2014 19:23:51 GMT
ETag: "5485fac7-ae74"Cache-Control: max-age=533280
Expires: Sun, 03 May 2015 23:02:37 GMT
X-Cache: HIT
X-Edge-Location: defr
Access-Control-Allow-Origin: *
Accept-Ranges: bytes
TL;DR
Cache-Control 和 ETag 头部域是用来控制资源新鲜度和有效性的现代机制。其他值则“仅仅”用来向后兼容。