一篇搞定http缓存(强缓存Cache-Control、协商缓存Last-Modified、ETag使用)

817 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

面试官:谈谈http缓存,你能说出个所以然吗?也许能说的出强缓存与协商缓存,但是你真的用过吗?我们可以通过实践来进行使用,用一遍就不会忘了,本文通过具体例子的形式让你掌握该知识点。

强缓存Cache-Control


创建一个简单的express服务器。

服务器端返回头设置Cache-Control。

max-age后是本地缓存的秒数。

服务端代码

import express from 'express'
import cors from 'cors'

const app = new express()
app.use(express.json())
app.use(cors())

//给app设置中间件,这样所有请求都会使用该缓存。
app.use((req, res, next) => {
    res.set({
        'Cache-Control': 'max-age=2',
    })
    next()
})

app.get('/getName', (req, res) => {
    res.send({
        name: '名字'
    })
})

app.listen(3000, () => {
    console.log('服务启动!')
})

客户端代码

<body>
<button onclick="test()">点击测试</button>
<script>
    const test = async () => {
        const response = await fetch('http://localhost:3000/getName')
        const data = await response.json()
        console.log(data)
    }
</script>
</body>

浏览器审查时,重复请求,会发现被强缓存在本地的2秒内都会disk cache从本地缓存请求。

2秒缓存过期后便会重新向服务器发送请求。

image.png

image.png

协商缓存


协商缓存一般在强缓存之后,每当强缓存过期,再次请求时将会去服务器请求。

此时请求头会生成一个与响应头对应的属性,来验证文件是否修改。

如若修改则返回最新文件,未修改则返回304不反回包体,提示浏览器继续使用原来保存的缓存。

协商缓存有两种。

第一种 Last-Modified和If-Modified-Since

强缓存是优先于协商缓存的,为了对比强缓存与协商缓存行为影响,我们保留两秒的强缓存,加上Last-Modified,即文件的最后修改时间戳。

服务端代码

import express from 'express'
import cors from 'cors'
import fs from 'fs'

const app = new express()
app.use(express.json())
app.use(cors())

//给app设置中间件,这样所有请求都会使用该缓存。
app.use((req, res, next) => {
    const stat = fs.statSync('./cache.js')
    const {mtime} = stat
    res.set({
        'Cache-Control': 'max-age=2',
        'Last-Modified': mtime.toUTCString(),
    })
    next()
})

app.get('/getName', (req, res) => {
    res.send({
        name: '名字'
    })
})

app.listen(3000, () => {
    console.log('服务启动!')
})

当浏览器每次去服务器请求,会将响应头的Last-Modified记录,也就是浏览器进行下一次请求时,能够知道上次的Last-Modified,记得上次服务端文件的修改时间。

这意味着什么呢?

连续请求时,首先会进行强缓存,2s缓存过期后,去服务器时,请求头会自动带上If-Modified-Since。

If-Modified-Since字段也就是上次请求响应头中的Last-Modified。

此时响应头仍然有一个Last-Modified最后修改时间,而请求头的If-Modified-Since是上次请求返回的最后修改时间,会对这两个值进行对比。

若是在该请求之前,服务器文件被修改过了,Last-Modified则更新了,会比If-Modified-Since时间更新,则表示请求文件被修改了,否则未修改。

未修改则返回304,表示浏览器可继续用原来的缓存内容。

这里用火狐浏览器测试,因为谷歌浏览器Edge浏览器确实是协商缓存,但是它并不能显示304而是继续显示200。

2秒强缓存结束,就会进行协商缓存,发现文件未改变,返回304继续读取缓存。

image.png

我们修改一下服务器文件并重启服务器,就是改变了服务器文件的修改时间,再请求一次。

此时文件被修改,没有从缓存读取。

image.png

第二种 ETag和If-None-Match

ETag代替了Last-Modified作为文件最后的标识,是一个随机生成的字符串。

If-None-Match会记录上次请求获取的ETag,与Last-Modified-Since差不多。

ETag优先级比Last-Modified高,如果ETag与Last-Modified同时存在,请求是优先判断ETag,ETag没变再去判断Last-Modified是否改变。

使用nodejs测试时,细心的我们会发现前面的请求头与响应头就已经有ETag和If-None-Match了,我没有考证是浏览器自带还是express服务器自带的,但是它确实存在,这不是我们本文研究的话题所以我们只需要将原来的缓存去掉测试即可,如果有人知道也可以留言交流~

服务端代码

import express from 'express'
import cors from 'cors'
import fs from 'fs'

const app = new express()
app.use(express.json())
app.use(cors())

app.get('/getName', (req, res) => {
    res.send({
        name: '名字'
    })
})

app.listen(3000, () => {
    console.log('服务启动!')
})

把前面设置缓存的中间件内容删去,仍然会存在304,也就是协商缓存,就是ETag在起作用。

image.png

我们也修改一次文件并重启服务器,最好就修改返回内容,因为ETag与Last-Modified不同,返回内容不变的话,ETag知道文件修改了等于没修改不会改变

image.png

为什么有了Last-Modified还需要ETag?

  1. 因为Last-Modified毕竟是时间戳只能精确到秒级,万一存在1s内多次修改怎么办?
  2. 文件修改了,但是内容没有变动,或者文件修改了,但是服务器没有重启,也会导致Last-Modified更新,此时没达到使用缓存的目的,并可能出现异常。
  3. 时间戳获取不一定精准。

尾言

如果觉得文章对你有帮助的话,欢迎点赞收藏哦,有什么错误或者意见建议也可以留言,感谢~