Desde un punto de vista muy técnico, cuando nos referimos a los headers de cache, nos referimos al encabezado HTTP «Cache-Control» que básicamente se encarga de dar las instrucciones necesarias para saber cómo almacenar tanto las peticiones como las respuestas.

Para simplificarlo un poco, cada vez que haces una petición a un servidor,  este, además de devolverte la información que solicitastes, ya sea un html+js+css+img o una respuesta JSON… te devolverá información adicional.

Esta información adicional, las cuales son totalmente transparentes (ocultas) para el usuario final, lo hará en forma de cabeceras (Headers) y permitirá al servidor y tu navegador tener una comunicación privada . 

Esta «conversación», que tienen tu navegador y el servidor, es totalmente bidireccional, es decir, del mismo modo que el servidor nos devuelve información extra, nuestro navegador también le proporciona información extra.

Es tan secillo como abrir el «inspeccionar elemento» de chrome y dirigirnos a «Network» filtramos por «Doc» y veremos un primer elemento con el nombre de la página o web en la que nos encontramos:

https headers
Los headers de www.drupalia.cat

Como veis, existen dos headers, los «Request Headers» y los «Response Header«, el primero es el que mandamos nosotros con la información de nuestro navegador y el segundo es el que nos manda el servidor con información para interpretar la respuesta.

No centraremos en que significan cada uno, pero si queréis podeis ver su significado aquí.

Directivas de Cache-Control

Volviendo al tema del artículo, de toda la larga lista, el que nos afecta hoy, es el Cache-Control. Este es el que se encargara de definir las directivas para los diferentes mecanismos de almacenamiento de cache.

En otras palabras, les va a decir a cada uno de los componentes que participan en el proceso del transporte de la información (Navegadores, CDN, Varnish, Nginx, Apache…) como tienen que almacenar esta información.

Aquí os paso la lista de tipos de cache-control que podemos encontrar:

Cache-Control: must-revalidate
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: public
Cache-Control: private
Cache-Control: proxy-revalidate
Cache-Control: max-age=<seconds>
Cache-Control: s-maxage=<seconds> 

Si queréis saber que hace cada una de ellas podéis ir aquí y encontraréis todo el detalle de cada uno, nosotros nos centramos en los que son más importantes, o por lo menos los más comunes.

Cache-Control: Public

Sin duda este es el más «estricto» a cuanto cache se refiere, puesto que cuando devolvemos algo con un cache-control: public, le estamos diciendo a todo el mundo que almacene esta información.

Y cuando digo a todo el mundo, es literalmente todo el mundo, cualquier componente que maneje este contenido con este header, almacenará de forma automática esta respuesta.

Este tipo de respuesta es la que generalmente nos interesa para los usuarios anónimos, pues hace que incluso nuestro navegador almacene la información y ni siquiera tenga que preguntar al servidor para recargar la página.

Pero OJO!, esto tiene trampa, si únicamente definimos public en la respuesta de cache ,le estaremos diciendo a todos que la almacene «de por vida», o al menos hasta que alguien limpie expresamente la cache (en tu navegador, tu CDN, tu servidor), tened muy presente esto.

Como norma general y como recomendación, os aconsejaria que añadierais la directiva max-age (la vemos más abajo), de esta forma al menos nos aseguraremos, que cada cierto tiempo purge su cache y vuelva a consultar de forma automática.

Cache-Control: Private

Esta directiva es la mas usada para usuarios registrados ya que nos sirve para cachear la respuesta únicamente en el navegador del usuario que hace la petición.

Esto quiere decir que ningún componente que interviene en el manejo de la información va a almacenar dicha información, es decir, que ni la CDN, ni el varnish, ni ninguna herramienta que usemos desde el servidor hasta el navegador va a almacenar esta información.

Porque existe esta directiva y que diferencia hay con la «public»? Seguro que te lo estarás preguntando y haces bien, pues aunque tengan comportamientos parecidos y ambos sirvan para almacenar información lo hacen de forma diferente.

