今天聊聊 HTTP 缓存控制

废话

最近在家不用上班,有点无聊,打了几天游戏,感觉好空虚,还是看点东西学习吧。看到了 HTTP 缓存,之前有看过一点,今天大概整理一下。

作用

重用资源,提高网站和应用的性能。减少网络延迟带来的影响,提升用户体验。一般用于 Get 请求。

缓存的类型

1. 不缓存

不使用缓存

2.(私有)浏览器缓存

用于单独用户,提供前进后退、保存网页的功能

3.(共享)代理缓存

可用于多个用户,比如大型公司都会架设一个 Web代理器,代理服务器可以提供代理缓存功能给用户使用。热门的资源可以被复用,减少网络拥堵。

缓存的控制

Cache-Control

用来定义缓存的策略,请求头和响应头都支持。

禁止缓存

缓存中不得存储任何 客户端请求 和 服务器响应 的内容。

Cache-Control: no-store

强制确认缓存

Cache-Control: no-cache

上面这个头信息,并不是说不缓存,而是说每次都要服务器确认缓存是否可用。使用了这个头信息,每次请求还是会发给服务器,如果服务器返回 304 ,则说明可以继续使用缓存,如果是其他则不使用缓存。

私有缓存 和 公共缓存

Cache-Control: private

默认的配置,表示缓存只属于某个用户,中间人(中间人指中间代理,CDN等)不应该存储。

Cache-Control: public

表示该信息可以被任何中间人缓存

缓存过期机制

Cache-Control: max-age=31536000

最常用的就是 max-age=xxxxxx 了,单位是秒,表示缓存距离上次请求之后能用多久,也就是新鲜度。

Expires: Wed, 21 Oct 2015 07:28:00 GMT

跟 max-age 相近的是 Expires ,但是 Expires 表示的是一个具体过期时间。

Pragma

HTTP 1.0 定义的 header ,之前没有明确定义,功能上又和 Cache-Control 重合,没什么用,废弃

Pragma 是HTTP/1.0标准中定义的一个header属性,请求中包含Pragma的效果跟在头信息中定义Cache-Control: no-cache相同,但是HTTP的响应头没有明确定义这个属性,所以它不能拿来完全替代HTTP/1.1中定义的Cache-control头。通常定义Pragma以向后兼容基于HTTP/1.0的客户端。

新鲜度

说到缓存,肯定不能缓存所有的资源,所以就会有一套算法来做 缓存驱逐 ,这个算法就是 驱逐算法。对应的有资源过期了,就会有新的资源更新缓存,以此来保持本地缓存的新鲜度。

当然,一个资源是不是过期了,有些时候不是本地缓冲器一个人说了算,还要跟服务器商量。我们就要分成两种情况了。

强制缓存

<code>Cache-Control: max-age=31536000
Expires: Wed, 21 Oct 2015 07:28:00 GMT/<code>

以上两种缓存,刚才已经说过了,就不多说了。如果服务器返回的响应头中有这两个头信息,而且没过期,就可以直接使用本地缓存了。

协商缓存

这种情况下,一个资源是不是过期要跟服务器商量,也是比较复杂的情况。我们先对几个相关的响应头说明一下。

ETag

响应头,相当是对一个资源做了唯一标识,可以更加准确判断一个资源是否进行更新。还可以有助于防止资源同时更新导致互相覆盖的问题(空中碰撞)。一般是使用资源的 Hash 。

格式如下:

<code>ETag: ""

ETag: W/""/<code>

W/ 表示 弱验证器(Weak validation),对大小写不敏感。

If-None-Match 和 If-Match

这两个都是请求的头信息,表示一个条件请求。所传的值是 ETag 。

If-None-Match

<code>If-None-Match: 
If-None-Match: , , …
If-None-Match: */<code>

If-None-Match 表示的是请求资源 ETag 跟这个 ETag 不同服务器就返回更新后资源。

但是如果请求的资源 的 ETag 跟这个相同,则返回 304 ,表示资源没有变更,可以继续使用旧的缓存,并且头部中还可能会添加 Cache-Control、Content-Location、Date、ETag、Expires 等头信息,用来更新新鲜值。

If-Match

<code>If-Match: 
If-Match: , , …
If-Match: */<code>

这个更缓存的关系并不是很大,但是容易跟 If-None-Match 混淆,这里也说一下。If-Match 表示如果请求的资源 ETag 跟这个 ETag 相同才会返回资源。一般有下面的两种用途:

1.对于 GET 和 HEAD ,搭配 Range 头信息来使用,保证新请求的范围和之前请求的范围是同一份。如果不同则服务器需返回 416。看到官方这个场景的描述,我也是一脸懵逼,没想到具体的使用场景。2.对于 POST ,可以用来防止更新丢失的问题,也就是之前说的“空中碰撞”的问题。比如两个人都对同一份文件进行修改,提交的时候使用这个请求头,肯定有一个人的更新会冲突,就像 Git 提交一样,因为两个人的请求头中 If-Match 的值都是最原始那个文件,慢提交的人就跟服务器上的 Etag 不同了,也就会提交不上,报冲突了。如果冲突了,服务器返回的是 412 。

Last-Modified 和 If-Modified-Since

<code>Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT 
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT /<code>

这两个是一对配合使用的,Last-Modified 是响应头的头信息,返回资源的最后更新时间,IF-Modified-Since 是请求头的头信息,表示如果资源在这个时间之后有更新,则返回资源,如果没更新则返回 304。

缓存处理流程

客户端第一次发请求的时候,服务器可能会返回 Connection-Control 、Expires 、ETag 、Last-Modified 等多个响应头来做缓存操作。

客户端第二次发请求的时候,如果上次响应中有 Connection-Control 、Expires 这两个响应头,客户端可以根据这个两个时间来判断资源是否过期,如果还没过期,就继续使用缓存,如果过期则重新向服务器发请求。

在重新向服务器发请求的时候,也会带上上次响应跟缓存控制有关的响应头。

比如 上次响应头有 ETag ,这次请求就会在请求头中加上 If-None-Match 并附上 ETag 的值。如果上次响应中有 last-Modified ,这次请求中就会在请求头中加上 If-Modified-Since 并附上 Last-Modified 的值。

当然这个时候,缓存过没过期就跟客户端没关系了,反正都塞给服务器,让服务器去做判断。

这几个响应头到了服务器后,服务器就要开始判断请求的这个资源是不是过期了。因为 ETag 比 Last-Modified 更准确(有时候会只修改更新时间,而不修改文件),所以首先判断的是 If-None—Match ,如果同时出现 If-None-Match 和 If-Modified-Since ,则 If-Modified-Since 会被忽略。

如果 If-None-Match 跟服务器上资源的 ETag 不同,就返回更新后的资源,如果相同,则返回 304 并不返回响应体,告诉客户端,这个资源还没过期,可以继续使用,并且响应头中可能会重新加入 Connection-Control 、Expires 、ETag 、Last-Modified 等用来更新客户端缓存的新鲜值。

如果发到服务器中的请求头中,没有 If-None-Match 只有 If-Modified-Since ,则根据服务器上该资源最后的更新时间是否大于 If-Modified-Since 的时间,如果大于,则返回新的资源,如果相同,则跟 If-None-Match 一样返回 304

如果请求中什么跟缓存控制相关的请求头,都没有的话,这就相当于第一次请求了,直接返回资源就可以了。

最后附上一张图示:


今天聊聊 HTTP 缓存控制


分享到:


相關文章: