在 Service Worker 中處理範圍要求

發布日期:2020 年 10 月 6 日

部分 HTTP 要求包含 Range: 標頭,表示只應傳回完整資源的一部分。這類要求通常用於串流音訊或影片內容,可讓系統視需要載入較小的媒體區塊,而非一次要求整個遠端檔案。

Service Worker 是位於網路應用程式和網路之間的 JavaScript 程式碼,可能會攔截傳出的網路要求,並為這些要求產生回應。

從前,範圍要求和服務工作站無法順利搭配運作。您必須採取特殊步驟,避免服務工作人員發生不良結果。幸好,這種情況已開始改變。在行為正確的瀏覽器中,範圍要求會通過 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:。這不一定會「中斷」任何項目,因為從技術上來說,即使原始要求中含有 Range: 標頭,伺服器仍可傳回完整的回應主體,並附上 200 狀態碼。但從瀏覽器的角度來看,這會導致傳輸的資料量超出嚴格需求。

瞭解這項行為的開發人員可以明確檢查 Range: 標頭是否存在,如果存在,則不呼叫 event.respondWith(),藉此規避這項行為。這樣一來,服務工作人員就會從回應產生程序中移除自身,改用預設的瀏覽器網路邏輯 (可保留範圍要求)。

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 近期版本功能正常。Chrome 和 Edge 87 以上版本也會正常運作。

截至 2020 年 10 月,Firefox 尚未修正這項行為,因此將服務工作人員程式碼部署至正式環境時,您可能仍需將此問題納入考量。

如要確認特定瀏覽器是否已修正這項行為,最好的方法是查看網路平台測試資訊主頁的「Include range header in network request」(在網路要求中加入範圍標頭) 列。

從快取提供範圍要求呢?

服務工作人員的功能不只是將要求傳遞至網路,常見用途是在本機快取中新增音訊和影片檔案等資源。服務工作人員接著就能從該快取滿足要求,完全略過網路。

所有瀏覽器 (包括 Firefox) 都支援檢查 fetch 處理常式內的要求、檢查 Range: 標頭是否存在,然後使用來自快取的 206 回應在本機滿足要求。不過,要正確剖析 Range: 標頭,並只傳回完整快取回應的適當區隔,並非易事。

幸好,開發人員可以求助於 Workbox,這是一組程式庫,可簡化常見的 Service Worker 用途。workbox-range-request module 會實作所有必要邏輯,直接從快取提供部分回應。如需這個用途的完整方案,請參閱 Workbox 說明文件