Service Worker で範囲リクエストを処理する

部分的レスポンスがリクエストされた場合の対処方法を Service Worker が把握できるようにします。

一部の HTTP リクエストには Range: ヘッダーが含まれており、リソース全体の一部のみを返す必要があることを示しています。通常は、リモート ファイル全体を一度にリクエストするのではなく、音声や動画コンテンツのストリーミングに使用し、小さなメディア チャンクをオンデマンドで読み込むことができます。

Service Worker は、ウェブアプリとネットワークの間に配置される JavaScript コードで、送信ネットワーク リクエストをインターセプトしてレスポンスを生成する可能性があります。

これまで、範囲リクエストと Service Worker は適切に連携して動作していませんでした。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.requestRange: ヘッダーが含まれている場合、そのヘッダーは自動的に破棄されます。リモート サーバーが受信したリクエストに 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.requestfetch() に渡されたときに Range: ヘッダーが保持されます。つまり、最初の例の Service Worker のコードでは、リモート サーバーが 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 ではこの動作はまだ修正されていないため、サービス ワーカーのコードを実運用環境にデプロイする際に、この動作を考慮する必要があります。

特定のブラウザでこの動作が修正されているかどうかを確認するには、ウェブ プラットフォーム テスト ダッシュボードの [ネットワーク リクエストに範囲ヘッダーを含める] 行を確認するのが最適です。

キャッシュから範囲リクエストを提供する場合はどうなりますか?

Service Worker は、リクエストをネットワークに渡すだけでなく、さまざまなことができます。一般的なユースケースは、音声ファイルや動画ファイルなどのリソースをローカル キャッシュに追加することです。Service Worker は、そのキャッシュからリクエストを処理し、ネットワークを完全にバイパスできます。

Firefox を含むすべてのブラウザでは、fetch ハンドラ内でリクエストを検査し、Range: ヘッダーの有無を確認してから、キャッシュから送られる 206 レスポンスをローカルでリクエストできます。ただし、Range: ヘッダーを適切に解析し、キャッシュに保存された完全なレスポンスの適切なセグメントのみを返すサービス ワーカー コードは簡単ではありません。

幸いなことに、開発者は Workbox を利用できます。Workbox は、Service Worker の一般的なユースケースを簡素化するライブラリ セットです。workbox-range-request module は、キャッシュから部分レスポンスを直接提供するのに必要なすべてのロジックを実装します。このユースケースの完全なレシピについては、Workbox のドキュメントをご覧ください。

この投稿のヒーロー画像は Natalie Rhea Riggs が Unsplash に投稿しました。