1. Foreword
As a front-end, it is very necessary to understand http caching. It is not only a necessary part of the interview, but also a must in actual development. There are essential knowledge points that need to be understood. The author of this article will talk about the concept of caching and how to design a reasonable caching architecture in the business, taking you step by step to unravel the mystery of http caching.
2. http cache definition
When the client requests resources from the server, it will first arrive at the browser cache. If the browser has a copy of "resources to be requested", it can directly browse from The resource is fetched from the server cache instead of fetching it from the origin server. http caching is generally for GET requests, and POST requests are generally not cached, because the tasks performed by POST requests are all side effects or non-idempotent tasks on the server. Since server resources are to be changed, the request naturally has to enter the server for processing. The benefits of caching are huge. Reducing http requests naturally reduces server pressure and increases the access speed of resources. However, indiscriminate use of cache will lead to untimely updates of resources and even misalignment of resource updates. , the disaster is also huge.
http caching is divided into strong caching and negotiated caching. Let’s take a look at the two caching mechanisms one by one.
3. Strong cache
3.1. Strong cache definition
If the cache is hit, the data will be taken directly from the cache. The request will not go through the server, and the returned http status code is 200 (from disk cache)
The following is a flow chart to illustrate the strong cache request process. For convenience, it is assumed that the browser has a cache database (actually a disk disk. Where cached data is stored).
Look carefully at the flow chart above. The biggest feature of strong cache is that it will not go through the server when the cache is hit. , but returns directly.
3.2. Http header Expires/Cache-Control sets strong cache
There are multiple attributes in Cache-Control to control cache. Setting strong cache means setting the validity period of resources. The attribute is max-age. .
Let’s use Express to demonstrate to you
app.get('/script1.js', function (req, res, next) { // res.header('Cache-Control', 'must-revalidate, max-age=600') // res.header('Content-Type', 'text/html') res.header('Cache-Control', 'max-age=20') res.sendFile(__dirname + '/script.js') })## Expires and max-age are both used to control the cache life cycle. The difference is that Expires specifies the specific time of expiration, such as Sun, 21 Mar 2027 08:52:14 GMT, while max-age specifies the lifetime seconds of 315360000. The difference is that Expires is a standard in HTTP/1.0, while max-age is the content of Cache-Control and is defined in HTTP/1.1. But for forward compatibility, these two attributes still need to exist at the same time. max-age takes precedence over Expires. 4. Negotiation/Comparison Cache4.1. Definition The difference between negotiation caching and forced caching is that the negotiation cache needs to communicate with the server every time it reads data. communication, and the cache ID will be added. When requesting the server for the first time, the server will return the resource and return a cache identifier of the resource, which will be stored in the browser's cache database. When requesting a resource for the second time, the browser will first send the cache identifier to the server. After the server gets the identifier, it will determine whether the identifier matches. If it does not match, it means that the resource has been updated, and the server will return the new data and the new cache identifier. to the browser; if the cache identifier matches, it means that the resource has not been updated, and a 304 status code is returned, and the browser reads the data in the local cache server. The biggest feature of the negotiation cache is that it must be verified by the server. Let’s explain the verification process of the negotiation cache. First visit:
4.2、协商缓存如何验证
第一次请求将response header的Last-Modified和Etag存起来,在第二次请求通过request header的If-Modified-Since和If-None-Match传到服务端进行验证,如果命中缓存,返回304,不带返回的数据,浏览器自动从缓存中获取数据资源,若未命中缓存返回200,带上数据资源。
** Last-Modified:**
服务器在响应请求时,告诉浏览器资源的最后修改时间。
** If-Modified-Since:**
再次请求服务器时,通过此字段通知服务器上次请求时,服务器返回的资源最后修改时间。
服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。
若资源的最后修改时间大于If-Modified-Since,说明资源又被改动过,则响应整片资源内容,返回状态码200;
若资源的最后修改时间小于或等于If-Modified-Since,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。
** Etag / If-None-Match(优先级高于Last-Modified / If-Modified-Since) **
** Etag:**
服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。
** If-None-Match:**
再次请求服务器时,通过此字段通知服务器客户段缓存数据的唯一标识。
服务器收到请求后发现有头If-None-Match 则与被请求资源的唯一标识进行比对,
不同,说明资源又被改动过,则响应整片资源内容,返回状态码200;
相同,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。
4.3、Http头如何设置协商缓存
在强缓存那一节说到使用Cache-Control的max-age来设置资源过期时间,那么当max-age=0的时候呢,自然浏览器第一时间发现资源过期,request header就会带着If-Modified-Since和If-None-Match去服务端验证。
所以设置response header为:
Cache-Control: max-age=0
就可以触发协商缓存了,其实Cache-Control中还有两个属性都可以设置协商缓存 must-revalidate和no-cache
must-revalidate的意义为必须进行验证,但是它一般是和max-age一起使用的,不会单独使用,
Cache-Control: must-revalidate, max-age=600
该头信息意义就是在资源有效期过后必须进行验证, 与只设置max-age=600的区别是,前面一个是MUST,而后面一个是SHOULD,理论上来说它们的效果是一致的。
no-cache的意义千万不能理解为不缓存,下面两段代码的意义是一样的,即请求必须进行验证,才可以使用缓存资源,注意是MUST
Cache-Control: no-cacheCache-Control: must-revalidate, max-age=0
如果要不缓存,每次都请求新的资源应该使用
Cache-Control: no-store
5、关于缓存的Http头总结
5.1、"no-cache", "no-store", "must-revalidate"
Cache-Control字段可以设置的不仅仅是max-age存储时间,还有其他额外的值可以填写,甚至可以组合。主要使用的值有如下:
no-cache: 虽然字面意义是“不要缓存”。但它实际上的机制是,仍然对资源使用缓存,但每一次在使用缓存之前必须(MUST)向服务器对缓存资源进行验证。
no-store: 不使用任何缓存
must-revalidate: 如果你配置了max-age信息,当缓存资源仍然新鲜(小于max-age)时使用缓存,否则需要对资源进行验证。所以must-revalidate可以和max-age组合使用Cache-Control: must-revalidate, max-age=60
有趣的事情是,虽然no-cache意为对缓存进行验证,但是因为大家广泛的错误的把它当作no-store来使用,所以有的浏览器也就附和了这种设计。这是一个典型的劣币驱逐良币。
5.2、Expires VS. max-age
Expires和max-age都是用于控制缓存的生命周期。不同的是Expires指定的是过期的具体时间,例如Sun, 21 Mar 2027 08:52:14 GMT,而max-age指定的是生命时长秒数315360000。
区别在于Expires是 HTTP/1.0 的中的标准,而max-age是属于Cache-Control的内容,是 HTTP/1.1 中的定义的。但为了想向前兼容,这两个属性仍然要同时存在。
但有一种更倾向于使用max-age的观点认为Expires过于复杂了。例如上面的例子Sun, 21 Mar 2027 08:52:14 GMT,如果你在表示小时的数字缺少了一个0,则很有可能出现出错;如果日期没有转换到用户的正确时区,则有可能出错。这里出错的意思可能包括但不限于缓存失效、缓存生命周期出错等。
5.3、Etag VS. Last-Modified
Etag和Last-Modified都可以用于对资源进行验证,而Last-Modified顾名思义,表示资源最后的更新时间。
我们把这两者都成为验证器(Validators),不同的是,Etag属于强验证(Strong Validation),因为它期望的是资源字节级别的一致;而Last-Modified属于弱验证(Weak Validation),只要资源的主要内容一致即可,允许例如页底的广告,页脚不同。
根据RFC 2616标准中的13.3.4小节,一个使用HTTP 1.1标准的服务端应该(SHOULD)同时发送Etag和Last-Modified字段。同时一个支持HTTP 1.1的客户端,比如浏览器,如果服务端有提供Etag的话,必须(MUST)首先对Etag进行Conditional Request(If-None-Match头信息);如果两者都有提供,那么应该(SHOULD)同时对两者进行Conditional Request(If-Modified-Since头信息)。如果服务端对两者的验证结果不一致,例如通过一个条件判断资源发生了更改,而另一个判定资源没有发生更改,则不允许返回304状态。但话说回来,是否返回还是通过服务端编写的实际代码决定的。所以仍然有操纵的空间。
5.4、max-age=0 VS. no-cache
max-age=0是在告诉浏览器,资源已经过期了,你应该(SHOULD)对资源进行重新验证了;而no-cache则是告诉浏览器在每一次使用缓存之前,你必须(MUST)对资源进行重新验证。
区别在于,SHOULD是非强制性的,而MUST是强制性的。在no-cache的情况下,浏览器在向服务器验证成功之前绝不会使用过期的缓存资源,而max-age=0则不一定了。虽然理论上来说它们的效果应该是一致的。
5.5、public VS. private
要知道从服务器到浏览器之间并非只有浏览器能够对资源进行缓存,服务器的返回可能会经过一些中间(intermediate)服务器甚至甚至专业的中间缓存服务器,还有CDN。而有些请求返回是用户级别、是私人的,所以你可能不希望这些中间服务器缓存返回。此时你需要将Cache-Control设置为private以避免暴露。
6、缓存实战
前面写了很多缓存的基础知识,那么如何设计一个可靠的缓存规则,这个其实得根据你的实际需求而定。
比如某个资源永远不会改变,比如某些第三方库(一般都放CDN做优化了),或者某些图片,比如百度的图片,就让它们永久缓存着吧,设置一个最大的max-age
Cache-Control: max-age=31536000
其他的资源可根据下面这张决策树来进行设置
7、memory cache
“内存缓存”中主要包含的是当前文档中页面中已经抓取到的资源。例如页面上已经下载的样式、脚本、图片等。我们不排除页面可能会对这些资源再次发出请求,所以这些资源都暂存在内存中,当用户结束浏览网页并且关闭网页时,内存缓存的资源会被释放掉。
这其中最重要的缓存资源其实是preloader相关指令(例如)下载的资源。总所周知preloader的相关指令已经是页面优化的常见手段之一,而通过这些指令下载的资源也都会暂存到内存中。根据一些材料,如果资源已经存在于缓存中,则可能不会再进行preload。
需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验
这个应该是浏览器做的一种优化,缓存也只是暂时的。
8. The browser may limit the Cache-Control header to be invalid
Recommended tutorial: HTTP Chinese Development Manual