Setting Cache Headers from JBoss

Home/ATG, JBoss/Setting Cache Headers from JBoss

Having control over the HTTP response headers allows you to set cache related headers in responses for various content you’d like cached on the browser (or an intermediary proxy). I created the ATG Cache Control DAS pipeline Servlet a year ago, but when you’re using JBoss you need another solution.

Since the DAF pipeline is only executed for JSPs the pipeline Servlet it doesn’t allow you to set headers for the static media items you’re more likely to want to cache. I created a Servlet Filter which allows you to set cache headers in JBoss based on URI patterns. It doesn’t allow the same fine grained control that the old pipeline Servlet does, but it should work for most situations.

Servlet Filters are very similar to ATG pipeline Servlets in that they are executed within the lifecycle of a request and can read the request and modify the response. The filter I created gets configured from the web.xml and sets the response headers relating to caching. You can configure different instances of the filter for each cache time you need, an hour, a day, or a week, and map different URL patterns to the appropriate instances.

I’ve added the filter into Foundation, the open source ATG e-commerce framework project, which is hosted at the Spark::red Development Community.

The Servlet filter code looks like this:

package org.foundation.servlet.filter;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

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.HttpServletResponse;

/**
 * The Class CacheHeaderFilter.
 * 
 * @author Devon Hillard
 */
public class CacheHeaderFilter implements Filter {

    /**
     * The Constant MILLISECONDS_IN_SECOND.
     */
    private static final int MILLISECONDS_IN_SECOND = 1000;

    /** The Constant POST_CHECK_VALUE. */
    private static final String POST_CHECK_VALUE = "post-check=";

    /** The Constant PRE_CHECK_VALUE. */
    private static final String PRE_CHECK_VALUE = "pre-check=";

    /** The Constant MAX_AGE_VALUE. */
    private static final String MAX_AGE_VALUE = "max-age=";

    /** The Constant ZERO_STRING_VALUE. */
    private static final String ZERO_STRING_VALUE = "0";

    /** The Constant NO_STORE_VALUE. */
    private static final String NO_STORE_VALUE = "no-store";

    /** The Constant NO_CACHE_VALUE. */
    private static final String NO_CACHE_VALUE = "no-cache";

    /** The Constant PRAGMA_HEADER. */
    private static final String PRAGMA_HEADER = "Pragma";

    /** The Constant CACHE_CONTROL_HEADER. */
    private static final String CACHE_CONTROL_HEADER = "Cache-Control";

    /** The Constant EXPIRES_HEADER. */
    private static final String EXPIRES_HEADER = "Expires";

    /** The Constant LAST_MODIFIED_HEADER. */
    private static final String LAST_MODIFIED_HEADER = "Last-Modified";

    /** The Constant CACHE_TIME_PARAM_NAME. */
    private static final String CACHE_TIME_PARAM_NAME = "CacheTime";

    /** The Static HTTP_DATE_FORMAT object. */
    private static final DateFormat HTTP_DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);

static {
HTTP_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
}

    /** The reply headers. */
    private String[][] mReplyHeaders = { {} };

    /** The cache time in seconds. */
    private Long mCacheTime = 0L;

    /**
     * Initializes the Servlet filter with the cache time and sets up the unchanging headers.
     * 
     * @param pConfig the config
     * 
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    public void init(final FilterConfig pConfig) {
        final ArrayList<String[]> newReplyHeaders = new ArrayList<String[]>();
        this.mCacheTime = Long.parseLong(pConfig.getInitParameter(CACHE_TIME_PARAM_NAME));
        if (this.mCacheTime > 0L) {
            newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, MAX_AGE_VALUE + this.mCacheTime.longValue() });
            newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, PRE_CHECK_VALUE + this.mCacheTime.longValue() });
            newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, POST_CHECK_VALUE + this.mCacheTime.longValue() });
        } else {
            newReplyHeaders.add(new String[] { PRAGMA_HEADER, NO_CACHE_VALUE });
            newReplyHeaders.add(new String[] { EXPIRES_HEADER, ZERO_STRING_VALUE });
            newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, NO_CACHE_VALUE });
            newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, NO_STORE_VALUE });
        }
        this.mReplyHeaders = new String[newReplyHeaders.size()][2];
        newReplyHeaders.toArray(this.mReplyHeaders);
    }

    /**
     * Do filter.
     * 
     * @param pRequest the request
     * @param pResponse the response
     * @param pChain the chain
     * 
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws ServletException the servlet exception
     * 
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse,
     *      javax.servlet.FilterChain)
     */
    public void doFilter(final ServletRequest pRequest, final ServletResponse pResponse, final FilterChain pChain)
            throws IOException, ServletException {
        // Apply the headers
        final HttpServletResponse httpResponse = (HttpServletResponse) pResponse;
        for (final String[] replyHeader : this.mReplyHeaders) {
            final String name = replyHeader[0];
            final String value = replyHeader[1];
            httpResponse.addHeader(name, value);
        }
        if (this.mCacheTime > 0L) {
            final long now = System.currentTimeMillis();
             final DateFormat httpDateFormat = (DateFormat) HTTP_DATE_FORMAT.clone();
            httpResponse.addHeader(LAST_MODIFIED_HEADER, httpDateFormat.format(new Date(now)));
            httpResponse.addHeader(EXPIRES_HEADER, httpDateFormat.format(new Date(now
                    + (this.mCacheTime.longValue() * MILLISECONDS_IN_SECOND))));
        }
        pChain.doFilter(pRequest, pResponse);
    }

    /**
     * Destroy all humans!
     * 
     * @see javax.servlet.Filter#destroy()
     */
    public void destroy() {
    }

}

