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(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]], 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

    Nested Classes
    Modifier and Type | Class and Description
    --- | ---
    `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

    Constructors
    Constructor and Description
    `ExpiresFilter()`

  - ### Method Summary

    All Methods Static Methods Instance Methods Concrete Methods Deprecated Methods
    Modifier and Type | Method and Description
    --- | ---
    `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)`
    `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)` Deprecated. Will be removed in Tomcat 10. Use `getExpirationDate(HttpServletRequest, XHttpServletResponse)`
    `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.
    `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 org.apache.catalina.filters.FilterBase

      `isConfigProblemFatal`

    - ### Methods inherited from class java.lang.Object

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

    - ### Methods inherited from interface javax.servlet.Filter

      `destroy`

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

      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

          @Deprecated
          protected Date getExpirationDate(ExpiresFilter.XHttpServletResponse response)

      Deprecated. Will be removed in Tomcat 10. Use `getExpirationDate(HttpServletRequest, XHttpServletResponse)`
      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:
        `response` - The wrapped HTTP response
      Returns:
        the expiration date
      See Also:
        `ServletResponse.getContentType()`

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

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