Tuesday, February 24, 2009

GWT Tips 2 - nocache.js getting cached in browser

I have recently discovered that, after version deployments, our GWT application does not work in some users' browsers unless they do a hard refresh on the browser.

A bit of investigation revealed that this is because their browsers are caching the all-important YOUR_MODULE_NAME.nocache.js file. This file is GWT's bootstrapping file and its content (not its name) is likely to change as you introduce changes and then gwt-compile your Java code. If a user has an older version of this js file cached in the browser, the application will not be able to properly communicate with your server code. One should make sure that this file does not get cached by the browser nor any proxy servers along the way.

You may wonder what is the significance the of the "nocache" bit in the name. What does it actually do? Well, it does not do anything! But having this identifiable string in the filename does allow you to configure your server to send HTTP header information to the client browser to stop it from caching the file.

If you are using Apache to serve your static files. You can simply edit your .htaccess file to send the correct HTTP header information to the client for this nocache.js file.

However, if you are using a Java application server such as Tomcat to server your static files, you have to create a servlet Filter to dispatch the necessary HTTP header information. Below is a filter I have written to do exactly this:



import java.io.IOException;
import java.util.Date;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* {@link Filter} to add cache control headers for GWT generated files to ensure
* that the correct files get cached.
*
* @author See Wah Cheng
* @created 24 Feb 2009
*/
public class GWTCacheControlFilter implements Filter {

public void destroy() {
}

public void init(FilterConfig config) throws ServletException {
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,
ServletException {

HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();

if (requestURI.contains(".nocache.")) {
Date now = new Date();
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setDateHeader("Date", now.getTime());
// one day old
httpResponse.setDateHeader("Expires", now.getTime() - 86400000L);
httpResponse.setHeader("Pragma", "no-cache");
httpResponse.setHeader("Cache-control", "no-cache, no-store, must-revalidate");
}

filterChain.doFilter(request, response);
}
}



Essentially, this filter checks whether the requested file name contains the string ".nocache.", and if it does, it sets the header to tell the browser and any proxy servers along the way to not cache the file. For an excellent explanation of the different header options, check out http://code.google.com/p/doctype/wiki/ArticleHttpCaching.

Of course, you would have to add this filter to your web.xml (host-mode as well as standalone mode):


<filter>
<filter-name>gwtCacheControlFilter</filter-name>
<filter-class>com.seewah.blog.GWTCacheControlFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>gwtCacheControlFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

21 comments:

Anonymous said...

Thanks, your solution works perfectly for me

See Wah Cheng said...

No problem!

Anonymous said...

Forcing cache of ".cache." files would perfectly finish your code.

Anonymous said...

Oh,
If only I'd seen your post some time ago :(
I wasted a good few days on this until I was helped on stackOverflow.
Do you happen to know why previous versions of GWT's nocache.js were not cached and this one is?
Because I can almost swear that it used to work.
Thanks in advance

P.G. Taboada said...

I am using apache to setup compression and expiration headers:

http://bit.ly/GwtApacheConfig

Anonymous said...

The server received an if-modified-since header for nocache.js, and the server can send a 304 status code if the file is the same on client/server (it can save bandwith to partially cache this nocache.js).
Thus, is this filter necessary ?

Alex said...

Hi See,

This solution worked for us as well. I verified with Firebug that the correct headers are now sent. No more 304's! :)

Thanks a bunch.

Alex

Daniel said...

Nice solution, no more Serialization exceptions!

Milind Bharambe said...

very useful...works great.
thanks..

Abhijeet said...
This comment has been removed by the author.
Abhijeet said...

Hello,

I am getting same problem, but using IIS, do you have any suggestion from IIS point of view.

Abhijeet said...
This comment has been removed by the author.
Abhijeet said...
This comment has been removed by the author.
Abhijeet said...
This comment has been removed by the author.
Abhijeet said...
This comment has been removed by the author.
Anonymous said...

Thanks for sharing. This is the exact problem we have been facing quite often, but never digged it that deeper reach your article before.

Anonymous said...

I have a gwt bootstrap application how should i call this on start up as il not be having a request object.

Nic the NZer said...

Brilliant, thanks for sharing this!

Anonymous said...


Hai i am using jboss server and also am using the same filter code you posted that but not working its problem for my code ..how to solve that

ImmortalMan said...

Thats a nice solution, but actually GWT should do it correctly right? What do you think?

Anonymous said...

Amazing! It works! Thanks a lot!!!