Como processar solicitações de intervalo em um service worker

Verifique se o service worker sabe o que fazer quando uma resposta parcial é solicitada.

Algumas solicitações HTTP contêm um cabeçalho Range:, indicando que apenas uma parte do recurso completo precisa ser retornado. Eles são normalmente usados para streaming de conteúdo de áudio ou vídeo para permitir que pequenas partes de mídia sejam carregadas sob demanda, em vez de solicitar todo o arquivo remoto de uma só vez.

Um service worker é o código JavaScript que fica entre o app da Web e a rede, possivelmente interceptando solicitações de rede enviadas e gerando respostas para elas.

Historicamente, as solicitações de intervalo e os service workers não funcionam bem juntos. É necessário tomar medidas especiais para evitar resultados ruins no seu service worker. Felizmente, isso está começando a mudar. Nos navegadores que apresentam o comportamento correto, as solicitações de intervalo "apenas funcionam" quando passam por um service worker.

Qual é o problema?

Considere um worker de serviço com o listener de eventos fetch a seguir, que recebe todas as solicitações e as transmite à rede:

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

Em navegadores com comportamento incorreto, se event.request incluísse um cabeçalho Range:, ele seria descartado silenciosamente. A solicitação recebida pelo servidor remoto não incluiria Range:. Isso não "quebra" necessariamente alguma coisa, já que um servidor tecnicamente tem permissão para retornar todo o corpo da resposta com um código de status 200, mesmo quando um cabeçalho Range: está presente na solicitação original. Mas isso resultaria na transferência de mais dados do que o necessário do ponto de vista do navegador.

Os desenvolvedores que estavam cientes desse comportamento poderiam contorná-lo verificando explicitamente a presença de um cabeçalho Range: e não chamando event.respondWith() se um estivesse presente. Ao fazer isso, o service worker se remove da imagem de geração de resposta, e a lógica de rede padrão do navegador, que sabe como preservar solicitações de intervalo, é usada.

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

Podemos dizer que a maioria dos desenvolvedores não sabia que isso era necessário. E não ficou claro por que isso é necessário. Essa limitação se deve ao fato de que os navegadores precisam acompanhar as mudanças na especificação, que adicionou suporte a essa funcionalidade.

O que foi corrigido?

Os navegadores que se comportam corretamente preservam o cabeçalho Range: quando event.request é transmitido para fetch(). Isso significa que o código do service worker no meu exemplo inicial vai permitir que o servidor remoto veja o cabeçalho Range:, se ele tiver sido definido pelo navegador:

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

Agora, o servidor tem a chance de processar corretamente a solicitação de intervalo e retornar uma resposta parcial com um código de status 206.

Quais navegadores se comportam corretamente?

As versões recentes do Safari têm o recurso correto. O Chrome e o Edge, a partir da versão 87, também funcionam corretamente.

Em outubro de 2020, o Firefox ainda não corrigiu esse comportamento. Por isso, talvez você ainda precise se preocupar com isso ao implantar o código do service worker na produção.

Verificar a linha "Include range header in network request" (Incluir cabeçalho de intervalo na solicitação de rede) do painel de testes da plataforma da Web é a melhor maneira de confirmar se um determinado navegador corrigiu esse comportamento.

E se você quiser atender solicitações de intervalo do cache?

Os service workers podem fazer muito mais do que apenas transmitir uma solicitação para a rede. Um caso de uso comum é adicionar recursos, como arquivos de áudio e vídeo, a um cache local. Um service worker pode atender às solicitações desse cache, ignorando totalmente a rede.

Todos os navegadores, incluindo o Firefox, oferecem suporte à inspeção de uma solicitação em um gerenciador fetch, verificando a presença do cabeçalho Range: e atendendo localmente a solicitação com uma resposta 206 vinda de um cache. No entanto, o código do service worker para analisar corretamente o cabeçalho Range: e retornar apenas o segmento apropriado da resposta completa em cache não é trivial.

Felizmente, os desenvolvedores que querem ajuda podem usar o Workbox, que é um conjunto de bibliotecas que simplifica os casos de uso comuns de service workers. O workbox-range-request module implementa toda a lógica necessária para exibir respostas parciais diretamente do cache. Uma receita completa para esse caso de uso pode ser encontrada na documentação do Workbox.

A imagem principal desta postagem é de Natalie Rhea Riggs no Unsplash.