Comprender el encabezado Vary

Publicado: 2022-03-10
Resumen rápido ↬ El encabezado Vary HTTP se envía en miles de millones de respuestas HTTP todos los días. Pero su uso nunca ha cumplido con su visión original, y muchos desarrolladores malinterpretan lo que hace o ni siquiera se dan cuenta de que su servidor web lo está enviando. Con la llegada de las sugerencias para el cliente, las variantes y las especificaciones clave, las respuestas variadas están teniendo un nuevo comienzo.

El encabezado Vary HTTP se envía en miles de millones de respuestas HTTP todos los días. Pero su uso nunca ha cumplido con su visión original, y muchos desarrolladores malinterpretan lo que hace o ni siquiera se dan cuenta de que su servidor web lo está enviando. Con la llegada de las sugerencias para el cliente, las variantes y las especificaciones clave, las respuestas variadas están teniendo un nuevo comienzo.

¿Qué es variar?

La historia de Vary comienza con una hermosa idea de cómo debería funcionar la web. En principio, una URL no representa una página web, sino un recurso conceptual, como su extracto bancario. Imagine que desea ver su extracto bancario: va a bank.com y envía una solicitud GET para /statement . Hasta ahora todo bien, pero no dijo en qué formato desea la declaración. Es por eso que su navegador también incluirá algo como Accept: text/html en su solicitud. En teoría, al menos, esto significa que podría decir Accept: text/csv en su lugar y obtener el mismo recurso en un formato diferente.

Ilustración de una conversación negociada por contenido entre el usuario y el banco
(Ver versión grande)

Debido a que la misma URL ahora produce diferentes respuestas según el valor del encabezado Accept , cualquier caché que almacene esta respuesta debe saber que ese encabezado es importante. El servidor nos dice que el encabezado Accept es importante así:

 Vary: Accept

Podría leer esto como "Esta respuesta varía según el valor del encabezado Accept de su solicitud".

Básicamente , esto no funciona en la web actual. La llamada "negociación de contenido" fue una gran idea, pero fracasó. Sin embargo, esto no significa que Vary sea inútil. Una parte decente de las páginas que visita en la web tienen un encabezado Vary en la respuesta; tal vez sus sitios web también los tengan y no lo sepa. Entonces, si el encabezado no funciona para la negociación de contenido, ¿por qué sigue siendo tan popular y cómo lo manejan los navegadores? Vamos a ver.

Anteriormente escribí sobre Vary en relación con las redes de entrega de contenido (CDN), esos cachés intermediarios (como Fastly, CloudFront y Akamai) que puede colocar entre sus servidores y el usuario. Los navegadores también deben comprender y responder a las reglas de Vary, y la forma en que lo hacen es diferente de la forma en que las CDN tratan a Vary. En esta publicación, exploraré el turbio mundo de la variación de caché en el navegador.

¡Más después del salto! Continúe leyendo a continuación ↓

Casos de uso actuales para variar en el navegador

Como vimos anteriormente, el uso tradicional de Vary es realizar la negociación de contenido utilizando los encabezados Accept , Accept-Language y Accept-Encoding y, históricamente, los dos primeros han fallado estrepitosamente. Variar en Accept-Encoding para entregar respuestas comprimidas con Gzip o Brotli, donde sea compatible, en su mayoría funciona razonablemente bien, pero todos los navegadores son compatibles con Gzip en estos días, por lo que no es muy emocionante.

¿Qué tal algunos de estos escenarios?

  • Queremos mostrar imágenes que tengan el ancho exacto de la pantalla del usuario. Si el usuario cambia el tamaño de su navegador, descargaríamos nuevas imágenes (variando según las sugerencias del cliente).
  • Si el usuario cierra la sesión, queremos evitar el uso de páginas que se almacenaron en caché mientras estaba conectado (usando una cookie como Key ).
  • Los usuarios de navegadores que admitan el formato de imagen WebP deberían obtener imágenes WebP; de lo contrario, deberían obtener archivos JPEG.
  • Al usar un navegador en una pantalla de alta densidad, el usuario debe obtener imágenes 2x. Si mueven la ventana del navegador a una pantalla de densidad estándar y la actualizan, deberían obtener imágenes 1x.

