HTTP缓存
web缓存是什么?
动机
当浏览器加载一个页面时,html引用的外部资源也会加载。但这些外部资源比如图片、css、js都不经常变化。如果每次都加载这些资源势必会带来资源的浪费,而且加载时间过长也会影响用户体验。
HTTP缓存技术就是为了解决这个问题出现的。简单的讲http缓存就是将静态资源存储在浏览器内部,下次请求相同资源时可以直接使用。当然是否使用缓存,要有一系列的策略保证,如果资源一旦更新,缓存也要随之而更新。
作用
1、提高首屏加载速度,进而优化用户体验
2、减少流量消耗
3、减轻服务器压力
常用两种缓存策略
第一:强缓存策略
直接从本地副本对比读取,不去请求服务器,返回的状态码是200
这里面就有一个问题,如果不去服务器请求,如果静态资源更新了而浏览器还在使用新的静态资源怎么办呢?
答案是:使用定时器的方式也就是强缓存可以设置静态资源的有效期,如果超过有效期就认为缓存作废。
HTTP 1.0
expires
expires是http 1.0中定义的缓存字段。当我们请求一个资源的时候,服务器返回时可以在response Headers中增加expires字段标识资源的过期时间。
expires: Thu, 03 Jan 2022 11:43:04 GMT
它是一个时间戳(准确点应该叫格林尼治时间),当客户端再次请求该资源的时候,会把客户端时间与该时间戳进行对比,如果大于该时间戳则已过期,否则直接使用该缓存资源。
但是,有个大问题,发送请求时使用的是客户端时间去对比。一是客户端和服务端时间可能快慢不一致,另一方面是客户端的时间是可以自行修改的(比如浏览器是跟随系统时间的,修改系统时间会影响到),所以不一定满足预期。
HTTP 1.1
cache-control
正由于http 1.0可能存在的问题,http 1.1新增了 cache-control 字段来解决该问题,所以当cache-control和expires都存在时,cache-control优先级更高。该字段是一个时间长度,单位是秒,表示该资源过了多少秒后失效。当客户端请求资源的时候,发现该资源还在有效时间内则使用该缓存,它不依赖客户端时间。cache-control主要有max-age和s-maxage、public和private、no-cache和no-store等值
public: 所有内容都将被缓存(客户端和代理服务器都可以缓存)
private: 内容只缓存到私有缓存中(客户端可以缓存)
no-cache: 需要使用协商缓存来验证缓存数据
no-store: 所有内容都不会缓存
must-revalidation/proxy-revalidation: 如果缓存的内容失效,请求必须发送到服务器/代理服务器以进行重新验证
max-age=xxx(xxx为妙): 缓存的内容将在xxx秒后失效,这个选项只在HTTP1.1可用,并且如果和last-modified一起使用时,优先级较高
实例验证
随时间变化的内容,我们用一个函数来完成 其中设置定时器每隔一段时间更新一下时间
function updateTime(){
this.timer = this.timer || setInterval(()=>{
this.time = new Date().toUTCString()
},1000)
return this.time
}
Web服务
访问跟目录会返回一个HTML页里面会输出时间。其中加载一个js作为静态资源,而js的功能是打印一个随时间变化字符串,这样的话如果js使用的是缓存,js的时间是旧的时间,HTML中的时间和JS产生的时间一定会不一致
const http = require('http')
const app = http.createServer((req, res)=>{
const {url} = req
if('/' === url){
res.end(`
<html>
Html Update Time at ${updateTime()}
<script src='main.js'></script>
</html>
`)
}else if('/main.js' === url){
const content = `document.writeln('<br>JS update Time at : ${updateTime()}')`
// 客户端时间和服务端时间存在差异
res.setHeader('Expires', new Date((Date.now()+ 10*1000)).toUTCString())
res.setHeader('Cache-Control', 'max-age=20')
res.statusCode = 200
res.end(content)
}else if(url === 'favicon.ico'){
res.end('')
}
})
app.listen(3000,()=>{
console.log('server run at ' + 3000); //sy-log
})
HTTP 1.0可以在header中设置Expires,设置10秒后过期:
res.setHeader('Expires', new Date((Date.now()+ 10*1000)).toUTCString())
HTTP 1.1可以在header中设置Cache-Control,设置20秒后过期
res.setHeader('Cache-Control', 'max-age=20')
注意这个时候有两个规则我们也可以趁机测试一下优先顺序。
第一次请求,从服务器获取
image.png
再次请求,超过10秒,未超过20秒,从缓存获取
image.png
测试结果:Cache-Control优先级高于Expires
第二:协商缓存策略
上面的expires和cache-control都会访问本地缓存直接验证看是否过期,如果没过期直接使用本地缓存,并返回200。但如果设置了no-cache和no-store,则本地缓存会被忽略,会去请求服务器验证资源是否更新,如果没更新才继续使用本地缓存,此时返回的是304,这就是协商缓存。
协商缓存主要包括last-modified和etag。
协商缓存简单的说就是浏览器和服务器间就是否要使用缓存在做协商。如果协商的结果是需要更新,就会返回200,并返回更新内容。如果不需要更新,只需要返回状态码304,不用返回内容,这样虽然需要后端应答,但是后端既不需要生成内容,也不需要传输内容,依然可以享受缓存的种种好处。
last-modified & if-Modified-Since
这是一组通过协商修改时间为基础的策略。
image.png
代码main.js部分修改:
// last-modified
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('last-modified', new Date().toUTCString())
//设置20s后失效
if(new Date(req.headers['if-modified-since']).getTime() + 20*1000 > Date.now()){
console.log('协商缓存命中......'); //sy-log
res.statusCode = 304
res.end()
return
}
测试对比:
image.png失效期内,再次请求(我这里20秒失效)
image.png
etag & if-None-Match
另一种方法是通过内容判断,一般的做法是将返回的内容进行摘要(Hash),然后通过对比摘要来判断内容是否更新。
image.png
代码main.js部分修改:
// etag
res.setHeader('Cache-Control', 'no-cache')
const crypto = require('crypto')//用来计算hash
const hash = crypto.createHash('sha1').update(content).digest('hex')
res.setHeader('Etag', hash)
if(req.headers['if-none-match'] === hash){
console.log('Etag缓存命中......'); //sy-log
res.statusCode = 304
res.end()
return
}
res.statusCode = 200
res.end(content)
测试对比:
这次因为要对比内容,所以将setInterval间隔时间改了10秒
image.png
有效期内再次请求,返回304,页面内容没变
image.png