Traiter les requêtes de plage dans un service worker

Assurez-vous que votre service worker sait quoi faire lorsqu'une réponse partielle est demandée.

Certaines requêtes HTTP contiennent un en-tête Range:, qui indique que seule une partie de la ressource complète doit être renvoyée. Ils sont couramment utilisés pour le streaming de contenus audio ou vidéo afin de permettre de charger des parties plus petites de contenus multimédias à la demande, au lieu de demander l'intégralité du fichier distant en une seule fois.

Un service worker est un code JavaScript qui se trouve entre votre application Web et le réseau. Il peut intercepter les requêtes réseau sortantes et générer des réponses pour elles.

Historiquement, les requêtes de plage et les services workers ne fonctionnaient pas bien ensemble. Des mesures spéciales ont été prises pour éviter les résultats indésirables dans votre service worker. Heureusement, cette situation commence à changer. Dans les navigateurs qui présentent le comportement correct, les requêtes de plage fonctionnent simplement lorsqu'elles passent par un service worker.

Quel est le problème ?

Prenons l'exemple d'un service worker avec l'écouteur d'événement fetch suivant, qui récupère chaque requête entrante et la transmet au réseau:

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

Dans les navigateurs dont le comportement est incorrect, si event.request inclut un en-tête Range:, cet en-tête est supprimé sans notification. La requête reçue par le serveur distant n'incluait pas du tout Range:. Cela ne "casserait" pas nécessairement quoi que ce soit, car un serveur est techniquement autorisé à renvoyer le corps de la réponse complet, avec un code d'état 200, même lorsqu'un en-tête Range: est présent dans la requête d'origine. Toutefois, cela entraînerait le transfert de plus de données que ce qui est strictement nécessaire du point de vue du navigateur.

Les développeurs qui étaient au courant de ce comportement pouvaient le contourner en vérifiant explicitement la présence d'un en-tête Range: et en n'appelant pas event.respondWith() si un tel en-tête était présent. De cette manière, le service worker s'élimine effectivement de la génération de la réponse, et la logique réseau du navigateur par défaut, qui sait comment conserver les requêtes de plage, est utilisée à la place.

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));
});

On peut toutefois affirmer que la plupart des développeurs n'étaient pas conscients de cette nécessité. Et il n'était pas clair pourquoi cela devait être obligatoire. En fin de compte, cette limitation était due au fait que les navigateurs devaient rattraper les modifications apportées à la spécification sous-jacente, qui a ajouté la prise en charge de cette fonctionnalité.

Problèmes résolus

Les navigateurs qui se comportent correctement conservent l'en-tête Range: lorsque event.request est transmis à fetch(). Cela signifie que le code du service worker dans mon exemple initial permettra au serveur distant de voir l'en-tête Range:, s'il a été défini par le navigateur:

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

Le serveur a désormais la possibilité de gérer correctement la requête de plage et de renvoyer une réponse partielle avec un code d'état 206.

Quels navigateurs se comportent correctement ?

Les versions récentes de Safari disposent de la fonctionnalité appropriée. Chrome et Edge, à partir de la version 87, se comportent également correctement.

En octobre 2020, Firefox n'a pas encore corrigé ce comportement. Vous devrez peut-être donc toujours en tenir compte lorsque vous déploierez le code de votre service worker en production.

La meilleure façon de vérifier si un navigateur donné a corrigé ce comportement est de consulter la ligne "Inclure l'en-tête de plage dans la requête réseau" du tableau de bord des tests de la plate-forme Web.

Que se passe-t-il si vous diffusez des requêtes de plage à partir du cache ?

Les services workers peuvent faire bien plus que simplement transmettre une requête au réseau. Un cas d'utilisation courant consiste à ajouter des ressources, telles que des fichiers audio et vidéo, à un cache local. Un service worker peut ensuite répondre aux requêtes de ce cache, en contournant complètement le réseau.

Tous les navigateurs, y compris Firefox, permettent d'inspecter une requête dans un gestionnaire fetch, de vérifier la présence de l'en-tête Range:, puis de répondre localement à la requête avec une réponse 206 provenant d'un cache. Le code du service worker permettant d'analyser correctement l'en-tête Range: et de ne renvoyer que le segment approprié de la réponse mise en cache complète n'est toutefois pas trivial.

Heureusement, les développeurs qui souhaitent obtenir de l'aide peuvent se tourner vers Workbox, un ensemble de bibliothèques qui simplifie les cas d'utilisation courants des services workers. workbox-range-request module implémente toute la logique nécessaire pour diffuser des réponses partielles directement à partir du cache. Vous trouverez une recette complète pour ce cas d'utilisation dans la documentation Workbox.

L'image principale de ce post est de Natalie Rhea Riggs sur Unsplash.