ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Angular Service Worker 生产运维指南:@angular/service-worker 的版本机制、完整性校验、调试与失效手段

Angular Service Worker 生产运维指南:@angular/service-worker 的版本机制、完整性校验、调试与失效手段 Angular Service Worker 生产运维指南angular/service-worker 的版本机制、完整性校验、调试与失效手段【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular本文基于 Angular 官方文档 devops.md 整理面向使用angular/service-worker的 Angular 应用的生产部署与日常运维如何理解 Service Worker 的多版本缓存与完整性校验模型、如何用ngsw/state调试页诊断运行状态以及在缓存损坏或站点迁移等紧急场景下如何快速停用 Service Worker。读完后你可以独立处理 Service Worker 相关的版本冲突、哈希失配、降级状态SAFE_MODE / EXISTING_CLIENTS_ONLY和卸载清理等生产问题。Service Worker 的定位浏览器里的前置缓存可以把 Angular Service Worker 想象成一个安装在最终用户浏览器中的前置缓存forward cache或 CDN 边缘节点。它拦截 Angular 应用发出的资源与数据请求直接从本地缓存中响应而无需等待网络往返。和任何缓存一样它有一套规则来决定内容何时过期、何时更新。从源码结构看这个缓存 路由角色由 Driver 类 承担它在构造时注册install、activate、fetch、message、push等事件处理器所有入站请求最终都进入onFetch()统一决策是命中缓存还是回源。应用版本Application Versions版本如何定义在 Angular Service Worker 的语境下一个版本version是代表某一次应用构建build的全部资源集合。只要部署了新的构建产物Service Worker 就把它视为一个新版本——哪怕只有一个文件发生了变化。任意时刻Service Worker 的缓存中可能同时存在多个版本并且可能同时在为不同标签页提供其中不同的版本。为什么必须把文件按版本分组为保持应用完整性Angular Service Worker 会把所有文件归入同一个版本一起管理。一个版本通常包含 HTML、JS 和 CSS 文件。这种分组对完整性至关重要因为这三类文件经常互相引用、依赖特定内容。举例说明index.html中有一个script标签引用了bundle.js并试图调用其中的startApp()函数。只要这个版本的index.html被响应就必须搭配定义startApp()的对应bundle.js一起给出。假设新版本里startApp()被重命名为runApp()那么把旧index.html调用startApp()配上新bundle.js只定义runApp()的组合是非法的。这一文件级完整性在懒加载场景下尤其关键。一个 JS bundle 可能引用许多懒加载 chunk而这些 chunk 的文件名是特定构建产物独有的。如果运行在版本X的应用尝试加载某个懒加载 chunk而服务器已经更新到版本X 1这次懒加载就会失败。版本标识与 ngsw.json应用的版本标识由所有资源的内容共同决定其中任何一个变化都会导致版本变化。实际上版本由ngsw.json文件的内容确定——它包含所有已知内容的哈希。任何缓存文件发生变化时ngsw.json中该文件的哈希就会改变Angular Service Worker 因此把当前活跃的文件集合视为一个新版本。构建过程使用ngsw-config.json中的信息生成ngsw.json清单文件manifest。依托这套版本化机制应用服务器可以确保 Angular 应用始终拿到一组自洽的文件。更新检查Update Checks用户每次打开或刷新应用时Angular Service Worker 都会通过检查ngsw.json清单是否有更新来执行应用更新检查。一旦发现更新会被自动下载并缓存下次加载应用时即生效。源码中这一过程对应 fetchLatestManifest()每次检查都会用带随机参数的 URL 回源——ngsw.json?ngsw-cache-bustrandom——以绕开中间缓存层成功后更新lastUpdateCheck时间戳这正是ngsw/state调试页中 Last update check 字段的来源。资源完整性Resource Integrity长周期缓存的一个潜在副作用是可能意外缓存了无效资源。普通 HTTP 缓存中强制刷新或缓存过期会限制这种影响而 Service Worker 不受这些约束实质上把整个应用长缓存了下来。因此 Service Worker 必须拿到正确内容它会为资源保留哈希来维护完整性。带哈希的资源为保证完整性Angular Service Worker 会为所有存有哈希的资源校验哈希。对用 Angular CLI 创建的应用覆盖范围是dist目录中、被用户src/ngsw-config.json配置涵盖的全部文件。某个文件校验失败时处理流程见 assets.ts 中的注释与实现是Service Worker 先用缓存粉碎cache-bustingURL 参数重新拉取内容防止浏览器或中间层缓存干扰如果这份内容仍然校验失败Service Worker 就判定整个应用版本无效停止从该版本提供应用必要时 Service Worker 进入安全模式safe mode请求直接回退到网络。当提供损坏、过期或无效内容的风险较高时它不会使用缓存。代码中缓存粉碎 URL 的构造方式是直接追加随机参数见 makeCacheBustedUrlreturn url (url.indexOf(?) -1 ? ? : ) ngsw-cache-bust Math.random();哈希失配可能由多种原因造成源站与最终用户之间的缓存层提供了陈旧内容非原子化部署non-atomic deployment导致 Service Worker 看到了一部分更新后的内容构建过程的错误导致资源更新了但ngsw.json没有更新或者反过来——ngsw.json更新了但资源没有更新。不带哈希的资源ngsw.json清单里只有构建清单生成时刻已存在于dist目录中的资源才有哈希。其他资源尤其是来自 CDN 的资源在构建时内容未知或更新频率高于应用部署频率。对于没有哈希可校验的资源Angular Service Worker 仍然会缓存其内容但同时遵循 HTTP 缓存头采用stale-while-revalidate过期但仍先服务策略即使 HTTP 缓存头已表明资源失效它仍会继续响应该资源同时在后台尝试刷新过期副本。这样损坏的无哈希资源不会在缓存中存活超过其配置的生命周期。assets.ts 中对该策略的注释明确写道traffic until its updated (stale-while-revalidate approach)。应用标签页Application Tabs如果应用接收到的资源版本突然且毫无预警地变化对应用来说是很有问题的参见上文应用版本中的完整性讨论。Angular Service Worker 提供一条保证正在运行的应用会继续运行同一版本而在新的浏览器标签页中打开的实例则获得当前最新版本。因此新标签页可能运行与原标签页不同的版本。这条保证强于普通 Web 部署模型的保证。没有 Service Worker 时并不存在任何保证说明懒加载的代码与应用初始代码来自同一版本。Service Worker 只会在以下错误条件下更换正在运行应用的版本当前版本因哈希校验失败而失效无关错误导致 Service Worker 进入安全模式并被临时停用。其他属于正常事件的版本变更来源页面被重新加载/刷新页面通过SwUpdate服务请求立即激活更新。当没有任何标签页再使用某个版本时Service Worker 会清理该版本。实现层面版本绑定关系由 Driver 的 clientVersionMap 维护MapClientId, ManifestHash记录每个客户端标签页被钉在哪个 manifest 哈希上assignVersion() 对新导航请求返回latestHash对应的AppVersion即新标签页拿最新版的保证来源。应用内调用更新激活则通过 SwUpdate 服务完成——它对外暴露checkForUpdate()与activateUpdate()两个方法底层向 Service Worker 发送CHECK_FOR_UPDATES/ACTIVATE_UPDATE消息对应 handleMessage()。Service Worker 脚本自身的更新Angular Service Worker 是一段运行在浏览器中的小型脚本会不定期更新以包含缺陷修复与功能改进。它的下载时机是应用首次被打开时以及应用在一段时间不活跃后再次被访问时。若脚本发生变化更新在后台完成。大多数更新对应用是透明的旧缓存依然有效内容照常提供。偶有情况某个修复或功能需要使旧缓存失效此时 Service Worker 会透明地从网络重新拉取应用。源码印证了这种脚本更新即重启的思路install 处理器 中注释说明SW 代码更新与应用更新相互独立因此skipWaiting()立即激活新版 SW 是安全的——因为新版 SW 会继续为旧应用提供服务activate阶段则调用clients.claim()接管现有客户端。绕过 Service Worker有些场景下你可能希望完全绕过 Service Worker、让浏览器直接处理请求。例如你依赖某个 Service Worker 当前尚不支持的能力如上传文件进度上报。绕过方法设置请求头ngsw-bypass或在 URL 上添加查询参数ngsw-bypass。该头/参数的值会被忽略可以为空或干脆省略。对应实现见 onFetch()if (req.headers.has(ngsw-bypass) || /[?]ngsw-bypass(?:[]|$)/i.test(requestUrlObj.search)) { return; }只要检测到请求头包含ngsw-bypass或查询串中出现ngsw-bypass作为独立参数后面跟、或结尾Service Worker 就直接让请求走浏览器默认路径。服务器不可达时的 Service Worker 行为除非被显式绕过Service Worker 会处理所有请求。它根据缓存状态与配置要么返回缓存响应要么把请求发给服务器。它只对非变更non-mutating请求做缓存如GET和HEAD。如果 Service Worker 从服务器收到错误、或收不到响应它会返回一个能反映调用结果的错误状态码。例如收不到响应时它会构造504 Gateway Timeout状态返回——这个504可能因为服务器离线也可能因为客户端断网。源码中 504 的生成点有多处data.ts 在请求超时与网络异常时构造{status: 504, statusText: Gateway Timeout}响应fetchLatestManifest() 还把服务器返回的503与504作为离线信号处理——在这些状态下可跳过更新检查而不视为故障。调试 Angular Service Worker有时你需要检查运行中的 Service Worker 来排查问题或确认它是否按设计工作。浏览器自带 Service Worker 调试工具Angular Service Worker 自身也内置了调试特性。ngsw/state 调试页Angular Service Worker 在虚拟目录ngsw/下暴露调试信息目前唯一的地址是ngsw/state。Driver 构造函数 中计算了该路径onFetch() 中它是唯一无条件由 Service Worker 响应而非回源的路径。一个典型响应内容如下NGSW Debug Info: Driver version: 13.3.7 Driver state: NORMAL ((nominal)) Latest manifest hash: eea7f5f464f90789b621170af5a569d6be077e5c Last update check: never Version eea7f5f464f90789b621170af5a569d6be077e5c Clients: 7b79a015-69af-4d3d-9ae6-95ba90c79486, 5bc08295-aaf2-42f3-a4cc-9e4ef9100f65 Idle Task Queue Last update tick: 1s496u Last update run: never Task queue: - init post-load (update, cleanup) Debug log:这段文本由 DebugHandler.handleFetch() 拼装并发取debugState()、debugVersions()、debugIdleState()三组数据后以text/plain返回。各字段含义如下。Driver state驱动状态Driver state: NORMAL ((nominal))NORMAL表示 Service Worker 正常运作不处于降级状态。源码中状态是枚举 DriverReadyState共有三种取值其中两种为降级状态降级状态说明EXISTING_CLIENTS_ONLYService Worker 没有持有最新已知版本的干净副本。旧缓存版本可用因此已有标签页继续从缓存运行但新加载的应用将从网络提供。当检测到新版本新的ngsw.json可用并安装后它会尝试从该状态恢复。SAFE_MODEService Worker 无法保证使用缓存数据是安全的——要么发生了意外错误要么所有缓存版本都已失效。此时所有流量都从网络提供Service Worker 运行尽可能少的自身代码。两种情况下括号里的注释如(nominal)、Degraded due to: ...都是导致进入该降级状态的具体错误。源码中stateMessage初始值为(nominal)driver.ts L107当最新版本初始化失败时versionFailed() 会把状态置为EXISTING_CLIENTS_ONLY并写入Degraded due to: error初始化本身失败时ensureInitialized()则直接置为SAFE_MODE。两个降级状态都是临时的状态只在一个 ServiceWorker 实例的生命周期内保存。浏览器有时会终止空闲的 Service Worker 以节省内存和 CPU并在网络事件到来时创建新实例。新实例一律从NORMAL模式启动与前一实例的状态无关。Latest manifest hashLatest manifest hash: eea7f5f464f90789b621170af5a569d6be077e5c这是 Service Worker 所知的最新应用版本的 SHA1 哈希。Last update checkLast update check: never表示 Service Worker 上次检查应用新版本更新的时间。never表示从未检查过。上面的示例中更新检查正处于已调度待执行的状态见下节Idle task queue。Version版本段 Version eea7f5f464f90789b621170af5a569d6be077e5c Clients: 7b79a015-69af-4d3d-9ae6-95ba90c79486, 5bc08295-aaf2-42f3-a4cc-9e4ef9100f65示例中 Service Worker 缓存了一个应用版本并正在用它服务两个不同的标签页。该版本哈希就是上文 Latest manifest hash两个客户端都处于最新版本每个客户端以其浏览器ClientsAPI 的客户端 ID 列出。Idle task queue空闲任务队列 Idle Task Queue Last update tick: 1s496u Last update run: never Task queue: - init post-load (update, cleanup)Idle Task Queue 是所有在 Service Worker 中后台执行的待处理任务队列队内任务会带描述列出。示例中调度了一个初始化后任务包含更新检查与陈旧缓存清理。两个计数器含义Last update run显示空闲任务实际执行距现在的时间Last update tick显示距最近一次队列可能被处理事件的时间。时间格式如1s496u由 DebugHandler.since() 生成单位为 d/h/m/s/u天/时/分/秒/毫秒。Debug log调试日志Debug log:Service Worker 内部发生的错误记录在这里。DebugHandler 采用双缓冲区环形日志debugLogA/debugLogB各 100 条DEBUG_LOG_BUFFER_SIZE 100满了就轮换保证日志写入恒为 O(1) 且总条数不超过 200。浏览器开发者工具的注意事项Chrome 等浏览器提供了与 Service Worker 交互的开发者工具。用得好非常有力但有几点必须留意开着 DevTools 时Service Worker 会一直保持在后台运行、永不重启因此工具打开时的行为可能与真实用户看到的行为不同在 Cache Storage 查看器中看到的缓存经常是过期的——右键 Cache Storage 标题选择刷新缓存在 Service Worker 面板中停止/启动 Service Worker 会触发一次更新检查。Service Worker 安全机制Safety缺陷或配置损坏可能导致 Angular Service Worker 表现出非预期行为。此时它内置了多个保险机制供管理員在需要时快速停用 Service Worker。Fail-safe失效保护要停用 Service Worker重命名或删除服务器上的ngsw.json文件。当 Service Worker 请求ngsw.json得到404时它会删除自己的全部缓存并注销自己——相当于自毁。源码中这就是 fetchLatestManifest() 的核心分支if (!res.ok) { if (res.status 404) { await this.deleteAllCaches(); // 删除所有 Cache Storage await this.scope.registration.unregister(); // 注销自身 } else if ((res.status 503 || res.status 504) ignoreOfflineError) { return null; // 503/504 视为离线不视为故障 } throw new Error(Manifest fetch failed! (status: ${res.status})); }deleteAllCaches() 会遍历caches.keys()并把每个缓存逐一删除。也就是说运维上只需让ngsw.json返回 404就能让所有客户端在下次更新检查时自动完成清缓存 退订。Safety Workersafety-worker.jsangular/service-worker这个 npm 包里还附带一个小脚本 safety-worker.js。它被加载后会把自己从浏览器中注销并删除 Service Worker 的缓存。它可以作为最后手段用来清除已经安装在客户端页面上的、不想要的 Service Worker。脚本全部逻辑只有几十行self.addEventListener(install, (event) { self.skipWaiting(); }); self.addEventListener(activate, (event) { event.waitUntil(self.clients.claim()); event.waitUntil( self.registration.unregister().then(() { console.log(NGSW Safety Worker - unregistered old service worker); }), ); event.waitUntil( caches.keys().then((cacheNames) { const ngswCacheNames cacheNames.filter((name) /^ngsw:/.test(name)); return Promise.all(ngswCacheNames.map((name) caches.delete(name))); }), ); });关键限制你不能直接注册这个 worker因为带有缓存状态的老客户端可能看不到安装新 worker 脚本的新index.html。正确做法是把safety-worker.js的内容放在你正在尝试注销的那个 Service Worker 的原 URL上持续提供直到确认所有用户都已成功注销旧 worker。对大多数站点而言意味着应当永久在旧的 Service Worker URL 上提供 safety worker。这个脚本既能停用angular/service-worker并删除相应缓存也能清除你的站点过去可能部署过的任何其它 Service Worker。更改应用部署位置Service Worker 在重定向redirect之后不工作。你可能已经遇到过错误The script resource is behind a redirect, which is disallowed。当必须更改应用位置时这会成为一个问题。例如设置从旧位置example.com到新位置www.example.com的重定向后worker 会停止工作。而且对于完全从 Service Worker 加载站点的用户连重定向都不会被触发注册在example.com上的旧 worker 尝试更新时向旧位置发请求该请求被重定向到www.example.com于是产生错误The script resource is behind a redirect, which is disallowed。补救办法用前述两种技术之一停用旧 worker——Fail-safengsw.json返回 404或Safety Worker在旧 worker URL 上提供safety-worker.js内容。延伸阅读配置文件说明ngsw-config.json与 Service Worker 通信SwUpdate / SwPush / SwRegistration相关实现与测试代码位于 packages/service-workerworker 主逻辑在 worker/src/driver.ts 与 worker/src/assets.ts应用侧服务在 src/update.tsSwUpdate、src/push.tsSwPush构建期配置生成器在 config/src/generator.ts行为验证测试见 worker/test/happy_spec.ts 与 worker/test/idle_spec.ts。【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表