Como vemos a simple vista la principal diferencia es que, private no permite el almacenado de información a nada que no sea el navegador, no como public que se almacena en todos los lados.

Esto nos permitirá almacenar información creada únicamente para un único usuario. Como os explicaba al principio, lo más común de esta directiva es para los usuarios registrados de nuestra web, porque nos permite almacenar información pero únicamente para ese usuario en concreto.

Veamos un ejemplo, imaginad que tenéis una web con usuarios registrados en el cual tenéis un bloque donde, vuestro queridísimo diseñador, no se le ocurrió otra mejor idea que poner el típico, «hola, Señor Luis».

Obviamente, cada usuario tendrá su propio nombre y esto hace que ese bloque, sección o incluso pagina completa, sea imposible de cachear, pues esa porción de código será diferente en función del usuario.

Lo que hacen la gran mayoría de desarrolladores es, simplemente, decir que esa página no se almacene, forzando así a tener que realizar todas las peticiones una y otra vez sobrecargando al servidor.

Esto, queda claro que no es óptimo, ni para la velocidad, ni para el servidor. Es aquí donde aparece esta directiva, la cual, aunque el servidor genere una respuesta diferente para cada usuario, durante un tiempo, este, almacenará esa información en su navegador, haciendo que no sea necesaria ninguna consulta ni al servidor ni a la base de datos.

Al igual que os explique en el apartado de public, el max-age es recomendable, si no la respuesta se almacenará «de por vida» o hasta que el usuario limpie la cache de su navegador.

Cache-Control: no-cache

Aunque, la palabra «no-cache» nos haga pensar, a priori, que esta respuesta no se va a almacenar en ningún sitio, la verdad es, que estaríamos totalmente equivocados.

Esta directiva tiene un comportamiento «parecido» a «public» pero con un importante matiz, y es que, «no-cache», SIEMPRE DEBE pasar una validación con el servidor antes de mostrar la información.

Como norma general, usando únicamente «no-cache» la respuesta seria almacenada por todos, por eso siempre la veréis acompañadas de otras directivas como, «must-revalidate», «expires: (con una fecha pasada)», «pragma:no-cache» o con «cache-control: max-age=0». Con esto lo que conseguiremos es forzar siempre que tenga que validar y refrescar nuestra cache.

No obstante, la recomendación, si lo que queremos es simplemente que no se almacene y que siempre consulte al servidor, la mejor directiva es «no-store» que la veremos ahora.

Cache-Control: no-store

La directiva «no-store», es sin duda, la única directiva que se necesita para prevenir el cacheo de las respuestas en los navegadores. Prácticamente le estaríamos diciendo al navegador, que solo guarde la información el tiempo que se esté visualizando.

El uso normal que se le da a esta directiva, por lo general, es para mostrar información muy sensible, como datos personales bancarios….

Si usamos esta directiva, las directivas «max-age=0» y «must-revalidate» carecen de sentido porque uno está implícito y el otro para usarlo necesitamos que la respuesta esté almacenada en el navegador, cosa que ya estamos diciendo que no haga.

Otro punto importante a tener en cuenta, es que «no-store» solo afecta a nivel de navegador, es decir, que es posible que haya algún componente intermedio que si almacene esta información, aunque por lo general, todos la excluyen.

 

Directivas de expiración

Las directivas de expiración nos van ayudar a complementar las directivas de cache que vimos anteriormente. Estas pueden combinarse entre sí y afectarán de una y otra manera.

  • Max-age=<segundos>: Sirve para definir a todos los componentes que almacenan información, cuánto tiempo, desde que hicieron la petición, tienen que mantener almacenada dicha información.
  • s-maxage=<segundos>: Anulala directiva «max-age» o «Expires», pero solo para las caches compartidas (Proxies, CDN…)

 

Directivas de revalidación y recarga

