Apache Tomcat 7.0.42

## org.apache.catalina.filters
Class ExpiresFilter

    java.lang.Object
      org.apache.catalina.filters.FilterBase
          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:

- `access`
- `now` (equivalent to '`access`')
- `modification`

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

- `years`
- `months`
- `weeks`
- `days`
- `hours`
- `minutes`
- `seconds`

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 :

- If `Cache-Control` header contains other directives than `max-age`, they are concatenated with the `max-age` directive that is added by the `ExpiresFilter`.

# 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 :

- `isEligibleToExpirationHeaderGeneration(HttpServletRequest, XHttpServletResponse)`
- `getExpirationDate(XHttpServletResponse)`

# 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.42

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