携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情
为什么需要http缓存
http缓存机制是web性能优化的重要手段,它可以通过复用获取过的资源,降低服务器压力,减少等待时间,节约网路流量。同时也提升了用户体验。
强制缓存
页面首次加载资源,一定会向服务器发送请求,服务器收到请求后,返回数据和缓存规则(缓存规则信息包含在响应header中,一般是服务端配置的),浏览器收到数据后根据缓存规则,将数据缓存在浏览器的缓存中。
当该资源再次需要请求时,浏览器会根据自己缓存记录,知道该url是否缓存过,如果缓存过,就直判断缓存是否失效,如果没有失效,就代表了命中缓存,此次请求无需向服务器发送请求,直接读取缓存中的数据即可,
缓存未命中情况
首先,当前一定不是第一次请求,浏览器缓存过当前的资源,经过缓存规则的校验,数据已经失效,或者被人为删除,这种情况就是缓存未命中,此时浏览器就会向服务器发送请求,服务器得到请求后,像第一处理该请求一样,返回数据和缓存规则,浏览器收到请求后,根据缓存规则记录,并且缓存数据,再有这个请求,就会走上面的缓存命中了,
强制缓存,规则一般有两个字段。Expires(过期时间),Cache-Control(缓存控制)
Expires
expires的值(格林威治时间)为服务器返回数据的过期时间,就是下次请求时,请求时间小于服务器返回的到期时间,直接使用缓存数据,否则视为缓存失效,重新向服务器发送请求。
expires 是 http1.0实现的,测试时,发现chrome 好像忽略了这个配置, 现在浏览器默认使用的是http1.1,因为过期时间是服务器生成的,由于服务器和客户端时间可能存在误差,这样就会导致缓存命中不按套路命中
Cache-Control
是重要的缓存规则,常见的取值有privat\pulic\no-cache\max-age\no-store,默认为private
private:客户端可以缓存
public:客户端和代理服务器都可以缓存
max-age=xxx 换窜的内容在xxx秒后失效
no-cache:不使用强制缓存,需要使用协商缓存来验证缓存数据
no-store: 所有内容都不会缓存
max-age 值为一个数字,单位为秒 当然要自己准备一个图,什么格式都行,路径对应就可以./public/1.gif demo node 启动后访问http://localhost:8080/
const http = require('http');
const fs = require('fs');
const path = require('path');
const readFile = (...reset) => {
let url = path.join(__dirname, './public', ...reset);
return new Promise((resolve, reject) => {
fs.readFile(url, (err, data) => {
if (err) {
reject(err)
}
resolve(data)
})
})
}
const indexHtml = `
<body>
<div>我是tests</div>
<img src="./1.gif">
</body>
`
const server = http.createServer(async (req, res) => {
const {
url
} = req;
let data
switch (url) {
case "/":
res.writeHead(200, {
'content-Type': "text/html;charset:=utf-8"
})
// 正常是读一个文件,但是为了demo简单,就把html接口直接用字符串替代了
// data = await readFile('./index.html');
data = indexHtml
break;
case "/1.gif":
res.writeHead(200, {
// 初次请求后,3秒内再次访问该资源,无需访问服务器,读取缓存即可
"Cache-Control":'max-age=3'
})
data = await readFile(url);
break;
}
res.end(data);
})
let port = 8080;
server.listen(port, () => {
console.log(`服务启动在http://localhost:${port}`)
})
命中缓存效果
强制缓存一般是给一些长时间不会改变的资源,就是能保证服务器端和混存里的东西等价,但是对于更新比较频繁的资源要慎用,例如天气预报信息,今天获取的信息是昨天的天气,用户肯定会骂人的
协商缓存
协商缓存不管缓存是否会命中,都会向服务器发送请求。 浏览器第一次请求数据,服务器端会将数据和缓存标识一起返回给客户端,客户端端将二者保存到浏览器缓存数据库中,当再次发起这个请求时,客户端将备份标识发送给服务器,服务器根据缓存标识进行判断,判断如果缓存命中,就返回304,告诉客户端,数据没失效,可以复用,浏览器器屁颠屁颠的直接将缓存数据做为返回数据了, 如果缓存未命中,服务器返回新的数据和新的缓存标识,浏览器更新缓存中的数据和标识,留着下次请求使用 协商缓存 http1.0中header中标识有last-Modified / if-Modified-Since last-Modified 服务器在响应请求时,告诉浏览器资源最后修改时间 if-Modifined-Since 再次发送请求时,通过此字端作为当前缓存数据的标识发送到服务器,服务器根据这个字段,和被请求的资源的最新的最后修改时间进行比对,如果资源最后修改时间和请求带来的if-Modified-Since不同,说名资源改动过,返回最新数据,状态码为200,如果最后修改时间相同,则命中缓存,响应304状态码,无需响应数据,浏览器自然懂的,继续使用缓存中的数据。 Etag /if-None-Match (http1.1 优先级高于last-Modified/if-Modified-Since) Etag 服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定,一般是content hash ,但是数据量大时,可以采用优化方法获取hash) if-None-Match 再次请求资源时,带着这个字端问服务器,用缓存还是给新的数据,服务器根据这个值,得到结果。命中缓存后,返回304状态码,缓存未命中时,返回新的数据和新的标识,浏览器和服务的默契不谋而合,狼狈为奸 配置方法 协商缓存和强缓存配合使用给个短时间的强缓存,之后使用协商缓存。 强缓存设置, "Cache-Control": 'max-age=0,no-cache' "last-Modified": lastModified
如下 增加./public/index.css
body{
background-color:red;
}
...
const getFileUpdatedDate = (path) => {
const stats = fs.statSync(path)
return new Date(stats.mtime).toUTCString()
}
...
case "/1.gif":
res.writeHead(200, {
// 初次请求后,3秒内再次访问该资源,无需访问服务器,读取缓存即可
"Cache-Control": 'max-age=3'
})
data = await readFile(url);
break;
case "/index.css":
const lastModified = getFileUpdatedDate("./public/index.css");
const ifModifiedSince = req.headers['if-modified-since'];
console.log(lastModified, ifModifiedSince)
if (ifModifiedSince && ifModifiedSince == lastModified) {
res.writeHead(304);
} else {
// 协商缓存和强缓存配合使用
// 给个短时间的强缓存,
res.writeHead(200, {
'content-Type': "text/css;charset=utf-8",
"Cache-Control": 'max-age=0,no-cache',
"last-Modified": lastModified
})
data = await readFile(url);
}
break;
...
无论怎么刷新,css都是304,但是改变一下css并且保存,就会返回最新的css了