Estas directivas, nos sirven para negociar con los navegadores y componentes que almacenan información cuando hemos de revalidar nuestro contenido.

  • must-revalidate: Indica que una vez un recurso se vuelve obsoleto, el cache no debe usar su copia obsoleta sin correctamente validar en el servidor de origen.
  • proxy-revalidate: Similar a must-revalidate, pero solo para caches compartidos (es decir, proxies). Ignorado por caches privados.
  • inmutable: Indica que la respuesta del cuerpo no debería cambiar con el tiempo. El recurso, si no ha expirado, no ha cambiado en el servidor y por lo tanto el cliente no debería enviar una revalidación condicional por ello (es decir, If-None-Match o If-Modified-Since) para revisar por actualizaciones, incluso cuando el usuario explícitamente recarga la página. Los clientes que no son conscientes de esta extensión las ignoran de acuerdo a la especificación HTTP

Otras directivas en los headers

También existen otras directivas en los header que influyen en la cache, pero que están fuera de «cache-control», estas son:

  • ETags: El encabezado de respuesta de HTTP ETag es un identificador para una versión específica de un recurso. Permite a la memoria caché ser más eficiente, y ahorrar ancho de banda, en tanto que un servidor web no necesita enviar una respuesta completa si el contenido no ha cambiado. Por otro lado, si el contenido cambió, los etags son útiles para ayudar a prevenir actualizaciones simultáneas de un recurso de sobre-escribirlo por otro («colisiones en el aire»). +info
  • Age: El encabezado Age contiene el tiempo medido en segundos que el objeto ha estado en la memoria caché de servidor proxy. +info
  • Expires: El encabezado «Expires» contiene la fecha y hora en la que se considerará la respuesta caducada. Fechas inválidas, como el valor 0, representan una fecha en el pasado, esto significa que el recurso ya ha expirado. Si existe un encabezado Cache-Control con la directiva «max-age» o «s-max-age» en la respuesta, el encabezado Expires será ignorado. +info
  • Pragma: encabezado general HTTP / 1.0 es un encabezado específico de la implementación que puede tener varios efectos a lo largo de la cadena de solicitud-respuesta. Se utiliza para la compatibilidad con versiones anteriores de las memorias caché HTTP / 1.0 en las que el Cache-Controlencabezado HTTP / 1.1 aún no está presente. +info
  • Last-Modified: Contiene la fecha y la hora exacta de la última modificación, que el servidor, entiende que se ha modificado esa información. +info

Conclusión

Como podéis ver, existen una gran cantidad de directivas, que además se entremezclan y que según cómo las uses producen un resultado u otro. En realidad, esto es algo de lo cual, el 90% de los que desarrollan web, no tienen en cuenta en el momento del desarrollo y desde mi punto de vista es una de las cosas más importantes.

La mayoria de CMS como wordpress, drupal… tienen un sistema interno con el cual gestionan estos headers y les asignan un valor u otro en funcion de la respuesta, los plugins usados, la configuración del sitio…

Por eso es muy importante cuando elijamos un CMS y nos pongamos a instalar plugins, nos aseguremos que estos no dificulten, ni generen headers que no queramos.

A Continuación os dejo un dibujo, para ver de una forma clara, cómo y cuándo usar cada directiva.

uso header cache-control
Cache-Control Workflow

Ejemplos

# Denegamos el almacenamiento en cache de esta información
# en ningun dispositivo
Cache-Control: no-store

# Almacenamos durante un año recursos de forma temporal ( css, js...)
Cache-Control: public, max-age=31536000

# Almacenamos la informacion solamente en la cache del navegador 
# durante un tiempo
Cache-Control: private, max-age=600

# Especificamos que los clientes pueden almacenar temporalmente
# un recurso y debe revalidarse en cada ocasión antes de utilizarse.
# Esto significa que las peticiones HTTP ocurren cada vez,
# pero se pueden saltar la descarga del cuerpo HTTP si el contenido
# es válido.
Cache-Control: no-cache, max-age=0, must-revalidate