ExpiresFilter (Apache Tomcat 10.1.55 API Documentation)
Description: declaration: package: org.apache.catalina.filters, class: ExpiresFilter

# 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 text/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(HttpServletRequest, 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]], text/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

  Nested Classes
  Modifier and Type
  Class
  Description
  `protected static class`
  `ExpiresFilter.Duration`
  Duration composed of an `ExpiresFilter.Duration.amount` and a `ExpiresFilter.Duration.unit`
  `static enum`
  `ExpiresFilter.DurationUnit`
  Duration unit
  `protected static class`
  `ExpiresFilter.ExpiresConfiguration`
  Main piece of configuration of the filter.
  `static enum`
  `ExpiresFilter.StartingPoint`
  Expiration configuration starting point.
  `class`
  `ExpiresFilter.XHttpServletResponse`
  Wrapping extension of the `HttpServletResponse` to wrap 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 FilterBase

  `sm`

- ## Constructor Summary

  Constructors
  Constructor
  Description
  `ExpiresFilter()`

- ## Method Summary

  Modifier and Type
  Method
  Description
  `protected static int[]`
  `commaDelimitedListToIntArray(String commaDelimitedInts)`
  Convert a comma-delimited list of numbers into an `int[]`.
  `protected static String[]`
  `commaDelimitedListToStringArray(String commaDelimitedStrings)`
  Deprecated.
  Unused.
  `protected static boolean`
  `contains(String str, String 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(HttpServletRequest request, 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 Date`
  `getExpirationDate(ExpiresFilter.ExpiresConfiguration configuration, ExpiresFilter.XHttpServletResponse response)`
  Returns the expiration date of the given `ExpiresFilter.ExpiresConfiguration`, `HttpServletRequest` and `ExpiresFilter.XHttpServletResponse`.
  `Map<String, ExpiresFilter.ExpiresConfiguration>`
  `getExpiresConfigurationByContentType()`
  `protected Log`
  `getLogger()`
  `void`
  `init(FilterConfig filterConfig)`
  Iterates over the configuration parameters and either logs a warning, or throws an exception for any parameter that does not have a matching setter in this filter.
  `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)`
  `protected static boolean`
  `isNotEmpty(String str)`
  `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)`
  `protected static String`
  `substringBefore(String str, String separator)`
  `String`
  `toString()`

  ### Methods inherited from class FilterBase

  `isConfigProblemFatal`

  ### Methods inherited from class Object

  `clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait`

  ### Methods inherited from interface Filter

  `destroy`

- ## Constructor Details

  - ### ExpiresFilter

    public ExpiresFilter()

- ## Method Details

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

    @Deprecated protected static String[] commaDelimitedListToStringArray(String commaDelimitedStrings)
    Deprecated.
    Unused. Will be removed in Tomcat 11.
    Convert a given comma delimited list of strings into an array of String
    Parameters:
      `commaDelimitedStrings` - the string to be split
    Returns:
      array of patterns (non `null`)

  - ### contains

    protected static boolean contains(String str, String searchStr)
    Parameters:
      `str` - String that will be searched
      `searchStr` - The substring to search
    Returns:
      `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
    Parameters:
      `ints` - The int array
    Returns:
      a comma separated string

  - ### isEmpty

    protected static boolean isEmpty(String str)
    Parameters:
      `str` - The String to check
    Returns:
      `true` if the given `str` is `null` or has a zero characters length.

  - ### isNotEmpty

    protected static boolean isNotEmpty(String str)
    Parameters:
      `str` - The String to check
    Returns:
      `true` if the given `str` has at least one character (can be a whitespace).

  - ### startsWithIgnoreCase

    protected static boolean startsWithIgnoreCase(String string, String prefix)
    Parameters:
      `string` - can be `null`
      `prefix` - can be `null`
    Returns:
      `true` if the given `string` starts with the given `prefix` ignoring case.

  - ### substringBefore

    protected static String substringBefore(String str, String separator)
    Parameters:
      `str` - can be `null`
      `separator` - can be `null`
    Returns:
      the subset of the given `str` that is before the first occurrence 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.

  - ### doFilter

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    Description copied from interface: `jakarta.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.

    Parameters:
      `request` - The request to process
      `response` - The response associated with the request
      `chain` - Provides access to the next filter in the chain for this filter to pass the request and response to for further processing
    Throws:
      `IOException` - if an I/O error occurs during this filter's processing of the request
      `ServletException` - if the processing fails for any other reason

  - ### getDefaultExpiresConfiguration

    public ExpiresFilter.ExpiresConfiguration getDefaultExpiresConfiguration()

  - ### getExcludedResponseStatusCodes

    public String getExcludedResponseStatusCodes()

  - ### getExcludedResponseStatusCodesAsInts

    public int[] getExcludedResponseStatusCodesAsInts()

  - ### getExpirationDate

    protected Date getExpirationDate(HttpServletRequest request, 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.

    Parameters:
      `request` - The HTTP request
      `response` - The wrapped HTTP response
    Returns:
      the expiration date
    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.

    Parameters:
      `configuration` - The parsed expires
      `response` - The Servlet response
    Returns:
      the expiration date

  - ### 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 class: `FilterBase`
    Iterates over the configuration parameters and either logs a warning, or throws an exception for any parameter that does not have a matching setter in this filter.
    Specified by:
      `init` in interface `Filter`
    Overrides:
      `init` in class `FilterBase`
    Parameters:
      `filterConfig` - The configuration information associated with the filter instance being initialised
    Throws:
      `ServletException` - if `FilterBase.isConfigProblemFatal()` returns `true` and a configured parameter does not have a matching setter

  - ### isEligibleToExpirationHeaderGeneration

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

    `protected` for extension.

    Parameters:
      `request` - The Servlet request
      `response` - The Servlet response
    Returns:
      `true` if an expire header may be added

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

    Parameters:
      `request` - The Servlet request
      `response` - The Servlet response

  - ### 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` - the input
    Returns:
      the parsed expires

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