废话
最近在家不用上班,有点无聊,打了几天游戏,感觉好空虚,还是看点东西学习吧。看到了 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的客户端。
新鲜度
说到缓存,肯定不能缓存所有的资源,所以就会有一套算法来做 缓存驱逐 ,这个算法就是 驱逐算法。对应的有资源过期了,就会有新的资源更新缓存,以此来保持本地缓存的新鲜度。
当然,一个资源是不是过期了,有些时候不是本地缓冲器一个人说了算,还要跟服务器商量。我们就要分成两种情况了。
强制缓存
Cache-Control: max-age=31536000
Expires: Wed, 21 Oct 2015 07:28:00 GMT
以上两种缓存,刚才已经说过了,就不多说了。如果服务器返回的响应头中有这两个头信息,而且没过期,就可以直接使用本地缓存了。
协商缓存
这种情况下,一个资源是不是过期要跟服务器商量,也是比较复杂的情况。我们先对几个相关的响应头说明一下。
ETag
响应头,相当是对一个资源做了唯一标识,可以更加准确判断一个资源是否进行更新。还可以有助于防止资源同时更新导致互相覆盖的问题(空中碰撞)。一般是使用资源的 Hash 。
格式如下:
ETag: "<etag_value>"
ETag: W/"<etag_value>"
W/
表示 弱验证器(Weak validation),对大小写不敏感。
If-None-Match 和 If-Match
这两个都是请求的头信息,表示一个条件请求。所传的值是 ETag 。
If-None-Match
If-None-Match: <etag_value>
If-None-Match: <etag_value>, <etag_value>, …
If-None-Match: *
If-None-Match 表示的是请求资源 ETag 跟这个 ETag 不同服务器就返回更新后资源。
但是如果请求的资源 的 ETag 跟这个相同,则返回 304 ,表示资源没有变更,可以继续使用旧的缓存,并且头部中还可能会添加 Cache-Control、Content-Location、Date、ETag、Expires 等头信息,用来更新新鲜值。
If-Match
If-Match: <etag_value>
If-Match: <etag_value>, <etag_value>, …
If-Match: *
这个更缓存的关系并不是很大,但是容易跟 If-None-Match 混淆,这里也说一下。If-Match 表示如果请求的资源 ETag 跟这个 ETag 相同才会返回资源。一般有下面的两种用途:
- 对于 GET 和 HEAD ,搭配 Range 头信息来使用,保证新请求的范围和之前请求的范围是同一份。如果不同则服务器需返回 416。看到官方这个场景的描述,我也是一脸懵逼,没想到具体的使用场景。
- 对于 POST ,可以用来防止更新丢失的问题,也就是之前说的“空中碰撞”的问题。比如两个人都对同一份文件进行修改,提交的时候使用这个请求头,肯定有一个人的更新会冲突,就像 Git 提交一样,因为两个人的请求头中 If-Match 的值都是最原始那个文件,慢提交的人就跟服务器上的 Etag 不同了,也就会提交不上,报冲突了。如果冲突了,服务器返回的是 412 。
Last-Modified 和 If-Modified-Since
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
这两个是一对配合使用的,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缓存图示
网友评论