Caching in Tomcat - SOLVED!

It took forever, but I've finally how to stop Tomcat adding a Pragma: no-cache header to any resources in a secure context. You need to set disableProxyCaching to false, so if you're using basic authentication you need a valve like:

<Valve className="org.apache.catalina.authenticator.BasicAuthenticator"  disableProxyCaching="false" />

That needs to go within the <context> definition for your web-app. So, within the META-INF (some sources say within WEB-INF?) create a context.xml file like:

<Context><Valve className="org.apache.catalina.authenticator.BasicAuthenticator"  disableProxyCaching="false" /></Context>

I found deploying as a WAR didn't work (it seemed to ignore the context.xml), but deploying the exploded files did work and that's fine by me.

Combine that with the awesome cache control filter that Danny posted in the comments and you have a very nice, easily configurable caching mechanism that doesn't cause browsers to download the CSS and JavaScript resources for every single page.


Dear lazyweb,

Does anyone have best practices for implementing correct, useful caching for a webapp running in Tomcat? I'm using container managed security and it's happily adding Pragma: No-cache to everything, including the static CSS file which is pretty brain dead. It looks like I can add a custom filter to get rid of that header and then manage caching myself, but it seems like something that everyone should have solved before so I thought I'd ask….

Thanks,

Lazy me.

12 Responses to “Caching in Tomcat - SOLVED!”

  1. Odi Says:

    Adrian,

    Web caching is a difficult task. Many who have tried to implement it, have failed. (I have personally experienced the completely broken implementation of the Magnolia CMS). The best cache I know is Squid. For caches the cache headers are extremely important. Unfortunately most content management software completely ignores these headers and provides no possibility for the content managers to control them.

    So the first thing is to get control of the cache headers. Without them, the cache can not know what to do.

    The second thing is to use Squid as a frontend to Tomcat. Do not be tempted to write your own cache. Do not be tempted to cache inside your servlet container. Maybe even set up an Apache HTTPD with mod_jk between Squid and Tomcat. Apache may give you more control over cache headers (you can rewrite them based on the URL if necessary). Apache can also help in devlivering static content like the CSS, that doesn’t need to go through your Webapp.

    Cheers

    Odi


  2. danny Says:

    Adrian,
    I’m not sure how Tomcat adds the no cache to everything, you’ll need to work that out yourself, perhaps it only doesn it if you haven’t done anything?
    The easy way to control the headers yourself is to write and configure a generic header filter in web.xml as follows:

    === web.xml ===

    NoCache
    x.y.z.filters.ResponseHeaderFilter

    Cache-Control
    no-cache, must-revalidate

    CacheForWeek
    uk.co.slc.crm.common.filters.ResponseHeaderFilter

    Cache-Control
    max-age=604800, public

    NoCache
    /*.do

    CacheForWeek
    /images/*

    CacheForWeek
    /*.js

    CacheForWeek
    /*.css

    === web.xml ===

    === x.y.z.filters.ResponseHeaderFilter ===

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
    HttpServletResponse httpResp = (HttpServletResponse)resp;

    Enumeration e = config.getInitParameterNames();

    while (e.hasMoreElements()) {
    String headerName = (String)e.nextElement();
    String headerValue = config.getInitParameter(headerName);
    httpResp.addHeader(headerName, headerValue);
    }
    chain.doFilter(req, resp);
    }

    === x.y.z.filters.ResponseHeaderFilter ===


  3. Adrian Sutton Says:

    Thanks Odi,
    I agree web caching is messed up regularly. In my particular case I’m trying to avoid causing headaches for people who need to set up caches in front of my web app and take advantage of browser caches as much as possible since it will be accessed from both sides of the pacific so anything we can do to speed it up is a good thing.

    My main focus at this stage (until actual performance data shows I need to do something more) is just to get some sane headers in place at the URL level (ie: not just Pragma: no-cache for everything). I want to avoid requiring squid or apache to do that just for ease of deployment which is why I was hoping someone had experience particularly with configuring Tomcat.

    Also, the idea of caching here isn’t to reduce workload on the server, just to reduce the amount of data that needs to be transferred to the client. So fortunately there’s no need to try and set up caching in the servlet etc, just need to get the headers right so browsers and any available proxies can cache content as needed.


  4. Adrian Sutton Says:

    Danny,
    That looks pretty much exactly like the strategy I was just starting down. From what I’ve read, Tomcat adds the Pragma no-cache whenever the resources are in a secure context which isn’t a bad default. You can set them back to null though so that should solve the problem.


  5. danny Says:

    Oops the xml has been emasculated by your blogging s’ware.
    Try this:

    <filter>
    <filter-name>NoCache</filter-name>
    <filter-class>x.y.z.filters.ResponseHeaderFilter</filter-class>
    <init-param>
    <param-name>Cache-Control</param-name>
    <param-value>no-cache, must-revalidate</param-value>
    </init-param>
    </filter>
    <filter>
    <filter-name>CacheForWeek</filter-name>
    <filter-class>x.y.z.filters.ResponseHeaderFilter</filter-class>
    <init-param>
    <param-name>Cache-Control</param-name>
    <param-value>max-age=604800, public</param-value>
    </init-param>
    </filter>

    <filter-mapping>
    <filter-name>NoCache</filter-name>
    <url-pattern>/*.do</url-pattern>
    </filter-mapping>
    <filter-mapping>
    <filter-name>CacheForWeek</filter-name>
    <url-pattern>/images/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
    <filter-name>CacheForWeek</filter-name>
    <url-pattern>/*.js</url-pattern>
    </filter-mapping>
    <filter-mapping>
    <filter-name>CacheForWeek</filter-name>
    <url-pattern>/*.css</url-pattern>
    </filter-mapping>


  6. danny Says:

    also.. the variety of special headers that control caching is large in http 1.1 and non existent in http 1.0. This [1] is a must-read. Remember that while all browsers probably support http 1.1 the same may not be assumed for proxies and caches. MSIE 6 has a setting which defaults it to use http 1.0 through proxies.

    [1]http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
    focus on sections 14.9, 14.21, 14.25, 14.28, 14.29 & 14.32 :-)


  7. How to configure cache-control in Tomcat « Dave Harris Says:

    [...] and hence the application cannot open the file. After much searching around the internet, I found this post which describes how to do [...]


  8. Myšlenky dne otce Fura » Blog Archive » Download binárního souboru přes HTTPS a Internet Explorer Says:

    [...] článek o tom, jak vypnout automatické nastavování “no-cache” v Tomcatu [...]


  9. Ralph’s TechBlog » Blog Archive » Enable Browser Caching for Tomcat’s Deliveries Says:

    [...] finer control, please have a look at Symphonious’ article on that [...]


  10. Arun Says:

    I’m having a similar problem…. I’m working on a spring based appl. This xml doesnt seem to affect the way tomcat sets cahce-control for a spring web appl.. may be i’m doing something worng, can anyone pls help..


  11. Bilal Says:

    Arun, I’m also working on a Spring based app and want to set the cache control for it. Were you able to solve this? I’d be very grateful for any tips.


  12. Arun Says:

    I wasn’t able to solve it using the XML method said here.
    I was able to set the filter in the web.xml and wrote a servlet to set the header for every response..
    I found out that http://www.xucia.com/#Resource%20Accelerate Resource Accelerate was very helpful, we just need to add the jar into the app, and make some simple changes in the web.xml.. It also caches the content in the web server also. It worked out gr8 for me..


Leave a Reply

Alternatively, subscribe to the Atom feed.