Cachés hasta el final

A diferencia de los cachés perimetrales, que actúan como un caché gigantesco compartido por todos los usuarios, el navegador es solo para un usuario, pero tiene muchos cachés diferentes para usos distintos y específicos:

Ilustración de cachés en el navegador
(Ver versión grande)

Algunos de estos son bastante nuevos, y comprender exactamente desde qué caché se está cargando el contenido es un cálculo complejo que no está bien respaldado por las herramientas de desarrollo. Esto es lo que hacen estos cachés:

  • caché de imagen
    Este es un caché de ámbito de página que almacena datos de imagen decodificados, de modo que, por ejemplo, si incluye la misma imagen en una página varias veces, el navegador solo necesita descargarla y decodificarla una vez.
  • caché de precarga
    Esto también tiene un alcance de página y almacena cualquier cosa que se haya cargado previamente en un encabezado de Link o una etiqueta <link rel="preload"> , incluso si el recurso normalmente no se puede almacenar en caché. Al igual que la caché de imágenes, la caché de precarga se destruye cuando el usuario sale de la página.
  • API de caché de trabajador de servicio
    Esto proporciona un back-end de caché con una interfaz programable; por lo tanto, no se almacena nada aquí a menos que lo coloque específicamente allí a través del código JavaScript en un trabajador de servicio. También se verificará solo si lo hace explícitamente en un controlador de fetch del trabajador del servicio. La memoria caché del trabajador del servicio tiene un ámbito de origen y, aunque no se garantiza que sea persistente, es más persistente que la memoria caché HTTP del navegador.
  • caché HTTP
    Este es el caché principal con el que la gente está más familiarizada. Es el único caché que presta atención a los encabezados de caché de nivel HTTP, como Cache-Control , y los combina con las propias reglas heurísticas del navegador para determinar si almacenar algo en caché y durante cuánto tiempo. Tiene el alcance más amplio, siendo compartido por todos los sitios web; por lo tanto, si dos sitios web no relacionados cargan el mismo activo (por ejemplo, Google Analytics), es posible que compartan el mismo hit de caché.
  • Caché de inserción HTTP/2 (o "caché de inserción H2")
    Esto se encuentra con la conexión y almacena objetos que han sido empujados desde el servidor pero que aún no han sido solicitados por ninguna página que esté usando la conexión. Está limitado a páginas que usan una conexión particular, que es esencialmente lo mismo que estar limitado a un único origen, pero también se destruye cuando se cierra la conexión.

De estos, el caché HTTP y el caché del trabajador del servicio son los que mejor se definen. En cuanto a los cachés de imagen y precarga, algunos navegadores pueden implementarlos como un solo "caché de memoria" vinculado al renderizado de una navegación en particular, pero el modelo mental que describo aquí sigue siendo la forma correcta de pensar sobre el proceso. Consulte la nota de especificaciones sobre preload si está interesado. En el caso de la inserción del servidor H2, la discusión sobre el destino de este caché permanece activa.

El orden en el que una solicitud verifica estos cachés antes de aventurarse en la red es importante, porque solicitar algo puede sacarlo de una capa externa de almacenamiento en caché a una interna. Por ejemplo, si su servidor HTTP/2 inserta una hoja de estilo junto con una página que la necesita, y esa página también precarga la hoja de estilo con una etiqueta <link rel="preload"> , entonces la hoja de estilo terminará tocando tres cachés en el navegador. Primero, se ubicará en la memoria caché de inserción H2, a la espera de que se solicite. Cuando el navegador está procesando la página y llega a la etiqueta de preload , extraerá la hoja de estilo de la caché de inserción, a través de la caché HTTP (que podría almacenarla, dependiendo del encabezado Cache-Control de la hoja de estilo), y guardará en la caché de precarga.

