We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
缓存,作为我们开发过程中经常碰到的一样东西,相信很多小伙伴和我一样对它熟悉又陌生,各种 expires 、etag 好像都知道,却又好像无法把它讲清楚,所以本篇文章就来总结整理下。
expires
etag
首先,什么是缓存?我的理解,缓存就是一个资源副本。当我们向服务器请求资源后,会根据情况将资源 copy 一份副本存在本地,以方便下次读取。它与本地存储 localStorage 、cookie 等不同,本地存储更多是数据记录,存储量较小,为了本地操作方便。而缓存更多是为了减少资源请求,多用于存储文件,存储量相对较大。
localStorage
cookie
那缓存有啥用呢?缓存最根本的作用就是减少没必要请求。有些资源,比如用户头像图片,很久才改变一次,但每次都要去请求这张一样的图片,通信一来一回增加了页面的显示时长,过多没必要请求也增加了服务器的压力。如果把这张图片直接缓存在本地,那每次就可以直接本地读取加载,不再发起请求。所以缓存的好处也就显而易见了,减少了时长从而优化用户体验,也减少了流量消耗,减轻了服务器的压力。
那么,缓存有哪些呢?就浏览器而言,一般缓存我们分为四类,按浏览器读取优先级顺序依次为:Memory Cache、Service Worker Cache、HTTP Cache、Push Cache。而本篇文章主要讲的就是 HTTP Cache ,其他有兴趣可以自行搜索。
Memory Cache
Service Worker Cache
HTTP Cache
Push Cache
HTTP Cache 是我们开发中接触最多的缓存,它分为强缓存和协商缓存。
强缓存主要包括 expires 和 cache-control。
cache-control
expires 是 HTTP1.0 中定义的缓存字段。当我们请求一个资源,服务器返回时,可以在 Response Headers 中增加 expires 字段表示资源的过期时间。
HTTP1.0
Response Headers
expires: Thu, 03 Jan 2019 11:43:04 GMT
它是一个时间戳(准确点应该叫格林尼治时间),当客户端再次请求该资源的时候,会把客户端时间与该时间戳进行对比,如果大于该时间戳则已过期,否则直接使用该缓存资源。
但是,有个大问题,发送请求时是使用的客户端时间去对比。一是客户端和服务端时间可能快慢不一致,另一方面是客户端的时间是可以自行修改的(比如浏览器是跟随系统时间的,修改系统时间会影响到),所以不一定满足预期。
正由于上面说的可能存在的问题,HTTP1.1 新增了 cache-control 字段来解决该问题,所以当 cache-control 和 expires 都存在时,cache-control 优先级更高。该字段是一个时间长度,单位秒,表示该资源过了多少秒后失效。当客户端请求资源的时候,发现该资源还在有效时间内则使用该缓存,它不依赖客户端时间。cache-control 主要有 max-age 和 s-maxage、public 和 private、no-cache 和 no-store 等值。
HTTP1.1
max-age
s-maxage
public
private
no-cache
no-store
cache-control: public, max-age=3600, s-maxage=3600
(1)max-age 和 s-maxage 两者是 cache-control 的主要字段,它们是一个数字,表示资源过了多少秒之后变为无效。在浏览器中,max-age 和 s-maxage 都起作用,而且 s-maxage 的优先级高于 max-age。在代理服务器中,只有 s-maxage 起作用。 可以通过设置 max-age 为 0 表示立马过期来向服务器请求资源。
(2)public 和 private public 表示该资源可以被所有客户端和代理服务器缓存,而 private 表示该资源仅能客户端缓存。默认值是 private,当设置了 s-maxage 的时候表示允许代理服务器缓存,相当于 public。
(3) no-cache 和 no-store no-cache 表示的是不直接询问浏览器缓存情况,而是去向服务器验证当前资源是否更新(即协商缓存)。no-store 则更狠,完全不使用缓存策略,不缓存请求或响应的任何内容,直接向服务器请求最新。由于两者都不考虑缓存情况而是直接与服务器交互,所以当 no-cache 和 no-store 存在时会直接忽略 max-age 等。
既然讲到了 no-cache 和 no-store,就顺便把 pragma 也讲了。他的值有 no-cache 和 no-store,表示意思同 cache-control,优先级高于 cache-control 和 expires,即三者同时出现时,先看 pragma -> cache-control -> expires。
pragma
pragma: no-cache
上面的 expires 和 cache-control 都会访问本地缓存直接验证看是否过期,如果没过期直接使用本地缓存,并返回 200。但如果设置了 no-cache 和 no-store 则本地缓存会被忽略,会去请求服务器验证资源是否更新,如果没更新才继续使用本地缓存,此时返回的是 304,这就是协商缓存。协商缓存主要包括 last-modified 和 etag。
last-modified
last-modified 记录资源最后修改的时间。启用后,请求资源之后的响应头会增加一个 last-modified 字段,如下:
last-modified: Thu, 20 Dec 2018 11:36:00 GMT
当再次请求该资源时,请求头中会带有 if-modified-since 字段,值是之前返回的 last-modified 的值,如:if-modified-since:Thu, 20 Dec 2018 11:36:00 GMT。服务端会对比该字段和资源的最后修改时间,若一致则证明没有被修改,告知浏览器可直接使用缓存并返回 304;若不一致则直接返回修改后的资源,并修改 last-modified 为新的值。
if-modified-since
if-modified-since:Thu, 20 Dec 2018 11:36:00 GMT
但 last-modified 有以下两个缺点:
为了解决 last-modified 上述问题,有了 etag。 etag 会基于资源的内容编码生成一串唯一的标识字符串,只要内容不同,就会生成不同的 etag。启用 etag 之后,请求资源后的响应返回会增加一个 etag 字段,如下:
etag: "FllOiaIvA1f-ftHGziLgMIMVkVw_"
当再次请求该资源时,请求头会带有 if-no-match 字段,值是之前返回的 etag 值,如:if-no-match:"FllOiaIvA1f-ftHGziLgMIMVkVw_"。服务端会根据该资源当前的内容生成对应的标识字符串和该字段进行对比,若一致则代表未改变可直接使用本地缓存并返回 304;若不一致则返回新的资源(状态码200)并修改返回的 etag 字段为新的值。
if-no-match
if-no-match:"FllOiaIvA1f-ftHGziLgMIMVkVw_"
可以看出 etag 比 last-modified 更加精准地感知了变化,所以 etag 优先级也更高。不过从上面也可以看出 etag 存在的问题,就是每次生成标识字符串会增加服务器的开销。所以要如何使用 last-modified 和 etag 还需要根据具体需求进行权衡。
我们将访问和刷新分为以下三种情况:
假设当前有这么一个 index 页面,返回的响应信息如下:
cache-control: max-age=72000 expires: Tue, 20 Nov 2018 20:41:14 GMT last-modified: Tue, 20 Nov 2018 00:41:14 GMT
这种情况下会根据实际设计的缓存策略去判断。
200(from cache)
304(not modified)
200(ok)
这种情况下,实际是浏览器将 cache-control 的 max-age 直接设置成了 0,让缓存立即过期,直接走协商缓存路线。发送的请求头如下:
cache-control: max-age=0 if-modified-since: Tue, 20 Nov 2018 00:41:14 GMT
强制刷新的情况下,浏览器会强行设置 no-cache,强制获取最新的资源,就连 if-modified-since 等其他缓存协议字段都会被吃掉。此时发送的请求头如下:
cache-control: no-cache pragma: no-cache
参考文章:文章一、文章二、文章三、文章四、文章五、文章六
The text was updated successfully, but these errors were encountered:
No branches or pull requests
缓存,作为我们开发过程中经常碰到的一样东西,相信很多小伙伴和我一样对它熟悉又陌生,各种
expires
、etag
好像都知道,却又好像无法把它讲清楚,所以本篇文章就来总结整理下。一、缓存
首先,什么是缓存?我的理解,缓存就是一个资源副本。当我们向服务器请求资源后,会根据情况将资源 copy 一份副本存在本地,以方便下次读取。它与本地存储
localStorage
、cookie
等不同,本地存储更多是数据记录,存储量较小,为了本地操作方便。而缓存更多是为了减少资源请求,多用于存储文件,存储量相对较大。那缓存有啥用呢?缓存最根本的作用就是减少没必要请求。有些资源,比如用户头像图片,很久才改变一次,但每次都要去请求这张一样的图片,通信一来一回增加了页面的显示时长,过多没必要请求也增加了服务器的压力。如果把这张图片直接缓存在本地,那每次就可以直接本地读取加载,不再发起请求。所以缓存的好处也就显而易见了,减少了时长从而优化用户体验,也减少了流量消耗,减轻了服务器的压力。
那么,缓存有哪些呢?就浏览器而言,一般缓存我们分为四类,按浏览器读取优先级顺序依次为:
Memory Cache
、Service Worker Cache
、HTTP Cache
、Push Cache
。而本篇文章主要讲的就是HTTP Cache
,其他有兴趣可以自行搜索。二、HTTP Cache
HTTP Cache
是我们开发中接触最多的缓存,它分为强缓存和协商缓存。(一)、强缓存
强缓存主要包括
expires
和cache-control
。1、expires
expires
是HTTP1.0
中定义的缓存字段。当我们请求一个资源,服务器返回时,可以在Response Headers
中增加expires
字段表示资源的过期时间。expires: Thu, 03 Jan 2019 11:43:04 GMT
它是一个时间戳(准确点应该叫格林尼治时间),当客户端再次请求该资源的时候,会把客户端时间与该时间戳进行对比,如果大于该时间戳则已过期,否则直接使用该缓存资源。
但是,有个大问题,发送请求时是使用的客户端时间去对比。一是客户端和服务端时间可能快慢不一致,另一方面是客户端的时间是可以自行修改的(比如浏览器是跟随系统时间的,修改系统时间会影响到),所以不一定满足预期。
2、cache-control
正由于上面说的可能存在的问题,
HTTP1.1
新增了cache-control
字段来解决该问题,所以当cache-control
和expires
都存在时,cache-control
优先级更高。该字段是一个时间长度,单位秒,表示该资源过了多少秒后失效。当客户端请求资源的时候,发现该资源还在有效时间内则使用该缓存,它不依赖客户端时间。cache-control
主要有max-age
和s-maxage
、public
和private
、no-cache
和no-store
等值。cache-control: public, max-age=3600, s-maxage=3600
(1)
max-age
和s-maxage
两者是
cache-control
的主要字段,它们是一个数字,表示资源过了多少秒之后变为无效。在浏览器中,max-age
和s-maxage
都起作用,而且s-maxage
的优先级高于max-age
。在代理服务器中,只有s-maxage
起作用。 可以通过设置max-age
为 0 表示立马过期来向服务器请求资源。(2)
public
和private
public
表示该资源可以被所有客户端和代理服务器缓存,而private
表示该资源仅能客户端缓存。默认值是private
,当设置了s-maxage
的时候表示允许代理服务器缓存,相当于public
。(3)
no-cache
和no-store
no-cache
表示的是不直接询问浏览器缓存情况,而是去向服务器验证当前资源是否更新(即协商缓存)。no-store
则更狠,完全不使用缓存策略,不缓存请求或响应的任何内容,直接向服务器请求最新。由于两者都不考虑缓存情况而是直接与服务器交互,所以当no-cache
和no-store
存在时会直接忽略max-age
等。3、pragma
既然讲到了
no-cache
和no-store
,就顺便把pragma
也讲了。他的值有no-cache
和no-store
,表示意思同cache-control
,优先级高于cache-control
和expires
,即三者同时出现时,先看pragma
->cache-control
->expires
。pragma: no-cache
(二)、协商缓存
上面的
expires
和cache-control
都会访问本地缓存直接验证看是否过期,如果没过期直接使用本地缓存,并返回 200。但如果设置了no-cache
和no-store
则本地缓存会被忽略,会去请求服务器验证资源是否更新,如果没更新才继续使用本地缓存,此时返回的是 304,这就是协商缓存。协商缓存主要包括last-modified
和etag
。1、last-modified
last-modified
记录资源最后修改的时间。启用后,请求资源之后的响应头会增加一个last-modified
字段,如下:last-modified: Thu, 20 Dec 2018 11:36:00 GMT
当再次请求该资源时,请求头中会带有
if-modified-since
字段,值是之前返回的last-modified
的值,如:if-modified-since:Thu, 20 Dec 2018 11:36:00 GMT
。服务端会对比该字段和资源的最后修改时间,若一致则证明没有被修改,告知浏览器可直接使用缓存并返回 304;若不一致则直接返回修改后的资源,并修改last-modified
为新的值。但
last-modified
有以下两个缺点:2、etag
为了解决
last-modified
上述问题,有了etag
。etag
会基于资源的内容编码生成一串唯一的标识字符串,只要内容不同,就会生成不同的etag
。启用etag
之后,请求资源后的响应返回会增加一个etag
字段,如下:etag: "FllOiaIvA1f-ftHGziLgMIMVkVw_"
当再次请求该资源时,请求头会带有
if-no-match
字段,值是之前返回的etag
值,如:if-no-match:"FllOiaIvA1f-ftHGziLgMIMVkVw_"
。服务端会根据该资源当前的内容生成对应的标识字符串和该字段进行对比,若一致则代表未改变可直接使用本地缓存并返回 304;若不一致则返回新的资源(状态码200)并修改返回的etag
字段为新的值。可以看出
etag
比last-modified
更加精准地感知了变化,所以etag
优先级也更高。不过从上面也可以看出etag
存在的问题,就是每次生成标识字符串会增加服务器的开销。所以要如何使用last-modified
和etag
还需要根据具体需求进行权衡。三、访问刷新分析
我们将访问和刷新分为以下三种情况:
假设当前有这么一个 index 页面,返回的响应信息如下:
1、标签进入、输入url回车进入
这种情况下会根据实际设计的缓存策略去判断。
no-cache
和no-store
,所以默认先走强缓存路线。根据cache-control
(expires
优先级低)判断缓存是否过期,若没有过期则此时返回200(from cache)
。last-modified
值去与服务器比对,若这个时间之后没有改过则去读取本地缓存,返回304(not modified)
。200(ok)
,并更新返回响应的last-modified
值。2、按刷新按钮、F5 刷新、网页右键“重新加载”
这种情况下,实际是浏览器将
cache-control
的max-age
直接设置成了 0,让缓存立即过期,直接走协商缓存路线。发送的请求头如下:3、ctrl + F5 强制刷新
强制刷新的情况下,浏览器会强行设置
no-cache
,强制获取最新的资源,就连if-modified-since
等其他缓存协议字段都会被吃掉。此时发送的请求头如下:参考文章:文章一、文章二、文章三、文章四、文章五、文章六
The text was updated successfully, but these errors were encountered: