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
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.
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>
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>
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.
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.
A response is eligible to be enriched by ExpiresFilter
if :
Expires
header or the
max-age
directive of the Cache-Control
header),ExpiresExcludedResponseStatusCodes
,Content-Type
of the response matches one of the types
defined the in ExpiresByType
directives or the
ExpiresDefault
directive is defined.Note :
Cache-Control
header contains other directives than
max-age
, they are concatenated with the max-age
directive
that is added by the ExpiresFilter
.The expiration configuration if elected according to the following algorithm:
ExpiresByType
matching the exact content-type returned by
HttpServletResponse.getContentType()
possibly including the charset
(e.g. 'text/xml;charset=UTF-8
'),ExpiresByType
matching the content-type without the charset if
HttpServletResponse.getContentType()
contains a charset (e.g. '
text/xml;charset=UTF-8
' -> 'text/xml
'),ExpiresByType
matching the major type (e.g. substring before
'/
') of HttpServletResponse.getContentType()
(e.g. 'text/xml;charset=UTF-8
' -> 'text
'),ExpiresDefault
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.
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>
.
The ExpiresFilter
has been designed for extension following the
open/close principle.
Key methods to override for extension are :
isEligibleToExpirationHeaderGeneration(HttpServletRequest, XHttpServletResponse)
getExpirationDate(XHttpServletResponse)
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
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. |
sm
Constructor and Description |
---|
ExpiresFilter() |
Modifier and Type | Method and Description |
---|---|
protected static int[] |
commaDelimitedListToIntArray(java.lang.String commaDelimitedInts)
Convert a comma delimited list of numbers into an
int[] . |
protected static java.lang.String[] |
commaDelimitedListToStringArray(java.lang.String commaDelimitedStrings)
Convert a given comma delimited list of strings into an array of String
|
protected static boolean |
contains(java.lang.String str,
java.lang.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() |
java.lang.String |
getExcludedResponseStatusCodes() |
int[] |
getExcludedResponseStatusCodesAsInts() |
protected java.util.Date |
getExpirationDate(ExpiresFilter.ExpiresConfiguration configuration,
ExpiresFilter.XHttpServletResponse response)
Returns the expiration date of the given
ExpiresFilter.ExpiresConfiguration ,
HttpServletRequest and ExpiresFilter.XHttpServletResponse . |
protected java.util.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. |
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 string
|
protected boolean |
isEligibleToExpirationHeaderGeneration(HttpServletRequest request,
ExpiresFilter.XHttpServletResponse response)
protected for extension. |
protected static boolean |
isEmpty(java.lang.String str)
Return
true if the given str is
null or has a zero characters length. |
protected static boolean |
isNotEmpty(java.lang.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(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)
Return
true if the given string starts with the
given prefix ignoring case. |
protected static java.lang.String |
substringBefore(java.lang.String str,
java.lang.String separator)
Return the subset of the given
str that is before the first
occurence of the given separator . |
java.lang.String |
toString() |
destroy, isConfigProblemFatal
protected static int[] commaDelimitedListToIntArray(java.lang.String commaDelimitedInts)
int[]
.commaDelimitedInts
- can be null
null
arrayprotected static java.lang.String[] commaDelimitedListToStringArray(java.lang.String commaDelimitedStrings)
null
)protected static boolean contains(java.lang.String str, java.lang.String searchStr)
true
if the given str
contains the given
searchStr
.protected static java.lang.String intsToCommaDelimitedString(int[] ints)
protected static boolean isEmpty(java.lang.String str)
true
if the given str
is
null
or has a zero characters length.protected static boolean isNotEmpty(java.lang.String str)
true
if the given str
has at least one
character (can be a withespace).protected static boolean startsWithIgnoreCase(java.lang.String string, java.lang.String prefix)
true
if the given string
starts with the
given prefix
ignoring case.string
- can be null
prefix
- can be null
protected static java.lang.String substringBefore(java.lang.String str, java.lang.String separator)
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.str
- can be null
separator
- can be null
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException
javax.servlet.Filter
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.
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
processingjava.io.IOException
- if an I/O error occurs during this filter's
processing of the requestServletException
- if the processing fails for any other reasonpublic ExpiresFilter.ExpiresConfiguration getDefaultExpiresConfiguration()
public java.lang.String getExcludedResponseStatusCodes()
public int[] getExcludedResponseStatusCodesAsInts()
protected java.util.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.
ServletResponse.getContentType()
protected java.util.Date getExpirationDate(ExpiresFilter.ExpiresConfiguration configuration, ExpiresFilter.XHttpServletResponse response)
Returns the expiration date of the given ExpiresFilter.ExpiresConfiguration
,
HttpServletRequest
and ExpiresFilter.XHttpServletResponse
.
protected
for extension.
public java.util.Map<java.lang.String,ExpiresFilter.ExpiresConfiguration> getExpiresConfigurationByContentType()
protected Log getLogger()
getLogger
in class FilterBase
public void init(FilterConfig filterConfig) throws ServletException
FilterBase
init
in interface Filter
init
in class FilterBase
filterConfig
- The configuration information associated with the
filter instance being initialisedServletException
- if FilterBase.isConfigProblemFatal()
returns
true
and a configured parameter does not
have a matching setterprotected boolean isEligibleToExpirationHeaderGeneration(HttpServletRequest request, ExpiresFilter.XHttpServletResponse response)
protected
for extension.
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).
protected ExpiresFilter.ExpiresConfiguration parseExpiresConfiguration(java.lang.String inputLine)
access plus 1 month 15 days 2 hours
' or
'modification 1 day 2 hours 5 seconds
'inputLine
- public void setDefaultExpiresConfiguration(ExpiresFilter.ExpiresConfiguration defaultExpiresConfiguration)
public void setExcludedResponseStatusCodes(int[] excludedResponseStatusCodes)
public void setExpiresConfigurationByContentType(java.util.Map<java.lang.String,ExpiresFilter.ExpiresConfiguration> expiresConfigurationByContentType)
public java.lang.String toString()
toString
in class java.lang.Object
Copyright © 2000-2018 Apache Software Foundation. All Rights Reserved.