Tuesday 18 March 2014

Setting HTTP Cache-Control header on Spring MVC contoller methods via annotations

I started using Spring MVC in a project and was really surprised to find that I could not specify HTTP-Cache-Control settings on my controller methods via annotations.

There are more people who miss this feature in Spring MVC and there are currently two issues open regarding this:

- https://jira.spring.io/browse/SPR-7129
- https://jira.spring.io/browse/SPR-8550

I was just about to implement this functionality myself when I found that thankfully Scott Rossillo already had: https://github.com/foo4u/spring-mvc-cache-control

Using "spring-mvc-cache-control" one simply registers a Spring MVC HandlerInterceptor in the Spring Dispatcher context file:

 <mvc:interceptors>
  <bean class="net.rossillo.spring.web.mvc.CacheControlHandlerInterceptor" />
 </mvc:interceptors>

Then one can happily annotate ones controller methods:


@Controller                                        
public final class MyTestController {

    @RequestMapping 
    @CacheControl(maxAge=300, policy = { CachePolicy.PUBLIC})
    public void test() {         
        System.out.println("In test method");   
    }
    
    @RequestMapping 
    @CacheControl(policy = { CachePolicy.NO_CACHE})
    public void test2() {         
        System.out.println("In test2 method");   
    }      
}

That's basically it - but as a bonus here are a few interesting things about caching:

1) When a user reloads the current page (e.g. hits F5) the browser will send a conditional request (using a If-Modified-Since or If-None-Match header in the request when a Last-Modified or ETag header was specified in the response) to validate the cached page has not changed. Only when a user gets to a page via a link will the browser not send a conditional request but directly display the cached page.

2) If there is not Cache-Control header but a Last-Modified header then Firefox calculates an expiration value as specified in the HTTP 1.1:
Also, if the response does have a Last-Modified time, the heuristic
expiration value SHOULD be no more than some fraction of the interval
since that time. A typical setting of this fraction might be 10%.
3) Imagine the scenario where you send your content gzip encoded to the browser and this gets cached by a proxy server. Now a different browser requests the same page from the proxy but does not support gzip encoded content. Dilemma. The solution is that one can use the Vary header to tell a proxy to cache different versions of your page depending on one or more header values specified by the browsers, e.g. Vary=Accept-Encoding