Buscar este blog

lunes, 31 de octubre de 2016

Spring MVC prevent caching static resources

Static resources like scripts or stylesheets are very good candidates for be cached by the browser. These resources don't  change during the life of the web application version, neither the adress where they are published, so there is no need to be actively fetch them in each request.

The problem is, what happen when one of these resources do change, for example, after the deployment of a new version of the web application. In this case, the browser keeps the previous resource version and the final user can't see the changes. In order to force the browser to refresh these resources, the user has to do something like Ctrl + F5, but this is quite cumbersome for the User Call Center.

That´s what happened to one of our web applications, so I started to thinking about some, more o less dirty, solution.

My first thought was to rename the resource file after each release. For example, if I have a /resources/css/styles.css file, I would rename it to styles-1.4.0.css file. In this way, when the resource was called from a HTML, the browser would interpret that this was a new one and made a new request.
This solution can be achieved by using Maven resource plugin, with filtering. But you have to parse all your JSP files where the resource is referenced, and also you need to rename these files to something like styles-${project.version}.css. I didn´t like.

Second try, google.
The first google solution was to use a cache control directive inside the mvc:resources configuration. You configure your resources to be valid only for a short period of time, so the browser will understand that they need to be reloaded after some time.
<mvc:resources mapping="/resources/**" location="/resources/">
    <mvc:cache-control max-age="3600" cache-public="true"/>
</mvc:resources>
This is valid, but you still depends on the browser goodwill. Besides, if your static resources don't change during the version lifespan, the browser should never reload then.

The second google solution was using Spring 4. As you can see in this good post, Spring 4 can create resources's fingeprints, so the file name is attached with some sort of hash automatically created.
This is exactly what I needed, but I was working with Spring 3. My web application had also dependencies with spring security and other spring components, so migrating all this stuff could take me some time.

Finally, I decided to make a mix with the Spring 4 aproximation and use the web application version as a query parameter. In this way, from the HTML the request will be something like http://domain/app/resources/css/styles.css?v=1.4.0

I created a custom tag (resources.tag) that is responsible for composing the full URL based on the resource path and on the application version.
<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>

<%-- Parámetros --%>
<%@ attribute name="resource" required="true" description="Path del resource"%>


<spring:eval expression="@applicationProperties.getProperty('version')" var="version"/>
<spring:url value="${resource}">
 <spring:param name="v" value="${version}"/>
</spring:url>

You need to have a properties file inside the application context which holds the version key.
<context:property-placeholder properties-ref="applicationProperties" order="1" />
<bean id="applicationProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
 <property name="locations">
  <list>
   <value>classpath:conf.properties</value>
  </list>
 </property>
</bean>

And here is the conf.properties.
version = ${project.version}

(... other keys ...)

As the version property value is also a placeholder, it is Maven who sets the proper value during the build phase. You need to set your configuration in the pom.xml file.
<build>
 <resources>   
  <resource>
   <directory>src/main/resources</directory>
   <includes>
    <include>conf.properties</include>    
   </includes>
   <filtering>true</filtering>
  </resource>
 </resources>

 <plugins>
  <plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>2.3.2</version>
   <configuration>
    <source>${java-version}</source>
    <target>${java-version}</target>
   </configuration>
  </plugin> 

  <plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-resources-plugin</artifactId>
   <version>2.5</version>
   <configuration>
    <encoding>UTF-8</encoding>
   </configuration>
  </plugin>

  (... Other plugins ...)
 </plugins>
</build>

Finally, inside your JSP, when you need to reference a static resource, you only need to use your custom resources.tag:
<link rel="stylesheet" type="text/css" href='<custom:resources resource="/resources/css/styles.css"/>'/>

No hay comentarios:

Publicar un comentario