通过HTTP Header控制缓存
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通过HTTP Header控制缓存相关的知识,希望对你有一定的参考价值。
参考技术A 我们经常通过缓存技术来加快网站的访问速度,从而提升用户体验。HTTP协议中也规定了一些和缓存相关的Header,来允许浏览器或共享高速缓存缓存资源。这些Header包括:Last-Modified 和 If-Modified-Since
ETag 和 If-None-Match
Expires
Cache-Control
以上Header又可以分成两种类型:
协商缓存:浏览器发送验证到服务器,由服务器决定是否从缓存中读取,如 1 和 2 。
强缓存:浏览器验证缓存的有效性,然后决定是否从缓存中读取数据,如 3 和 4 。
本文将会分别介绍这四种配置的作用以及可能产生的影响。
1、Last-Modified 和 If-Modified-Since
Last-Modified:服务器在响应请求时,告知浏览器资源的最后修改时间。
If-Modified-Since:浏览器再次发送请求时,会通过此Header通知服务器在上次请求时所得到的资源最后修改时间。服务器会将If-Modified-Since与被请求资源的最后修改时间进行比对。若资源的最后修改时间晚于If-Modified-Since,表示资源已被改动,则响最新的资源,返回200状态码;若资源的最后修改时间早于或等于If-Modified-Since,表示浏览器端的资源已经是最新版本,响应304状态码,通知浏览器继续使用缓存中的资源。
2、ETag 和 If-None-Match
ETag:服务器分配给资源的唯一标识符,资源被修改后,ETag也会随之发生变化。
If-None-Match:浏览器再次发送请求时,会通过此Header通知服务器已缓存资源的ETag。服务器会将If-None-Match与被请求资源的最新ETag进行比对。若不相同,表示资源已被改动,则响应最新的资源,返回200状态码;若值相同,则直接响应304状态码,通知浏览器继续使用缓存中的资源。
3、Expires
服务器可以通过此Header向浏览器传递一个具体的时间(格林威治格式,例如:Thu, 19 Jul 2018 07:43:05 GMT) ,来明确地宣告资源的有效期。在资源过期之前,浏览器不再发送请求,而是直接从缓存中读取数据。只有当资源过期之后,浏览器才会再次向服务器请求该资源。
4、Cache-Control
服务器使用此Header来向客户端建议缓存策略,它有一下几个可选值:
max-age=秒:告知浏览器缓存的有效时长,在该时间内浏览器将直接从缓存中读取数据。
s-maxage=秒:作用同max-age,但是只对共享高速缓存(如CDN)有效,对浏览器无效。
no-cache:告知浏览器不要直接使用缓存,而是必须向服务器发送请求。
no-store:告知浏览器不要缓存本次请求和响应的任何信息。
public:宣告任何缓存媒介都可以缓存该响应。
private:宣告该响应只允许个体客户端(如浏览器)去缓存,而不允许共享高速缓存(如CDN)去缓存。
在上面的介绍中我们了解到浏览器会根据max-age设置的时间进行缓存。而通过研究发现CDN也会识别源站响应头中Cache-Control属性,根据max-age设置的时间进行缓存,但是,如果源站同时设置了s-maxage和max-age,那么CDN会优先采用s-maxage。
下面通过图例来展示一下这些可选值的效果。
首先了解一下浏览器是怎样根据max-age进行缓存的:
从上图不难发现,服务器在Header中返回了Cache-Control: max-age=100后,浏览器成功缓存100秒,该时间段内的请求都从直接以本地缓存来响应。
那么,服务器在Header中返回Cache-Control:s-maxage=100时,又会对浏览器产生什么样的影响呢?
如上图所示,浏览器没有采取任何缓存策略,这是因为s-maxage面向的是共享高速缓。
上面这两个例子很容易理解,在现实世界中,为了加快网站响应速度,我们可能会在浏览器和服务器之间引入CDN服务。浏览器的请求会先到达CDN,然后CDN判断是从缓存中读取数据还是回源到服务器。接下来,让我们看看max-age和s-maxage会对CDN的缓存策略带来哪些影响。
可以看出CDN也会利用max-age来缓存,所以在100秒内强制刷新浏览器时,CDN会直接用缓存来响应。
如果服务器使用了s-maxage又会如何呢?
不难发现CDN对max-age和s-maxage采取了同样的缓存策略,但浏览器并不会根据s-maxage来进行缓存。
CDN供应商的特殊规则
我们分别测试了阿里云和腾讯云的CDN对Cache-Control的支持情况,发现他们都有一些独特的规则。阿里云CDN可以在控制台里设置Cache-Control,该设置会覆盖源服务器的Cache-Control。
腾讯云CDN虽然没有再控制台提供覆盖Cache-Control的功能,但其规则却一点也不简单,在使用的时候一定要特别注意:
服务器和CDN均不对缓存进行配置时,CDN会采用默认的缓存机制(静态文件缓存30天,动态请求不缓存);
CDN配置缓存机制(但并未开启高级缓存配置)且服务器设置Cache-Control: s-maxage=200,max-age=100时,CDN会按照其控制台设置的规则进行缓存,浏览器则按照max-age进行缓存;
服务器不设置Cache-Control时,CDN会自动在响应的Header中添加Cache-Control: max-age=600,这就会让浏览器将该资源缓存600秒;
服务器设置为禁用缓存时,CDN和浏览器均不进行缓存;
服务器设置Cache-Control: s-maxage=200,max-age=100并开启CDN的高级缓存配置时,CDN会从s-maxage和控制台中设置的缓存时间中选择最小值来作为缓存时间,而浏览器则始终使用max-age;
服务器设置Cache-Control:max-age=100并开启CDN的高级缓存配置时,CDN会从max-age和控制台中设置的缓存时间中选择最小值来作为缓存时间,不影响浏览器的缓存策略。
组合使用
如果同时设置了这些Header,浏览器和高速共享缓存会按照下面的优先级进行缓存:
Cache-Control > Expires > ETag > Last-Modified
也就是说,Cache-Control不仅是强缓存,而且拥有最高的优先级,我们可以为不经常发生变化的资源应用该Header来提升响应时间。
在Ada中使用缓存
Ada提供了UI脚手架和API脚手架,这两类脚手架的服务器端入口文件分别为index.server.js和index.js,我们只需要在入口文件的请求处理函数中为响应添加适当的Header,即可通知客户端进行响应的缓存,比如:
// 设置CDN缓存300秒,浏览器缓存200秒 ctx.response.headers.set('Cache-Control',public,s-maxage=300,max-age=200)
在为请求添加缓存Header之前,应该先为其制定适当的缓存策略,需要考虑该URL是否适合缓存(数据是否特定于用户)以及需要缓存的时长等等。
总结
通过使用这些HTTP Header,我们可以主动影响浏览器甚至CDN的缓存策略,从而减少请求数量,提升网页性能,减轻服务器压力。
Ada的灵活机制能让我们为不同的URL设置不同的缓存策略,能够更有针对性地进行主动缓存。
轻松理解HTTP缓存策略
上一篇文章我写了koa-static的源码解析[1],其中用到了HTTP
的缓存策略,给返回的静态文件设置了一些缓存的头,比如Cache-Control
之类的。于是我就跟朋友讨论了一下HTTP
的缓存策略:
朋友说:“HTTP
里面控制缓存的头(header
)太多了,啥Cache-Control
,ETag
,Last-Modified
,一大堆,乱七八糟的,而且之间逻辑关系不强,要掌握基本靠背!”
我有点惊讶:“为什么要去背这个呢?所有的技术都是为了解决问题而存在的,不了解问题而去单纯的学习技术,去,背,去,死记,确实很枯燥,而且效果不好。HTTP
缓存策略只是为了解决客户端和服务端信息不对称的问题而存在的,客户端为了加快速度会缓存部分资源,但是下次请求时,客户端不知道这个资源有没有更新,服务端也不知道客户端缓存的是哪个版本,不知道该不该再返回资源,其实就是一个信息同步问题,HTTP
缓存策略就是来解决这个问题的。如果我们跳出这种纯粹的技术思维,我们会发现生活中这种信息同步问题也很常见。而我们解决这些问题的思路很多时候都是司空见惯了,如果从这个角度来说,这个问题就很好理解!”
于是我给他讲了一个我小时候租光碟看奥特曼的故事。
租光碟看奥特曼
事情是这样的,我小时候特别喜欢看动画片,尤其是奥特曼,但是那时候没有电脑啊,也没有网络。我只有一台DVD播放机,于是我会经常跑去租光碟的店租奥特曼。
ETag
某天,我看完了《艾斯奥特曼》第10集,我还想继续看。于是我找到了光碟店的老板:“老板,第10集我看完了哦,你还有没有新的啊?”老板说:“有有有,刚出了第11集,你拿去吧!”
上面这一个简单的交流过程其实就包含了一个HTTP
的缓存技术,那就是ETag
!类比于网络请求,我其实就是客户端,光碟店就是服务端,我去租光碟就相当于发起一个请求。但是我去租光碟时,老板并不知道我看到哪集了,我们的信息是不同步的。所以我告诉了他一个标记(Tag
),在这里这个标记就是第10集,老板拿到这个标记,跟他自己库存的标记比较一下,发现他最新标记是第11集,于是知道有更新了,将第11集给了我。
Last-Modified & If-Modified-Since
再来,我《艾斯奥特曼》看完了,我开始看《泰罗奥特曼》了。可是老板这次比较鸡贼,《泰罗奥特曼》没买正版的,是他自己翻录的,他翻录的时候自己也不知道是第几集,但是他聪明的在光盘上写上了翻录日期。于是我正在看的这盘也没啥封面,只光秃秃的写了一个2000年12月1日。当我这盘看完了,我又去找老板了:“老板,你这个2000年12月1日的我已经看完了,你还有没有新的啊?”这里的2000年12月1日其实就是标记了我手上副本的更新日期,这也对应了HTTP
的一个缓存技术,那就是Last-Modified
和If-Modified-Since
。你可以理解为,老板给日期还取了一个名字,叫Last-Modified
,所以光碟上完整文字是Last-Modified:2000年12月1日
,而我去问的时候就这么问:“Do you have any updates IF-Modified-Since 2000年12月1日?”。
Expires和Max-Age
继续,我《泰罗奥特曼》也看完了,开始看《雷欧奥特曼》了。这《雷欧奥特曼》跟前面两个都不一样,我去租的时候老板就说了:“你小子别天天跑来问了!《雷欧奥特曼》我每周去进一次货,你每周一来拿就行!”这句话也对应了一个HTTP
缓存技术,那就是Expires
和Max-Age
。我知道了下周一之前,我手上都是最新的,到了下周一就过期(Expire
)了。所以“我手上的是最新的”这个说法有个生命周期,他的年龄是有限的,他的年龄等于下周一更新时间减去当前时间,这就是他的最大年龄(Max-Age
)。
Immutable
再来一个,我《雷欧奥特曼》也看完了,开始看《奈克斯特奥特曼》了。这《奈克斯特奥特曼》跟前面几个都不一样,我去租的时候老板说了:“小子,你这次运气好,这《奈克斯特奥特曼》已经出完了,你全部拿去吧,也不用天天跑来问了!”这句话对应的HTTP
缓存技术是啥?当然是Immutable!Immutable
就跟字面意思一样,不可变的!就像《奈克斯特奥特曼》一样,已经出完了,不用再去问更新了。
言归正传
扯蛋到这里结束,咱们言归正传!之所以举这么个例子,是为了说明HTTP
缓存技术要解决的问题在生活中很常见,从这些常见的场景入手,理解起来更简单。下面我们正儿八经的来说说HTTP
缓存技术:
两种机制
从上面的几个小例子可以看出,有时候为了知道是不是有更新,我必须去问老板,比如第一个例子里面:“老板,第10集我看完了哦,你还有没有新的啊?”。这种为了知道有没有更新,必须跟服务端沟通过才知道的,我们称之为协商缓存。还有些场景,我不去问就知道有没有更新,比如第三个例子,因为知道是周更的,当周一来之前,我都不会去问了,到了周一再去问,这种不用跟服务器协商直接用本地副本的叫做强制缓存。换成技术的话说就是,强制缓存不用发请求直接用本地缓存,协商缓存要发请求去问服务器有没有更新。下面我们详细来讲下这两种缓存:
协商缓存
前面第一个例子和第二个例子每次都需要向服务器端询问,所以是协商缓存。
ETag和If-None-Match
ETag
是URL的Entity Tag
,就是一个URL资源的标识符,类似于文件的md5
,计算方式也类似,当服务器返回时,可以根据返回内容计算一个hash
值或者就是一个数字版本号,类似于我们的第10集
,具体返回什么值要看服务器的计算策略。然后将它加到response
的header
里面,可能长这样:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
客户端拿到后会将这个ETag
和返回值一起存下来,等下次请求时,使用配套的If-None-Match
,将这个放到request
的header
里面,可能长这样:
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
然后服务端拿到请求里面的If-None-Match
跟当前版本的ETag
比较下:
1.如果是一样的话,直接返回304
,语义为Not Modified
,不返回内容(body
),只返回header
,告诉浏览器直接用缓存。2.如果不一样的话,返回200
和最新的内容
与ETag
配套的还有一个不太常用的request header
----If-Match
,这个和前面If-None-Match
的语义是相反的。前面If-None-Match
的语义是如果不匹配就下载。而If-Match
通常用于post
或者put
请求中,语义为如果匹配才提交,比如你在编辑一个商品,其他人也可能同时在编辑。当你提交编辑时,其他人可能已经先于你提交了,这时候服务端的ETag
就已经变了,If-Match
就不成立了,这时候服务端会给你返回412
错误,也就是Precondition Failed
,前提条件失败。如果If-Match
成立,就正常返回200
。
Last-Modified & If-Modified-Since
Last-Modified
和If-Modified-Since
也是配套使用的,类似于ETag
和If-None-Match
的关系。只不过ETag
放的是一个版本号或者hash
值,Last-Modified
放的是资源的最后修改时间。Last-Modified
是放到response
的header
里面的,可能长这样:
Last-Modified: Wed, 21 Oct 2000 07:28:00 GMT
而客户端浏览器在使用时,应该将配套的If-Modified-Since
放到request
的header
里面,长这样:
If-Modified-Since: Wed, 21 Oct 2000 07:28:00 GMT
服务端拿到这个头后,会跟当前版本的修改时间进行比较:
1.当前版本的修改时间比这个晚,也就是这个时间后又改过了,返回200
和新的内容2.当前版本的修改时间和这个一样,也就是没有更新,返回304
,不返回内容,只返回头,客户端直接使用缓存
与If-Modified-Since
对应的还有If-Unmodified-Since
,If-Modified-Since
可以理解为有更新才下载,那If-Unmodified-Since
就是没有更新才下载。如果客户端传了If-Unmodified-Since
,像这样:
If-Unmodified-Since: Wed, 21 Oct 2000 07:28:00 GMT
服务端拿到这个头后,也会跟当前版本的修改时间进行比较:
1.如果这个时间后没有更新,服务器返回200
,并返回内容。2.如果这个时间后有更新,其实就是这个if
不成立,会返回错误代码412
,语义为Precondition Failed
ETag和Last-Modified优先级
ETag
和Last-Modified
都是协商缓存,都需要服务器进行计算和比较,那如果这两个都存在,用哪个呢?答案是ETag
,ETag
的优先级比Last-Modified
高。因为Last-Modified
在设计上有个问题,那就是Last-Modified
的精度只能到秒,如果一个资源频繁修改,在同一秒进行多次修改,你从Last-Modified
上是看不出来区别的。但是ETag
每次修改都会生成新的,所以他比Last-Modified
精度高,更准确。但是ETag
也不是完全没问题的,你的ETag
如果设计为一个hash
值,每次请求都要计算这个值,需要额外耗费服务器资源。具体使用哪一个,需要根据自己的项目情况来进行取舍。
强制缓存
上面扯蛋那里的第三个例子和第四个例子就是强制缓存,就是我知道在某个时间段完全不用去问服务端,直接去用缓存就行。这两个例子里面提到的Expires
是一个单独的header
,max-age
和immutable
同属于Cache-Control
这个header
。
Expires
Expires
比较简单,就是服务器response
的header
带上这个字段:
Expires: Wed, 21 Oct 2000 07:28:00 GMT
然后在这个时间前,客户端浏览器都不会再发起请求,而是直接用缓存资源。
Cache-Control
Cache-Control
相对比较复杂,可设置属性也比较多,max-age
只是其中一个属性,长这样:
Cache-Control: max-age=20000
这表示当前资源在20000秒
内都不用再请求了,直接使用缓存。
上面提到的immutable
也是Cache-Control
的一个属性,但是是个实验性质的,各个浏览器兼容并不好。设置了Cache-control: immutable
表示这辈子都用缓存了,再请求是不可能的了。
其他常用属性还有:
no-cache
:使用缓存前,强制要求把请求提交给服务器进行验证(协商缓存验证)。
no-store
:不存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。
另外Cache-Control
还有很多属性,大家可以参考MDN的文档[2]。
Expires和Cache-Control的优先级
就一句话:如果在Cache-Control
响应头设置了 max-age
或者 s-maxage
指令,那么 Expires
头会被忽略。
协商缓存和强制缓存优先级
这个其实很好理解,协商缓存需要发请求跟服务器协商,强制缓存如果生效,根本就不会发请求。所以这个优先级就是:先判断强制缓存,如果强制缓存生效,直接使用缓存;如果强制缓存失效,再发请求跟服务器协商,看要不要使用缓存。
总结
本文从生活中常见的场景入手,阐述了HTTP
缓存机制其实是提高访问速度和解决信息不同步的一种机制。这种信息不同步在生活中很常见,很多解决思路我们已经司空见惯,带着这种思维,我们可以很好的理解HTTP
缓存机制。HTTP
缓存机制要点如下:
1.HTTP
缓存机制分为强制缓存和协商缓存两类。2.强制缓存的意思就是不要问了(不发起请求),直接用缓存吧。3.强制缓存常见技术有Expires
和Cache-Control
。4.Expires
的值是一个时间,表示这个时间前缓存都有效,都不需要发起请求。5.Cache-Control
有很多属性值,常用属性max-age
设置了缓存有效的时间长度,单位为秒
,这个时间没到,都不用发起请求。6.immutable
也是Cache-Control
的一个属性,表示这个资源这辈子都不用再请求了,但是他兼容性不好,Cache-Control
其他属性可以参考MDN的文档[3]。7.Cache-Control
的max-age
优先级比Expires
高。8.协商缓存常见技术有ETag
和Last-Modified
。9.ETag
其实就是给资源算一个hash
值或者版本号,对应的常用request header
为If-None-Match
。10.Last-Modified
其实就是加上资源修改的时间,对应的常用request header
为If-Modified-Since
,精度为秒
。11.ETag
每次修改都会改变,而Last-Modified
的精度只到秒
,所以ETag
更准确,优先级更高,但是需要计算,所以服务端开销更大。12.强制缓存和协商缓存都存在的情况下,先判断强制缓存是否生效,如果生效,不用发起请求,直接用缓存。如果强制缓存不生效再发起请求判断协商缓存。
参考资料:
ETag MDN
文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/ETag
Last-Modified MDN
文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Last-Modified
Expires MDN
文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Expires
Cache-Control MDN
文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control
文章的最后,感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是作者持续创作的动力。
作者掘金文章汇总:https://juejin.im/post/5e3ffc85518825494e2772fd
References
[1]
koa-static的源码解析: https://juejin.cn/post/6903350655474204680
以上是关于通过HTTP Header控制缓存的主要内容,如果未能解决你的问题,请参考以下文章