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

部分レスポンスをリクエストされたときにサービス ワーカーが何をすべきかを認識するようにします。

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

この投稿のヒーロー画像は、Unsplash の Natalie Rhea Riggs によるものです。