Search code examples
javaspringjbossprimefacesspring-webflow

What is setting Cache-Control no-cache, no-store in my deployment?


I have an issue that my application deployment always returns response headers with:

Cache-Control: no-cache
Cache-Control: no-store
Expires:Thu, 01 Jan 1970 00:00:00 GMT
Pragma:no-cache



I'm using:

Spring 3.1.2.RELEASE

Primefaces JSF 3.4.1

Spring Webflow 2.3.0.RELEASE

JBoss AS 7.0.1



I've tried nearly every solution on the application side I could find:

  1. Configuring the WebContentInterceptor (tried various permutations of it) Right out of the box cache-control header filter?
  2. Writing a custom interceptor that adds different Cache-Control header (tested with Cache-Control: private)
  3. Writing a customer filter that adds HTTP response parameters. Configure it with Cache-Control: private as init-params in web.xml
  4. Use the context.xml file (tried both in META-INF/ and WEB-INF/) to disable the Cache-Control in JBoss/Tomcat http://daveharris.wordpress.com/2007/07/09/how-to-configure-cache-control-in-tomcat/

In all of the above cases, the response headers never ended up different, always no-cache, no-store, 1970 expires, pragma: no-cache

I'm running out of ideas, does anyone know what is setting these headers in my response so I can target the appropriate deployment component to resolve this?


Solution

  • The root code causing this is in Spring MVC, called from the WebContentGenerator. This class is used as base class for several classes in the MVC/Webflow stack: WebContentInterceptor (MVC interceptor), AbstractController (MVC controller), AbstractHandlerMethodAdapter (MVC HandlerAdapter), AnnotationMethodHadlerAdapter (MVC HandlerAdapter), FlowHandlerAdapter (Webflow HandlerAdapter), JsfFlowHandlerAdapter (Webflow + JSF HandlerAdapter)

    The CacheControl seconds setting of 0 calls the preventCaching method. So it seems the application is defaulting to a setting of 0.

    org.springframework.web.servlet.support.WebContentGenerator

    protected final void preventCaching(HttpServletResponse response) {
        response.setHeader(HEADER_PRAGMA, "no-cache");
        if (this.useExpiresHeader) {
            // HTTP 1.0 header
            response.setDateHeader(HEADER_EXPIRES, 1L);
        }
        if (this.useCacheControlHeader) {
            // HTTP 1.1 header: "no-cache" is the standard value,
            // "no-store" is necessary to prevent caching on FireFox.
            response.setHeader(HEADER_CACHE_CONTROL, "no-cache");
            if (this.useCacheControlNoStore) {
                response.addHeader(HEADER_CACHE_CONTROL, "no-store");
            }
        }
    }
    

    I found out that since I am using JSF + Webflow, the JsfFlowHandlerAdapter is handling the server requests for the flows/views first. This is why configuring interceptors does not help because the JsfFlowHandlerAdapter has already set the Cache-Control and other HTTP Headers at this point. It turns out I had already extended the JsfFlowHandlerAdapter to handle FlowExecutionRestorationFailureException (see Sping Web Flow Preventing Back Button Use) so all I needed to do was set the configuration I wanted ala WebContentInterceptor (since the configurations belong to the base class WebContentGenerator).

    Custom JsfFlowHandlerAdapter

    public class MyAppFlowHandlerAdapter extends org.springframework.faces.webflow.JsfFlowHandlerAdapter {
         ...
        }
    

    webmvc-config.xml

    <!-- Dispatches requests mapped to flows to FlowHandler implementations -->
        <bean
            class="com.myapp.MyAppFlowHandlerAdapter">
            <property name="flowExecutor" ref="flowExecutor" />
                <!-- Disable built in Cache-Control settings -->
            <property name="cacheSeconds" value="-1" />
            <property name="useExpiresHeader" value="false" />
            <property name="useCacheControlHeader" value="false" />
            <property name="useCacheControlNoStore" value="false" />
        </bean>
    
    <!-- Maps request paths to flows in the flowRegistry; e.g. a path of /hotels/booking 
        looks for a flow with id "hotels/booking" -->
    <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
        <!-- snip out unimportant -->
        <property name="interceptors">
            <list>
                <ref bean="cacheControlInterceptor" />  
            </list>
        </property>
    </bean>
        <bean id="cacheControlInterceptor"
        class="com.myapp.CacheControlInterceptor">
    

    CacheControlInterceptor (to set your own HTTP Headers. The methods that do it in WebContentGenerator are final so cannot @Override)

    public class CacheControlInterceptor extends HandlerInterceptorAdapter {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                //Example below: set your Cache-Control, expires, pragma headers here
            response.setHeader("Cache-Control", "private");
    
            return true;
        }
    }