2019-05-27
前言
Web的缓存可以粗略分为两种:服务器缓存和浏览器缓存。服务器缓存(如代理服务器缓存、CDN等)让资源加载得更快,可有效提高第一次访问的加载速度;而浏览器缓存可以跳过请求重复的资源,大幅度提高第二次访问的加载速度。
浏览器缓存又可以粗略分为两种:localstorage、indexDB、Cache等功能可以实现对特定资源和数据的储存,需要在业务层编写代码去实现缓存功能;Http协议缓存通过http header里的若干字段控制,浏览器自动判断是否使用或缓存,不需要前端业务代码参与。
本文只讨论http协议的缓存机制。服务器在Http返回的header中带上Expires、Cache-Control、Last-Modified、Etag等字段,浏览器把返回的资源或数据缓存到本地。下次需要用到相同资源的时候,通过这些字段来判断是直接从缓存中获取数据,还是重新发起请求。
强制缓存
使用http response header中的Expires和Cache-Control标注资源的有效期。在有效期内重复请求,浏览器不会向服务器发起请求,而是读取上一次请求的响应。
强制缓存由浏览器判断。缓存触发时,,在浏览器调试界面查看此请求,状态是“Status Code: 200 OK (from memory cache)”,表示从内存读取缓存,或“Status Code: 200 OK (from disk cache)”,表示从硬盘读取缓存。
缓存到期的日期/时间,有效格式为:“Expires: Wed, 29 May 2019 06:00:00 GMT”。
以客户端时间为准,在这个时间之后,缓存过期,需要重新发起请求。不符合格式的日期、过去的日期,同样意味着已经过期。
控制该请求是否应该缓存,如果缓存的话有效期是多久。可以带有以下参数:
public:可以被任何对象(客户端、代理服务器)缓存。
private:只能被单个用户缓存,代理服务器不能缓存。
no-store:不能被任何对象缓存。
no-cache:可以被用户和代理服务器缓存,但每次使用前必须先校验缓存是否过期。(参考下文“协商缓存”)
max-age=<seconds>:缓存的有效时间(单位秒),超过这个时间缓存被认为过期。
s-maxage=<seconds>:代理服务器缓存的有效时间(单位秒),私有缓存会忽略它。
must-revalidate:可以被用户和代理服务器缓存,一旦过期,需要校验缓存是否有效才能使用。(参考下文“协商缓存”)
proxy-revalidate:仅能被代理服务器缓存,一旦过期,需要校验缓存是否有效才能使用。(参考下文“协商缓存”)
immutable:资源未过期时,不需要发送验证请求就能直接使用。
no-transform:不得对资源进行转换或转变,代理服务器不能修改http头。(有些非透明代理会对图像格式进行转换,no-transform就是为了禁用这种转换)
only-if-cached:客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新。
举个例子,对于静态图片,通常希望代理服务器和客户端都缓存较长时间,可以设置为:“Cache-Control:public, max-age=31536000”。
如上图所示,当Cache-Control和Expires字段同时存在时,Cache-Control的优先级更高。Expires是一个时间戳,如果服务器和客户端的时间不一致,可能会有误差,因此只能做粗略的控制。而Cache-Control使用相对于请求的有效时长,还可以进行更复杂的缓存设置。
协商缓存
当强制缓存未命中时,浏览器无法判断缓存是否已经过期,需要重新发起请求。服务器根据请求header里的If-Modify-Since和If-None-Match等字段判断资源是否有更新。如果有更新,直接返回更新后的结果;如果没更新,返回一个body为空的304响应:“Status Code: 304 Not Modified”,表示可以直接使用缓存。
每一次http返回时,服务端在header中标明此资源最后更新的时间,格式为“Last-Modified: Wed, 1 May 2019 06:00:00 GMT”。当下次请求相同资源,并且强制缓存已过期,浏览器会自动在请求header带上这个时间,Get和Head请求使用“If-Modified-Since”字段,其他请求使用“If-Unmodified-Since”字段。服务端根据这个时间,判断返回内容从那时起是否有修改,来决定返回200还是304。
每一次http返回时,服务端在header中标明此资源的版本标识符,格式为“ETag: "<etag_value>"”。当下次请求相同资源,并且强制缓存已过期,浏览器会自动在请求header带上这个标识符,使用“If-None-Match”或“If-Match”字段。服务端根据标识符,判断返回内容和客户端缓存是否一致,来决定返回200还是304。
没有明确指定生成ETag值的方法,服务端可以自己制定规则,自己判断。可以在值前面添加'W/',表示使用弱验证器。
如上图所示,当Last-Modified和Etag字段同时存在时,Etag的优先级更高。
清除缓存
缓存是个好东西,但有时候我们又需要禁用它。以下提供几种不修改接口就能禁用缓存的方法。
在浏览器中使用“清空缓存并重新加载”功能,或者ctrl+F5快捷键。
在浏览器开发者工具的“Network”中勾选“Disable cache”。
在HTML的<header>中加入以下代码:
<meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0">
在运维部署时候,尤其是版本更替的时候,可能会遇到更复杂的情况。以前写过一篇《前端资源缓存与版本号更新》,也可以作为解决方案的参考。
总结
强制缓存可以减少了一次http请求/响应的过程。协商缓存虽然不能减少请求,但可以节省返回的内容。合理配置缓存逻辑,提高缓存命中率,可以有效优化加载速度。
参考文献
本文未经许可禁止转载,如需转载请联系 JAY@oonne.com