我们先来瞅一眼 HTTP/1.1 协议报文首部字段中与缓存相关的字段。
1、通用首部字段:
字段名称 | 说明 |
---|---|
Cache-Control | 控制缓存的行为 |
Progrma | HTTP/1.0 的旧社会遗物,值为 no-cache 时禁用缓存 |
2、请求首部字段:
字段名称 | 说明 |
---|---|
If-Match | 比较 ETag 是否一致 |
If-None-Match | 比较 ETag 是不否一致 |
If-Modified-Since | 比较资源最后更新的时间是否一致 |
If-Unmodified-Since | 比较资源最后更新的时间是否不一致 |
3、响应首部字段:
字段名称 | 说明 |
---|---|
ETag | 资源的标识 |
4、实体首部字段:
字段名称 | 说明 |
---|---|
Expires | HTTP/1.0 的遗留物,资源过期时间 |
Last-Modified | 资源最后一次的修改时间 |
HTTP/1.0 缓存字段
在 HTTP/1.0 时代,给客户端设定缓存方式可通过 Pragma
和 Expires
来规范。虽然这两个字段早可抛弃,但 HTTP 协议做了向下兼容,所以依然可以看到。
Pragma
Pragma
:设置页面是否缓存,为 Pragma
则缓存,no-cache
则不缓存。
当该字段值为 no-cache
的时候,会通知客户端不要对该资源读缓存,即每次都得向服务器发一次请求才行。
Expires
有了 Pragma 来禁用缓存,自然也需要有个东西来启用缓存和定义缓存时间,对 HTTP/1.0 而言,Expires
就是做这件事的首部字段。 Expires
的值对应一个 GMT(格林尼治时间),比如 Mon, 22 Jul 2002 11:12:01 GMT
来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求。
如果 Pragma
头部和 Expires
头部同时存在,则起作用的会是 Pragma
。
需要注意的是,响应报文中 Expires
所定义的缓存时间是相对服务器上的时间而言的,其定义的是资源“失效时刻”,如果客户端上的时间跟服务器上的时间不一致(特别是用户修改了自己电脑的系统时间),那缓存时间可能就没啥意义了。
浏览器接受到响应后,如果发现没有 Expires 字段,浏览器根据文件的类型和 Last-Modified
字段来推断出一个合适的失效时间,并存储在客户端。推测出的时间一般是接受到响应时间后的三天左右。
可以发现浏览器总是缓存所有页面,不管失效、不失效还是没有声明失效时间。即使缓存中声明了一个网页的实效日期是 1970-01-01 08:00:00
,浏览器仍然会发送该文件在缓存中的 Last-Modified
和 ETag
字段。 如果在服务器端验证通过,返回 304 状态,浏览器就还会使用此缓存。
HTTP/1.1 缓存字段
Cache-Control
针对上述的“Expires
时间是相对服务器而言,无法保证和客户端时间统一”的问题,HTTP/1.1 新增了 Cache-Control
来定义缓存过期时间。注意:若报文中同时出现了 Expires
和 Cache-Control
,则以 Cache-Control
为准。
也就是说优先级从高到低分别是 Pragma -> Cache-Control -> Expires
。
Cache-Control
也是一个通用首部字段,这意味着它能分别在请求报文和响应报文中使用。在 RFC 中规范了 Cache-Control 的格式为:
"Cache-Control" ":" cache-directive
作为请求首部时,cache-directive
的可选值有:
字段名称 | 说明 |
---|---|
no-cache | 告知(代理)服务器不直接使用缓存,要求向原服务器发起请求 |
no-store | 所有内容都不会被保存到缓存或 Internet 临时文件中 |
max-age=delta-seconds | 告知服务器客户端希望接收一个存在时间(Age) 不大于 delta-seconds 秒的资源 |
max-stale [=delta-seconds] | 告知(代理)服务器客户端愿意接收一个超过缓存时间的资源,若有定义 delta-seconds 则为 delta-seconds 秒,若没有则为任意超出的时间 |
min-fresh=delta-seconds | 告知(代理)服务器客户端希望接收一个在小于 delta-seconds 秒内被更新过的资源 |
no-transform | 告知(代理)服务器客户端希望获取实体数据没有被转换(比如压缩)过的资源 |
only-if-cached | 告知(代理)服务器客户端希望获取缓存的内容(若有) ,而不用向原服务器发去请求 |
cache-extension | 自定义扩展值,若服务器不识别该值将被忽略掉 |
这里的 Cache-Control: no-cache
这个很容易让人产生误解,使人误以为是响应不被缓存。实际上 Cache-Control: no-cache
是会被缓存的,只不过每次在向客户端(浏览器)提供响应数据时,缓存都要向服务器评估缓存响应的有效性。
Cache-Control: no-store
这个才是响应不被缓存的意思。
作为响应首部时,cache-directive
的可选值有:
字段名称 | 说明 |
---|---|
public | 表明任何情况下都得缓存该资源(即使是需要 HTTP 认证的资源) |
Private [= "field-name"] | 表明返回报文中全部或部分(若指定了 field-name 则为 field-name 的字段数据)仅开放给某些用户(服务器指定的 share-user ,如代理服务器)做缓存使用,其他用户则不能缓存这些数据 |
no-cache | 不直接使用缓存,要求向服务器发起(新鲜度校验)请求 |
no-store | 所有内容都不会被保存到缓存或 Internet 临时文件中 |
no-transform | 告知客户端缓存文件时不得对实体数据做任何改变 |
only-if-cached | 告知(代理)服务器客户端希望获取缓存的内容(若有),而不用向原服务器发去请求 |
must-revalidate | 当前资源一定是向原服务器发去验证请求的,若请求失败会返回 504(而非代理服务器上的缓存) |
proxy-revalidate | 与 must-revalidate 类似,但仅能应用于共享缓存(如代理) |
max-age=delta-seconds | 告知客户端该资源在 delta-seconds 秒内是新鲜的,无需向服务器发请求 |
s-maxage=delta-seconds | 同 max-age ,但仅应用于共享缓存(如代理) |
cache-extension | 自定义扩展值,若服务器不识别该值将被忽略掉 |
Cache-Control 允许自由组合可选值,例如:
Cache-Control: max-age=3600, must-revalidate
它意味着该资源是从原服务器上取得的,且其缓存(新鲜度)的有效时间为一小时,在后续一小时内,用户重新访问该资源则无须发送请求。
当然这种组合的方式也会有些限制,比如 no-cache
就不能和 max-age
、min-fresh
、max-stale
一起搭配使用。
ETag & If-None-Match
ETag 是一个可以与 Web 资源关联的记号(token)。典型的 Web 资源可以一个 Web 页,但也可能是 JSON 或 XML 文档。服务器单独负责判断记号是什么及其含义,并在 HTTP 响应头中将其传送到客户端,以下是服务器端返回的格式:
ETag:"50b1c1d4f775c61:df3"
客户端的查询更新格式是这样的:
If-None-Match : W / "50b1c1d4f775c61:df3"
如果 ETag 没改变,则返回状态 304 状态码。
其流程如下:
- 客户端请求一个页面(A);
- 服务器返回页面 A,并在给 A 加上一个 ETag;
- 客户端展现该页面,并将页面连同 ETag 一起缓存;
- 客户再次请求页面 A,并将上次请求时服务器返回的 ETag 一起传递给服务器;
- 服务器检查该 ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应 304(未修改——Not Modified)和一个空的响应体。
Etag
是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified
与 ETag
是可以一起使用的,服务器会优先验证 ETag
,一致的情况下,才会继续比对 Last-Modified
,最后才决定是否返回304。
Last-Modified & If-Modified-Since
在浏览器第一次请求某一个 URL 时,服务器端的返回状态会是 200,内容是客户端请求的资源,同时有一个 Last-Modified
的属性标记此文件在服务器端最后被修改的时间。
Last-Modified
格式类似这样:
Last-Modified : Fri , 12 May 2019 18:53:33 GMT
客户端第二次请求此 URL 时,根据 HTTP 协议的规定,浏览器会向服务器传送If-Modified-Since
报头,询问该时间之后文件是否有被修改过:
If-Modified-Since : Fri , 12 May 2019 18:53:33 GMT
如果服务器端的资源没有变化,则自动返回 HTTP 304(Not Modified)状态码,响应体内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。
评论区