Flujo HTTP/2 PUSH a través de cachés de navegador
(Ver versión grande)

Presentamos Vary como validador

Bien, entonces, ¿qué sucede cuando tomamos esta situación y agregamos Vary a la mezcla?

A diferencia de las cachés intermedias (como las CDN), los navegadores normalmente no implementan la capacidad de almacenar múltiples variaciones por URL . La razón de esto es que las cosas para las que normalmente usamos Vary (principalmente Accept-Encoding y Accept-Language ) no cambian con frecuencia dentro del contexto de un solo usuario. Accept-Encoding podría (pero probablemente no) cambiar con una actualización del navegador, y Accept-Language probablemente solo cambiaría si editas la configuración regional de idioma de tu sistema operativo. También resulta mucho más fácil implementar Vary de esta manera, aunque algunos autores de especificaciones creen que fue un error.

No es una gran pérdida que un navegador almacene solo una variación la mayor parte del tiempo, pero es importante que no usemos accidentalmente una variación que ya no es válida si los datos "variados" cambian.

El compromiso es tratar a Vary como un validador, no como una clave. Los navegadores calculan las claves de caché de la manera normal (esencialmente, usando la URL), y luego, si aciertan, verifican que la solicitud cumpla con las reglas Vary que se integran en la respuesta almacenada en caché. Si no es así, el navegador trata la solicitud como un error en la memoria caché y pasa a la siguiente capa de memoria caché o sale a la red. Cuando se recibe una respuesta nueva, sobrescribirá la versión almacenada en caché, aunque técnicamente es una variación diferente.

Demostración de comportamiento variable

Para demostrar la forma en que se maneja Vary , he creado un pequeño conjunto de pruebas. La prueba carga un rango de diferentes URL, que varían en diferentes encabezados, y detecta si la solicitud ha llegado al caché o no. Originalmente estaba usando ResourceTiming para esto, pero para una mayor compatibilidad, terminé cambiando solo a medir cuánto tiempo tarda en completarse la solicitud (e intencionalmente agregué un retraso de 1 segundo a las respuestas del lado del servidor para que la diferencia sea realmente clara).

Veamos cada uno de los tipos de caché y cómo debería funcionar Vary y si realmente funciona así. Para cada prueba, muestro aquí si debemos esperar ver un resultado del caché ("HIT" versus "MISS") y lo que realmente sucedió.

Precarga

Actualmente, la precarga solo se admite en Chrome, donde las respuestas precargadas se almacenan en un caché de memoria hasta que la página las necesita. Las respuestas también llenan la caché de HTTP en su camino hacia la caché de precarga, si se pueden almacenar en caché de HTTP. Debido a que es imposible especificar encabezados de solicitud con una precarga, y el caché de precarga dura solo lo que dura la página, probar esto es difícil, pero al menos podemos ver que los objetos con un encabezado Vary se precargan con éxito:

Resultados de la prueba del enlace rel=preload en Google Chrome
(Ver versión grande)

API de caché de Service Worker

Los trabajadores de servicio de soporte de Chrome y Firefox, y al desarrollar la especificación del trabajador de servicio, los autores querían corregir lo que veían como implementaciones defectuosas en los navegadores, para hacer que Vary en el navegador funcionara más como CDN. Esto significa que, si bien el navegador debe almacenar solo una variación en la caché HTTP, se supone que debe contener múltiples variaciones en la API de caché. Firefox (54) hace esto correctamente, mientras que Chrome usa la misma lógica de variación como validador que usa para el caché HTTP (se está rastreando el error).

Resultados de la prueba para el caché del trabajador del servicio en Google Chrome
(Ver versión grande)

caché HTTP

El caché HTTP principal debe observar Vary y lo hace de manera consistente (como validador) en todos los navegadores. Para mucho, mucho más sobre esto, vea la publicación de Mark Nottingham "Estado del almacenamiento en caché del navegador, revisado".

Caché de inserción HTTP/2

