一、浏览器第一次发起一个http/https请求,读取服务器的资源html
二、服务端设置响应头(cache-control、Expires、last-modified、Etag)给浏览器web
2.1. cache-control、Expires 属于强缓存,last-modified、Etag属于对比缓存(协商缓存)数据库
三、浏览器不关闭tab、f5刷新页面(再次发起一个请求给服务器)浏览器
3.一、若是cache-control的max-age 和 Expires 未超过缓存时间,全部资源除了index.html 都来自于内存缓存(from memory cache)加载。且状态码为200缓存
3.二、若是cache-control的max-age缓存时间为5s, Expires的过时时间是超过5s,则cache-control会覆盖Expiresbash
3.三、若是强缓存失效,则下一步会走对比缓存。浏览器会从第二步的拿到的响应头,在刷新发起请求会设置 a、if-modified-since值为响应的last-modified的值; b、if-none-match 值为响应的Etag的值;服务器
3.四、若是if-modified-since 和if-none-match都存在,则if-none-match的优先比if-modified-since高。直接对比第二步给浏览器的Etag的值,若是相等就直接返回一个状态为304不返回内容,若是不相等就返回一个状态码为200,而且会返回内容和cache-control 、Expires、last-modified、Etag等响应头;网络
3.五、若是if-modified-since 存在, if-none-match不存在,步骤跟上述的3.4相似,只不过服务端对比的是if-modified-since 和第一次返回给浏览器last-modified的值性能
四、若是浏览器关闭tab。从新打开新tab,发起请求资源。步骤跟上述3相似,只不过在上述3.1中,左右资源除了index.html缓存(from disk cache)都从磁盘加载。优化
当客户端请求后,会先访问缓存数据库看缓存是否存在。若是存在则直接返回,不存在则请求真的服务器。
强制缓存直接减小请求数,是提高最大的缓存策略。 它的优化覆盖了文章开头提到过的请求数据的所有三个步骤。若是考虑使用缓存来优化网页性能的话,强制缓存应该是首先被考虑的。
能够形成强制缓存的字段是 Cache-control 和 Expires
Expires
这是 HTTP 1.0 的字段,表示缓存到期时间,是一个绝对的时间 (当前时间+缓存时间)。在响应消息头中,设置这个字段以后,就能够告诉浏览器,在未过时以前不须要再次请求。
Expires: Thu, 22 Mar 2029 16:06:42 GMT
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs')
http.createServer((req, res) => {
let { pathname } = url.parse(req.url, true);
console.log(pathname)
let abs = path.join(__dirname, pathname);
res.setHeader('Expires', new Date(Date.now() + 20000).toGMTString());
fs.stat(path.join(__dirname, pathname), (err, stat) => {
if(err) {
res.statusCode = 404;
res.end('not found')
return
}
if(stat.isFile()) {
fs.createReadStream(abs).pipe(res)
}
})
}).listen(3000)
复制代码
以上代码给Expires设置过时时间为20s。
首次请求 首次请求 所有走网络请求
20s内F5刷新当前,从内存里面加载。由于咱们没有关闭TAB,因此浏览器把缓存的应用加到了内存缓存。(耗时0ms,也就是1ms之内)
20s内关闭tab,打开请求的url,从磁盘加载
20s之后请求,缓存已经失效,重复第1步
过时的缺点:
在这里,其余电脑访问服务器,若修改电脑的本地时间,会致使浏览器判断缓存失效 这里修从新修改缓存时间: res.setHeader('Expires',new Date(Date.now()+ 2000000).toGMTString())
Cache-control
已知Expires的缺点以后,在HTTP/1.1中,增长了一个字段Cache-control,该字段表示资源缓存的最大有效时间,在该时间内,客户端不须要向服务器发送请求
Expires 和 Cache-control 区别
Expires设置的是 绝对时间
Cache-control设置的是 相对时间
缓存控制的优先级大于到期
复制代码
Cache-control: max-age=20
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs')
http.createServer((req, res) => {
let { pathname } = url.parse(req.url, true);
console.log(pathname)
let abs = path.join(__dirname, pathname);
res.setHeader('Cache-Control', 'max-age=20')
fs.stat(path.join(__dirname, pathname), (err, stat) => {
if(err) {
res.statusCode = 404;
res.end('not found')
return
}
if(stat.isFile()) {
fs.createReadStream(abs).pipe(res)
}
})
}).listen(3000)
复制代码
以上代码给cache-control设置max-age为20s
解析:首次请求->关闭tab再次请求参考Expires的图
no-store 和 no-cache的区别
no-store: 若是服务器再响应中设置了no-store。那么浏览器不会存储此次相应的数据,当下次请求时,浏览器会在请求一次,就是说不会对比Etag res.setHeader('Cache-control', 'no-store')
no-cache 若是服务器在响应中设置了no-cache,那么说明浏览器在使用缓存前会对比Etag,返回304就会避免修改
public 和 private
当强制缓存失效(超过规定时间)时,就须要使用对比缓存,由服务器决定缓存内容是否失效。对比缓存是能够和强制缓存一块儿使用。
last-modified
一、服务器在响应头中设置last-modified字段返回给客户端,告诉客户端资源最后一次修改的时间。
Last-Modified: Sat, 30 Mar 2019 05:46:11 GMT
二、浏览器在这个值和内容记录在浏览器的缓存数据库中。
三、下次请求相同资源,浏览器将在请求头中设置if-modified-since的值(这个值就是第一步响应头中的Last-Modified的值)传给服务器
四、服务器收到请求头的if-modified-since的值与last-modified的值比较,若是相等,表示未进行修改,则返回状态码为304;若是不相等,则修改了,返回状态码为200,并返回数据
http.createServer((req, res) => {
let { pathname } = url.parse(req.url, true);
console.log(pathname);
let abs = path.join(__dirname, pathname);
fs.stat(path.join(__dirname, pathname), (err, stat) => {
if(err) {
res.statusCode = 404;
res.end('Not Fount');
return
}
if(stat.isFile()) {
res.setHeader('Last-Modified', stat.ctime.toGMTString())
console.log(stat.ctime.toGMTString())
if(req.headers['if-modified-since'] === stat.ctime.toGMTString()) {
console.log('if-modifined-since', req.headers['if-modified-since'])
res.statusCode = 304;
res.end()
return
}
fs.createReadStream(abs).pipe(res)
}
})
}).listen(3000)
复制代码
last-modified的缺点:
Etag
为了解决上述问题,出现了一组新的字段 Etag 和 If-None-Match
Etag是根绝文件内容,算出一个惟一的值。服务器存储着文件的 Etag 字段。以后的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新时间改变成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 变成了 If-None-Match。服务器一样进行比较,命中返回 304, 不命中返回新资源和 200。 Etag 的优先级高于 Last-Modified
http.createServer(function(req, res) {
let { pathname } = url.parse(req.url, true);
console.log(pathname)
let abs = path.join(__dirname, pathname);
fs.stat(path.join(__dirname, pathname), (err, stat) => {
if(err) {
res.statusCode = 404;
res.end('Not Found')
return
}
if(stat.isFile()) {
//Etag 实体内容,他是根绝文件内容,算出一个惟一的值。
let md5 = crypto.createHash('md5')
let rs = fs.createReadStream(abs)
let arr = []; // 你要先写入响应头再写入响应体
rs.on('data', function(chunk) {
md5.update(chunk);
arr.push(chunk)
})
rs.on('end', function() {
let etag = md5.digest('base64');
if(req.headers['if-none-match'] === etag) {
console.log(req.headers['if-none-match'])
res.statusCode = 304;
res.end()
return
}
res.setHeader('Etag', etag)
// If-None-Match 和 Etag 是一对, If-None-Match是浏览器的, Etag是服务端的
res.end(Buffer.concat(arr))
})
}
})
}).listen(3000)
复制代码
Etag的缺点: