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 themax-age
directive of theCache-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 thanmax-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 themax-age
directive of theCache-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. TheCache-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, andA
means the client's access time should be used. The duration is expressed in seconds.A2592000
stands foraccess 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. IfA
(access
ornow
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 theExpiresDefault
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, the304
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 of304 Not modified
(which does specify aContent-Type
header) combined withExpires
andCache-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
andExpiresByType
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 toInteger.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 :- no expiration header is defined (
Expires
header or themax-age
directive of theCache-Control
header), - the response status code is not excluded by the directive
ExpiresExcludedResponseStatusCodes
, - the
Content-Type
of the response matches one of the types defined the inExpiresByType
directives or theExpiresDefault
directive is defined.
Note :
- If
Cache-Control
header contains other directives thanmax-age
, they are concatenated with themax-age
directive that is added by theExpiresFilter
.
Expiration configuration selection
The expiration configuration if elected according to the following algorithm:
ExpiresByType
matching the exact content-type returned byHttpServletResponse.getContentType()
possibly including the charset (e.g. 'text/xml;charset=UTF-8
'),ExpiresByType
matching the content-type without the charset ifHttpServletResponse.getContentType()
contains a charset (e.g. 'text/xml;charset=UTF-8
' -> 'text/xml
'),ExpiresByType
matching the major type (e.g. substring before '/
') ofHttpServletResponse.getContentType()
(e.g. 'text/xml;charset=UTF-8
' -> 'text
'),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 methodswrite()
,print()
,close()
andflush()
. For empty response body (e.g. empty files), thewrite()
,print()
,close()
andflush()
methods are not called; to handle this case, theExpiresFilter
, at the end of itsdoFilter()
method, manually triggers theonBeforeWriteResponseBody()
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 withExpiresByType
in the<filter>
declaration. Indeed, SeveralExpiresByType
directives can be declared whenweb.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 dateMar 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 dateMar 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 anExpiresFilter.Duration.amount
and aExpiresFilter.Duration.unit
protected static class
ExpiresFilter.DurationUnit
Duration unitprotected 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 theHttpServletResponse
to yrap the "Start Write Response Body" event.class
ExpiresFilter.XPrintWriter
Wrapping extension ofPrintWriter
to trap the "Start Write Response Body" event.class
ExpiresFilter.XServletOutputStream
Wrapping extension ofServletOutputStream
to trap the "Start Write Response Body" event.
-
Field Summary
-
Fields inherited from class org.apache.catalina.filters.FilterBase
sm
-
-
Constructor Summary
Constructors Constructor Description ExpiresFilter()
-
Method Summary
All Methods Static Methods Instance Methods Concrete Methods Modifier and Type Method Description protected static int[]
commaDelimitedListToIntArray(java.lang.String commaDelimitedInts)
Convert a comma delimited list of numbers into anint[]
.protected static java.lang.String[]
commaDelimitedListToStringArray(java.lang.String commaDelimitedStrings)
Convert a given comma delimited list of strings into an array of Stringprotected static boolean
contains(java.lang.String str, java.lang.String searchStr)
void
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
ThedoFilter
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()
java.lang.String
getExcludedResponseStatusCodes()
int[]
getExcludedResponseStatusCodesAsInts()
protected java.util.Date
getExpirationDate(HttpServletRequest request, ExpiresFilter.XHttpServletResponse response)
Returns the expiration date of the givenExpiresFilter.XHttpServletResponse
ornull
if no expiration date has been configured for the declared content type.protected java.util.Date
getExpirationDate(ExpiresFilter.ExpiresConfiguration configuration, ExpiresFilter.XHttpServletResponse response)
Returns the expiration date of the givenExpiresFilter.ExpiresConfiguration
,HttpServletRequest
andExpiresFilter.XHttpServletResponse
.java.util.Map<java.lang.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 java.lang.String
intsToCommaDelimitedString(int[] ints)
Convert an array of ints into a comma delimited stringprotected boolean
isEligibleToExpirationHeaderGeneration(HttpServletRequest request, ExpiresFilter.XHttpServletResponse response)
protected
for extension.protected static boolean
isEmpty(java.lang.String str)
protected static boolean
isNotEmpty(java.lang.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 theExpiresFilter
configuration, sets the 'Expires
' header and the attribute 'max-age
' of the 'Cache-Control
' header.protected ExpiresFilter.ExpiresConfiguration
parseExpiresConfiguration(java.lang.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(java.util.Map<java.lang.String,ExpiresFilter.ExpiresConfiguration> expiresConfigurationByContentType)
protected static boolean
startsWithIgnoreCase(java.lang.String string, java.lang.String prefix)
protected static java.lang.String
substringBefore(java.lang.String str, java.lang.String separator)
java.lang.String
toString()
-
Methods inherited from class org.apache.catalina.filters.FilterBase
isConfigProblemFatal
-
-
-
-
Method Detail
-
commaDelimitedListToIntArray
protected static int[] commaDelimitedListToIntArray(java.lang.String commaDelimitedInts)
Convert a comma delimited list of numbers into anint[]
.- Parameters:
commaDelimitedInts
- can benull
- Returns:
- never
null
array
-
commaDelimitedListToStringArray
protected static java.lang.String[] commaDelimitedListToStringArray(java.lang.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(java.lang.String str, java.lang.String searchStr)
- Parameters:
str
- String that will be searchedsearchStr
- The substring to search- Returns:
true
if the givenstr
contains the givensearchStr
.
-
intsToCommaDelimitedString
protected static java.lang.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(java.lang.String str)
- Parameters:
str
- The String to check- Returns:
true
if the givenstr
isnull
or has a zero characters length.
-
isNotEmpty
protected static boolean isNotEmpty(java.lang.String str)
- Parameters:
str
- The String to check- Returns:
true
if the givenstr
has at least one character (can be a whitespace).
-
startsWithIgnoreCase
protected static boolean startsWithIgnoreCase(java.lang.String string, java.lang.String prefix)
- Parameters:
string
- can benull
prefix
- can benull
- Returns:
true
if the givenstring
starts with the givenprefix
ignoring case.
-
substringBefore
protected static java.lang.String substringBefore(java.lang.String str, java.lang.String separator)
- Parameters:
str
- can benull
separator
- can benull
- Returns:
- the subset of the given
str
that is before the first occurrence of the givenseparator
. Returnnull
if the givenstr
or the givenseparator
is null. Return and empty string if theseparator
is empty.
-
doFilter
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException
Description copied from interface:jakarta.servlet.Filter
ThedoFilter
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 processresponse
- The response associated with the requestchain
- Provides access to the next filter in the chain for this filter to pass the request and response to for further processing- Throws:
java.io.IOException
- if an I/O error occurs during this filter's processing of the requestServletException
- if the processing fails for any other reason
-
getDefaultExpiresConfiguration
public ExpiresFilter.ExpiresConfiguration getDefaultExpiresConfiguration()
-
getExcludedResponseStatusCodes
public java.lang.String getExcludedResponseStatusCodes()
-
getExcludedResponseStatusCodesAsInts
public int[] getExcludedResponseStatusCodesAsInts()
-
getExpirationDate
protected java.util.Date getExpirationDate(HttpServletRequest request, ExpiresFilter.XHttpServletResponse response)
Returns the expiration date of the givenExpiresFilter.XHttpServletResponse
ornull
if no expiration date has been configured for the declared content type.protected
for extension.- Parameters:
request
- The HTTP requestresponse
- The wrapped HTTP response- Returns:
- the expiration date
- See Also:
ServletResponse.getContentType()
-
getExpirationDate
protected java.util.Date getExpirationDate(ExpiresFilter.ExpiresConfiguration configuration, ExpiresFilter.XHttpServletResponse response)
Returns the expiration date of the given
ExpiresFilter.ExpiresConfiguration
,HttpServletRequest
andExpiresFilter.XHttpServletResponse
.protected
for extension.- Parameters:
configuration
- The parsed expiresresponse
- The Servlet response- Returns:
- the expiration date
-
getExpiresConfigurationByContentType
public java.util.Map<java.lang.String,ExpiresFilter.ExpiresConfiguration> getExpiresConfigurationByContentType()
-
getLogger
protected Log getLogger()
- Specified by:
getLogger
in classFilterBase
-
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 interfaceFilter
- Overrides:
init
in classFilterBase
- Parameters:
filterConfig
- The configuration information associated with the filter instance being initialised- Throws:
ServletException
- ifFilterBase.isConfigProblemFatal()
returnstrue
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 requestresponse
- 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 byLog.isDebugEnabled()
becauseHttpServletRequest.getRequestURI()
andServletResponse.getContentType()
costsString
objects instantiations (as of Tomcat 7).- Parameters:
request
- The Servlet requestresponse
- The Servlet response
-
parseExpiresConfiguration
protected ExpiresFilter.ExpiresConfiguration parseExpiresConfiguration(java.lang.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(java.util.Map<java.lang.String,ExpiresFilter.ExpiresConfiguration> expiresConfigurationByContentType)
-
toString
public java.lang.String toString()
- Overrides:
toString
in classjava.lang.Object
-
-