Se debe observar Vary , pero en la práctica ningún navegador lo respeta, y los navegadores coincidirán y consumirán felizmente las respuestas enviadas con solicitudes que llevan valores aleatorios en los encabezados en los que varían las respuestas.

Resultados de la prueba de caché de inserción H2 en Google Chrome
(Ver versión grande)

La arruga “304 (no modificada)”

El estado de respuesta HTTP "304 (no modificado)" es fascinante. Nuestro "querido líder", Artur Bergman, me señaló esta joya en la especificación de almacenamiento en caché HTTP (énfasis mío):

El servidor que genera una respuesta 304 debe generar cualquiera de los siguientes campos de encabezado que se habrían enviado en una respuesta 200 (OK) a la misma solicitud: Cache-Control , Content-Location , Date , ETag , Expires y Vary .

¿Por qué una respuesta 304 devolvería un encabezado Vary ? La trama se complica cuando lees lo que se supone que debes hacer al recibir una respuesta 304 que contiene esos encabezados:

Si se selecciona una respuesta almacenada para su actualización, la memoria caché debe \[…] usar otros campos de encabezado provistos en la respuesta 304 (No modificada) para reemplazar todas las instancias de los campos de encabezado correspondientes en la respuesta almacenada.

¿Esperar lo? Entonces, si el encabezado Vary del 304 es diferente al del objeto en caché existente, ¿se supone que debemos actualizar el objeto en caché? ¡Pero eso podría significar que ya no coincide con la solicitud que hicimos!

En ese escenario, a primera vista, el 304 parece decirle simultáneamente que puede y no puede usar la versión en caché. Por supuesto, si el servidor realmente no quisiera que usaras la versión en caché, habría enviado un 200 , no un 304 ; por lo tanto, definitivamente se debe usar la versión en caché, pero después de aplicarle las actualizaciones, es posible que no se vuelva a usar para una solicitud futura idéntica a la que realmente llenó el caché en primer lugar.

(Nota al margen: en Fastly, no respetamos esta peculiaridad de la especificación. Por lo tanto, si recibimos un 304 de su servidor de origen, continuaremos usando el objeto en caché sin modificar, además de restablecer el TTL).

Los navegadores parecen respetar esto, pero con una peculiaridad. No solo actualizan los encabezados de respuesta, sino también los encabezados de solicitud que se emparejan con ellos, para garantizar que, después de la actualización, la respuesta almacenada en caché coincida con la solicitud actual. Esto parece tener sentido. La especificación no menciona esto, por lo que los proveedores de navegadores son libres de hacer lo que quieran; afortunadamente, todos los navegadores exhiben este mismo comportamiento.

Consejos para el cliente

La función Client Hints de Google es una de las cosas nuevas más importantes que le han ocurrido a Vary en el navegador en mucho tiempo. A diferencia Accept-Encoding y Accept-Language , Client Hints describe valores que bien podrían cambiar regularmente a medida que un usuario se mueve por su sitio web, específicamente los siguientes:

  • DPR
    Proporción de píxeles del dispositivo, la densidad de píxeles de la pantalla (puede variar si el usuario tiene varias pantallas)
  • Save-Data
    Si el usuario ha habilitado el modo de ahorro de datos
  • Viewport-Width
    Ancho de píxel de la ventana gráfica actual
  • Width
    Ancho de recurso deseado en píxeles físicos

Estos valores no solo pueden cambiar para un solo usuario, sino que el rango de valores para los relacionados con el ancho es grande. Por lo tanto, podemos usar totalmente Vary con estos encabezados, pero corremos el riesgo de reducir la eficiencia de nuestra caché o incluso hacer que el almacenamiento en caché sea ineficaz.

La propuesta de encabezado clave

Las sugerencias de clientes y otros encabezados altamente granulares se prestan a una propuesta en la que Mark ha estado trabajando, llamada Clave. Veamos un par de ejemplos:

 Key: Viewport-Width;div=50

