martes, 30 de septiembre de 2008

Evitar caché de los navegadores al actualizar una aplicación web

Los navegadores web suelen cachear algunos ficheros que se importan desde el html (imágenes, css , js, ...). De una forma simplista, se puede decir que cuando un usuario entra por primera vez en una página web el navegador lo descarga todo, incluido imágenes, css, js, ... En las sucesivas visitas a la página únicamente descarga el html y usa los css, js e imágenes que previamente había cacheado.

El resultado es una carga más rápida de la página y un problema para el desarrollador web a la hora de actualizar la página. Si alguna vez te has preguntado cómo "limpiar" la caché de los navegadores de los usuarios visitantes, este es tu post.


Planteamiento del problema

Cuando se actualiza una aplicación web, de cara al usuario final pueden existir cambios tanto en el html de la página como en los ficheros que la complementan, típicamente css, js e imágenes. Puesto que los navegadores suelen cachear este tipo de ficheros, puede ocurrir lo siguiente:

El usuario Pepito navega tranquilamente por la web A. Su navegador cachea los css, js e imágenes. Poco después, los desarrolladores de la web A deciden realizar una actualización que implica tanto cambios en el html final como cambios en los css, js e imágenes. Pepito vuelve a entrar en la web A y su navegador descarga únicamente el html, tomando el resto de ficheros de su cache. Por lo tanto, no actualiza los css, js ni imágenes. Pepito estaría viendo el html nuevo junto con los css, js e imágenes antiguos. Probablemente esta mezcla de versiones haría que Pepito no visualizara correctamente la web.


Puedes pensar que es un caso bastante extraño. Sin embargo, ¿qué ocurre si la web tiene un sistema de cacheo por delante? Por ejemplo, un squid. Dependiendo de la aplicación web, esto puede ser desde una simple anécdota a un problema bastante grande.

En el caso del squid y siempre que lo administremos nosotros, siempre podemos limpiar la cache del mismo. ¿Pero qué ocurre con la caché de los navegadores de todos los ordenadores del mundo mundial? Podíamos pensar en usar javascript para eliminar la cache de cada usuario que entre en la página (no sé si se puede hacer :-p), pero la solución es mucho más simple que eso.

Solución al problema

La solución que propongo es cambiar la url con la que se piden los css, js e imágenes de una actualización a otra. Ni siquiera es necesario cambiar el nombre de los ficheros. Basta con añadir un parámetro a la url que cambie con cada actualización.

Por ejemplo, en lugar de pedir los ficheros así:

<link type="text/css" rel="stylesheet" href="/css/mi_css.css"/>
<script type="text/javascript" src="/js/mi_javascript.js"/>
<img src="/img/mi_imagen.gif"/>

habría que solicitarlos de la siguiente manera:

<link type="text/css" rel="stylesheet" href="/css/mi_css.css?1.0.0"/>
<script type="text/javascript" src="/js/mi_javascript.js?1.0.0"/>
<img src="/img/mi_imagen.gif?1.0.0"/>

y tras una actualización, así:

<link type="text/css" rel="stylesheet" href="/css/mi_css.css?1.1.0"/>
<script type="text/javascript" src="/js/mi_javascript.js?1.1.0"/>
<img src="/img/mi_imagen.gif?1.1.0"/>

Como podrás observar, simplemente he añadido un parámetro a las urls. Puedes poner cualquier cosa siempre y cuando cambie entre una actualización y otra. En mi caso se trata de la versión de la aplicación. En el primer caso era la versión 1.0.0 y tras actualizar la aplicación web a la versión 1.1.0, el html quedaría como el último ejemplo.

Con Maven mucho más fácil

Si usas Maven esta solución no te llevará más de unos minutos. Simplemente tienes que añadir en tus jsp's, plantillas freemarker, hojas de estilo xslt o donde sea que generes el código html, la variable pom.version y filtrar los ficheros mediante maven.

Por ejemplo, supongamos que usamos freemarker. Colocaríamos lo siguiente en nuestros ficheros:

<link type="text/css" rel="stylesheet" href="/css/mi_css.css?${pom.version}"/>
<script type="text/javascript" src="/js/mi_javascript.js?${pom.version}"/>
<img src="/img/mi_imagen.gif?${pom.version}"/>


Cada vez que generemos el war de nuestra aplicación, Maven sustituirá ${pom.version} por la versión de nuestra aplicación.

Filtrar los ficheros con Maven