And the web.xml configuration looks like this:

	<filter>
		<filter-name>CacheFilterOneWeek</filter-name>
		<filter-class>org.foundation.servlet.filter.CacheHeaderFilter</filter-class>
		<init-param>
			<param-name>CacheTime</param-name>
			<param-value>604800</param-value>
		</init-param>
	</filter>

	<filter>
		<filter-name>CacheFilterOneDay</filter-name>
		<filter-class>org.foundation.servlet.filter.CacheHeaderFilter</filter-class>
		<init-param>
			<param-name>CacheTime</param-name>
			<param-value>86400</param-value>
		</init-param>
	</filter>

	<filter>
		<filter-name>CacheFilterOneHour</filter-name>
		<filter-class>org.foundation.servlet.filter.CacheHeaderFilter</filter-class>
		<init-param>
			<param-name>CacheTime</param-name>
			<param-value>3600</param-value>
		</init-param>
	</filter>

.......

<filter-mapping>
		<filter-name>CacheFilterOneDay</filter-name>
		<url-pattern>*.js</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>CacheFilterOneDay</filter-name>
		<url-pattern>*.css</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>CacheFilterOneWeek</filter-name>
		<url-pattern>*.jpg</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>CacheFilterOneWeek</filter-name>
		<url-pattern>*.gif</url-pattern>
	</filter-mapping>
By | 2017-05-18T15:16:59+00:00 January 15th, 2009|ATG, JBoss|10 Comments

About the Author:

10 Comments

  1. Jakub G?uszecki April 15, 2009 at 1:35 am - Reply

    Hey
    Not a good idea to share DateFormat instance across multiple threads.

    • Devon April 15, 2009 at 6:03 am - Reply

      Ack! You’re right! Thanks for the call-out. I’ll update the code shortly to solve that.

      ———–

      Code is updated. Enjoy!

  2. Ondrej Medek February 10, 2010 at 1:35 am - Reply

    Hi,
    another option is to use thread safe FastDateFormat from commons-lang http://commons.apache.org/lang/api-release/org/apache/commons/lang/time/FastDateFormat.html

    Or just make a static instance of the DateFormat and create a separate instance by clone() method each time you need to use it. It’s a little bit faster then to call the constructor, because the constructor has to parse the format string.

    • Devon February 10, 2010 at 7:31 pm - Reply

      Ondrej,

      I don’t want to make a dependancy on commons-lang, but the second suggestion is very good!

      I have edited the code to reflect that approach, as in this sort of thing, speed is critical!

      Thanks!

      • Ondrej Medek February 11, 2010 at 12:05 am - Reply

        Hi Devon.

        you can also set the time zone in the static class initializer, put it just bellow the HTTP_DATE_FORMAT declaration:

        private static final DateFormat HTTP_DATE_FORMAT = new SimpleDateFormat(“EEE, d MMM yyyy HH:mm:ss zzz”, Locale.ROOT);
        static {
        HTTP_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone(“GMT”));
        }

        • Devon February 11, 2010 at 7:31 am - Reply

          Right again! Man I need to hire you to review all my code:) Thanks!

  3. Luke Samad September 17, 2010 at 12:08 pm - Reply

    Nicely done, well written and goo catch too. Thanks to all….

    Luke S.

  4. kvic September 22, 2010 at 7:08 pm - Reply

    Im trying use as portlet application in JBoss Portal. but not work.

    • Devon September 23, 2010 at 6:38 am - Reply

      Hmm I haven’t used Portlets in JBoss so I’m not sure what gotchas there may be. What errors are you getting, if any?

  5. robercl January 16, 2013 at 10:52 am - Reply

    Amazing! is exactly what was looking for, thanks!

Leave A Comment