Esto dice que la respuesta varía según el valor del encabezado de solicitud Viewport-Width , ¡pero redondeado al múltiplo más cercano de 50 píxeles!

 Key: cookie;param=sessionAuth;param=flags

Agregar este encabezado a una respuesta significa que estamos variando en dos cookies específicas: sessionAuth y flags . Si no han cambiado, podemos reutilizar esta respuesta para una solicitud futura.

Entonces, las principales diferencias entre Key y Vary son:

  • Key permite variar los subcampos dentro de los encabezados, lo que de repente hace factible variar las cookies, porque puede variar solo una cookie; esto sería enorme;
  • los valores individuales se pueden agrupar en rangos , para aumentar la posibilidad de un acierto de caché, particularmente útil para variar cosas como el ancho de la ventana gráfica.
  • todas las variaciones con la misma URL deben tener la misma clave. Por lo tanto, si un caché recibe una nueva respuesta para una URL para la que ya tiene algunas variantes existentes, y el valor del encabezado de Key de la nueva respuesta no coincide con los valores de esas variantes existentes, entonces todas las variantes deben ser desalojadas del caché.

En el momento de redactar este artículo, ningún navegador o CDN es compatible con Key , aunque en algunos CDN es posible que pueda obtener el mismo efecto dividiendo los encabezados entrantes en varios encabezados privados y variando entre ellos (consulte nuestra publicación, "Cómo aprovechar al máximo Vary With Fastly”), por lo que los navegadores son el área principal donde Key puede tener un impacto.

El requisito de que todas las variaciones tengan la misma receta clave es algo limitante, y me gustaría ver algún tipo de opción de "salida anticipada" en la especificación. Esto le permitiría hacer cosas como, "Variar el estado de autenticación y, si inició sesión, también variar las preferencias".

La propuesta de variantes

Key es un buen mecanismo genérico, pero algunos encabezados tienen reglas más complejas para sus valores, y comprender la semántica de esos valores puede ayudarnos a encontrar formas automatizadas de reducir la variación de caché. Por ejemplo, imagine que llegan dos solicitudes con diferentes valores Accept-Language , en-gb y en-us , pero aunque su sitio web admite la variación de idioma, solo tiene un "inglés". Si respondemos a la solicitud en inglés de EE. UU. y esa respuesta se almacena en caché en una CDN, entonces no se puede reutilizar para la solicitud en inglés del Reino Unido, porque el valor Accept-Language sería diferente y la memoria caché no es lo suficientemente inteligente como para saberlo mejor. .

Entra, con considerable fanfarria, la propuesta Variants. Esto permitiría a los servidores describir qué variantes admiten, lo que permitiría que los cachés tomen decisiones más inteligentes sobre qué variaciones son realmente distintas y cuáles son efectivamente iguales.

En este momento, Variants es un borrador muy inicial y, debido a que está diseñado para ayudar con Accept-Encoding y Accept-Language , su utilidad se limita a cachés compartidos, como CDN, en lugar de cachés de navegador. Pero se combina muy bien con Key y completa la imagen para un mejor control de la variación de caché.

Conclusión

Hay mucho que asimilar aquí, y si bien puede ser interesante comprender cómo funciona el navegador bajo el capó, también hay algunas cosas simples que puede extraer de él:

  • La mayoría de los navegadores tratan a Vary como un validador. Si desea que se almacenen en caché múltiples variaciones separadas, encuentre una manera de usar diferentes URL en su lugar.
  • Los navegadores ignoran Vary para los recursos enviados mediante el envío del servidor HTTP/2, por lo que no varíe nada de lo que envíe.
  • Los navegadores tienen un montón de cachés y funcionan de diferentes maneras. Vale la pena tratar de comprender cómo sus decisiones de almacenamiento en caché afectan el rendimiento en cada uno, especialmente en el contexto de Vary .
  • Vary no es tan útil como podría ser, y Key emparejado con Client Hints está comenzando a cambiar eso. Siga junto con el soporte del navegador para averiguar cuándo puede comenzar a usarlos.

Avanza y sé variable.