Volver al blog

Síguenos y suscríbete

Sólo disponible en inglés

Por el momento, esta página solo está disponible en inglés. Lamentamos las molestias. Vuelva a visitar esta página más tarde.

Understanding the Vary header in the browser

Andrew Betts

Principal Developer Advocate, Fastly

*El artículo que reproducimos a continuación es una adaptación de [una entrada de blog que Andrew escribió para](https://www.smashingmagazine.com/2017/11/understanding-vary-header/) Smashing Magazine.* He escrito sobre [Vary en relación con redes CDN](https://www.fastly.com/blog/getting-most-out-vary-fastly): las cachés intermediarias \(como la de Fastly\) que se pueden interponer entre tus servidores y el usuario. Los navegadores también han de entender las normas Vary y responder a estas, y la forma en que lo hacen difiere respecto del trato que Vary recibe de las CDN. En esta entrada de blog, voy a adentrarme en el turbio mundo de las variaciones de la caché *en el navegador* . Casos de uso actuales respecto de variaciones en el navegador ------------------------------------------------------------- Tal y como vimos [con anterioridad](https://www.fastly.com/blog/getting-most-out-vary-fastly/), el uso tradicional de Vary está destinado a hacer [negociaciones de contenido](https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation) con los encabezados `Accept`, `Accept-Language` y `Accept-Encoding`, los cuales han cosechado fracasos estrepitosos a lo largo de la historia. Las variaciones en `Accept-Encoding`, para entregar respuestas gzip o comprimidas por Brotli cuando hay compatibilidad, funcionan en su mayoría razonablemente bien, pero todos los navegadores admiten gzip, lo que no es un detalle muy fascinante. ¿Pero qué hay de algunas de estas ideas? * Nos interesa proporcionar imágenes que tengan exactamente la misma anchura que la de la pantalla del usuario. Si el usuario redimensiona el tamaño de su navegador, descargamos nuevas imágenes \(con lo que se obtienen variaciones respecto de client\-hints\). * Si el usuario cierra la sesión, lo que nos interesa es evitar utilizar páginas que hayan sido almacenadas en caché mientras este tenía la sesión abierta \(por medio de una cookie a modo de clave\) para limpiar la caché del navegador. * Los usuarios de navegadores que admiten el formato de imágenes WebP deberían obtener imágenes WebP; de lo contrario, deberían obtener imágenes JPEG. * Al utilizar un navegador con una pantalla de alta densidad, el usuario debe obtener una imagen de resolución ×2\. Si este desplaza la ventana del navegador a una pantalla de densidad estándar y la actualiza, se obtienen imágenes de resolución ×1\. Cachés por doquier ------------------ A diferencia de las cachés de borde, que actúan a modo de caché gigante que es compartida por todos los usuarios, el navegador está destinado a un solo usuario, pero posee multitud de cachés distintas para usos diferenciados y específicos: ![Cachés por doquier: Vary parte 2](//images.contentful.com/6pk8mg3yh2ee/4ECSZtOwYgOiS0EIES060e/2a38d0de88a218213983a75d8acf31f3/caches_all_the_way_down_-_Vary_part2.png) Algunas de estas son bastante recientes y entender con exactitud a partir de qué caché se carga el contenido es una operación compleja que no admite bien las herramientas de desarrollador. A continuación explicamos la función de estas memorias caché: * **Caché de imágenes:** memoria limitada a la página que almacena datos de imágenes decodificadas, de modo que, por ejemplo, si incluyes la misma imagen en una página varias veces, el navegador solo tiene que descargarla y decodificarla una vez. * **Caché [de precarga](https://w3c.github.io/preload):** también se limita a la página y almacena cualquier contenido que haya sido cargado previamente en un encabezado Link: o una etiqueta ``, a pesar de que el recurso no sea usualmente susceptible al almacenamiento en caché. Al igual que la caché de imágenes, la caché de precarga se destruye cuando el usuario se marcha de la página. * **[API de la caché](https://w3c.github.io/ServiceWorker/#cache) de trabajos de servicio:** dota a un servidor back end de caché de una interfaz programable, de forma que no se almacene nada aquí a menos que lo coloques específicamente ahí a través de código JavaScript en un trabajo de servicio. Además, solo será verificado si lo verificas explícitamente con un controlador de capturas de trabajos de servicio. La caché de trabajos de servicio pertenece al ámbito del origen y, aunque no tiene la garantía de ser persistente, es más persistente que la caché HTTP del navegador. * **[Caché HTTP:](http://httpwg.org/specs/rfc7234.html)** es la principal memoria caché, con la que todo el mundo está más familiarizado. Se trata de la única caché que presta atención a encabezados de caché del nivel HTTP, como `Cache-Control`, y los combina con las propias normas heurísticas del navegador para decidir si almacenar algo en caché y durante cuánto tiempo. Posee el alcance más amplio, al ser compartida por todos los sitios web; de modo que, si dos sitios no relacionados cargan el mismo activo \(p. ej., Google Analytics\), podrían compartir el mismo resultado de memoria caché. * **[Caché de inserción HTTP/2](http://httpwg.org/specs/rfc7540.html#rfc.section.10.4)** \(o "caché de push H2"\): ubicada en la conexión, almacena objetos que han sido insertados desde el servidor pero que ninguna página que esté utilizando la conexión ha solicitado aún. Está limitada a páginas que utilizan una conexión concreta —lo cual es, fundamentalmente, lo mismo que tener como ámbito un único origen—, pero también se destruye al cerrarse la conexión. De todas estas, la caché HTTP y la caché de trabajos de servicio son las que están mejor definidas. En cuanto a las cachés de imágenes y de precarga, es posible que algunos navegadores las implementen a modo de una sola "caché en memoria" asociada a la presentación de una navegación concreta, pero la idea que describo en esta entrada sigue siendo la forma adecuada de concebir el proceso. Consulta la [nota de especificaciones](https://w3c.github.io/preload/#h-note3) sobre la precarga si te interesa. En el caso de la caché de push H2, [sigue habiendo un debate activo sobre su destino](https://github.com/whatwg/fetch/issues/354). El orden en el que toda solicitud verifica estas cachés antes de adentrarse en la red es importante, porque la solicitud de un elemento podría extraerlo de las capas externas de una caché y traerlo a una capa interior. Por ejemplo, si tu servidor HTTP/2 inserta una hoja de estilos junto con una página que la necesita y esta también la precarga con una etiqueta ``, la hoja de estilos acabará abarcando tres memorias caché en el navegador. En primer lugar, reside en la caché de push H2, donde aguarda a ser solicitada. Al presentar la página y llegar a la etiqueta de precarga, el navegador extraerá la hoja de estilos de la caché de inserción *a través* de la caché HTTP \(que posiblemente la almacene, en función de su encabezado `Cache-Control`\) y la guardará en la caché de precarga. ![Vary parte 2](//images.contentful.com/6pk8mg3yh2ee/1UWIlhJUFCS84euIcGCG4e/cad25f8147558bc14c27e46649b6f403/Vary_part_2.png) Os presentamos Vary como validador ---------------------------------- Así pues, ¿qué ocurre si ante esta situación añadimos `Vary` a la ecuación? A diferencia de las cachés intermediarias \(como las CDN\), los navegadores acostumbran a **no implantar la capacidad de almacenar diversas variaciones por URL** . El principio que lo sustenta es que los fines con los que solemos utilizar `Vary` \(sobre todo, `Accept-Encoding` y `Accept-Language`\) no cambian con frecuencia dentro del contexto de un solo usuario. `Accept-Encoding` *podría* cambiar con la actualización del navegador \(aunque probablemente no sea así\), y Accept\-Language cambiaría con toda probabilidad únicamente si modifico los ajustes de configuración regional de idioma en mi SO. Además, da la casualidad de que resulta mucho más fácil implementar `Vary` de este modo, aunque hay algunos autores especializados que opinan que es un error. Aunque en la mayoría de los casos almacenar una sola variación no supone una pérdida notable para el navegador, es importante que no utilicemos por accidente una variación que haya dejado de tener validez si da la casualidad de que los datos sujetos a variación cambian. La solución intermedia es tratar a `Vary` como [validador](http://httpwg.org/specs/rfc7234.html#validation.model), no como clave. Los navegadores calculan claves de caché de la forma habitual \(básicamente, a través de la URL\) y, a continuación, si registran un resultado, verifican que la solicitud satisfaga las normas de `Vary` que vayan aparejadas a la respuesta almacenada en caché. Si no es el caso, el navegador trata la solicitud como error de la caché y pasa la siguiente capa de esta o sale a la red. Al recibir una respuesta nueva, sobrescribe la versión almacenada en caché aun cuando desde el punto de vista técnico se trate de una variación distinta. Demostración del comportamiento de Vary --------------------------------------- Para demostrar la forma en que se administra `Vary`, he llevado a cabo un [conjunto de pruebas a pequeña escala](https://vary-test.fastlydemo.net). La prueba consiste en cargar un grupo de URL distintas, que introducen variaciones respecto de diversos encabezados, y detectar si la solicitud obtuvo resultados de la caché o no. Para ello utilicé en un principio [ResourceTiming](https://developer.mozilla.org/en-US/docs/Web/API/Resource_Timing_API/Using_the_Resource_Timing_API), pero para lograr una compatibilidad más amplia acabé midiendo el tiempo que tarda la solicitud en completarse \(y añadí a propósito un retardo de un segundo a las respuestas del lado del servidor para resaltar bien la diferencia\). Examinemos todos y cada uno de los tipos de caché, la forma en que debería funcionar `Vary` y si realmente funciona así. Respecto de cada prueba, en la tabla siguiente expongo si cabe esperar un resultado de la caché \(HIT —resultado— o MISS —error—\) y qué ocurrió en realidad. **Caché de precarga** : la compatibilidad con la precarga únicamente está disponible con Chrome, en cuya caché se almacenan las respuestas precargadas hasta que la página las necesite. Estas también rellenan la caché HTTP a medida que se dirigen a la caché de precarga, en caso de que sean susceptibles de almacenamiento en caché HTTP. Ante la imposibilidad de especificar encabezados de solicitud de modo que tengan una precarga y dado que la caché de precarga dura tanto como dure la página, resulta difícil hacer una prueba al respecto; sin embargo, se puede al menos verificar que los objetos que presenten un encabezado de Vary sean precargados correctamente: [![Vary parte 2 link preload](//images.contentful.com/6pk8mg3yh2ee/3V8PQSge7uioeKAuiMG8yQ/16248dd51f3114a057447db5f589570e/Vary_part_2_link_preload.png)](https://vary-test.fastlydemo.net/#preload) **API de la caché de trabajos de servicio** : Chrome y Firefox admiten los trabajos de servicio. Además, al desarrollar la especificación de trabajos de servicio, los autores pretendían corregir lo que interpretaron como implementaciones desbaratadas en los navegadores con el fin de lograr que `Vary` funcionara en estos de una forma parecida a las redes CDN. Esto quiere decir que, aunque el navegador debería almacenar una sola variación en la caché HTTP, se supone que debería conservar múltiples variaciones en la API de caché. Firefox \(versión 54\) hace lo propio correctamente, mientras que Chrome utiliza la misma lógica de "Vary entendido como validador" que utiliza para la caché HTTP \([presentación de error CRBug](https://bugs.chromium.org/p/chromium/issues/detail?id=756796)\). [![Vary parte 2 caché de trabajos de servicio](//images.contentful.com/6pk8mg3yh2ee/4yDTEeNLiMY4KUIyYCk6AM/39a49d4a39eca78d3048a458660d2114/Vary_part_2_serviceworker_cache.png)](https://vary-test.fastlydemo.net/#sw-cache) **Caché HTTP** : la memoria caché HTTP principal debe cumplir con `Vary` y lo hace de forma sistemática \(en calidad de validador\) con todos los navegadores. Para obtener mucha más información al respecto, consulta el artículo [Retorno al estado de almacenamiento en caché en los navegadores](https://www.mnot.net/blog/2017/03/16/browser-caching), de Mark Nottingham. **Caché de inserción HTTP/2** : aunque debe cumplir con `Vary`, en la práctica ningún navegador realmente lo respeta; a los navegadores no les suele importar emparejar respuestas insertadas con solicitudes que presentan valores aleatorios en encabezados respecto de los que las respuestas hacen variaciones ni consumirlas. [![Vary parte 2 caché de push h2](//images.contentful.com/6pk8mg3yh2ee/39qYr9sx56w8WgqySGWWoG/d305f745406bab335cb422c396fe9c86/Vary_part_2_h2_push.png)](https://vary-test.fastlydemo.net/#h2push) El giro 304\-Not\-Modified \(sin modificar\) ------------------------------------------------ El estado de respuesta **HTTP 304 “Not Modified”** me parece fascinante. Nuestro estimado líder, Artur Bergman, hizo esta valiosa apreciación en las [especificaciones del almacenamiento en caché de HTTP](http://httpwg.org/specs/rfc7232.html#status.304) \(la negrita es mía\): > > *El servidor que genere cualquier respuesta del tipo 304 DEBE OBLIGATORIAMENTE generar cualquiera de los siguientes campos de encabezado que habrían sido enviados en una respuesta del tipo 200 \(estado correcto\) a dicha solicitud: Cache\-Control, Content\-Location, Date, ETag, Expires y **Vary** .* ¿Por qué una respuesta del tipo 304 iba a devolver un encabezado Vary? La cosa se complica cuando uno lee sobre los pasos que supuestamente han de seguirse al recibir una respuesta 304 que contenga esos encabezados: > > *Si se selecciona para su actualización una respuesta almacenada, la caché DEBE OBLIGATORIAMENTE \[...\] utilizar otros campos de encabezado facilitados en la respuesta 304 \(No modificado\) para reemplazar todas las instancias de los campos de encabezado correspondientes de la respuesta almacenada.* Espera, ¿qué significa esto? Que, si el encabezado `Vary` de la respuesta 304 y el que figura en el objeto existente almacenado en caché son diferentes, se supone que debemos actualizar el objeto en caché; pero ¡eso podría indicar que ha dejado de concordar con la solicitud que hicimos\! En ese hipotético caso, a primera vista, **la respuesta 304 parece estar diciéndonos que la versión almacenada en caché puede utilizarse y, al mismo tiempo, lo contrario** . Naturalmente, si el servidor no hubiera querido que utilizaras la versión almacenada en caché, este habría enviado una respuesta 200, no 304; así que está claro que la versión almacenada en caché debe utilizarse, pero, tras aplicarle las actualizaciones, quizás no vuelva a utilizarse para una futura solicitud que sea idéntica a la que realmente rellenó la caché al principio. *\(Nota: en Fastly no respetamos esta anomalía de las especificaciones; de modo que, si recibes una respuesta 304 de tu servidor de origen, seguiremos utilizando el objeto almacenado en caché sin modificar, en lugar de restablecer el TTL\).* Los navegadores [sí parecen respetar esto](https://vary-test.fastlydemo.net/#304-nomatch), aunque con una peculiaridad: no solo actualizan los encabezados de respuesta, sino también los encabezados de la solicitud que se corresponden con aquellos, de modo que garantizan que, tras la actualización, la respuesta almacenada en caché concuerde con la solicitud actual. Lo cual me parece que tiene sentido. Puesto que las especificaciones no mencionan esto, los navegadores tienen libertad para hacer lo que les parezca; sin embargo, por suerte, todos los navegadores presentan el mismo comportamiento. Sugerencias de cliente \(Client Hints\) ----------------------------------------- La función [Client Hints](http://httpwg.org/http-extensions/client-hints.html) que ofrece Google es una las novedades más significativas que han aparecido en mucho tiempo y que influyen en Vary en el contexto del navegador. A diferencia de `Accept-Encoding` y `Accept-Language`, Client Hints describe valores que bien podrían cambiar con regularidad a medida que el usuario navega por el sitio web; específicamente, se trata de los siguientes: * **DPR** : proporción de píxeles del dispositivo o densidad de píxeles de la pantalla \(posiblemente varíe si el usuario tiene varias pantallas\). * **Save\-Data** : si el usuario ha activado el modo data\-saving \(ahorro de datos\). * **Viewport\-Width** : anchura de píxeles de la ventanilla actual. * **Width** : anchura deseada del recurso en términos de píxeles físicos. Estos valores podrían cambiar respecto de un solo usuario; además, el intervalo de valores correspondiente a los valores relativos a la anchura es amplio. Por tanto, aunque podemos utilizar `Vary` con estos encabezados, nos arriesgamos a restar eficacia a nuestra caché e incluso a dejar inoperativo el almacenamiento en caché. Propuesta de encabezado Key --------------------------- A pesar de la larga vida que ha tenido el encabezado `Vary`, en la actualidad existe una propuesta para reemplazarlo con un encabezado de nuevo cuño llamado [Key](http://httpwg.org/http-extensions/key.html). Veamos un par de ejemplos: Key: Viewport-Width;div=50 Con esta línea se quiere decir que la respuesta varía en función del valor del encabezado de la solicitud `Viewport-Width`, pero redondeado a la baja al múltiplo de 50 píxeles más cercano. Key: cookie;param=sessionAuth;param=flags Si se añade este encabezado a una respuesta, introducimos variaciones respecto de dos cookies concretas: `sessionAuth` y `flags`. Si no han sido modificadas, podemos reutilizar esta respuesta para una futura solicitud. Así pues, las principales características que diferencian a Key de `Vary` son las siguientes: * `Key` permite variaciones respecto de **subcampos** existentes dentro de encabezados, con lo que de repente resulta viable introducir variaciones respecto de cookies, ya que es posible hacer esto respecto de una sola cookie. Se da pie así a la posibilidad de hacer variaciones respecto de prácticamente cualquier dato arbitrario, como por ejemplo *si el usuario ha iniciado sesión* . * Se pueden **crear depósitos en intervalos** con valores concretos para incrementar la amplitud del cambio de un resultado de caché, lo cual es especialmente útil para hacer variaciones respecto de aspectos como la anchura de ventanilla. * Todas las variaciones que posean la misma URL deben tener obligatoriamente el mismo encabezado `Key`; así, si una caché recibe una respuesta nueva respecto de una URL en relación con la cual ya tiene algunas variantes existentes —y el valor del encabezado Key de la nueva respuesta no coincide con los valores de estas—, todas las variantes deberán ser expulsadas de la caché. En el momento de publicación de este artículo, no hay navegadores ni redes CDN que sean compatibles con `Key`, aunque en algunas CDN quizás se pueda lograr el mismo efecto dividiendo los encabezados entrantes en varios encabezados privados e introduciendo variaciones respecto de estos \(si deseas aprender a hacer esto, [consulta este otro artículo](https://www.fastly.com/blog/getting-most-out-vary-fastly)\); por tanto, los navegadores constituyen el principal ámbito en el que Key puede causar sensación. El requisito de que todas las variaciones tengan la misma receta de Key es un tanto restrictivo; además, me gustaría que [las especificaciones incluyeran alguna clase de opción de "salida anticipada"](https://github.com/httpwg/http-extensions/issues/232). Se posibilitarían así "variaciones respecto del estado de autenticación y, en caso de haber iniciado sesión, respecto de preferencias". Propuesta sobre variantes ------------------------- Aunque `Key` es un estupendo mecanismo genérico, algunos encabezados tienen normas respecto de sus valores que son más complejas; la comprensión de la semántica de estos posiblemente nos ayude a encontrar formas automatizadas de reducir la variación de la caché. Pongamos que recibes dos solicitudes con dos valores distintos respecto de `Accept-Language` —`en-gb` \(inglés Reino Unido\) y `en-us` \(inglés EE. UU.—\), pero, aunque tu sitio web es verdaderamente compatible con la variación de idioma, solo tienes una variedad de "inglés". Si respondemos a la solicitud de inglés EE. UU. y esa respuesta es almacenada en caché en una CDN, aquella no se podrá reutilizar para la solicitud de inglés Reino Unido, porque el valor de Accept\-Language habría sido diferente y la caché no tiene inteligencia suficiente para distinguirlo bien. Pasemos a concretar, a bombo y platillo, la propuesta sobre [variantes](https://mnot.github.io/I-D/variants/). Esta permitiría a los servidores describir qué variantes admiten, posibilitando así que las cachés adoptaran decisiones más inteligentes sobre qué variaciones son realmente diferentes y cuáles son realmente idénticas. En la actualidad, la propuesta está en una fase muy incipiente de redacción y, dado que está concebida para prestar asistencia con `Accept-Encoding` y `Accept-Language`, su utilidad se limita más bien a cachés compartidas, como redes CDN, en vez de cachés de navegadores. Sin embargo, concuerda estupendamente con Key y completa el panorama de control mejorado de la variación de la caché. Conclusiones ------------ Hay muchos detalles que asimilar en este artículo y, aunque pueda ser interesante comprender cómo funciona el navegador por dentro, también existen determinadas ideas simples que se pueden extraer de ello: * La mayoría de los navegadores tratan a `Vary` como si fuera un validador. Si lo que deseas es almacenar en caché múltiples variaciones diferenciadas, encuentra una forma de utilizar URL distintas. * Los navegadores ignoran `Vary` y se centran en recursos insertados mediante caché de inserción HTTP/2, de modo que no introduzcas variaciones respecto de cualquier contenido que insertes. * Los navegadores poseen muchísimas memorias caché, las cuales funcionan de diversas formas. Merece la pena tratar de entender de qué modo afectan las decisiones que tomas sobre almacenamiento en caché a todas y cada una de dichas memorias, sobre todo en el contexto de `Vary`. * `Vary` no tiene tanta utilidad como podría tener, pero eso está empezando a cambiar gracias al emparejamiento de `Key` con Client Hints \(sugerencias de cliente\). Haz un seguimiento formulando una consulta al [soporte técnico del navegador](http://caniuse.com/client-hints-dpr-width-viewport) para averiguar en qué momento se podrán empezar a utilizar.