一文搞懂 HTTP 缓存

HTTP 缓存可谓前端工程师必备技能之一,在最近的面试中,也经常被问到,那么为什么要有HTTP 缓存,以及它是如何工作的,下面咱们展开来好好聊聊。

还不知道什么是HTTP?可以跳转到我的前一篇文章------ 捋一下 HTTP 发展史 - 掘金 (juejin.cn)

为什么要有 HTTP 缓存

缓存缓存,顾名思义就是将一些资源保存在本地,后续请求时就可以直接在本地读取,而不用请求服务器。使用HTTP缓存是提高性能优化和提升用户体验的关键技术,它主要体现在几个方面:

  1. 减少延迟:当资源被缓存后,用户可以直接从浏览器缓存或更近的网络节点(如CDN)获取数据,而不必每次都请求原始服务器。网络延迟和数据传输时间都大大缩短了。
  2. 减轻服务器负担:通过缓存常用资源(如Logo),服务器不必每次都处理相同的请求。
  3. 节约带宽:缓存可以显著减少需要通过网络传输的数据量,降低用户和供应商的流量成本。
  4. 改善用户体验:缓存使得网页、图片等内容的加载更快,从而直接提高了用户的浏览体验。
  5. 离线浏览:HTTP缓存在一些场景可以让用户在没有网络连接的情况下,仍然能够访问先前加载过的内容。(如网易云音乐可以离线听之前缓存过的歌)
  6. 负载均衡:通过在多个地理位置缓存内容,避免所有请求都集中在一个服务器上。可以有效处理高流量和大规模分布的用户。

总之,HTTP缓存是前端开发者必备一项至关重要的技能,话不多说,开始介绍!

HTTP 缓存

HTTP缓存是一种客户端缓存,当浏览器向服务器发送资源请求时,服务器可以在响应头中查看是否包含缓存相关的信息。浏览器依据这些信息缓存响应资源,再次请求时如果命中缓存则直接读取本地缓存资源,而不再发请求。HTTP缓存分为强缓存协商缓存

强缓存

强缓存直接决定了浏览器是否需要向服务器发起请求,只需要设置缓存的过期时间。如果强缓存有效,浏览器将直接从本地缓存中读取资源,不会与服务器进行任何交互。

Cache-Control

Cache-Control是通用消息头字段,被用于在 HTTP 请求和响应中,通过指定指令来实现缓存机制,通过设置max-age=xxx来指定资源在缓存中可以存活的最大时间(单位是秒)。

举个栗子🌰:

这里我们缓存一个图片资源,过期时间为 86400 秒即一天

js 复制代码
res.writeHead(200,{
    "Content-Type": mime.lookup(ext),
    'cache-control':'max-age=86400',//缓存一天
})

当我们第一次请求时,会正常发请求拿到资源

但当我们刷新页面再次发请求时发现,此时的履行者变为memory cache,大小和时间都变为0,说明这次的资源并不是从服务器请求回来的,而是来自本地内存缓存。

并且我们可以在响应头中查看Cache-Control字段和我们设置的过期时间。

当然Cache-Control不止只允许我们设置max-age过期时间,还有其他字段,详情可参考 Cache-Control - HTTP | MDN (mozilla.org)

Expires

Expires响应头是一个具体的日期/时间,告诉浏览器在这个时间点之前不需要重新请求资源。

⚠️注意:如果响应中有指令为 max-ages-maxageCache-Control标头,则 Expires 标头会被忽略。

举个栗子🌰:

js 复制代码
 res.writeHead(200,{
    "Content-Type": mime.lookup(ext),
    'Expires': 'Mon Apr 22 2024 21:03:31 GMT+0800' //过期时间戳自己选择
})

Cache-Control用法相似,就不过多赘述。

协商缓存

当强缓存过期或无效时,浏览器就会与服务器进行交互,通过发送带有特定验证头的请求来检查资源是否被修改。如果服务器确认缓存资源仍然是最新的,则返回 304 状态码,浏览器会从本地缓存加载资源。如果资源不是最新的,则返回 200 状态码和新的资源数据。

Last-Modified 和 If-Modified-Since

服务器在响应中提供Last-Modified日期,表明资源最后修改时间。浏览器在后续请求中使用If-Modified-Since头包含这个日期,如果服务器上的资源自那以后未修改,就会返回304状态码。

举个栗子🌰:

js 复制代码
const stats = fs.statSync(filePath) //获取文件文件修改时的时间戳
const timeStamp = req.headers['if-modified-since'] //获取请求头字段
let status = 200 //资源修改
if(timeStamp && Number(timeStamp) === stats.mtimeMs) { //时间戳不变
    status = 304 //资源未修改
}

res.writeHead(status,{
    "Content-Type": mime.lookup(ext),
    'last-modified': stats.mtimeMs //时间戳
})

通过对比这次请求头的if-modified-since和响应头的last-modified判断资源是否修改

当资源未修改,返回 304 状态码,从缓存中拿资源。

现在我们试试手动修改资源,再次刷新页面:

资源修改,两次时间戳不同,返回 200 状态码,和新的资源。

ETag 和 If-None-Match

ETag是资源的唯一标识符,反映资源的内容状态。浏览器存储这个标识并在后续请求中通过If-None-Match头发送它。如果ETag未改变,服务器返回304状态码。

js 复制代码
if (req.headers['if-none-match'] === etag) {
    res.writeHead(304, {// 未修改,返回304
        'Content-Type':  mime.lookup(ext),
        'ETag': etag,
    });
    return res.end();
}

res.writeHead(200, { //修改,返回200
    'Content-Type':  mime.lookup(ext),
    'ETag': etag
});
return res.end(content);

两种协商缓存的区别是一个比较时间戳一个比较哈希值。

总结

强缓存通过设置过期时间来控制资源的缓存;而协商缓存则是通过与服务器验证资源的状态来决定是否使用缓存。协商缓存可以避免强缓存的不足之处,比如当资源在有效期内但发生了变化时,协商缓存可以保证客户端获取到最新的资源。

最后配一张个人觉得总结十分到位的图(图片地址在文末)

参考

最后

码字不易,感谢点赞评论收藏!

已将学习代码上传至 github,欢迎大家学习指正!

技术小白记录学习过程,有错误或不解的地方还请评论区留言,如果这篇文章对你有所帮助请 "点赞 收藏+关注" ,感谢支持!!

相关推荐
SEEONTIME2 分钟前
python-24-一篇文章彻底掌握Python HTTP库Requests
开发语言·python·http·http库requests
速盾cdn3 分钟前
速盾:vue的cdn是干嘛的?
服务器·前端·网络
四喜花露水36 分钟前
Vue 自定义icon组件封装SVG图标
前端·javascript·vue.js
前端Hardy1 小时前
HTML&CSS: 实现可爱的冰墩墩
前端·javascript·css·html·css3
李老头探索1 小时前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
web Rookie1 小时前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
Au_ust1 小时前
css:基础
前端·css
帅帅哥的兜兜1 小时前
css基础:底部固定,导航栏浮动在顶部
前端·css·css3
yi碗汤园1 小时前
【一文了解】C#基础-集合
开发语言·前端·unity·c#
就是个名称1 小时前
购物车-多元素组合动画css
前端·css