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 retornada. Elas são usadas com frequência para streaming de conteúdo de áudio ou vídeo, permitindo que partes menores de mídia sejam carregadas sob demanda, em vez de solicitar o arquivo remoto inteiro de uma só vez.

Um service worker é um código JavaScript que fica entre o app da Web e a rede, podendo interceptar solicitações de rede de saída e gerar respostas para elas.

Historicamente, as solicitações de intervalo e os service workers não funcionam bem juntos. Foi necessário tomar medidas especiais para evitar resultados ruins no 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 o comportamento incorreto, se event.request incluir um cabeçalho Range:, ele será descartado silenciosamente. A solicitação recebida pelo servidor remoto não incluiria Range:. Isso não "quebraria" necessariamente nada, já que um servidor tem permissão técnica para retornar o corpo da resposta completo, 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 a funcionalidade correta. 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. Portanto, talvez seja necessário considerar isso ao implantar o código do worker de serviço na produção.

Verificar a linha "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 worker de serviço 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. O código do worker de serviço 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 veicular 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.