Entendendo o cabeçalho Vary
Publicados: 2022-03-10O cabeçalho HTTP Vary é enviado em bilhões de respostas HTTP todos os dias. Mas seu uso nunca cumpriu sua visão original, e muitos desenvolvedores não entendem o que ele faz ou nem percebem que seu servidor web o está enviando. Com a chegada das dicas de clientes, variantes e especificações-chave, respostas variadas estão começando de novo.
O que é Variar?
A história de Vary começa com uma bela ideia de como a web deveria funcionar. Em princípio, uma URL não representa uma página da web, mas um recurso conceitual, como seu extrato bancário. Imagine que você deseja ver seu extrato bancário: você bank.com
e envia uma solicitação GET
para /statement
. Até aí tudo bem, mas você não disse em qual formato deseja a declaração. É por isso que seu navegador também incluirá algo como Accept: text/html
em sua solicitação. Em teoria, pelo menos, isso significa que você poderia dizer Accept: text/csv
e obter o mesmo recurso em um formato diferente.

Como a mesma URL agora produz respostas diferentes com base no valor do cabeçalho Accept
, qualquer cache que armazene essa resposta precisa saber que esse cabeçalho é importante. O servidor nos diz que o cabeçalho Accept
é importante assim:
Vary: Accept
Você pode ler isso como "Esta resposta varia com base no valor do cabeçalho Accept
de sua solicitação".
Isso basicamente não funciona na web de hoje. A chamada “negociação de conteúdo” foi uma ótima ideia, mas falhou. Isso não significa que Vary
é inútil, no entanto. Uma parte decente das páginas que você visita na web traz um cabeçalho Vary
na resposta – talvez seus sites também os tenham, e você não sabe disso. Então, se o cabeçalho não funciona para negociação de conteúdo, por que ainda é tão popular e como os navegadores lidam com isso? Vamos dar uma olhada.
Já escrevi anteriormente sobre o Vary em relação às redes de entrega de conteúdo (CDNs), aqueles caches intermediários (como Fastly, CloudFront e Akamai) que você pode colocar entre seus servidores e o usuário. Os navegadores também precisam entender e responder às regras do Vary, e a maneira como fazem isso é diferente da maneira como o Vary é tratado pelas CDNs. Neste post, explorarei o mundo obscuro da variação de cache no navegador.
Casos de uso de hoje para variar no navegador
Como vimos anteriormente, o uso tradicional de Vary é realizar a negociação de conteúdo usando os cabeçalhos Accept
, Accept-Language
e Accept-Encoding
e, historicamente, os dois primeiros falharam miseravelmente. Variar em Accept-Encoding
para fornecer respostas compactadas com Gzip ou Brotli, quando suportado, geralmente funciona razoavelmente bem, mas todos os navegadores suportam Gzip atualmente, então isso não é muito empolgante.
Que tal alguns desses cenários?
- Queremos veicular imagens que tenham a largura exata da tela do usuário. Se o usuário redimensionar seu navegador, faremos o download de novas imagens (variando nas Dicas do Cliente).
- Se o usuário fizer logout, queremos evitar o uso de quaisquer páginas que foram armazenadas em cache enquanto estavam logadas (usando um cookie como uma
Key
). - Os usuários de navegadores que suportam o formato de imagem WebP devem obter imagens WebP; caso contrário, eles devem obter JPEGs.
- Ao usar um navegador em uma tela de alta densidade, o usuário deve obter imagens 2x. Se eles moverem a janela do navegador para uma tela de densidade padrão e atualizarem, deverão obter imagens 1x.
Caches até o fim
Ao contrário dos caches de borda, que atuam como um cache gigantesco compartilhado por todos os usuários, o navegador é apenas para um usuário, mas possui muitos caches diferentes para usos distintos e específicos:

Alguns deles são bastante novos, e entender exatamente de qual cache o conteúdo está sendo carregado é um cálculo complexo que não é bem suportado pelas ferramentas do desenvolvedor. Veja o que esses caches fazem:
- cache de imagem
Este é um cache com escopo de página que armazena dados de imagem decodificados, de modo que, por exemplo, se você incluir a mesma imagem em uma página várias vezes, o navegador só precisará fazer o download e decodificá-la uma vez. - pré-carregar cache
Isso também tem escopo de página e armazena qualquer coisa que tenha sido pré-carregada em um cabeçalhoLink
ou uma tag<link rel="preload">
, mesmo se o recurso normalmente não puder ser armazenado em cache. Assim como o cache de imagem, o cache de pré-carregamento é destruído quando o usuário sai da página. - API de cache do service worker
Isso fornece um back-end de cache com uma interface programável; portanto, nada é armazenado aqui, a menos que você o coloque especificamente lá por meio de código JavaScript em um service worker. Ele também só será verificado se você fizer isso explicitamente em um manipulador defetch
do service worker. O cache do service worker tem escopo de origem e, embora não seja garantido que seja persistente, é mais persistente que o cache HTTP do navegador. - cache HTTP
Esta é a cache principal com a qual as pessoas estão mais familiarizadas. É o único cache que presta atenção aos cabeçalhos de cache de nível HTTP, comoCache-Control
, e os combina com as próprias regras heurísticas do navegador para determinar se deve armazenar algo em cache e por quanto tempo. Tem o escopo mais amplo, sendo compartilhado por todos os sites; portanto, se dois websites não relacionados carregarem o mesmo recurso (por exemplo, Google Analytics), eles poderão compartilhar o mesmo hit de cache. - Cache push HTTP/2 (ou “cache push H2”)
Isso fica com a conexão e armazena objetos que foram enviados por push do servidor, mas ainda não foram solicitados por nenhuma página que esteja usando a conexão. Ele tem escopo para páginas usando uma conexão específica, que é essencialmente o mesmo que ter escopo para uma única origem, mas também é destruído quando a conexão é fechada.
Destes, o cache HTTP e o cache do service worker são os mais bem definidos. Quanto aos caches de imagem e pré-carregamento, alguns navegadores podem implementá-los como um único “cache de memória” vinculado à renderização de uma navegação específica, mas o modelo mental que estou descrevendo aqui ainda é a maneira correta de pensar sobre o processo. Consulte a nota de especificação no preload
-carregamento se estiver interessado. No caso do push do servidor H2, a discussão sobre o destino desse cache permanece ativa.
A ordem em que uma solicitação verifica esses caches antes de se aventurar na rede é importante, porque solicitar algo pode puxá-lo de uma camada externa de cache para uma interna. Por exemplo, se seu servidor HTTP/2 enviar uma folha de estilo junto com uma página que precisa dela, e essa página também pré-carregar a folha de estilo com uma tag <link rel="preload">
, a folha de estilo acabará tocando três caches no navegador. Primeiro, ele ficará no cache de push H2, esperando para ser solicitado. Quando o navegador estiver renderizando a página e chegar à tag de preload
-carregamento, ele puxará a folha de estilo do cache push, por meio do cache HTTP (que pode armazená-la, dependendo do cabeçalho Cache-Control
da folha de estilo) e salvará no cache de pré-carregamento.

Apresentando o Vary como um validador
OK, então o que acontece quando pegamos essa situação e adicionamos Vary à mistura?
Ao contrário dos caches intermediários (como CDNs), os navegadores normalmente não implementam a capacidade de armazenar várias variações por URL . A justificativa para isso é que as coisas que normalmente usamos para Vary
(principalmente Accept-Encoding
e Accept-Language
) não mudam com frequência no contexto de um único usuário. Accept-Encoding
pode (mas provavelmente não muda) mudar em uma atualização do navegador, e Accept-Language
provavelmente só mudaria se você editar as configurações de localidade de idioma do seu sistema operacional. Também é muito mais fácil implementar o Vary dessa maneira, embora alguns autores de especificações acreditem que isso foi um erro.
Na maioria das vezes, não é uma grande perda para um navegador armazenar apenas uma variação, mas é importante que não usemos acidentalmente uma variação que não seja mais válida se os dados “variados” mudarem.
O compromisso é tratar o Vary
como um validador, não como uma chave. Os navegadores calculam as chaves de cache da maneira normal (essencialmente, usando a URL) e, se obtiverem uma ocorrência, verificam se a solicitação atende a qualquer regra Vary incorporada à resposta em cache. Se isso não acontecer, o navegador trata a solicitação como uma falta no cache e passa para a próxima camada de cache ou para a rede. Quando uma nova resposta for recebida, ela substituirá a versão em cache, mesmo que seja tecnicamente uma variação diferente.
Demonstrando Comportamento Variado
Para demonstrar como o Vary
é tratado, fiz um pequeno conjunto de testes. O teste carrega uma variedade de URLs diferentes, variando em diferentes cabeçalhos, e detecta se a solicitação atingiu o cache ou não. Eu estava originalmente usando ResourceTiming para isso, mas para maior compatibilidade, acabei mudando para apenas medir quanto tempo a solicitação leva para ser concluída (e intencionalmente adicionei um atraso de 1 segundo às respostas do lado do servidor para tornar a diferença realmente clara).

