前端HTTP缓存

前端缓存分为两部份:浏览器缓存、HTTP缓存,其中HTTP缓存是核心

HTTP缓存又分为强制缓存协商缓存

强制缓存

强制缓存的工作原理是通过HTTP响应头中的特定字段来控制的。这些字段通常包括ExpiresCache-Control,它们指示了资源的缓存有效时间。

当浏览器在有效时间内再次请求同一资源时,它会直接从本地缓存中获取该资源,而不会向服务器发送请求。

强缓存的实现方式一【Expires】

js 复制代码
Expires:new Date("2022-7-30 23:59:59");

Expires字段的作用是,设定一个强缓存时间。在此时间范围内,从内存(或磁盘)中读取缓存返回。

缺陷

Expires判断强缓存是否过期的机制是:客户端获取本地时间戳,与缓存的资源中的Expires字段的时间做比较。来判断是否需要对服务器发起请求。

这里有一个巨大的漏洞:如果本地时间不准确,或者说本地事件与服务器时间误差过大,则会出现该资源将无法被缓存或者资源被永远缓存的情况。

所以,Expires字段几乎不被使用了。现在的项目中,我们并不推荐使用Expires,强缓存功能通常使用cache-control字段来代替Expires字段。

强缓存的实现方式二【Cache-control】

js 复制代码
//后端往响应头中写入需要缓存的时间
res.writeHead(200,{
    'Cache-Control':'max-age=10'
});

max-age 后面的值是一个滑动时间,从服务器第一次返回该资源时开始倒计时。该方式解决解决了Expires所存在的巨大漏洞,不用再比对客户端和服务端的时间去判断缓存是否有效了。

Cache-controlmax-ages-maxageno-cacheno-storeprivatepublic这六个属性。

  • max-age:决定客户端资源被缓存多久。
  • s-maxage:决定代理服务器缓存的时长。
  • no-cache:表示是强制进行协商缓存。
  • no-store:是表示禁止任何缓存策略。
  • public:表示资源即可以被浏览器缓存也可以被代理服务器缓存。
  • private:表示资源只能被浏览器缓存。

no-cache 和 no-store

如果某一资源的Cache-control中设置了no-cache ,那么该资源会直接跳过强缓存的校验,直接去服务器进行协商缓存。而no-store就是禁止所有的缓存策略了。

注意,no-cache和no-store是一组互斥属性,这两个属性不能同时出现在Cache-Control中。

public 和 private

某些情况下,客户端和浏览器的通信中间会出现代理服务器,publicprivate 决定了资源是否可以在代理服务器进行缓存。

  • public 表示资源在客户端和代理服务器都可以被缓存。
  • private 则表示资源只能在客户端被缓存,拒绝资源在代理服务器缓存。
  • 如果这两个属性值都没有被设置,则默认为private

注意,publicprivate 也是一组互斥属性。他们两个不能同时出现在响应头的cache-control字段中。

max-age 和 s-maxage

  • max-age 表示的时间资源在客户端缓存的时长。
  • s-maxage 表示的是资源在代理服务器可以缓存的时长。

注意,max-ages-maxage并不互斥。他们可以一起使用。

Cache-control设置多个值的示例

js 复制代码
Cache-control:max-age=10000,s-maxage=200000,public

协商缓存

协商缓存是浏览器与服务器之间进行通信以确认缓存资源是否仍然有效的过程。

协商缓存主要涉及两组HTTP头字段:Last-ModifiedIf-Modified-Since,以及ETagIf-None-Match

协商缓存的实现方式一【last-modified】

基于last-modified的协商缓存实现方式分为以下三个步骤(后端设置):

  1. 首先需要在服务器端读出文件修改时间;
  2. 将读出来的修改时间赋给响应头的last-modified字段;
  3. 最后设置Cache-control:no-cache
js 复制代码
const http = require('http');
const fs = require('fs');

http.createServer((req,res)=>{
    if(req.url === '/bg.png'){
        // 协商缓存
        const data = fs.readFileSync('./bg.png'); //读取资源
        const {mtime} = fs.statSync('./bg.png'); //读取修改时间
        res.setHeader('last-modified',mtime,toUTCString()); //设置文件最后修改时间
        res.setHeader('Catch-Control','no-cache'); //Cache-control:no-cache的意思是跳过强缓存校验,直接进行协商缓存
        res.send(data)
    }else{
        res.statusCode = 404
    }
    
}).listen(8080,()=>{
     console.log("8080启动成功")
})

