View Javadoc
1   /*
2    * Copyright 2002-2015 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.web.filter;
18  
19  import java.io.IOException;
20  import javax.servlet.FilterChain;
21  import javax.servlet.ServletException;
22  import javax.servlet.ServletRequest;
23  import javax.servlet.ServletResponse;
24  import javax.servlet.http.HttpServletRequest;
25  import javax.servlet.http.HttpServletResponse;
26  
27  import org.springframework.web.context.request.async.WebAsyncManager;
28  import org.springframework.web.context.request.async.WebAsyncUtils;
29  import org.springframework.web.util.WebUtils;
30  
31  /**
32   * Filter base class that aims to guarantee a single execution per request
33   * dispatch, on any servlet container. It provides a {@link #doFilterInternal}
34   * method with HttpServletRequest and HttpServletResponse arguments.
35   *
36   * <p>As of Servlet 3.0, a filter may be invoked as part of a
37   * {@link javax.servlet.DispatcherType#REQUEST REQUEST} or
38   * {@link javax.servlet.DispatcherType#ASYNC ASYNC} dispatches that occur in
39   * separate threads. A filter can be configured in {@code web.xml} whether it
40   * should be involved in async dispatches. However, in some cases servlet
41   * containers assume different default configuration. Therefore sub-classes can
42   * override the method {@link #shouldNotFilterAsyncDispatch()} to declare
43   * statically if they should indeed be invoked, <em>once</em>, during both types
44   * of dispatches in order to provide thread initialization, logging, security,
45   * and so on. This mechanism complements and does not replace the need to
46   * configure a filter in {@code web.xml} with dispatcher types.
47   *
48   * <p>Subclasses may use {@link #isAsyncDispatch(HttpServletRequest)} to
49   * determine when a filter is invoked as part of an async dispatch, and use
50   * {@link #isAsyncStarted(HttpServletRequest)} to determine when the request
51   * has been placed in async mode and therefore the current dispatch won't be
52   * the last one for the given request.
53   *
54   * <p>Yet another dispatch type that also occurs in its own thread is
55   * {@link javax.servlet.DispatcherType#ERROR ERROR}. Subclasses can override
56   * {@link #shouldNotFilterErrorDispatch()} if they wish to declare statically
57   * if they should be invoked <em>once</em> during error dispatches.
58   *
59   * <p>The {@link #getAlreadyFilteredAttributeName} method determines how to
60   * identify that a request is already filtered. The default implementation is
61   * based on the configured name of the concrete filter instance.
62   *
63   * @author Juergen Hoeller
64   * @author Rossen Stoyanchev
65   * @since 06.12.2003
66   */
67  public abstract class OncePerRequestFilter extends GenericFilterBean {
68  
69  	/**
70  	 * Suffix that gets appended to the filter name for the
71  	 * "already filtered" request attribute.
72  	 * @see #getAlreadyFilteredAttributeName
73  	 */
74  	public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
75  
76  
77  	/**
78  	 * This {@code doFilter} implementation stores a request attribute for
79  	 * "already filtered", proceeding without filtering again if the
80  	 * attribute is already there.
81  	 * @see #getAlreadyFilteredAttributeName
82  	 * @see #shouldNotFilter
83  	 * @see #doFilterInternal
84  	 */
85  	@Override
86  	public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
87  			throws ServletException, IOException {
88  
89  		if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
90  			throw new ServletException("OncePerRequestFilter just supports HTTP requests");
91  		}
92  		HttpServletRequest httpRequest = (HttpServletRequest) request;
93  		HttpServletResponse httpResponse = (HttpServletResponse) response;
94  
95  		String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
96  		boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
97  
98  		if (hasAlreadyFilteredAttribute || skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
99  
100 			// Proceed without invoking this filter...
101 			filterChain.doFilter(request, response);
102 		}
103 		else {
104 			// Do invoke this filter...
105 			request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
106 			try {
107 				doFilterInternal(httpRequest, httpResponse, filterChain);
108 			}
109 			finally {
110 				// Remove the "already filtered" request attribute for this request.
111 				request.removeAttribute(alreadyFilteredAttributeName);
112 			}
113 		}
114 	}
115 
116 
117 	private boolean skipDispatch(HttpServletRequest request) {
118 		if (isAsyncDispatch(request) && shouldNotFilterAsyncDispatch()) {
119 			return true;
120 		}
121 		if (request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE) != null && shouldNotFilterErrorDispatch()) {
122 			return true;
123 		}
124 		return false;
125 	}
126 
127 	/**
128 	 * The dispatcher type {@code javax.servlet.DispatcherType.ASYNC} introduced
129 	 * in Servlet 3.0 means a filter can be invoked in more than one thread over
130 	 * the course of a single request. This method returns {@code true} if the
131 	 * filter is currently executing within an asynchronous dispatch.
132 	 * @param request the current request
133 	 * @since 3.2
134 	 * @see WebAsyncManager#hasConcurrentResult()
135 	 */
136 	protected boolean isAsyncDispatch(HttpServletRequest request) {
137 		return WebAsyncUtils.getAsyncManager(request).hasConcurrentResult();
138 	}
139 
140 	/**
141 	 * Whether request processing is in asynchronous mode meaning that the
142 	 * response will not be committed after the current thread is exited.
143 	 * @param request the current request
144 	 * @since 3.2
145 	 * @see WebAsyncManager#isConcurrentHandlingStarted()
146 	 */
147 	protected boolean isAsyncStarted(HttpServletRequest request) {
148 		return WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted();
149 	}
150 
151 	/**
152 	 * Return the name of the request attribute that identifies that a request
153 	 * is already filtered.
154 	 * <p>The default implementation takes the configured name of the concrete filter
155 	 * instance and appends ".FILTERED". If the filter is not fully initialized,
156 	 * it falls back to its class name.
157 	 * @see #getFilterName
158 	 * @see #ALREADY_FILTERED_SUFFIX
159 	 */
160 	protected String getAlreadyFilteredAttributeName() {
161 		String name = getFilterName();
162 		if (name == null) {
163 			name = getClass().getName();
164 		}
165 		return name + ALREADY_FILTERED_SUFFIX;
166 	}
167 
168 	/**
169 	 * Can be overridden in subclasses for custom filtering control,
170 	 * returning {@code true} to avoid filtering of the given request.
171 	 * <p>The default implementation always returns {@code false}.
172 	 * @param request current HTTP request
173 	 * @return whether the given request should <i>not</i> be filtered
174 	 * @throws ServletException in case of errors
175 	 */
176 	protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
177 		return false;
178 	}
179 
180 	/**
181 	 * The dispatcher type {@code javax.servlet.DispatcherType.ASYNC} introduced
182 	 * in Servlet 3.0 means a filter can be invoked in more than one thread
183 	 * over the course of a single request. Some filters only need to filter
184 	 * the initial thread (e.g. request wrapping) while others may need
185 	 * to be invoked at least once in each additional thread for example for
186 	 * setting up thread locals or to perform final processing at the very end.
187 	 * <p>Note that although a filter can be mapped to handle specific dispatcher
188 	 * types via {@code web.xml} or in Java through the {@code ServletContext},
189 	 * servlet containers may enforce different defaults with regards to
190 	 * dispatcher types. This flag enforces the design intent of the filter.
191 	 * <p>The default return value is "true", which means the filter will not be
192 	 * invoked during subsequent async dispatches. If "false", the filter will
193 	 * be invoked during async dispatches with the same guarantees of being
194 	 * invoked only once during a request within a single thread.
195 	 * @since 3.2
196 	 */
197 	protected boolean shouldNotFilterAsyncDispatch() {
198 		return true;
199 	}
200 
201 	/**
202 	 * Whether to filter error dispatches such as when the servlet container
203 	 * processes and error mapped in {@code web.xml}. The default return value
204 	 * is "true", which means the filter will not be invoked in case of an error
205 	 * dispatch.
206 	 * @since 3.2
207 	 */
208 	protected boolean shouldNotFilterErrorDispatch() {
209 		return true;
210 	}
211 
212 
213 	/**
214 	 * Same contract as for {@code doFilter}, but guaranteed to be
215 	 * just invoked once per request within a single request thread.
216 	 * See {@link #shouldNotFilterAsyncDispatch()} for details.
217 	 * <p>Provides HttpServletRequest and HttpServletResponse arguments instead of the
218 	 * default ServletRequest and ServletResponse ones.
219 	 */
220 	protected abstract void doFilterInternal(
221 			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
222 			throws ServletException, IOException;
223 
224 }