ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【浏览器】强缓存和协商缓存(HTTP 缓存)

【浏览器】强缓存和协商缓存(HTTP 缓存) 部分内容来源渡一教育。为什么有缓存来自服务器的缓存指令当客户端发出一个get请求到服务器服务器希望客户端将相应资源缓存起来服务器在响应头中加入了以下内容Cache-Control:max-age3600 ETag:W/121-171ca289ebf Date:Thu,30 Apr 2020 12:39:56 GMT Last-Modified:Thu, 30Apr2o20 081631GMT这个响应头表达了下面的信息Cache-Control:max-age3600, 缓存时间是3600秒(1小时)ETag:W/“121-171ca289ebf”, 这个资源的编号可以认为是哈希/唯一id是W/“121-171ca289ebf”Date:Thu,30Apr202012:39:56GMT, 我给你响应这个资源的服务器时间是格林威治时间2020-04-30123956Last-Modified:Thu,30Apr2020g8:16:31GMT, 这个资源的上一次修改时间是格林威治时间2020-04-30881631如果客户端是其他应用程序可能并不会理会服务器的愿望也就是说可能根本不会缓存任何东西。如果刚好是一个浏览器那么就会执行缓存浏览器把这次请求得到的响应体缓存到本地文件中浏览器标记这次请求的请求方法和请求路径浏览器标记这次缓存的时间是3600秒浏览器记录服务器的响应时间是格林威治时间2020-04-30123956浏览器记录服务器给予的资源编号W/“121-171ca289ebf”浏览器记录资源的上一次修改时间是格林威治时间2020-04-30881631来自客户端的缓存协议当客户端准备再次请求GET /index.js时它突然想起了一件事我需要的东西在不在缓存里呢此时客户端会到缓存中去寻找是否有缓存的资源寻找的过程如下缓存中是否有匹配的请求方法和路径如果有该缓存资源是否有效呢以上两个验证会导致浏览器产生不同的行为缓存无效协商缓存如果缓存有效则直接使用缓存结果。当浏览器发现缓存已经过期它并不会简单的把缓存删除而是抱着一丝希望想问问服务器我这个缓存还能继续使用吗于是浏览器向服务器发出了一个带缓存的请求又称之为协商缓存。所谓带缓存的请求无非就是加入了以下的请求头If-Modified-Since: Thu,30 Apr 2020 08:16:31 GMT If-None-Match: W/121-171ca289ebf它们表达了下面的信息If-Modified-Since:Thu,30Apr202088:16:31GMT 这个资源的上一次修改时间是格林威治时间2020-04-30881631请问这个资源在这个时间之后有发生变动吗If-None-Match:W/“121-171ca289ebf” 这个资源的编号是W/121-171ca289ebf, 请问这个资源的编号发生变动了吗其实这两个问题可以合并为一个问题就是问服务器资源到底变化了没有之所以要发两个信息是为了兼容不同的服务器因为有些服务器只认If-Modified-Since有些服务器只认If-None-Match优先级更高有些服务器两个都认。服务器可能会产生两个情况缓存已经失效缓存仍然有效如果是第一种情况一一缓存已经失效那么非常简单服务端再次给予一个正常的响应响应码 200 带响应体)同时可以附带上新的缓存指令这就回到了上一节一一来自服务器的缓存指令。这样一来客户端就会重新缓存新的内容。但如果服务器觉得缓存仍然有效它可以通过一种极其简单的方式告诉客户端响应码为 304 Not Modified 原本响应内容未修改无响应体响应头带上新的缓存指令见上一节一一来自服务器的缓存指令这样一来就相当于告诉客户端「你的缓存资源仍然可用我给你一个新的缓存时间你那边更新一下就可以了」于是客户端就继续使用缓存了。这样一来可以最大程度的减少网络传输因为如果资源还有效服务器就不会传输消息体。强缓存和协商缓存http 缓存分为强缓存和协商缓存。他们用于优化网站性能并减少服务器负载。这两种缓存都通过HTTP 响应头来控制它们分别基于不同的缓存验证方式可以根据资源的特性和需求来选择合适的缓存策略。客户端htmlheadmetacharsetUTF-8metanameviewportcontentwidthdevice-width, user-scalableno, initial-scale1.0, maximum-scale1.0, minimum-scale1.0metahttp-equivX-UA-CompatiblecontentieedgetitleDocument/title/headbodybuttonidbtnsend/buttonscriptconstbtndocument.querySelector(#btn)btn.addEventListener(click,(){fetch(http://localhost:3000/api3)})/script/body/html1.强缓存Strong Cache客户端在请求资源时会检查缓存的相关响应头如使用Cache-Control、Expires来控制缓存在本地的有效期进行缓存后续的请求如果资源的缓存尚未过期存在 memory cache / disk cache客户端将直接从本地缓存中获取资源而不会发送请求到服务器。常用的强缓存响应头包括Cache-Control: max-ageseconds指定资源的缓存时间单位为秒。HTTP1.1 提出优先级更高请求头和响应头都支持这个属性通过它提供的不同的值来定义缓存策略。此外Cache-Control还支持其他的属性字段如no-cache 、no-store、public、private 、must-revalidate等。Expires: date指定资源的过期时间是一个日期字符串表示绝对时间由服务器返回。它是一个HTTP 1.0的头部字段。受限于本地时间修改了本地时间可能会导致缓存失效。如果在Expires之内则浏览器会直接读取缓存不再请求服务器。从浏览器读取缓存分为内存缓存(memory cache浏览器内存关闭页面后会被清除通常用于在同一个页面会话中的快速重复请求)和硬盘缓存(disk cache计算机硬盘空间大读取效率低持久化存储资源直到缓存策略决定缓存到期或被手动清除)而这两种缓存策略由浏览器自身分配。此外 还有 Service Worker 缓存应用程序控制的缓存层通过 Service Worker 脚本精确控制缓存和资源请求逻辑浏览器请求顺序1. Memory Cache 2. Service Worker Cache 3. HTTP Disk Cache 4. 网络请求状态码为 200network结果表示from disk cache强缓存磁盘缓存命中from memory cache强缓存内存缓存命中from network走网络请求但不一定是协商缓存还需看 Headerimportexpressfromexpressimportcorsfromcorsimportfsfromnode:fsimportcryptofromnode:cryptoconstappexpress()app.use(cors())//静态资源缓存 html, css, js, png// app.use(express.static(./static, {// maxAge: 100 * 60 * 5,// lastModified: true// }))//动态资源缓存//Expiresapp.get(/api,(req,res){// 到了过期时间之后将不再进行缓存res.setHeader(Expires,newDate(2024-4-6 8:57:00).toUTCString())res.send(Expires1111)})// Cache-Control// public 任何服服务器都可以缓存包括代理服务器 cdn// private 只能浏览器缓存 不包括代理服务器// max-age 缓存的时间app.get(/api2,(req,res){res.setHeader(Cache-Control,public, max-age10)res.send(Cache-Control1111)})app.listen(3000,(){console.log(3000端口已启用)})2.协商缓存Conditional Cache当浏览器对某个资源的请求没有命中强缓存当强缓存如 max-age过期或响应头设置 no-cache时就会发一个请求包含If-Modified-Since或If-None-Match的请求头到服务器验证协商缓存是否命中判断资源是否更新。协商缓存无过期时间。如果协商缓存命中资源仍可以继续使用请求响应返回的HTTP状态为304 (Not Modified)该请求不携带实体数据浏览器复用本地缓存若未命中则返回200并携带资源实体数据。协商缓存是利用的是Last-ModifiedIf-Modified-Since和ETag、If-None-Match这两对Header来管理的。常用的协商缓存响应头包括Last-Modified指定资源的最后修改时间。HTTP1.0提出。浏览器会在请求头加上If-Modified-Since即上次响应的Last-Modified的值询问服务器在该日期后资源是否有更新有更新的话就会将新的资源发送回来但是如果在本地打开缓存文件或者重新编译就会造成Last-Modified被修改因此导致误判而且他在 1s 内多次修改也无法检测得到所以在HTTP 1.1出现了ETag。ETag指定资源的实体标签(服务器生成资源的唯一标识符 / 哈希)。HTTP1.1提出只有资源变化才会被修改跟最后修改时间没有关系。If-None-Match 的请求头字段会将上次返回的 ETag 发送给服务器询问该资源的 ETag 是否有更新有变动就会发送新的资源回来。ETag 的优先级比 Last-Modified 更高。强 ETag无前缀内容任何变化都会更新标识。弱 ETagW/前缀仅当内容语义变化时更新如 ETag: W/“0815”。强缓存和协商缓存同时出现默认强缓存的优先级更高。ETag 判断条件场景文件由版本1变到版本2再变回版本1etag是否还原“ETag 是否还原不由 HTTP 协议保证取决于服务器的 ETag 生成策略。如果是基于内容 hash文件回退后 ETag 会一致如果基于时间戳、版本号或构建号即使内容相同ETag 也可能不同。”Last-Modified 和 ETag 的区别Last-Modified精度只能到秒但是性能更好。ETag更精细但是性能不如前者因为文件变化都要重新计算hash值。importexpressfromexpressimportcorsfromcorsimportfsfromnode:fsimportcryptofromnode:cryptoconstappexpress()app.use(cors())//获取文件的最后修改时间constgetFileModifyTime(){returnfs.statSync(./index.js).mtime.toISOString()}app.get(/api3,(req,res){// no-cache 使用协商缓存而不是强缓存// no-store 不走任何缓存res.setHeader(Cache-Control,no-cache)constmodifyTimegetFileModifyTime();// 浏览器根据 Last-Modified 的值自动设置 if-modified-since 的值(与上一次 Last-Modified 的值相同)constifModifiedSincereq.headers[if-modified-since]// 如果文件不修改则不用重新缓存if(ifModifiedSincemodifyTime){console.log(缓存了)res.statusCode304res.end()return}console.log(没有缓存)res.setHeader(Last-Modified,modifyTime)res.send(Last-Modified111)})app.listen(3000,(){console.log(3000端口已启用)})importexpressfromexpressimportcorsfromcorsimportfsfromnode:fsimportcryptofromnode:cryptoconstappexpress()app.use(cors())//获取文件内容哈希constgetFileHash(){returncrypto.createHash(sha256).update(fs.readFileSync(index.js)).digest(hex)}app.get(/api3,(req,res){// no-cache 使用协商缓存而不是强缓存// no-store 不走任何缓存res.setHeader(Cache-Control,no-cache)constfileHashgetFileHash();constifNoneMatchreq.headers[if-none-match]// 如果文件不修改则不用重新缓存if(ifNoneMatchfileHash){console.log(缓存了)res.statusCode304res.end()return}console.log(没有缓存)res.setHeader(ETag,fileHash)res.send(Etag111)})app.listen(3000,(){console.log(3000端口已启用)})强缓存和协商缓存可以结合使用以提高缓存效果。通常情况下可以首先使用强缓存来尽可能减少对服务器的请求如果资源已经过期或者需要重新验证则使用协商缓存来验证缓存的有效性。这样可以在保证性能的同时确保客户端始终能够获取到最新的资源。知识点同一个接口、Web 可以走缓存但移动端不行视频Cache-Control: public, max-age31536000 ETag: abc Accept-Ranges: bytes浏览器行为第一次请求200 OK 视频资源后续读取本地 cache或用 If-None-Match 请求确认 → 304 Not Modified原生 App / React Native / Flutter 行为使用 Range 分段请求按字节流式加载每次进入页面重新从服务器拉默认不开磁盘缓存即使服务端设置了缓存Service Worker 缓存PWA 渐进式的 web app旨在将 web 像是原生桌面 app 那样使用可安装可卸载。其中service worker 是 pwa 的核心。Service Worker 是运行在浏览器后台的独立线程可拦截网络请求并实现精细化缓存管理显著提升离线体验和加载性能。基本使用​​离线缓存​​预缓存关键资源HTML/CSS/JS确保无网络时应用仍可运行可以在控制台 Application - Cache Storage 中看到 workbox 缓存了该应用必要的文件// service-worker.js const CACHE_NAME v1; const urlsToCache [/, /styles.css, /app.js]; self.addEventListener(install, (event) { event.waitUntil(caches.open(CACHE_NAME).then(cache cache.addAll(urlsToCache))); }。​​动态缓存策略​​根据资源类型选择策略​​Cache First​​静态资源如图片优先从缓存读取。​​Network First​​动态数据如API优先网络请求失败时回退缓存。​​Stale While Revalidate​​先返回缓存后台更新缓存适合频繁更新资源。​​缓存更新与冲突解决​​​​版本控制​​更新缓存名称如my-cache-v2激活时清理旧缓存self.addEventListener(activate, (event) { caches.keys().then(names names.map(name { if (name ! CACHE_NAME) caches.delete(name); })) }。​​数据一致性保障​​对关键数据采用 ​​Network First 策略​​确保获取最新结果。通过 ​​Background Sync API​​ 在网络恢复后同步数据。​​资源优先级管理​​按需加载非关键资源加速首屏渲染self.addEventListener(fetch, (event) { if (event.request.url.includes(/non-critical/)) { // 延迟加载非关键资源 event.respondWith(caches.match(event.request)); } else { // 关键资源直接网络请求 event.respondWith(fetch(event.request)); } }。与 WebSocket 协同​​​实时更新缓存​​WebSocket 接收更新通知 → Service Worker 后台刷新缓存// WebSocket 监听更新 socket.onmessage (event) { if (event.data.type DATA_UPDATE) { caches.open(my-cache).then(cache cache.put(/api/data, new Response(event.data.payload))); } }。​​离线消息队列​​Service Worker 暂存离线期间的 WebSocket 消息网络恢复后重发。
返回列表