Vejamos cada um dos tipos de cache e como o Vary
deve funcionar e se ele realmente funciona assim. Para cada teste, mostro aqui se devemos esperar ver um resultado do cache (“HIT” versus “MISS”) e o que realmente aconteceu.
Pré-carregar
No momento, o pré-carregamento é compatível apenas com o Chrome, onde as respostas pré-carregadas são armazenadas em um cache de memória até que sejam necessárias para a página. As respostas também preenchem o cache HTTP no caminho para o cache de pré-carregamento, se elas puderem ser armazenadas em cache HTTP. Como é impossível especificar cabeçalhos de solicitação com um pré-carregamento e o cache de pré-carregamento dura apenas o tempo que a página, testar isso é difícil, mas podemos pelo menos ver que objetos com um cabeçalho Vary
são pré-carregados com sucesso:

API de cache do Service Worker
O Chrome e o Firefox dão suporte aos trabalhadores de serviço e, ao desenvolver a especificação do trabalhador de serviço, os autores queriam corrigir o que viram como implementações quebradas nos navegadores, para fazer com que o Vary
no navegador funcionasse mais como CDNs. Isso significa que, embora o navegador deva armazenar apenas uma variação no cache HTTP, ele deve armazenar várias variações na API de cache. O Firefox (54) faz isso corretamente, enquanto o Chrome usa a mesma lógica varia como validador que usa para o cache HTTP (o bug está sendo rastreado).

Cache HTTP
O cache HTTP principal deve observar Vary
e faz isso de forma consistente (como um validador) em todos os navegadores. Para muito, muito mais sobre isso, veja o post de Mark Nottingham “State of Browser Caching, Revisited”.
Cache de envio HTTP/2
A Vary
deve ser observada, mas na prática nenhum navegador realmente a respeita, e os navegadores combinarão e consumirão respostas enviadas com solicitações que carregam valores aleatórios nos cabeçalhos em que as respostas estão variando.

