|
|||||||||
| PREV CLASS NEXT CLASS | FRAMES NO FRAMES | ||||||||
| SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD | ||||||||
java.lang.Objectfr.xebia.servlet.filter.ExpiresFilter
public class ExpiresFilter
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.
The filter will process the x-forwarded-for http header.
<web-app ...>
...
<filter>
<filter-name>ExpiresFilter</filter-name>
<filter-class>fr.xebia.servlet.filter.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>
This directive enables or disables the generation of the Expires and Cache-Control headers by this ExpiresFilter. If set to Off, the headers will not be generated for any HTTP response. If set to On or true, the headers will be added to served HTTP responses according to the criteria defined by the ExpiresByType <content-type> and ExpiresDefault directives. Note that this directive does not guarantee that an Expires or Cache-Control header will be generated. If the criteria aren't met, no header will be sent, and the effect will be as though this directive wasn't even specified.
This parameter is optional, default value is true.
Enable filter
<init-param>
<!-- supports case insensitive 'On' or 'true' -->
<param-name>ExpiresActive</param-name><param-value>On</param-value>
</init-param>
Disable filter
<init-param>
<!-- supports anything different from case insensitive 'On' and 'true' -->
<param-name>ExpiresActive</param-name><param-value>Off</param-value>
</init-param>
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.
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>
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:
The plus keyword is optional. <num> should be an integer value (acceptable to Integer.parseInt()), and <type> is one of:
<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 :
Note :
The expiration configuration if elected according to the following algorithm:
1.0.3 is not yet available, please build the snapshot
<project ...>
...
<repositories>
<repository>
<id>xebia-france-googlecode-repository</id>
<url>http://xebia-france.googlecode.com/svn/repository/maven2/</url>
</repository>
</repositories>
...
<dependencies>
...
<dependency>
<groupId>fr.xebia.web.extras</groupId>
<artifactId>xebia-servlet-extras</artifactId>
<version>1.0.3</version>
<scope>runtime</scope>
</dependency>
...
</dependencies>
...
</project>
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(HttpServletRequest, XHttpServletResponse)To troubleshoot, enable logging on the fr.xebia.servlet.filter.ExpiresFilter. Logging relies on SLF4J.
Extract of log4j.properties
log4j.logger.fr.xebia.servlet.filter.ExpiresFilter = DEBUG
Sample of initialization log message :
2010/03/24 18:18:12,637 INFO [main] fr.xebia.servlet.filter.ExpiresFilter - Filter initialized with configuration ExpiresFilter[
active=true,
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]],
text/html=ExpiresConfiguration[startingPoint=ACCESS_TIME, duration=[5 MINUTE]]}]
Sample of per-request log message where ExpiresFilter adds an expiration date
2010/03/24 18:43:34,586 DEBUG [http-8080-1] fr.xebia.servlet.filter.ExpiresFilter -
Request '/' with response status '200' content-type 'text/html', set expiration date Wed Mar 24 18:48:34 CET 2010
Sample of per-request log message where ExpiresFilter does not add an expiration date
2010/03/24 18:43:34,564 DEBUG [http-8080-2] fr.xebia.servlet.filter.ExpiresFilter -
Request '/services/helloWorldService' with response status '200' content-type 'text/xml;charset=UTF-8' status , no expiration configured
| Nested Class Summary | |
|---|---|
protected static class |
ExpiresFilter.Duration
Duration composed of an ExpiresFilter.Duration.amount and a ExpiresFilter.Duration.unit |
protected static class |
ExpiresFilter.DurationUnit
Duration unit |
protected static class |
ExpiresFilter.ExpiresConfiguration
Main piece of configuration of the filter. |
protected static class |
ExpiresFilter.StartingPoint
Expiration configuration starting point. |
class |
ExpiresFilter.XHttpServletResponse
Wrapping extension of the HttpServletResponse to yrap the
"Start Write Response Body" event. |
class |
ExpiresFilter.XPrintWriter
Wrapping extension of PrintWriter to trap the
"Start Write Response Body" event. |
class |
ExpiresFilter.XServletOutputStream
Wrapping extension of ServletOutputStream to trap the
"Start Write Response Body" event. |
| Constructor Summary | |
|---|---|
ExpiresFilter()
|
|
| Method Summary | |
|---|---|
protected static int[] |
commaDelimitedListToIntArray(String commaDelimitedInts)
Convert a comma delimited list of numbers into an int[]. |
protected static String[] |
commaDelimitedListToStringArray(String commaDelimitedStrings)
Convert a given comma delimited list of strings into an array of String |
protected static boolean |
contains(String str,
String searchStr)
Return true if the given str contains the given
searchStr. |
void |
destroy()
|
void |
doFilter(javax.servlet.ServletRequest request,
javax.servlet.ServletResponse response,
javax.servlet.FilterChain chain)
|
ExpiresFilter.ExpiresConfiguration |
getDefaultExpiresConfiguration()
|
String |
getExcludedResponseStatusCodes()
|
int[] |
getExcludedResponseStatusCodesAsInts()
|
protected Date |
getExpirationDate(javax.servlet.http.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()
|
void |
init(javax.servlet.FilterConfig filterConfig)
|
protected static String |
intsToCommaDelimitedString(int[] ints)
Convert an array of ints into a comma delimited string |
boolean |
isActive()
Indicates that the filter is active. |
protected boolean |
isEligibleToExpirationHeaderGeneration(javax.servlet.http.HttpServletRequest request,
ExpiresFilter.XHttpServletResponse response)
protected for extension. |
protected static boolean |
isEmpty(String str)
Return true if the given str is
null or has a zero characters length. |
protected static boolean |
isNotEmpty(String str)
Return true if the given str has at least one
character (can be a withespace). |
void |
onBeforeWriteResponseBody(javax.servlet.http.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 line)
Parse configuration lines like ' access plus 1 month 15 days 2 hours' or ' modification 1 day 2 hours 5 seconds' |
void |
setActive(boolean active)
|
void |
setDefaultExpiresConfiguration(ExpiresFilter.ExpiresConfiguration defaultExpiresConfiguration)
|
void |
setExcludedResponseStatusCodes(int[] excludedResponseStatusCodes)
|
void |
setExpiresConfigurationByContentType(Map<String,ExpiresFilter.ExpiresConfiguration> expiresConfigurationByContentType)
|
protected static boolean |
startsWithIgnoreCase(String string,
String prefix)
Return true if the given string starts with the
given prefix ignoring case. |
protected static String |
substringBefore(String str,
String separator)
Return the subset of the given str that is before the first
occurence of the given separator. |
String |
toString()
|
| Methods inherited from class java.lang.Object |
|---|
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait |
| Constructor Detail |
|---|
public ExpiresFilter()
| Method Detail |
|---|
protected static int[] commaDelimitedListToIntArray(String commaDelimitedInts)
commaDelimitedInts - can be null
null arrayprotected static String[] commaDelimitedListToStringArray(String commaDelimitedStrings)
null)
protected static boolean contains(String str,
String searchStr)
true if the given str contains the given
searchStr.
protected static String intsToCommaDelimitedString(int[] ints)
protected static boolean isEmpty(String str)
true if the given str is
null or has a zero characters length.
protected static boolean isNotEmpty(String str)
true if the given str has at least one
character (can be a withespace).
protected static boolean startsWithIgnoreCase(String string,
String prefix)
true if the given string starts with the
given prefix ignoring case.
string - can be nullprefix - can be null
protected static String substringBefore(String str,
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 nullseparator - can be null
public void destroy()
destroy in interface javax.servlet.Filter
public void doFilter(javax.servlet.ServletRequest request,
javax.servlet.ServletResponse response,
javax.servlet.FilterChain chain)
throws IOException,
javax.servlet.ServletException
doFilter in interface javax.servlet.FilterIOException
javax.servlet.ServletExceptionpublic ExpiresFilter.ExpiresConfiguration getDefaultExpiresConfiguration()
public String getExcludedResponseStatusCodes()
public int[] getExcludedResponseStatusCodesAsInts()
protected Date getExpirationDate(javax.servlet.http.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.
ServletResponse.getContentType()public Map<String,ExpiresFilter.ExpiresConfiguration> getExpiresConfigurationByContentType()
public void init(javax.servlet.FilterConfig filterConfig)
throws javax.servlet.ServletException
init in interface javax.servlet.Filterjavax.servlet.ServletExceptionpublic boolean isActive()
false, the filter is
pass-through. Default is true.
protected boolean isEligibleToExpirationHeaderGeneration(javax.servlet.http.HttpServletRequest request,
ExpiresFilter.XHttpServletResponse response)
protected for extension.
public void onBeforeWriteResponseBody(javax.servlet.http.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
Logger.isDebugEnabled() because
HttpServletRequest.getRequestURI() and
ServletResponse.getContentType() costs String
objects instantiations (as of Tomcat 7).
protected ExpiresFilter.ExpiresConfiguration parseExpiresConfiguration(String line)
line - public void setActive(boolean active)
public void setDefaultExpiresConfiguration(ExpiresFilter.ExpiresConfiguration defaultExpiresConfiguration)
public void setExcludedResponseStatusCodes(int[] excludedResponseStatusCodes)
public void setExpiresConfigurationByContentType(Map<String,ExpiresFilter.ExpiresConfiguration> expiresConfigurationByContentType)
public String toString()
toString in class Object
|
|||||||||
| PREV CLASS NEXT CLASS | FRAMES NO FRAMES | ||||||||
| SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD | ||||||||