001 /*
002 * Created by IntelliJ IDEA.
003 * User: d@hd.org
004 * Date: 25-Aug-02
005 * Time: 19:35:35
006
007 Copyright (c) 1996-2012, Damon Hart-Davis
008 All rights reserved.
009
010 Redistribution and use in source and binary forms, with or without
011 modification, are permitted provided that the following conditions are
012 met:
013
014 * Redistributions of source code must retain the above copyright
015 notice, this list of conditions and the following disclaimer.
016
017 * Redistributions in binary form must reproduce the above copyright
018 notice, this list of conditions and the following disclaimer in the
019 documentation and/or other materials provided with the
020 distribution.
021
022 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
023 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
024 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
025 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
026 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
027 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
028 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
029 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
030 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
031 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
032 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
033
034 */
035 package org.hd.d.pg2k.webSvr.util;
036
037 import java.io.IOException;
038
039 import javax.servlet.Filter;
040 import javax.servlet.FilterChain;
041 import javax.servlet.FilterConfig;
042 import javax.servlet.ServletException;
043 import javax.servlet.ServletRequest;
044 import javax.servlet.ServletResponse;
045 import javax.servlet.http.HttpServletRequest;
046 import javax.servlet.http.HttpServletResponse;
047 import javax.servlet.http.HttpServletResponseWrapper;
048
049 import ORG.hd.d.IsDebug;
050
051 /**Explicitly encourages browser to cache filtered items.
052 * This sets headers that explicitly encourage
053 * the client to cache the filtered item for a long-ish time
054 * (of the order of hours to weeks) on the assumption that
055 * the filtered item changes slowly if at all.
056 * <p>
057 * This should work even when the items are delivered over HTTPS,
058 * which would normally prevent them being cached.
059 * <p>
060 * This should work for HTTP/1.0 clients (we set Expires), and
061 * HTTP/1.1 client (we set Cache-Control).
062 * <p>
063 * This filter is stateless and as lightweight and simple as possible.
064 */
065 public final class ExplicitCacheTimeFilter implements Filter
066 {
067 /**Logger; never null. */
068 private final WebUtils.ServletLoggerWithFallback logger = new WebUtils.ServletLoggerWithFallback();
069
070 /**Bring filter into service. */
071 public void init(final FilterConfig filterConfig)
072 {
073 logger.setContext(filterConfig.getServletContext());
074 }
075
076 /**Take this filter out of service. */
077 public void destroy()
078 {
079 logger.setContext(null);
080 }
081
082 /**Headers that we set to make cacheing work.
083 * We reject attempts to override any of these (and warn on System.err).
084 * <p>
085 * These should be matched case-insensitively.
086 */
087 private static final String DANGER_HEADERS[] =
088 { "Expires", "Pragma", "Cache-Control" };
089
090 /**Name of attribute that we use to avoid duplicate application of this filter on one filter chain. */
091 private static final String ANTI_DUP_ATTR_NAME = "org.hd.d.pg2k.webSvr.util.ExplicitCacheTimeFilter.DUPFLAG";
092
093 /**Filter one item.
094 * If none of the cache-controlling headers for HTTP/1.0 or 1.1 are
095 * already set, set them ourselves to a reasonably lengthy time.
096 */
097 public void doFilter(final ServletRequest request,
098 final ServletResponse response,
099 final FilterChain chain)
100 throws IOException, ServletException
101 {
102 // Avoid duplicate application of this filter,
103 // eg during servlet redirection.
104 if(null != request.getAttribute(ANTI_DUP_ATTR_NAME))
105 {
106 // Avoid counting the same traffic more than once.
107 if(IsDebug.isDebug) { logger.log("WARNING: ExplicitCacheTimeFilter: avoiding duplicate application to " + request); }
108 chain.doFilter(request, response);
109 return;
110 }
111 request.setAttribute(ANTI_DUP_ATTR_NAME, Boolean.TRUE);
112
113 // If this is not an HTTP response, do not fiddle with it.
114 if(!(response instanceof HttpServletResponse) ||
115 !(request instanceof HttpServletRequest))
116 {
117 chain.doFilter(request, response);
118 return;
119 }
120
121 final HttpServletResponse hsr = (HttpServletResponse) response;
122
123 // If any of the `danger' headers is already set,
124 // indicating that the cache behaviour is already being
125 // explicitly handled/manipulated,
126 // then back out immediately!
127 for(int i = DANGER_HEADERS.length; --i >= 0; )
128 {
129 if(hsr.containsHeader(DANGER_HEADERS[i]))
130 {
131 logger.log("WARNING: ExplicitCacheTimeFilter: backing off setting cache headers (having found header "+DANGER_HEADERS[i]+") for: " + ((HttpServletRequest) request).getRequestURI());
132 chain.doFilter(request, response);
133 return;
134 }
135 }
136
137 // Make wrapper to force headers to end up set the way we want...
138 final class Wrapper extends HttpServletResponseWrapper
139 {
140 /**Fixed cache time (ms); strictly positive. */
141 private static final long CACHE_MS = WebConsts.DEFAULT_STATIC_WEBITEMS_CACHE_MS;
142 /**Fixed cache-control header value including 'public' attribute; non-null. */
143 private static final String CACHE_CONTROL_VALUE = "public,max-age="+(CACHE_MS/1000);
144
145 /**Pass the original response to the base class.
146 * We also immediately set the values we want set.
147 * We complain later if there is any attempt to set any of them.
148 */
149 Wrapper(final HttpServletResponse hsResponse)
150 {
151 // Pass in original response...
152 super(hsResponse);
153
154 //logger.log("Setting cache headers (for "+CACHE_MS+"ms) for: " + ((HttpServletRequest) request).getRequestURI());
155
156 // Add Expires for HTTP/1.0 clients and
157 // add Cache-Control for HTTP/1.1 client.
158 // Maybe we should wrap the response with a modified one,
159 // rather than modifying the existing one.
160 hsResponse.setHeader("Cache-Control", CACHE_CONTROL_VALUE);
161 hsResponse.setDateHeader("Expires", System.currentTimeMillis() + CACHE_MS);
162 }
163
164 @Override
165 public void setHeader(final String s, final String s1)
166 {
167 for(int i = DANGER_HEADERS.length; --i >= 0; )
168 {
169 if(DANGER_HEADERS[i].equalsIgnoreCase(s))
170 {
171 logger.log("WARNING: ExplicitCacheTimeFilter: vetoing attempt to set header "+s+" for: " + ((HttpServletRequest) request).getRequestURI());
172 return;
173 }
174 }
175 super.setHeader(s, s1);
176 }
177
178 @Override
179 public void addHeader(final String s, final String s1)
180 {
181 for(int i = DANGER_HEADERS.length; --i >= 0; )
182 {
183 if(DANGER_HEADERS[i].equalsIgnoreCase(s))
184 {
185 logger.log("ExplicitCacheTimeFilter: WARNING: vetoing attempt to add header "+s+" for: " + ((HttpServletRequest) request).getRequestURI());
186 return;
187 }
188 }
189 super.addHeader(s, s1);
190 }
191 }
192
193 // Continue, with new explicit cacheing headers in place.
194 chain.doFilter(request, new Wrapper(hsr));
195 }
196 }
197