在 Service Worker 中处理范围请求

确保您的服务工知道在请求部分响应时该怎么做。

某些 HTTP 请求包含 Range: 标头,表示应仅返回完整资源的一部分。它们通常用于流式传输音频或视频内容,以便按需加载较小的媒体内容块,而不是一次请求整个远程文件。

Service Worker 是位于 Web 应用和网络之间的 JavaScript 代码,可能会拦截传出网络请求并为其生成响应。

从历史上看,范围请求和服务工作器无法很好地协同工作。您必须采取特殊措施,以避免 Service Worker 出现不良结果。幸运的是,这种情况正在开始改变。在表现出正确行为的浏览器中,范围请求在通过 Service Worker 时会“正常运行”。

假设有一个服务工件,其中包含以下 fetch 事件监听器,该监听器会接收每个传入请求并将其传递给网络:

self.addEventListener('fetch', (event) => {
  // The Range: header will not pass through in
  // browsers that behave incorrectly.
  event.respondWith(fetch(event.request));
});

在存在错误行为的浏览器中,如果 event.request 包含 Range: 标头,系统会静默地舍弃该标头。远程服务器收到的请求根本不会包含 Range:。这不一定会破坏任何内容,因为从技术层面讲,服务器可以返回包含 200 状态代码的完整响应正文,即使原始请求中存在 Range: 标头也是如此。但这会导致传输的数据超出浏览器严格需要的数据量。

了解此行为的开发者可以通过明确检查是否存在 Range: 标头来规避此问题,如果存在,则不调用 event.respondWith()。这样一来,Service Worker 便可有效地将自己从响应生成画面中移除,并改为使用知道如何保留范围请求的默认浏览器网络逻辑。

self.addEventListener('fetch', (event) => {
  // Return without calling event.respondWith()
  // if this is a range request.
  if (event.request.headers.has('range')) {
    return;
  }

  event.respondWith(fetch(event.request));
});

不过,可以肯定的是,大多数开发者都不知道需要这样做。而且,我们也不清楚为什么需要这样做。最终,这一限制是由于浏览器需要跟上底层规范的变化,后者增加了对此功能的支持。

修复了哪些问题?

行为正常的浏览器会在将 event.request 传递给 fetch() 时保留 Range: 标头。这意味着,我最初示例中的服务工作器代码将允许远程服务器查看 Range: 标头(如果它是由浏览器设置的):

self.addEventListener('fetch', (event) => {
  // The Range: header will pass through in browsers
  // that behave correctly.
  event.respondWith(fetch(event.request));
});

现在,服务器有机会正确处理范围请求,并返回包含 206 状态代码的部分响应。

哪些浏览器的行为正常?

最新版本的 Safari 具有正确的功能。从 87 版开始,Chrome 和 Edge 也会正常运行。

截至 2020 年 10 月,Firefox 尚未修复此行为,因此在将服务工件的代码部署到正式版时,您可能仍需要考虑这一点。

如需确认给定浏览器是否已更正此行为,最好的方法是查看“网站平台测试”信息中心的“在网络请求中添加范围标头”行。

如何从缓存中提供范围请求?

Service Worker 不仅可以将请求传递给网络,还可以执行更多操作。一个常见的用例是将音频和视频文件等资源添加到本地缓存中。然后,Service Worker 可以从该缓存中执行请求,完全绕过网络。

所有浏览器(包括 Firefox)都支持在 fetch 处理程序中检查请求、检查是否存在 Range: 标头,然后使用来自缓存的 206 响应在本地执行请求。不过,用于正确解析 Range: 标头并仅返回完整缓存响应的适当段的 Service Worker 代码并非易事。

幸运的是,需要帮助的开发者可以求助 Workbox,这是一组可简化常见服务工作器用例的库。workbox-range-request module 会实现直接从缓存中传送部分响应所需的所有逻辑。如需查看此用例的完整方案,请参阅 Workbox 文档

此帖子的主打图片由 Unsplash 用户 Natalie Rhea Riggs 提供。