Apache Tomcat 7.0.37

org.apache.catalina.filters
Class ExpiresFilter

java.lang.Object
  extended by org.apache.catalina.filters.FilterBase
      extended by org.apache.catalina.filters.ExpiresFilter
All Implemented Interfaces:
Filter

public class ExpiresFilter
extends FilterBase

ExpiresFilter is a Java Servlet API port of Apache mod_expires to add ' Expires' and ' Cache-Control: max-age=' headers to HTTP response according to its ' Content-Type'.

Following documentation is inspired by mod_expires .

Summary

This filter controls the setting of the Expires HTTP header and the max-age directive of the Cache-Control HTTP header in server responses. The expiration date can set to be relative to either the time the source file was last modified, or to the time of the client access.

These HTTP headers are an instruction to the client about the document's validity and persistence. If cached, the document may be fetched from the cache rather than from the source until this time has passed. After that, the cache copy is considered "expired" and invalid, and a new copy must be obtained from the source.

To modify Cache-Control directives other than max-age (see RFC 2616 section 14.9), you can use other servlet filters or Apache Httpd mod_headers module.

Filter Configuration

Basic configuration to add ' Expires' and ' Cache-Control: max-age=' headers to images, css and javascript

 <web-app ...>
    ...
    <filter>
       <filter-name>ExpiresFilter</filter-name>
       <filter-class>org.apache.catalina.filters.ExpiresFilter</filter-class>
       <init-param>
          <param-name>ExpiresByType image</param-name>
          <param-value>access plus 10 minutes</param-value>
       </init-param>
       <init-param>
          <param-name>ExpiresByType text/css</param-name>
          <param-value>access plus 10 minutes</param-value>
       </init-param>
       <init-param>
          <param-name>ExpiresByType application/javascript</param-name>
          <param-value>access plus 10 minutes</param-value>
       </init-param>
    </filter>
    ...
    <filter-mapping>
       <filter-name>ExpiresFilter</filter-name>
       <url-pattern>/*</url-pattern>
       <dispatcher>REQUEST</dispatcher>
    </filter-mapping>
    ...
 </web-app>
 

Configuration Parameters

ExpiresByType <content-type>

This directive defines the value of the Expires header and the max-age directive of the Cache-Control header generated for documents of the specified type (e.g., text/html). The second argument sets the number of seconds that will be added to a base time to construct the expiration date. The Cache-Control: max-age is calculated by subtracting the request time from the expiration date and expressing the result in seconds.

The base time is either the last modification time of the file, or the time of the client's access to the document. Which should be used is specified by the <code> field; M means that the file's last modification time should be used as the base time, and A means the client's access time should be used. The duration is expressed in seconds. A2592000 stands for access plus 30 days in alternate syntax.

The difference in effect is subtle. If M (modification in alternate syntax) is used, all current copies of the document in all caches will expire at the same time, which can be good for something like a weekly notice that's always found at the same URL. If A ( access or now in alternate syntax) is used, the date of expiration is different for each client; this can be good for image files that don't change very often, particularly for a set of related documents that all refer to the same images (i.e., the images will be accessed repeatedly within a relatively short timespan).

Example:

 <init-param>
    <param-name>ExpiresByType text/html</param-name><param-value>access plus 1 month 15   days 2 hours</param-value>
 </init-param>
  
 <init-param>
    <!-- 2592000 seconds = 30 days -->
    <param-name>ExpiresByType image/gif</param-name><param-value>A2592000</param-value>
 </init-param>
 

Note that this directive only has effect if ExpiresActive On has been specified. It overrides, for the specified MIME type only, any expiration date set by the ExpiresDefault directive.

You can also specify the expiration time calculation using an alternate syntax, described earlier in this document.

ExpiresExcludedResponseStatusCodes

This directive defines the http response status codes for which the ExpiresFilter will not generate expiration headers. By default, the 304 status code ("Not modified") is skipped. The value is a comma separated list of http status codes.

This directive is useful to ease usage of ExpiresDefault directive. Indeed, the behavior of 304 Not modified (which does specify a Content-Type header) combined with Expires and Cache-Control:max-age= headers can be unnecessarily tricky to understand.

Configuration sample :

 <init-param>
    <param-name>ExpiresExcludedResponseStatusCodes</param-name><param-value>302, 500, 503</param-value>
 </init-param>
 

ExpiresDefault

This directive sets the default algorithm for calculating the expiration time for all documents in the affected realm. It can be overridden on a type-by-type basis by the ExpiresByType directive. See the description of that directive for details about the syntax of the argument, and the "alternate syntax" description as well.

Alternate Syntax

The ExpiresDefault and ExpiresByType directives can also be defined in a more readable syntax of the form:

 <init-param>
    <param-name>ExpiresDefault</param-name><param-value><base> [plus] {<num>   <type>}*</param-value>
 </init-param>
  
 <init-param>
    <param-name>ExpiresByType type/encoding</param-name><param-value><base> [plus]   {<num> <type>}*</param-value>
 </init-param>
 

where <base> is one of:

The plus keyword is optional. <num> should be an integer value (acceptable to Integer.parseInt()), and <type> is one of:

For example, any of the following directives can be used to make documents expire 1 month after being accessed, by default:

 <init-param>
    <param-name>ExpiresDefault</param-name><param-value>access plus 1 month</param-value>
 </init-param>
  
 <init-param>
    <param-name>ExpiresDefault</param-name><param-value>access plus 4 weeks</param-value>
 </init-param>
  
 <init-param>
    <param-name>ExpiresDefault</param-name><param-value>access plus 30 days</param-value>
 </init-param>
 

The expiry time can be fine-tuned by adding several ' <num> <type>' clauses:

 <init-param>
    <param-name>ExpiresByType text/html</param-name><param-value>access plus 1 month 15   days 2 hours</param-value>
 </init-param>
  
 <init-param>
    <param-name>ExpiresByType image/gif</param-name><param-value>modification plus 5 hours 3   minutes</param-value>
 </init-param>
 

Note that if you use a modification date based setting, the Expires header will not be added to content that does not come from a file on disk. This is due to the fact that there is no modification time for such content.

Expiration headers generation eligibility

A response is eligible to be enriched by ExpiresFilter if :

  1. no expiration header is defined (Expires header or the max-age directive of the Cache-Control header),
  2. the response status code is not excluded by the directive ExpiresExcludedResponseStatusCodes,
  3. the Content-Type of the response matches one of the types defined the in ExpiresByType directives or the ExpiresDefault directive is defined.

Note :

Expiration configuration selection

The expiration configuration if elected according to the following algorithm:

  1. ExpiresByType matching the exact content-type returned by HttpServletResponse.getContentType() possibly including the charset (e.g. 'text/xml;charset=UTF-8'),
  2. ExpiresByType matching the content-type without the charset if HttpServletResponse.getContentType() contains a charset (e.g. ' text/xml;charset=UTF-8' -> 'text/xml'),
  3. ExpiresByType matching the major type (e.g. substring before '/') of HttpServletResponse.getContentType() (e.g. 'text/xml;charset=UTF-8' -> 'text '),
  4. ExpiresDefault

Implementation Details

When to write the expiration headers ?

The ExpiresFilter traps the 'on before write response body' event to decide whether it should generate expiration headers or not.

To trap the 'before write response body' event, the ExpiresFilter wraps the http servlet response's writer and outputStream to intercept calls to the methods write(), print(), close() and flush(). For empty response body (e.g. empty files), the write(), print(), close() and flush() methods are not called; to handle this case, the ExpiresFilter, at the end of its doFilter() method, manually triggers the onBeforeWriteResponseBody() method.

Configuration syntax

The ExpiresFilter supports the same configuration syntax as Apache Httpd mod_expires.

A challenge has been to choose the name of the <param-name> associated with ExpiresByType in the <filter> declaration. Indeed, Several ExpiresByType directives can be declared when web.xml syntax does not allow to declare several <init-param> with the same name.

The workaround has been to declare the content type in the <param-name> rather than in the <param-value>.

Designed for extension : the open/close principle

The ExpiresFilter has been designed for extension following the open/close principle.

Key methods to override for extension are :

Troubleshooting

To troubleshoot, enable logging on the org.apache.catalina.filters.ExpiresFilter.

Extract of logging.properties

 org.apache.catalina.filters.ExpiresFilter.level = FINE
 

Sample of initialization log message :

 Mar 26, 2010 2:01:41 PM org.apache.catalina.filters.ExpiresFilter init
 FINE: Filter initialized with configuration ExpiresFilter[
    excludedResponseStatusCode=[304], 
    default=null, 
    byType={
       image=ExpiresConfiguration[startingPoint=ACCESS_TIME, duration=[10 MINUTE]], 
       text/css=ExpiresConfiguration[startingPoint=ACCESS_TIME, duration=[10 MINUTE]], 
       application/javascript=ExpiresConfiguration[startingPoint=ACCESS_TIME, duration=[10 MINUTE]]}]
 

Sample of per-request log message where ExpiresFilter adds an expiration date

 Mar 26, 2010 2:09:47 PM org.apache.catalina.filters.ExpiresFilter onBeforeWriteResponseBody
 FINE: Request "/tomcat.gif" with response status "200" content-type "image/gif", set expiration date 3/26/10 2:19 PM
 

Sample of per-request log message where ExpiresFilter does not add an expiration date

 Mar 26, 2010 2:10:27 PM org.apache.catalina.filters.ExpiresFilter onBeforeWriteResponseBody
 FINE: Request "/docs/config/manager.html" with response status "200" content-type "text/html", no expiration configured
 


Nested Class Summary
protected static class ExpiresFilter.Duration
          Duration composed of an ExpiresFilter.Duration.amount and a ExpiresFilter.Duration.unit
protected static class ExpiresFilter.DurationUnit
          Duration unit
protected static class ExpiresFilter.ExpiresConfiguration
           Main piece of configuration of the filter.
protected static class ExpiresFilter.StartingPoint
          Expiration configuration starting point.
 class ExpiresFilter.XHttpServletResponse
           Wrapping extension of the HttpServletResponse to yrap the "Start Write Response Body" event.
 class ExpiresFilter.XPrintWriter
          Wrapping extension of PrintWriter to trap the "Start Write Response Body" event.
 class ExpiresFilter.XServletOutputStream
          Wrapping extension of ServletOutputStream to trap the "Start Write Response Body" event.
 
Field Summary
 
Fields inherited from class org.apache.catalina.filters.FilterBase
sm
 
Constructor Summary
ExpiresFilter()
           
 
Method Summary
protected static int[] commaDelimitedListToIntArray(String commaDelimitedInts)
          Convert a comma delimited list of numbers into an int[].
protected static String[] commaDelimitedListToStringArray(String commaDelimitedStrings)
          Convert a given comma delimited list of strings into an array of String
protected static boolean contains(String str, String searchStr)
          Return true if the given str contains the given searchStr.
 void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
          The doFilter method of the Filter is called by the container each time a request/response pair is passed through the chain due to a client request for a resource at the end of the chain.
 ExpiresFilter.ExpiresConfiguration getDefaultExpiresConfiguration()
           
 String getExcludedResponseStatusCodes()
           
 int[] getExcludedResponseStatusCodesAsInts()
           
protected  Date getExpirationDate(ExpiresFilter.ExpiresConfiguration configuration, ExpiresFilter.XHttpServletResponse response)
           Returns the expiration date of the given ExpiresFilter.ExpiresConfiguration, HttpServletRequest and ExpiresFilter.XHttpServletResponse.
protected  Date getExpirationDate(ExpiresFilter.XHttpServletResponse response)
           Returns the expiration date of the given ExpiresFilter.XHttpServletResponse or null if no expiration date has been configured for the declared content type.
 Map<String,ExpiresFilter.ExpiresConfiguration> getExpiresConfigurationByContentType()
           
protected  Log getLogger()
           
 void init(FilterConfig filterConfig)
          Called by the web container to indicate to a filter that it is being placed into service.
protected static String intsToCommaDelimitedString(int[] ints)
          Convert an array of ints into a comma delimited string
protected  boolean isEligibleToExpirationHeaderGeneration(HttpServletRequest request, ExpiresFilter.XHttpServletResponse response)
           protected for extension.
protected static boolean isEmpty(String str)
          Return true if the given str is null or has a zero characters length.
protected static boolean isNotEmpty(String str)
          Return true if the given str has at least one character (can be a withespace).
 void onBeforeWriteResponseBody(HttpServletRequest request, ExpiresFilter.XHttpServletResponse response)
           If no expiration header has been set by the servlet and an expiration has been defined in the ExpiresFilter configuration, sets the ' Expires' header and the attribute 'max-age' of the ' Cache-Control' header.
protected  ExpiresFilter.ExpiresConfiguration parseExpiresConfiguration(String inputLine)
          Parse configuration lines like ' access plus 1 month 15 days 2 hours' or ' modification 1 day 2 hours 5 seconds'
 void setDefaultExpiresConfiguration(ExpiresFilter.ExpiresConfiguration defaultExpiresConfiguration)
           
 void setExcludedResponseStatusCodes(int[] excludedResponseStatusCodes)
           
 void setExpiresConfigurationByContentType(Map<String,ExpiresFilter.ExpiresConfiguration> expiresConfigurationByContentType)
           
protected static boolean startsWithIgnoreCase(String string, String prefix)
          Return true if the given string starts with the given prefix ignoring case.
protected static String substringBefore(String str, String separator)
          Return the subset of the given str that is before the first occurence of the given separator.
 String toString()
           
 
Methods inherited from class org.apache.catalina.filters.FilterBase
destroy, isConfigProblemFatal
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
 

Constructor Detail

ExpiresFilter

public ExpiresFilter()
Method Detail

commaDelimitedListToIntArray

protected static int[] commaDelimitedListToIntArray(String commaDelimitedInts)
Convert a comma delimited list of numbers into an int[].

Parameters:
commaDelimitedInts - can be null
Returns:
never null array

commaDelimitedListToStringArray

protected static String[] commaDelimitedListToStringArray(String commaDelimitedStrings)
Convert a given comma delimited list of strings into an array of String

Returns:
array of patterns (non null)

contains

protected static boolean contains(String str,
                                  String searchStr)
Return true if the given str contains the given searchStr.


intsToCommaDelimitedString

protected static String intsToCommaDelimitedString(int[] ints)
Convert an array of ints into a comma delimited string


isEmpty

protected static boolean isEmpty(String str)
Return true if the given str is null or has a zero characters length.


isNotEmpty

protected static boolean isNotEmpty(String str)
Return true if the given str has at least one character (can be a withespace).


startsWithIgnoreCase

protected static boolean startsWithIgnoreCase(String string,
                                              String prefix)
Return true if the given string starts with the given prefix ignoring case.

Parameters:
string - can be null
prefix - can be null

substringBefore

protected static String substringBefore(String str,
                                        String separator)
Return the subset of the given str that is before the first occurence of the given separator. Return null if the given str or the given separator is null. Return and empty string if the separator is empty.

Parameters:
str - can be null
separator - can be null

doFilter

public void doFilter(ServletRequest request,
                     ServletResponse response,
                     FilterChain chain)
              throws IOException,
                     ServletException
Description copied from interface: javax.servlet.Filter
The doFilter method of the Filter is called by the container each time a request/response pair is passed through the chain due to a client request for a resource at the end of the chain. The FilterChain passed in to this method allows the Filter to pass on the request and response to the next entity in the chain.

A typical implementation of this method would follow the following pattern:-
1. Examine the request
2. Optionally wrap the request object with a custom implementation to filter content or headers for input filtering
3. Optionally wrap the response object with a custom implementation to filter content or headers for output filtering
4. a) Either invoke the next entity in the chain using the FilterChain object (chain.doFilter()),
4. b) or not pass on the request/response pair to the next entity in the filter chain to block the request processing
5. Directly set headers on the response after invocation of the next entity in the filter chain.

Throws:
IOException
ServletException

getDefaultExpiresConfiguration

public ExpiresFilter.ExpiresConfiguration getDefaultExpiresConfiguration()

getExcludedResponseStatusCodes

public String getExcludedResponseStatusCodes()

getExcludedResponseStatusCodesAsInts

public int[] getExcludedResponseStatusCodesAsInts()

getExpirationDate

protected Date getExpirationDate(ExpiresFilter.XHttpServletResponse response)

Returns the expiration date of the given ExpiresFilter.XHttpServletResponse or null if no expiration date has been configured for the declared content type.

protected for extension.

See Also:
ServletResponse.getContentType()

getExpirationDate

protected Date getExpirationDate(ExpiresFilter.ExpiresConfiguration configuration,
                                 ExpiresFilter.XHttpServletResponse response)

Returns the expiration date of the given ExpiresFilter.ExpiresConfiguration, HttpServletRequest and ExpiresFilter.XHttpServletResponse.

protected for extension.


getExpiresConfigurationByContentType

public Map<String,ExpiresFilter.ExpiresConfiguration> getExpiresConfigurationByContentType()

getLogger

protected Log getLogger()
Specified by:
getLogger in class FilterBase

init

public void init(FilterConfig filterConfig)
          throws ServletException
Description copied from interface: javax.servlet.Filter
Called by the web container to indicate to a filter that it is being placed into service. The servlet container calls the init method exactly once after instantiating the filter. The init method must complete successfully before the filter is asked to do any filtering work.

The web container cannot place the filter into service if the init method either
1.Throws a ServletException
2.Does not return within a time period defined by the web container

Specified by:
init in interface Filter
Overrides:
init in class FilterBase
Throws:
ServletException

isEligibleToExpirationHeaderGeneration

protected boolean isEligibleToExpirationHeaderGeneration(HttpServletRequest request,
                                                         ExpiresFilter.XHttpServletResponse response)

protected for extension.


onBeforeWriteResponseBody

public void onBeforeWriteResponseBody(HttpServletRequest request,
                                      ExpiresFilter.XHttpServletResponse response)

If no expiration header has been set by the servlet and an expiration has been defined in the ExpiresFilter configuration, sets the ' Expires' header and the attribute 'max-age' of the ' Cache-Control' header.

Must be called on the "Start Write Response Body" event.

Invocations to Logger.debug(...) are guarded by Log.isDebugEnabled() because HttpServletRequest.getRequestURI() and ServletResponse.getContentType() costs String objects instantiations (as of Tomcat 7).


parseExpiresConfiguration

protected ExpiresFilter.ExpiresConfiguration parseExpiresConfiguration(String inputLine)
Parse configuration lines like ' access plus 1 month 15 days 2 hours' or ' modification 1 day 2 hours 5 seconds'

Parameters:
inputLine -

setDefaultExpiresConfiguration

public void setDefaultExpiresConfiguration(ExpiresFilter.ExpiresConfiguration defaultExpiresConfiguration)

setExcludedResponseStatusCodes

public void setExcludedResponseStatusCodes(int[] excludedResponseStatusCodes)

setExpiresConfigurationByContentType

public void setExpiresConfigurationByContentType(Map<String,ExpiresFilter.ExpiresConfiguration> expiresConfigurationByContentType)

toString

public String toString()
Overrides:
toString in class Object

Apache Tomcat 7.0.37

Copyright © 2000-2013 Apache Software Foundation. All Rights Reserved.