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!!!

sábado, 13 de septiembre de 2008

Configurar Eclipse para trabajar con Maven

Trabajo desarrollando aplicaciones web en Java y uso Eclipse para ello. Afortunadamente usamos Maven 2 en nuestros proyectos. En este post expongo la versión de Eclipse que uso y los plugins que tengo instalado para facilitarme el trabajo.

Llevo usando Eclipse desde la versión 2.x (x porque no me acuerdo exactamente cual era) y muchos han sido los cambios desde aquella versión hasta la actual: 3.4, también conocida como Ganymede. De entre todas las versiones que he probado, Ganymede es sin duda la mejor, más estable y más rápida. Esto puede parecer una perogrullada, pero te aseguro que no siempre la versión más nueva es mejor que las anteriores. Al menos no tiene por qué ser más estable.

En Eclipse dentro de una misma versión te puedes descargar distintas configuraciones: Eclipse for Java Developers, Eclipse for Java EE Developers, Eclipse Classic, ... La estabilidad de Eclipse varía mucho de una versión a otra. Me ha pasado varias veces que me he descargado la de Java o Java EE e iban muy mal y otra configuración, la Classic, iba perfectamente. Esto ocurre también en Ganymede, así que te recomiendo que te descargues e instales la versión Eclipse Classic. Lo puedes hacer en http://www.eclipse.org/downloads/.



Si trabajas con Maven, existe un plugin para Eclipse: m2eclipse. La versión estable de este plugin es paradójicamente más inestable que la de desarrollo. Al menos mi experiencia ha sido así. Por tanto, te recomiendo que te instales la versión de desarrollo de este plugin (0.9.6). Para ello basta con añadir en la lista de repositorios de Eclipse el siguiente repositorio: http://m2eclipse.sonatype.org/update-dev/ . Observa que acaba en update-dev en lugar de update. Si no sabes como se hace pulsa en Help -> Software Updates... -> Add Site... y pega la url en la ventana que se muestra.

Al ser versión de desarrollo, también tenéis que actualizar el repositorio denominado Ganymede. Borra el que ya hay y añade el este http://download.eclipse.org/releases/ganymede/staging/. Edición: Esto ya no es necesario. Ya lo han pasado a la rama principal.

Aparte de ser más estable, la versión de desarrollo incorpora el POM Editor. Con esta funcionalidad se puede editar el XML del POM (versiones, dependencias, plugins, ...) como si fuera un simple formulario. Además, te informa de todas las dependencias de tu proyectos, tanto directas como indirectas. Para poner la guinda al pastel, comentar que también es capaz de general el árbol de dependencias del proyecto. Se trata de un gráfico en el que cada artefacto maven se representa como un nodo y cada uno enlaza a los artefactos de los que depende. Dejaré unas capturas de ejemplo en cuanto pueda. La siguiente imagen es un ejemplo del dependency graph de un proyecto en el que trabajo.



Otra funcionalidad que incluye es la de Update dependencies. En versiones anteriores había que recurrir a trucos como editar el POM o deshabilitar y volver a habilitar el Gestor de Dependencias de Maven (Disable/Enable Dependency Management). Si has trabajado con Maven y Eclipse sabrás de lo que hablo. Ahora se soluciona fácilmente con esa opción.

También me gustaría comentar los otros plugins que tengo:
  • Subclipse (http://subclipse.tigris.org/update_1.4.x). Para integración con subversion. Imprescindible para proyectos en los que trabaje más de una persona. :-p
  • Freemarker (http://www.freemarker.org/eclipse/update)(http://sourceforge.net/projects/freemarker-ide/). Lo usamos como motor de plantillas.
  • SpringIDE (http://springide.org/updatesite/). Muy util si usas Spring, ya que te ayuda en la creación y configuración de ficheros XML.
  • WebTools (http://download.eclipse.org/webtools/updates/). Imprescindible para el desarrollo web.
  • Sysdeo Eclipse Tomcat Launcher plugin. Para poder arrancar un Tomcat local desde Eclipse y depurar en él. La instalación de este plugin ha de hacerse manualmente. Es decir, descomprimiendo el fichero que te bajes en la carpeta plugins de Eclipse.

Como digo, la versión Classic de Eclipse Ganymede con estos plugins me funciona bastante bien. Es rápida, estable y con muchas funcionalidades muy útiles en mi día a día. Esto no quiere decir que no falle de vez en cuando, pero sí que falla bastante menos que las anteriores versiones. Si usas versiones antiguas de Eclipse, merece la pena y mucho actualizarse a Ganymede.

jueves, 4 de septiembre de 2008

Depuración remota en Tomcat con Eclipse

La depuración es muy importante en los desarrollos software. Según recuerdo, había una teoría que decía que el 50% del tiempo de desarrollo se dedica en tareas de depuración.

Si trabajas desarrollando aplicaciones web en Java y usas Tomcat como servidor de aplicaciones, este post puede ser bastante útil para tí.

Uso Eclipse para desarrollar mis aplicaciones. Normalmente aplicaciones webs. Para desarrollar y depurar suelo tener un tomcat en mi equipo (local) y mediante el plugin para Eclipse de Sysdeo puedo arrancar el tomcat desde Eclipse y depurar el código.

Sin embargo, hay ocasiones en las que por circustancias de la vida no tienes más remedio que depurar tu aplicación corriendo en algún tomcat remoto (instalado en otro equipo). En este caso tienes dos opciones:
  1. Llenar tu aplicación de mensajes de log que saquen información suficiente para poder detectar los fallos.
  2. Seguir los pasos que expongo a continuación para depurar desde Eclipse exactamente igual que si el tomcat estuviese corriendo en local.

1. Configurar Tomcat para permitir depuración remota

Para ello tan sólo basta con añadir las siguientes opciones en la variable JAVA_OPTS de tomcat. Esta variable se define en el fichero TOMCAT_HOME/bin/catalina.sh. También se puede definier en el script de inicio/parada de tomcat /etc/init.d/tomcat.

JAVA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n"

El puerto puedes poner el que quieras. Por defecto suele ser el 8000.

Por supuesto, después de cambiar la configuración hay que reiniciar tomcat.

Una vez hecho esto, tomcat tendrá abierto el puerto 8000 para que las aplicaciones externas (como Eclipse) se puedan conectar a él e intercambiar información para poder depurar el código de forma remota.

2. Depurar de manera remota con Eclipse

Abrimos Eclipse y nos vamos a Run -> Open Debug Dialog...

Hacemos click derecho en Remote Java Application y seleccionamos New... Aparecerá una ventana como la siguiente.

Debug DialogAquí tan sólo tenemos que rellenar lo siguiente:
  • Name: es el nombre de la configuración. Puedes poner lo que quieras
  • Project: pincha en el botón Browser y selecciona el proyecto que deseas depurar. Por supuesto, esa misma aplicación tiene que estar corriendo en el tomcat remoto. De aquí únicamente se tomarán los fuentes.
  • Host: ip o nombre del servidor donde está corriendo el tomcat.
  • Port: puerto que configuraste en el paso anterior.

Una vez hecho esto pulsamos en Apply y Debug. Acto seguido Eclipse conectará con el tomcat y ya podemos poner nuestro breakpoint.


Para posteriores veces podemos conectar Eclipse con nuestro tomcat tan sólo pulsando en el botón de debug y en el nombre de nuestra configuración.



Eso es todo. Simple pero muy útil. Por cierto, que no me entere que te dejas esta configuración en algún tomcat de producción, eh? Es únicamente para desarrollo y depuración. ;-)