1.1 - 缓存
- 缓存可以重用已获取的资源能够有效的提升网站与应用的性能。
- Web 缓存能够减少延迟与网络阻塞,进而减少显示某个资源所用的时间。
- 借助 HTTP 缓存,Web 站点变得更具有响应性。
- 缓存分为两点:强制缓存和协商缓存
1.2 - 强制缓存
- 概念
- 强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。
- 简单来讲就是强制浏览器使用当前缓存
- 所请求的数据在缓存数据库中尚未过期时,不与服务器进行交互,直接使用缓存数据库中的数据。当缓存未过期时基本流程如下:
- 实现:通过服务器端设置响应头字段来控制强制缓存的过期时间
- expires (http1.0) 其指定了一个日期/时间, 在这个日期/时间之后,HTTP响应被认为是过时的。但是它本身是一个HTTP1.0标准下的字段,所以如果请求中还有一个置了 “max-age” 或者 “s-max-age” 指令的Cache-Control响应头,那么 Expires 头就会被忽略。
- cache-control (http1.1) 通用消息头用于在http 请求和响应中通过指定指令来实现缓存机制。
- cache-control 优先级比 expires 高
- expires
- 日期(new Date().toGMTString()) 缓存的最大有效时间
- cache-control
- max-age(单位s) 缓存的最大有效时间
- no-cache 使用协商缓存
- no-store 不使用任何缓存
- public (客户端、代理服务器)缓存所有资源
- private 默认值,只有客户端缓存所有资源
- 比如 jQuery 用强制缓存(长久不变,读取不了新东西),因为变化慢,还有一些图片,一直变,有些广告变动很快,就走协商缓存。
1.3-协商缓存
- 概念
- 协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。
- 当强缓存过期未命中或者响应报文Cache-Control中有must-revalidate标识必须每次请求验证资源的状态时,便使用协商缓存的方式去处理缓存文件。
协商缓存主要原理是从缓存数据库中取出缓存的标识,然后向浏览器发送请求验证请求的数据是否已经更新,如果已更新则返回新的数据,若未更新则使用缓存数据库中的缓存数据,具体流程如下,当协商缓存命中:
- 实现:
- Last-Modified / If-Modified-Since
- Etag / If-None-Match
- Etag / If-None-Match 优先级比 Last-Modified / If-Modified-Since 高。
- Last-modified
- 记录服务器返回资源的最后修改时间
- 由客户端发送给服务器
- If-Modified-Since
- 记录服务器返回资源的最后修改时间
- 由服务器发送给客户端
- Etag
- 当前文件的唯一标识(由服务器生成)
- 由客户端发送给服务器
- If-None-Match
- 当前文件的唯一标识(由服务器生成)
- 由服务器发送给客户端
- 工作流程:
- 第一次:由服务器返回 If-None-Match 和 If-Modified-Since 字段通过响应头方式返回
- 第二次:下次浏览器请求时,携带了Etag(值为上一次的If-None-Match的值)和 Last-modified(值为上一次的If-None-Since的值)发送给服务器
- 服务器先检查Etag是否等于最新的If-None-Match的值,如果相等直接走浏览器本地缓存,不相等就返回新的资源
- 如果没有Etag,才看Last-modified的值,检查Last-modified是否等于最新的If-None-Since的值,如果相等直接走浏览器本地缓存,不相等就返回新的资源
1.4 - 缓存返回值
- 200(from memory cache)
- 命中强制缓存
- 缓存来自于内存
- 200(from disk cache)
- 命中强制缓存
- 缓存来自于磁盘
- 304 Not Modified
- 命中协商缓存
- 200
- 没有命中缓存
1.5-浏览器缓存机制详解流程图
1.6-总结缓存策略
- 强制缓存(优先级更高)
- Expires cache-control
- 不会重新发送请求,直接走缓存
- 协商缓存
- last-modified和etag if-modified-since和if-None-Match
- 通常和cache-control配合使用,no-cached,会再次发送请求,由服务器判断请求资源是否走缓存,
- 如果走 返回 304 Not Modefined
- 如果不走缓存 返回新的资源
1.7-cache缓存案例
const express = require('express');
const { resolve } = require('path');
const { readFile, stat, watchFile } = require('fs');
const etag = require('etag');
const app = express();
/*
缓存:
强制缓存
http 1.1 cache-control
http 1.0 expires
特点:在缓存的期限内,不会发送请求,直接读取缓存
协商缓存
响应头(服务器发给浏览器)
etag 文件唯一标识
last-modified 文件最近一次修改时间
请求头
if-none-match 文件唯一标识
if-modified-since 文件最近一次修改时间
流程:
第一次浏览器访问服务器资源: 服务器设置响应头并返回资源给浏览器
etag xxx
last-modified xxx
浏览器接受到服务器返回的响应头,就会存储起来。
第二次浏览器访问服务器资源,自动携带上之前存的响应头,作为请求头发送过去(会改名字)
etag --> if-none-math
last-modified --> if-modified-since
服务器
判断服务器保存的etag和浏览器发送过来if-none-math的值是否相等
判断服务器保存的last-modified和浏览器发送过来if-modified-since的值是否相等
如果都相等,走协商缓存 304
如果有一个不相等,说明资源发生过变化,返回新资源
特点:一定会访问服务器(一定发送请求),由服务器决定是否走缓存
当强制缓存和协商缓存都存在:
先看强制缓存。命中了,就直接读取缓存
如果强制缓存失效(过期了),看协商缓存
*/
app.get('/', (req, res) => {
// 访问当前路由,返回index.html
// sendFile方法默认设置强制缓存、协商缓存
// res.sendFile(resolve(__dirname, 'public/index.html'));
const filePath = resolve(__dirname, 'public/index.html');
readFile(filePath, (err, data) => {
if (!err) {
// 返回响应
res.end(data);
} else {
console.log(err);
}
});
});
// 强制缓存
app.get('/js/index.js', (req, res) => {
const filePath = resolve(__dirname, 'public/js/index.js');
readFile(filePath, (err, data) => {
if (!err) {
// 设置强制缓存 设置响应头
res.set('cache-control', 'max-age=20');
// res.set('expires', new Date(Date.now() + 10000).toGMTString());
// 返回响应
res.end(data);
} else {
console.log(err);
}
});
});
let etagName = '';
let lastModified = 0;
const filePath = resolve(__dirname, 'public/css/index.css');
// 监视文件的变化
watchFile(filePath, (curr, prev) => {
// 说明文件发生了变化,修改etagName/lastModified
etagName = etag(curr);
lastModified = new Date().toGMTString();
});
// 读取文件,获取初始化etagName, lastModified
stat(filePath, (err, stats) => {
if (!err) {
etagName = etag(stats);
lastModified = new Date().toGMTString();
}
});
// 协商缓存
app.get('/css/index.css', (req, res) => {
// 就要和etag对比
const ifNoneMatch = req.headers['if-none-match'];
// 就要和lastModified对比
const ifModifiedSince = req.headers['if-modified-since'];
if (ifNoneMatch === etagName && ifModifiedSince === lastModified) {
// 都相等,走缓存
res.status(304).end();
return
}
// 返回新资源
readFile(filePath, (err, data) => {
if (!err) {
// 设置协商缓存
// 接受浏览器发送过的etag和last-modified。 与当前的值对比。 如果一样,走缓存,不一样就返回最新的资源
res.set('etag', etagName);
res.set('last-modified', lastModified);
// 返回响应
res.end(data);
} else {
console.log(err);
}
});
});
app.listen(3000, (err) => {
if (!err) console.log('服务器启动成功了~');
else console.log(err);
});
1.8举个栗子看缓存