Recently I re-experienced some classic but annoying problem we web developers all have run into at least once. And like for the most of those kind of problems, we do a quick google search, find the answer, implement it and try to remember it. Sometimes we succeed doing so, sometimes some other strange problem overwrites that spot on our hard disk or like for the most of these problems and their solutions, time starts switching bits randomly in our head so we first start fail at reconstructing the total story and eventually start forgetting the whole story..
That being said, the problem I ran into was disabling the browser cache for dynamic generated html pages. That’s easy I hear you thinking and your right, it is. Only, how do you achieve it with SpringMVC 3.0?
I remembered vaguely something about cache parameters in the header of the response. So I first tried adding the following parameters in the section of my html page.
<meta http-equiv="Expires" content="0"/> <meta http-equiv="Cache-Control" content="no-cache"/> <meta http-equiv="Pragma" content="no-cache"/>
A good attempt, but all of my browsers (Safari, Chrome, Firefox – didn’t try other ones) kindly ignored me. So I googled and I learned that most of the browsers ignore these settings and even if some browser would take them into account, proxy servers never will. Proxy servers don’t read the HTML itself, they look at the HTTP headers and see the HTML as some response stream they don’t look into.
Aha! Let’s see what kind of HTTP header parameters we can set. Again my friend Google learned me:
Pragma: no-cache Expires: Thu, 01 Jan 1970 00:00:00 GMT Cache-Control: max-age=0, no-cache, no-store
So now the only problem remained how to get these parameters on the response stream of my SpringMVC 3.0 Controller objects. If you are using Spring MVC with xml bean declaration, your controllers will inherit from the org.springframework.web.servlet.support.WebContentGenerator class where you set some properties to enable the above mentioned parameters. But when you are using annotations like I do, you’ll have to figure out something else..
You could add the HttpServletResponse as a parameter to your @RequestMapping annotated method and manually add the parameters for each request. It will work. But I don’t like it. One of the ideas behind annotating the controller objects was to make unit testing easy by excluding all the HttpServletRequest/Response stuff so one doesn’t have to mock them. So I don’t want to pull them back in.
I fixed my problem by using the org.springframework.web.servlet.mvc.WebContentInterceptor. The interceptor extends the WebContentGenerator class so it has the setter methods again I need to enable the header parameters. In my spring config file I have now this:
<context:component-scan base-package="com.idevelop.abc"/>
<mvc:annotation-driven/>
<mvc:interceptors>
<bean id="webContentInterceptor" class="org.springframework.web.servlet.mvc.WebContentInterceptor">
<property name="cacheSeconds" value="0"/>
<property name="useExpiresHeader" value="true"/>
<property name="useCacheControlHeader" value="true"/>
<property name="useCacheControlNoStore" value="true"/>
</bean>
</mvc:interceptors>
Again, it works, but I don’t like it.
Now I have enabled on all the requests that are handled by SpringMVC the ‘no cache’ parameters. Not something you would like to do in a real application. E.g. some pages can be cached, some can live for e.g. 1 hour, some for a week, and so on.
To resolve this, you could extend the WebContentInterceptor and write your own interceptor and tweak your cache management based on the incoming urls. But again, I don’t like it.
What I would really like is a new annotation: @Response And I would put it on my @RequestMapping methods. After all, if your handling the request with annotations, why not the response ?
The parameters it should take could be the same as the ones in the WebContentInterceptor to start with.
Such an annotation could look like this:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface Response {
boolean useExpiresHeader = false;
boolean useCacheControlHeader = true;
boolean useCacheControlNoStore = true;
int cacheSeconds = -1;
}
What do you think? Good enough to put it as a feature request on the SpringMVC jira ?