A ruga “304 (não modificada)”
O status de resposta HTTP “304 (Não Modificado)” é fascinante. Nosso “querido líder”, Artur Bergman, apontou para mim esta joia na especificação de cache HTTP (ênfase minha):
O servidor que gera uma resposta 304 deve gerar qualquer um dos campos de cabeçalho a seguir que seriam enviados em uma resposta 200 (OK) para a mesma solicitação:
Cache-Control
,Content-Location
,Date
,ETag
,Expires
eVary
.
Por que uma resposta 304
retornaria um cabeçalho Vary
? O enredo se complica quando você lê sobre o que deve fazer ao receber uma resposta 304
que contém esses cabeçalhos:
Se uma resposta armazenada for selecionada para atualização, o cache deve \[…] usar outros campos de cabeçalho fornecidos na resposta 304 (Não modificado) para substituir todas as instâncias dos campos de cabeçalho correspondentes na resposta armazenada.
Espere o que? Então, se o cabeçalho Vary
do 304
for diferente daquele no objeto em cache existente, devemos atualizar o objeto em cache? Mas isso pode significar que não corresponde mais ao pedido que fizemos!
Nesse cenário, à primeira vista, o 304
parece estar dizendo simultaneamente que você pode e não pode usar a versão em cache. Claro, se o servidor realmente não quisesse que você usasse a versão em cache, ele teria enviado um 200
, não um 304
; portanto, a versão em cache definitivamente deve ser usada — mas depois de aplicar as atualizações a ela, ela pode não ser usada novamente para uma solicitação futura idêntica àquela que realmente preencheu o cache em primeiro lugar.
(Observação: na Fastly, não respeitamos essa peculiaridade da especificação. Portanto, se recebermos um 304
do seu servidor de origem, continuaremos a usar o objeto em cache inalterado, exceto redefinir o TTL.)
Os navegadores parecem respeitar isso, mas com uma peculiaridade. Eles atualizam não apenas os cabeçalhos de resposta, mas também os cabeçalhos de solicitação que fazem par com eles, a fim de garantir que, após a atualização, a resposta em cache corresponda à solicitação atual. Isso parece fazer sentido. A especificação não menciona isso, então os fornecedores de navegadores são livres para fazer o que quiserem; felizmente, todos os navegadores exibem esse mesmo comportamento.
Dicas do cliente
O recurso de dicas de clientes do Google é uma das novidades mais significativas que aconteceram com o Vary no navegador em muito tempo. Ao contrário Accept-Encoding
e Accept-Language
, as dicas do cliente descrevem valores que podem mudar regularmente à medida que um usuário se move em seu site, especificamente o seguinte:
-
DPR
Proporção de pixels do dispositivo, a densidade de pixels da tela (pode variar se o usuário tiver várias telas) -
Save-Data
Se o usuário ativou o modo de economia de dados -
Viewport-Width
Largura do pixel da janela de visualização atual -
Width
Largura do recurso desejada em pixels físicos
Não apenas esses valores podem mudar para um único usuário, mas o intervalo de valores para os relacionados à largura é grande. Portanto, podemos usar Vary
totalmente com esses cabeçalhos, mas corremos o risco de reduzir nossa eficiência de cache ou até mesmo tornar o cache ineficaz.
A Proposta de Cabeçalho de Chave
Dicas de cliente e outros cabeçalhos altamente granulares se prestam a uma proposta na qual Mark está trabalhando, chamada Key. Vejamos alguns exemplos:
Key: Viewport-Width;div=50
Isso diz que a resposta varia com base no valor do cabeçalho da solicitação Viewport-Width
, mas arredondada para o múltiplo mais próximo de 50 pixels!
Key: cookie;param=sessionAuth;param=flags
Adicionar esse cabeçalho a uma resposta significa que estamos variando dois cookies específicos: sessionAuth
e flags
. Se eles não foram alterados, podemos reutilizar essa resposta para uma solicitação futura.
Assim, as principais diferenças entre Key
e Vary
são:
-
Key
permite a variação de subcampos dentro de cabeçalhos, o que de repente torna viável a ativação de cookies, porque você pode ativar apenas um cookie - isso seria enorme; - valores individuais podem ser agrupados em intervalos , para aumentar a chance de um acerto de cache, particularmente útil para variar em coisas como a largura da janela de visualização.
- todas as variações com o mesmo URL devem ter a mesma chave. Portanto, se um cache receber uma nova resposta para um URL para o qual já possui algumas variantes existentes e o valor do cabeçalho
Key
da nova resposta não corresponder aos valores dessas variantes existentes, todas as variantes deverão ser removidas do cache.
No momento da escrita, nenhum navegador ou CDN suporta Key
, embora em alguns CDNs você possa obter o mesmo efeito dividindo os cabeçalhos de entrada em vários cabeçalhos privados e variando-os (consulte nossa postagem, “Obtendo o máximo de Vary With Fastly”), então os navegadores são a principal área onde o Key
pode causar impacto.
O requisito para que todas as variações tenham a mesma receita de chave é um pouco limitante, e eu gostaria de ver algum tipo de opção de “saída antecipada” na especificação. Isso permitiria que você fizesse coisas como "Ativar o estado de autenticação e, se estiver conectado, também variar as preferências".
A proposta de variantes
Key
é um bom mecanismo genérico, mas alguns cabeçalhos têm regras mais complexas para seus valores, e entender a semântica desses valores pode nos ajudar a encontrar maneiras automatizadas de reduzir a variação do cache. Por exemplo, imagine que duas solicitações chegam com valores Accept-Language
diferentes, en-gb
e en-us
, mas embora seu site tenha suporte para variação de idioma, você tem apenas um "inglês". Se respondermos à solicitação de inglês dos EUA e essa resposta for armazenada em cache em um CDN, ela não poderá ser reutilizada para a solicitação de inglês do Reino Unido, porque o valor Accept-Language
seria diferente e o cache não é inteligente o suficiente para saber melhor .
Entra, com considerável alarde, a proposta Variantes. Isso permitiria que os servidores descrevessem quais variantes eles suportam, permitindo que os caches tomassem decisões mais inteligentes sobre quais variações são realmente distintas e quais são efetivamente as mesmas.
No momento, Variants é um rascunho muito inicial e, como foi projetado para ajudar com Accept-Encoding
e Accept-Language
, sua utilidade é bastante limitada a caches compartilhados, como CDNs, em vez de caches de navegador. Mas combina muito bem com o Key
e completa a imagem para melhor controle da variação do cache.
Conclusão
Há muito o que aprender aqui e, embora possa ser interessante entender como o navegador funciona nos bastidores, também há algumas coisas simples que você pode extrair dele:
- A maioria dos navegadores trata o
Vary
como um validador. Se você quiser que várias variações separadas sejam armazenadas em cache, encontre uma maneira de usar URLs diferentes. - Os navegadores ignoram o
Vary
para recursos enviados usando o push do servidor HTTP/2, portanto, não varie em nada que você enviar. - Os navegadores têm muitos caches e funcionam de maneiras diferentes. Vale a pena tentar entender como suas decisões de cache impactam no desempenho de cada uma, principalmente no contexto de
Vary
. -
Vary
não é tão útil quanto poderia ser, eKey
emparelhado com Client Hints está começando a mudar isso. Acompanhe o suporte do navegador para descobrir quando você pode começar a usá-los.
Vá em frente e seja variável.