Si usas Maven, lo normal es que tengas tu aplicación bajo el directorio src/main/webapp. Puedes filtrar toda esa carpeta si así lo deseas, pero no es aconsejable ya que Maven incluso aplica el filtro a las imágenes, y créeme, aplicar un filtro de texto a un fichero binario normalmente no es lo que se desea ¿verdad, Fran? ;-)

Por tanto, te recomiendo que muevas los ficheros que desees filtrar a otra carpeta. Por ejemplo, src/main/webresources. Si te has perdido y no tienes claro qué es lo que "quieres" filtrar, te recuerdo que debes filtrar todos los ficheros donde hayas puesto ${pom.version}, ya sean .ftl, .xsl, .css, .jsp, ....

Después añade la etiqueta webResources y todo su contenido en el maven-war-plugin de tu pom.xml:

[...]

<groupId>es.latascadexela</groupId>
<artifactId>blog</artifactId>
<version>1.0.0</version>

[...]

<build>

[...]

   <plugins>
      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-war-plugin</artifactId>
         <configuration>
            <webResources>
               <resource>
                  <filtering>true</filtering>
                  <directory>src/main/webresources</directory>
               </resource>
            </webResources>
         </configuration>
      </plugin>

[...]

Si quieres filtrar algún fichero situado en src/main/resources, tan sólo tendrás que poner el filtering a true en la etiqueta resources de tu pom.xml

[...]

<groupId>es.latascadexela</groupId>
<artifactId>blog</artifactId>
<version>1.0.0</version>

[...]

<build>
   <resources>
      <resource>
         <filtering>true</filtering>
         <directory>src/main/resources</directory>
      </resource>
   </resources>

[...]

Cuando se genere una nueva versión de la aplicación, en todas las urls aparecerá la versión de la misma. Al actualizar la aplicación web, los navegadores tendrán cacheados los ficheros con la url que conteniene la versión anterior. Al tratarse de una url nueva, ya que habrás subido la versión y por tanto es distinta, se descargarán los nuevos ficheros css, js e imágenes, obtienendo un html nuevo y sus correspondientes nuevas versiones de los ficheros css, js, e imágenes.

¡¡¡Problema resuelto!!!

10 comentarios:

Alejandro dijo...

Buenas, les presento el sitio www.conexionit.com una comunidad de profesionales de it. Posee un directorio de blogs de IT que queremos que sea el mas grande de habla hispana. Les pido autorización para agregar su blog al directorio. A cambio me gustaría si puedes escribir un post sobre nuestro sitio o agregar un boton a tu blog de los que hay en la sección prensa.

Saludos cordiales,
Alejandro
admin[at]conexionit[dot]com

Xela dijo...

Alejandro, me encantará formar parte de vuestra comunidad.

Tiene muy buena pinta ;-)

Leonardo dijo...

Bueno, no importa lo antiguo si no lo útil que es. Gracias por escribir, y mantener el blog.

Saludos

IEE Departamento de Mtto dijo...

Buen tip...

Henrri Trujillo Romero dijo...

Tengo que poner a cada url de js o css, en mi caso utilizo muchos js propias... como lo haria

gblumen dijo...

simplemente puedes usar meta tags, y te evitas estar corrigiendo cada ruta de archivo, los meta tags necesarios son:

meta http-equiv="Pragma" content="no-cache"

meta http-equiv="expires" content="0" /

Alex Guerra (Xela) dijo...

Gracias gblumen. La idea no es no usar cache. La caché es bueno, sobre todo para archivos que cambian poco como son las hojas de estilo. Se trata de que cada vez actualices esos archivos todo el mundo tenga la nueva versión de dichos ficheros de forma inmediata. Además, usando maven y la variable ${pom.version} todo es automático ;)

gblumen dijo...

Alex, muchos desarrolladores no usamos maven,pero si tenemos problemas con la caché y las actualizaciones, tengo una pregunta usando ese metodo de querystring que proponen, y si hubieron bastante cambios en un periodo corto de tiempo, esto no originaria que se llene la cache del navegador de los usuarios? que ocurriria en ese caso?, gracias y saludos

diosmetal dijo...

Muy Interesante.

Saludos.

Alan dijo...

Excelente post amigo! me ahorra muchos dolores de cabeza de cara a la planificación de actualizaciones de mi web, con esto no tengo que estresarme tanto cuando quiero introducir cambios en el diseño.