发布日期:2020 年 10 月 6 日
某些 HTTP 请求包含 Range: 标头,表明应仅返回完整资源的一部分。它们通常用于流式传输音频或视频内容,以便按需加载较小的媒体块,而不是一次性请求整个远程文件。
Service Worker 是一种介于 Web 应用和网络之间的 JavaScript 代码,可能会拦截传出的网络请求并生成相应的响应。
从历史上看,范围请求和 Service Worker 并不兼容。您必须采取特殊步骤,以避免 Service Worker 中出现不良结果。幸运的是,这种情况正在开始改变。在行为正确的浏览器中,范围请求在通过 Service Worker 时会“正常工作”。
具体是什么问题?
假设有一个具有以下 fetch 事件监听器的 service worker,该监听器会接收每个传入的请求并将其传递给网络:
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:。这不一定会“破坏”任何内容,因为即使原始请求中存在 Range: 标头,服务器从技术上讲也允许返回完整的响应正文(带有 200 状态代码)。但从浏览器的角度来看,这会导致传输的数据量超出严格所需的量。
了解此行为的开发者可以通过显式检查是否存在 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: 标头,我初始示例中的 Service Worker 代码将允许远程服务器看到该标头:
self.addEventListener('fetch', (event) => {
// The Range: header will pass through in browsers
// that behave correctly.
event.respondWith(fetch(event.request));
});
服务器现在有机会正确处理范围请求,并返回带有 206 状态代码的部分响应。
哪些浏览器行为正确?
最新版本的 Safari 具有正确的功能。Chrome 和 Edge(自版本 87 起)的行为也正确无误。
截至 2020 年 10 月,Firefox 尚未修复此行为,因此在将 Service Worker 的代码部署到生产环境时,您可能仍需要考虑此问题。
检查 Web 平台测试信息中心的“在网络请求中包含范围标头”行是确认给定浏览器是否已修正此行为的最佳方式。
从缓存处理范围请求呢?
Service Worker 不仅可以将请求传递到网络,还可以执行更多操作。一个常见的使用情形是将资源(例如音频和视频文件)添加到本地缓存。然后,Service Worker 可以通过该缓存满足请求,完全绕过网络。
所有浏览器(包括 Firefox)都支持在 fetch 处理程序中检查请求、检查是否存在 Range: 标头,然后使用来自缓存的 206 响应在本地满足请求。不过,用于正确解析 Range: 标头并仅返回完整已缓存响应的相应部分的 Service Worker 代码并不简单。
幸运的是,需要一些帮助的开发者可以求助于 Workbox,这是一组可简化常见 Service Worker 应用场景的库。workbox-range-request module 实现直接从缓存提供部分响应所需的所有逻辑。如需查看此用例的完整配方,请参阅 Workbox 文档。