当客户端在响应头中读取到last-modified的时候,会在下次的请求头中携带一个字段:If-Modified-Since,它的值就是代码中后端设置的last-modified的值。

之后每次对该资源的请求,都会带上If-Modified-Since这个字段,服务端就需要拿到这个时间并再次读取该资源的修改时间,让两个时间做比对来决定是读取缓存还是返回新的资源。

协商缓存过程结束,过程可通过如下流程图梳理:

缺陷

使用以上方式的协商缓存已经存在两个非常明显的漏洞。这两个漏洞都是基于文件是通过比较修改时间来判断是否更改而产生的。

1、在文件内容本身不修改的情况下,依然有可能更新文件修改时间(比如修改文件名再改回来),这样,就有可能文件内容明明没有修改,但是缓存依然失效了。

2、如果文件在几百毫秒内完成修改的话,文件修改时间不会改变。(因为文件修改时间记录的最小单位是秒)

为了解决上述的这两个问题。从http1.1开始新增了一个头信息,ETag(Entity 实体标签)

协商缓存的实现方式二【ETag】

ETag就是将原先协商缓存的比较时间戳 的形式修改成了比较文件指纹

文件指纹:根据文件内容计算出的唯一哈希值。文件内容一旦改变则指纹改变。

实现流程:

1、第一次请求某资源的时候,服务端读取文件,计算出文件指纹,将文件指纹放在响应头的Etag字段中跟资源一起返回给客户端。

2、第二次请求某资源的时候,客户端从缓存中读取出上一次服务端返回的ETag赋给请求头的if-None-Match字段,让上一次的文件指纹跟随请求一起回到服务端。

3、服务端拿到请求头中的is-None-Match字段值后,再次读取目标资源并生成文件指纹,两个指纹做对比。如果指纹完全吻合,说明文件没有被改变,直接返回304状态码和一个空的响应体。如果指纹不吻合,则说明文件被更改,那么将新的文件指纹重新存储到响应头的ETag中并返回给客户端。

js 复制代码
const http = require('http');
const fs = require('fs');
const etag = require('etag');

http.createServer((req,res)=>{
    if(req.url === '/bg.png'){
        // 协商缓存 基于Etag
        const data = fs.readFileSync('./bg.png'); //读取资源
        const etagContent = etag(data); //根据文件生成唯一标识符
        const {mtime} = fs.statSync('./bg.png'); //读取修改时间
        const ifNoneMatch = req.headers['if-none-match']; //获取请求头中的上一次的文件指纹
        // 比较文件指纹是否一致
        if(ifNoneMatch === etagContent){
            //如果文件指纹一致,则返回304状态码并返回一个空的响应体,并结束代码
            res.stausCode = 304;
            res.send();
            return;
        }
        res.setHeader('etag',etagContent); //给实体赋予唯一标识
        res.setHeader('Catch-Control','no-cache'); //开启协商缓存
        res.send(data)
    }else{
        res.statusCode = 404
    }
    
}).listen(8080,()=>{
     console.log("8080启动成功")
})

从校验流程上来说,协商缓存的修改时间比对和文件指纹比对,几乎是一样的:

缺陷

1、ETag需要计算文件指纹这样意味着,服务端需要更多的计算开销。如果文件尺寸大,数量多,并且计算频繁,那么ETag的计算就会影响服务器的性能。显然,ETag在这样的场景下就不是很适合。

2、ETag有强验证弱验证

  • 强验证:ETag生成的哈希码深入到每个字节。哪怕文件中只有一个字节改变了,也会生成不同的哈希值,它可以保证文件内容绝对的不变。但是,强验证非常消耗计算量。
  • 弱验证:弱验证是提取文件的部分属性来生成哈希值。因为不必精确到每个字节,所以他的整体速度会比强验证快,但是准确率不高。会降低协商缓存的有效性。

总结

强缓存中,能用cache-control就不要用expiress。协商缓存中,ETag并不是last-modified的完全替代方案。而是last-modified的补充方案,项目中到底是用ETag还是last-modified完全取决于业务场景,这两个没有谁更好谁更坏。

文章参考出处: 中高级前端工程师都需要熟悉的技能--前端缓存

相关推荐
还是大剑师兰特39 分钟前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解39 分钟前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
Mr.咕咕1 小时前
Django 搭建数据管理web——商品管理
前端·python·django
张张打怪兽1 小时前
css-50 Projects in 50 Days(3)
前端·css