HTTP缓存相信都不陌生,由于它是在前端性能优化中必不可少的一个环节。在首次进入或者请求数据正常传输数据,而当再次进入或者请求数据时,能够走本地或者服务器上的缓存,来节省流量、优化性能、提升用户体验、下降网络负荷等等。javascript
web缓存主要用来缓存html文件、js文件、css文件、数据,基本上都是提高客户端/浏览器请求到服务器之间的速度,固然也能够结合数据压缩如gzip、7z等等加快响应数据传输。css
在整个应用中能够错多层缓存结构这里很少作介绍,由于前面已经大体介绍过了,这里主要介绍和前端比较相关的HTTP缓存。html
大体分为下面几步来加深对HTTP中的缓存理解和应用场景。前端
大体把协议分为强缓存(过时策略)和协商缓存(协商策略)两类缓存,可能不太准确只是本身的如今的看法,浏览器/客户端经过这两种策略决定使用缓存中的副本仍是从服务器中获取最新的资源。java
key | 描述 | 缓存策略 | 首部类型 |
---|---|---|---|
Pragma | 指定缓存机制(http 1.0 字段) | 强缓存(过时策略) | 响应首部字段 |
Cache-COntrol | Cache-Control 通用消息头字段,被用于在http请求和响应中,经过指定指令来实现缓存机制。 |
强缓存(过时策略) | 响应/请求首部字段 |
Expires | Expires 响应头包含日期/时间, 即在此时候以后,响应过时。 |
强缓存(过时策略) | 响应首部字段 |
Last-Modified | Last-Modified 是一个响应首部,其中包含源头服务器认定的资源作出修改的日期及时间。 |
协商缓存(协商策略) | 响应首部字段 |
If-Modified-Since | If-Modified-Since 是一个条件式请求首部,服务器只在所请求的资源在给定的日期时间以后对内容进行过修改的状况下才会将资源返回,状态码为 200 。 |
协商缓存(协商策略) | 请求首部字段 |
ETag | ETag HTTP响应头是资源的特定版本的标识符。 |
协商缓存(协商策略) | 响应首部字段 |
If-None-Match | If-None-Match 是一个条件式请求首部。对于 GET 和 HEAD 请求方法来讲,当且仅当服务器上没有任何资源的 ETag 属性值与这个首部中列出的相匹配的时候,服务器端会才返回所请求的资源,响应码为 200 。 |
协商缓存(协商策略) | 请求首部字段 |
If-Match(辅助) | If-Match 的使用表示这是一个条件请求。在请求方法为 GET 和 HEAD 的状况下,服务器仅在请求的资源知足此首部列出的 ETag 值时才会返回资源。 |
协商缓存(协商策略) | 请求首部字段 |
If-Unmodified-Since(辅助) | If-Unmodified-Since 只有当资源在指定的时间以后没有进行过修改的状况下,服务器才会返回请求的资源,或是接受 POST 或其余 non-safe 方法的请求。 |
协商缓存(协商策略) | 请求首部字段 |
Vary(辅助) | Vary 是一个HTTP 响应头部信息,它决定了对于将来的一个请求头,应该用一个缓存的回复(response)仍是向源服务器请求一个新的回复。 |
协商缓存(协商策略) | 响应首部字段 |
缓存又分为强缓存和协商缓存。其中强缓存包括Expires
和Cache-Control
,主要是在过时策略生效时应用的缓存。弱缓存包括Last-Modified
和ETag
,是在协商策略后应用的缓存。强弱缓存之间的主要区别在于获取资源时是否会发送请求。node
强缓存和协商缓存git
200 OK
304 Not Modified
属于**强缓存(过时策略)**的有以下:web
Cache-Control
用于指定资源的缓存机制,能够同时在请求和响应头中设定。可是Cache-Control
中的属性也分为请求和响应缓存指令,大体分为以下:算法
缓存请求指令 客户端能够在HTTP请求中使用的标准 Cache-Control
指令。chrome
Cache-Control: max-age= / max-stale[=] / min-fresh= / no-cache / no-store / no-transform / only-if-cached
缓存响应指令 服务器能够在响应中使用的标准 Cache-Control
指令。
Cache-control: must-revalidate / no-cache / no-store / no-transform / public / private / proxy-revalidate / max-age= / s-maxage=
Cache-Control
: cache-directive[,cache-directive]
。cache-directive
为缓存指令,大小写不敏感,共有12个与HTTP缓存标准相关,以下所示。其中请求指令7种,响应指令9种。Cache-Control
能够设置多个缓存指令,以逗号,
分隔。
no-cache
以后并不表明浏览器不缓存,而是在获取缓存前要向服务器确认资源是否被更改。至关于max-age: 0, must-revalidate
max-age
或者Expires
头,可是仅适用于共享缓存(好比各个代理),私有缓存会忽略它。must-revalidate
做用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。Content-Encoding
、Content-Range
、Content-Type
等字段的修改,所以代理服务器的gzip
压缩将不被容许。还有一点须要注意的是,no-cache
并非指不缓存文件,no-store
才是指不缓存文件。no-cache
仅仅是代表跳过强缓存,强制进入协商策略。
禁止缓存 Cache-Control: no-cache, no-store, must-revalidate
缓存静态资源 Cache-Control:public, max-age=86400
Expires
指定缓存的过时时间,为绝对时间,即某一时刻。
注意:参考本地时间进行比对,在指定时刻后过时。RFC 2616建议最大值不要超过1年。
Cache-Control
中的max-age
指令用于指定缓存过时的相对时间。资源达到指定时间后过时。该功能与Expires
相似。但其优先级高于Expires,若是同时设置max-age
和Expires
,max-age
生效,忽略Expires
。
Cache-Control > Expires
强缓的设置流程图大体以下:
在没有强缓存时,就会走协商缓存,协商缓存大体流程:
属于**协商缓存(协商策略)**的有以下:
Last-Modified/If-Modified-Since
大体流程以下:
Last-Modified
,返回给客户端If-Modified-Since
字段If-Modified-Since
中的时间对比,若是没有变化返回304
状态码(浏览器得知304状态码,资源从缓存中获取),若是改变返回200
而且更新资源、更新Last-Modified
上面的流程是在设置不使用强缓存时的场景,这个只是如今的理解可能有不少的不太完善的地方。
Last-Modified
用于标记请求资源的最后一次修改时间。
语法
Last-Modified: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
复制代码
注意
Last-Modified
只能精确到秒,所以不适合在一秒内屡次改变的资源。If-Modified-Since
是一个条件式请求首部,与Last-Modified
何用。有两种结果以下:
If-Modified-Since/Last-Modified
相同返回304
状态码,客户端使用缓存If-Modified-Since/Last-Modified
不相同返回200
状态码,返回新的资源
If-Modified-Since
只能够用在GET
或HEAD
请求中。
If-Unmodified-Since
表示资源未修改则正常执行更新,不然返回412(Precondition Failed)
状态码的响应。主要有以下两种场景。
If-Range
字段同时使用时,能够用来保证新的片断请求来自一个未修改的文档。根据实体内容生成一段惟一hash字符串,标识资源的状态,由服务端产生。浏览器会将这串字符串传回服务器,验证资源是否已经修改,若是没有修改,过程以下:
ETag
HTTP响应头是资源的特定版本的标识符。 语法
ETag: W/"<etag_value>"
ETag: "<etag_value>"
复制代码
W/ 可选 'W/'(大小写敏感) 表示使用弱验证器。 弱验证器很容易生成,但不利于比较。 强验证器是比较的理想选择,但很难有效地生成。
"<etag_value>" 实体标签惟一地表示所请求的资源。 它们是位于双引号之间的ASCII字符串(如“675af34563dc-tr34”)。
注意:ETag和If-None-Match的值均为双引号包裹的。
ETag
的优先级高于Last-Modified
。当ETag
和Last-Modified
,ETag
优先级更高,但不会忽略Last-Modified
,须要服务端实现。
ETag
和 If-None-Match
常被用来处理协商缓存。而 ETag
和 If-Match
能够 避免“空中碰撞”。
ETag
HTTP 响应头是资源的特定版本的标识符。这可让缓存更高效,并节省带宽,由于若是内容没有改变,Web 服务器不须要发送完整的响应。而若是内容发生了变化,使用 ETag
有助于防止资源的同时更新相互覆盖(“空中碰撞”)。
当编辑MDN时,当前的WIki内容被散列,并在相应中放入Etag
:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
复制代码
将更改保存到WIKI页面(发布数据)时,POST请求将包含有ETag
值的If-Match
头来检车是否为最新版本。
If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
复制代码
若是哈希值不匹配,则意味着文档已经被编辑,抛出 412 ( Precondition Failed)
前提条件失败错误。
If-None-Match
是客户端发送给服务器时的请求头,其值是服务器返回给客户端的 ETag
,当 If-None-Match
和服务器资源最新的 Etag
不一样时,返回最新的资源及其 Etag
。
缓存策略分为强缓存和协商缓存,首先通过强缓存的过时策略,才会走后面的协商缓存的协商策略,大体把缓存分为三个阶段本地缓存阶段(强缓存)、协商缓存阶段(本地+服务器)、缓存失败阶段。
大体在每一个阶段中作的什么判断:
http
请求到服务器。(主要应用是强缓存、serverWorker);304
,让浏览器使用本地找到的那个资源;200
, 固然这个是指找到资源的状况下,若是服务器上没有这个资源,则返回404
。大体流程以下图所示:
这张图中没有包含serverWorker
的缓存判断流程b,可是在后面会有一篇文章专门介绍serverWorker,由于他是属于PWA中的内容。
存储策略发生在收到请求响应后,用于决定是否缓存相应资源;过时策略发生在请求前,用于判断缓存是否过时;协商策略发生在请求中,用于判断缓存资源是否更新。
在用户刷新页面(F5)时,会对缓存产生影响,这里就会记录用户操做对缓存产生的影响。用户操做事项以下表所示:
用户操做 | 强缓存 | 协商缓存 |
---|---|---|
(新标签)地址栏回车 | 有效 | 有效 |
(地址不变)地址栏回车 | 兼容性问题Chrome(失效)/Firefox(有效) | 有效 |
连接跳转 | 有效 | 有效 |
前进/后退 | 有效 | 有效 |
从收藏栏打开连接 | 有效 | 有效 |
(window.open)新开窗口 | 有效 | 有效 |
刷新(Command/Ctrl + R / F5) | 失效 | 有效 |
强制刷新(Command + Shift + R / Ctrl + F5) | 失效 | 失效 |
基本上包含了一些常见的用户操做对强缓存和协商缓存的影响,大体的判断流程以下:
注意
- (地址不变)地址栏回车:它比较特殊,为何它在Chrome是失效,在Firefox中是有效。由于Chrome把地址不变回车等同于刷新当前页面,而在Firefox都是做为新地址回车处理的。
webkit(Chrome内核)
资源分为主资源和派生资源。主资源是地址栏输入的URL请求返回的资源,派生资源是主资源中所引用的JS、CSS、图片等资源。- 在
Chrome
下刷新时,只有主资源的缓存应用方式如上图所示,派生资源的缓存应用方式与新标签打开相似,会判断缓存是否过时。强缓存生效时的区别在于新标签打开为from disk cache
,而当前页刷新派生资源是from memory cache
。- 而在
Firefox
下,当前页面刷新,全部资源都会如上图所示。
从缓存的位置上来讲分为四种,而且各自有优先级,当依次由上到下查找缓存且都没有命中的时候,才会去请求网络,大体以下:
Service Worker 是一种独立于主线程以外的 Javascript 线程。它能够帮咱们实现离线缓存、消息推送和网络代理等功能。
使用
Service Worker
的话,传输协议必须为HTTPS
。由于Service Worker
中涉及到请求拦截,因此必须使用HTTPS
协议来保障安全。
Service Worker 实现缓存大体分为如下几个步骤:
在这里就不细说了,后面有一个单独的章节来说述Service Worker, Service Worker 的缓存与浏览器其余内建的缓存机制不一样,它可让咱们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,而且缓存是持续性的。
Memory Cache 是内存中的缓存。主要包含的是当前页面中请求到的数据如图片(base64)、脚本(JavaScript)、样式(css)等静态数据。读取内存中的数据确定比磁盘中的快,可是内存中的缓存持续性很短,它会随着当前Tab页面关闭,内存中的缓存也就被释放。
好比在百度首页刷新页面,效果以下图所示:
<link>
元素的
rel
属性的属性值
preload
,
<link rel="preload">
来显示的指定的预加载资源,也会被放入
memory cache
中。
prefetch <link rel="prefetch">
已经被许多浏览器支持了至关长的时间,但它是意图预获取一些资源,以备下一个导航/页面使用(好比,当你去到下一个页面时)。 浏览器会给使用prefetch
的资源一个相对较低的优先级与使用preload
的资源相比。
subresource <link rel="subresource">
被Chrome支持了有一段时间,而且已经有些搔到预加载当前导航/页面(所含有的资源)的痒处了。这些资源会以一个至关低的优先级被加载。 Memory Cache
不会轻易的命中一个请求,除了要有匹配的URL,还要有相同的资源类型、CORS模式以及一些其余特性。 Memory Cache
是不关心HTTP语义
的,好比Cache-Control: max-age=0
的资源,仍然能够在同一个导航中被重用。可是在特定的状况下,Memory Cache
会遵照Cache-Control: no-store
指令,不缓存相应的资源。
Memory Cache
匹配规则在标准中没有详尽的描述,因此不一样的浏览器内核在实现上会有所不一样。
HTTP Cache
也被叫作Disk Cache
。从字面的意思上理解Disk Cache
就是储存在硬盘上的缓存,所以它是持久存储的,是实际存在于文件系统中的。 并且它容许相同的资源在跨会话,甚至跨站点的状况下使用,例如两个站点都使用了同一张图片。
HTTP Cache
会根据HTTP Herder
中的字段判断哪些资源须要缓存,哪些资源能够不请求直接使用,哪些资源已通过期须要从新请求。 当命中缓存以后,浏览器会从硬盘中读取资源,虽然比起从内存中读取慢了一些,但比起网络请求仍是快了很多的。绝大部分的缓存都来自 disk cache
。
凡是持久性存储都会面临容量增加的问题,
disk cache
也不例外。在浏览器自动清理时,会有神秘的算法去把“最老的”或者“最可能过期的”资源删除,所以是一个一个删除的。不过每一个浏览器识别“最老的”和“最可能过期的”资源的算法不尽相同,可能也是它们差别性的体现。
Push Cache
(推送缓存)是 HTTP/2
中的内容,当以上三种缓存都没有命中时,它才会被使用。 它只在会话(Session)中存在,一旦会话结束就被释放,而且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并不是严格执行HTTP头中的缓存指令。
Push Cache
在国内可以查到的资料不多,也是由于 HTTP/2
在国内不够普及。这里推荐阅读Jake Archibald
的 HTTP/2 push is tougher than I thought 这篇文章,文章中的几个结论:
若是以上四种缓存都没有命中的话,那么只能发起请求来获取资源了。
那么为了性能上的考虑,大部分的接口都应该选择好缓存策略,一般浏览器缓存策略分为两种:强缓存和协商缓存,而且缓存策略都是经过设置 HTTP Header
来实现的。
这两种缓存类型存在于 Chrome 中。 disk cache
存在硬盘,能够存不少,容量上限比内容缓存高不少,而 memory cache
从内存直接读取,速度上占优点,这两个各有各的好处!
由于关于在何时用到什么缓存的文档至关的少因此真的很差判断,是当前使用的是哪一个缓存,好比下面这个例子:
其实缓存之间也没有太好的对比性,大体能够从缓存策略和缓存位置两个角度对比缓存的优缺点。
Service Worker
相对于Disk Cache/Memory Cache
配置会麻烦一点,可是Service Worker
应用场景更广,性能也会好一点。Service Worker
必需要在Https
协议中才会生效。它们的值优缺点如上所示,如在chrome、firefox、ie中Memory cache和Disk cache也是不太相同的。
Chrome浏览器的速度比其余两个浏览器的速度更快一点,主要是由于V8引擎的执行速度更快,另外一方面应该就是它的缓存策略的使用。 从这四个方面强缓存、协商缓存、Disk Cache、Memory Cache来对比,为何说Chrome执行效果比其它的两个浏览器的执行速度和加载速度更快。
就以百度首页为例看一下Chrome和Firefox的差异。
在Chrome
和Firefox
中打开https://www.baidu.com/
首页,结果以下图所示 Firefox效果以下:
咱们以百度的bd_logo1.png
的请求为例,logo
的请求是一个Get
请求,同时它被设置了四个缓存配置,可是它在两个浏览器中表现并不相同,以下图所示
Firefox效果以下:
Chrome效果以下:
首先在再次请求时浏览器端都没有携带协商缓存须要的头部字段,因此它们确定走的是强缓存,在强缓存中Cache-Control
的优先级是最高的,因此都是走的Cache-Control的策略。
能够看到它们的区别以下几点:
200
,Firefox返回的状态码是304
。0(耗时 0ms,也就是 1ms 之内)
,那么它使用的本地的资源。而Firefox中它是从服务器获取的资源。在这里咱们来一个一个测试expires/cache-control/etag/last-modified/pragma
它们是否和咱们上面所总结的一致。
测试环境chrome 78.0.3904.70
、node 12.9.1
、koa 2.x
.
总体的目录结构以下图所示:
代码可能写的比较粗糙,可是后面会优化一下,公共代码以下:
index.html代码以下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="/index/index.css">
<script src="/index/index.js"></script>
</head>
<body>
测试cache
<img src="/index/rotateX.png" alt="">
</body>
</html>
复制代码
app.js代码以下
const Koa = require('koa');
const Router = require('koa-router');
const Static = require('koa-static');
const fs = require('fs-extra');
const Path = require('path');
const mime = require('mime');
const app = new Koa();
const router = new Router();
router.get('/', async (ctx, next) => {
ctx.type = mime.getType('.html');
// console.log(__dirname)
const content = await fs.readFile(Path.resolve(__dirname + '/index/index.html'), 'UTF-8');
// console.log(content);
ctx.body = content;
await next();
})
// 待优化
router.get('/index/rotateX.png', async (ctx, next) => {
const { response, path } = ctx;
ctx.type = mime.getType(path);
const imageBuffer = await fs.readFile(Path.resolve(__dirname, `.${path}`));
ctx.body = imageBuffer;
await next();
})
// 待优化
router.get('/index/index.css', async (ctx, next) => {
const { path } = ctx;
ctx.type = mime.getType(path);
const content = await fs.readFile(Path.resolve(__dirname, `.${path}`), 'UTF-8');
ctx.body = content;
await next();
});
// 待优化
router.get('/index/index.js', async (ctx, next) => {
const { path } = ctx;
ctx.type = mime.getType(path);
const content = await fs.readFile(Path.resolve(__dirname, `.${path}`), 'UTF-8');
ctx.body = content;
await next();
});
// app.use(Static('./index'))
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, function (err) {
// console.log()
if (err) {
console.log(err)
} else {
console.log('启动成功')
}
})
复制代码
下面的代码都是在这个代码上修改,index.js
、index.css
、rotateX.png
本身写就能够,或者去网上下载一个稍微超过2kb
大小的文件。
使用Cache-Control
缓存测试效果,修改代码以下:
修改app.js
// ...省略代码
router.get('/index/rotateX.png', async (ctx, next) => {
// ...省略代码
// 添加代码
ctx.set('Cache-Control', 'max-age=' + 10);
})
// ...省略代码
router.get('/index/index.css', async (ctx, next) => {
// ...省略代码
// 添加代码
ctx.set('Cache-Control', 'max-age=' + 10);
})
// ...省略代码
router.get('/index/index.js', async (ctx, next) => {
// ...省略代码
// 添加代码
ctx.set('Cache-Control', 'max-age=' + 10);
})
复制代码
咱们在经过nodemon app.js
运行代码,运行效果大体以下:
localhost:3000
时,由于没有任何缓存因此资源是从服务器中请求
来的,以下图所示
Cache-Control: max-age=10
,因此会走本地缓存
,以下图所示
memory cache
。由于咱们没有关闭 TAB,因此浏览器把缓存的应用加到了memory cache
。(耗时 0ms,也就是 1ms 之内)https://www.baidu.com
,再返回页面时,它也会走本地缓存
,以下图所示
由于跳转页面等因而关闭了 TAB,memory cache
也随之清空。可是 disk cache
是持久的,因而全部资源来自 disk cache
。(大约耗时 3ms,由于文件有点小)并且对比 2 和 3,很明显看到 memory cache
仍是比 disk cache
快得多的。
咱们来对比一下no-cache
和no-store
的区别,修改代码以下:
修改index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="/index/index.css">
<link rel="stylesheet" href="/index/index.css">
<script src="/index/index.js"></script>
<script src="/index/index.js"></script>
</head>
<body>
测试cache
<img src="/index/rotateX.png" alt="">
<img src="/index/rotateX.png" alt="">
<!-- 异步请求图片 -->
<script> setTimeout(function () { let img = document.createElement('img') img.src = '/index/rotateX.png' document.body.appendChild(img) }, 1000) </script>
</body>
</html>
复制代码
咱们暂时不修改缓存的配置,经过nodemon app.js
运行代码,运行效果大体以下:
HTML
中的资源存入到缓存 (memory cache
),这样碰到相同 src
的图片就会自动读取缓存(但不会在 Network
中显示出来)Network
中显示。下面咱们修改app.js中的代码以下:
// ...省略代码
router.get('/index/rotateX.png', async (ctx, next) => {
// ...省略代码
// 替换原来代码
ctx.set('Cache-Control', 'no-cache');
})
// ...省略代码
router.get('/index/index.css', async (ctx, next) => {
// ...省略代码
// 替换原来代码
ctx.set('Cache-Control', 'no-cache');
})
// ...省略代码
router.get('/index/index.js', async (ctx, next) => {
// ...省略代码
// 替换原来代码
ctx.set('Cache-Control', 'no-cache');
})
复制代码
咱们运行代码看的效果以下图所示:
HTML
中的资源存入到缓存 (memory cache
),这样碰到相同 src
的图片就会自动读取缓存(但不会在 Network
中显示出来)若是把no-cache
修改成no-store
修改app.js
// ...省略代码
router.get('/index/rotateX.png', async (ctx, next) => {
// ...省略代码
// 替换原来代码
ctx.set('Cache-Control', 'no-store');
})
// ...省略代码
router.get('/index/index.css', async (ctx, next) => {
// ...省略代码
// 替换原来代码
ctx.set('Cache-Control', 'no-store');
})
// ...省略代码
router.get('/index/index.js', async (ctx, next) => {
// ...省略代码
// 替换原来代码
ctx.set('Cache-Control', 'no-store');
})
复制代码
咱们运行代码看的效果以下图所示:
当咱们设置了Cache-Control: no-store
时,能够看到css
、js
文件都被请求了两次,png
请求了三次。
memory cache
是无视 HTTP
头信息的,可是 no-store
是特别的。在这个设置下,memory cache
也不得不每次都请求资源。no-store
状况下,依然是每次都发送请求,不进行任何缓存。这里来设置协商缓存Last-Modified/If-Modified-Since
,代码修改以下:
修改app.js
const responseFile = async (path, context, encoding) => {
const fileContent = await fs.readFile(path, encoding);
context.type = mime.getType(path);
context.body = fileContent;
};
router.get('/index/rotateX.png', async (ctx, next) => {
ctx.set('Pragma', 'no-cache');
const { response, request, path } = ctx;
const imagePath = Path.resolve(__dirname, `.${path}`);
const ifModifiedSince = request.headers['if-modified-since'];
console.log(ifModifiedSince)
const imageStatus = await fs.stat(imagePath);
const lastModified = imageStatus.mtime.toGMTString();
if (ifModifiedSince === lastModified) {
response.status = 304;
} else {
response.lastModified = lastModified;
await responseFile(imagePath, ctx);
}
await next();
})
复制代码
大体流程以下:
在Chrome
中选中Disable Cache
禁用缓存,能够经过下面图片看到服务器端发送给客户端Last-Modified: Thu, 24 Oct 2019 05:12:37 GMT
。
关闭 disable cache
后再次访问图片时,发现带上了 if-modified-since
请求头,值就是上次请求响应的 last-modified
值,由于图片最后修改时间不变,因此 304 Not Modified
。效果以下图所示
启用
Disable Cache
时,咱们能够看到客户端/浏览器端自动带上了Pragma':'no-cache'
、'Cache-Control': 'no-cache'
这两个字段,不适用缓存。
修改app.js,经过npm i crypto -D
安装crypto
,用于生成md5
。
// 处理 css 文件
router.get('/index/index.css', async (ctx, next) => {
const { request, response, path } = ctx;
ctx.type = mime.getType(path);
response.set('pragma', 'no-cache');
const ifNoneMatch = request.headers['if-none-match'];
const imagePath = Path.resolve(__dirname, `.${path}`);
const hash = crypto.createHash('md5');
const imageBuffer = await fs.readFile(imagePath);
hash.update(imageBuffer);
const etag = `"${hash.digest('hex')}"`;
if (ifNoneMatch === etag) {
response.status = 304;
} else {
response.set('etag', etag);
ctx.body = imageBuffer;
}
await next();
});
复制代码
运行效果以下图所示:
他的过程和Last-Modified/If-Modified-Since
,可是由于Last-Modified/If-Modified-Since
它不能监听1s
之内的资源变化,因此通常用他来作Etag/If-None-Match
的补充方案。
缓存大体分为:强缓存
、协商缓存
。
强缓存
: pragma
、cache-control
、expires
协商缓存
: last-modified/If-modified-since
、etag/if-none-match
强缓存优先级
: cache-control > pragma > expires
协商缓存优先级
: etag/if-none-match > last-modified/If-modified-since
缓存位置分为: Service Worker
、Memory Cache
、Disk Cache
、Push Cache
,也是从左到右若是命中就使用。
上面的实例只是比较简单的应用,其实还有不少有意思的实例能加深对缓存的理解,以下:
pragma
、cache-control
、expires
优先级last-modified/If-modified-since
、etag/if-none-match
优先级cache-control: no-cache
与cache-control: max-age=0, must-revalidate
效果是否相同chrome
、firefox
、ie
之间的缓存差异本篇文章有意避开Service Worker
的详细介绍,由于会有单独的一篇文章来介绍Service Worker
在真实应用的使用。
在线代码,能够刷新页面(刷新内部页面)在控制台中查看当前效果。
感受写的不错请点一下赞,据说长的帅的人都会点赞,而且点赞后走上人生巅峰。