Processar solicitações de intervalo em um service worker

Publicado em: 6 de outubro de 2020

Algumas solicitações HTTP contêm um cabeçalho Range:, indicando que apenas uma parte do recurso completo deve ser retornada. Eles são usados com frequência para streaming de conteúdo de áudio ou vídeo, permitindo que pequenos trechos de mídia sejam carregados on demand, 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 funcionavam bem juntos. Foi necessário tomar medidas especiais para evitar resultados ruins no service worker. Felizmente, isso está começando a mudar. Em navegadores com o comportamento correto, as solicitações de intervalo "simplesmente funcionam" ao passar por um service worker.

Qual é o problema?

Considere um service worker com o seguinte listener de eventos fetch, que recebe todas as solicitações e as transmite para a 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 incluísse um cabeçalho Range:, ele seria descartado silenciosamente. A solicitação recebida pelo servidor remoto não vai incluir Range:. Isso não vai "quebrar" nada, já que um servidor tecnicamente pode retornar o corpo da resposta completa, 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 estritamente necessário do ponto de vista do navegador.

Os desenvolvedores que conhecem esse comportamento podem contorná-lo verificando explicitamente a presença de um cabeçalho Range: e não chamando event.respondWith() se um estiver presente. Ao fazer isso, o service worker se remove efetivamente da geração de respostas, 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));
});

É seguro dizer que a maioria dos desenvolvedores não sabia da necessidade de fazer isso. E não estava claro por que isso deveria ser necessário. Essa limitação ocorreu porque os navegadores precisavam acompanhar as mudanças na especificação subjacente, 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 funcionam 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 havia corrigido esse comportamento. Portanto, talvez seja necessário considerar isso ao implantar o código do service worker na produção.

Marcar a linha "Incluir cabeçalho de intervalo na solicitação de rede" do painel de testes da plataforma Web é a melhor maneira de confirmar se um determinado navegador corrigiu ou não esse comportamento.

E quanto a veicular 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 a solicitações desse cache, ignorando completamente a rede.

Todos os navegadores, incluindo o Firefox, permitem inspecionar uma solicitação em um gerenciador fetch, verificar a presença do cabeçalho Range: e atender localmente à solicitação com uma resposta 206 que vem 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, um conjunto de bibliotecas que simplifica 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.