001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.filter;
018
019import java.io.IOException;
020
021import javax.servlet.DispatcherType;
022import javax.servlet.FilterChain;
023import javax.servlet.ServletException;
024import javax.servlet.ServletRequest;
025import javax.servlet.ServletResponse;
026import javax.servlet.http.HttpServletRequest;
027import javax.servlet.http.HttpServletResponse;
028
029import org.springframework.web.context.request.async.WebAsyncManager;
030import org.springframework.web.context.request.async.WebAsyncUtils;
031import org.springframework.web.util.WebUtils;
032
033/**
034 * Filter base class that aims to guarantee a single execution per request
035 * dispatch, on any servlet container. It provides a {@link #doFilterInternal}
036 * method with HttpServletRequest and HttpServletResponse arguments.
037 *
038 * <p>As of Servlet 3.0, a filter may be invoked as part of a
039 * {@link javax.servlet.DispatcherType#REQUEST REQUEST} or
040 * {@link javax.servlet.DispatcherType#ASYNC ASYNC} dispatches that occur in
041 * separate threads. A filter can be configured in {@code web.xml} whether it
042 * should be involved in async dispatches. However, in some cases servlet
043 * containers assume different default configuration. Therefore sub-classes can
044 * override the method {@link #shouldNotFilterAsyncDispatch()} to declare
045 * statically if they should indeed be invoked, <em>once</em>, during both types
046 * of dispatches in order to provide thread initialization, logging, security,
047 * and so on. This mechanism complements and does not replace the need to
048 * configure a filter in {@code web.xml} with dispatcher types.
049 *
050 * <p>Subclasses may use {@link #isAsyncDispatch(HttpServletRequest)} to
051 * determine when a filter is invoked as part of an async dispatch, and use
052 * {@link #isAsyncStarted(HttpServletRequest)} to determine when the request
053 * has been placed in async mode and therefore the current dispatch won't be
054 * the last one for the given request.
055 *
056 * <p>Yet another dispatch type that also occurs in its own thread is
057 * {@link javax.servlet.DispatcherType#ERROR ERROR}. Subclasses can override
058 * {@link #shouldNotFilterErrorDispatch()} if they wish to declare statically
059 * if they should be invoked <em>once</em> during error dispatches.
060 *
061 * <p>The {@link #getAlreadyFilteredAttributeName} method determines how to
062 * identify that a request is already filtered. The default implementation is
063 * based on the configured name of the concrete filter instance.
064 *
065 * @author Juergen Hoeller
066 * @author Rossen Stoyanchev
067 * @since 06.12.2003
068 */
069public abstract class OncePerRequestFilter extends GenericFilterBean {
070
071        /**
072         * Suffix that gets appended to the filter name for the
073         * "already filtered" request attribute.
074         * @see #getAlreadyFilteredAttributeName
075         */
076        public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
077
078
079        /**
080         * This {@code doFilter} implementation stores a request attribute for
081         * "already filtered", proceeding without filtering again if the
082         * attribute is already there.
083         * @see #getAlreadyFilteredAttributeName
084         * @see #shouldNotFilter
085         * @see #doFilterInternal
086         */
087        @Override
088        public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
089                        throws ServletException, IOException {
090
091                if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
092                        throw new ServletException("OncePerRequestFilter just supports HTTP requests");
093                }
094                HttpServletRequest httpRequest = (HttpServletRequest) request;
095                HttpServletResponse httpResponse = (HttpServletResponse) response;
096
097                String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
098                boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
099
100                if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
101
102                        // Proceed without invoking this filter...
103                        filterChain.doFilter(request, response);
104                }
105                else if (hasAlreadyFilteredAttribute) {
106
107                        if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
108                                doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
109                                return;
110                        }
111
112                        // Proceed without invoking this filter...
113                        filterChain.doFilter(request, response);
114                }
115                else {
116                        // Do invoke this filter...
117                        request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
118                        try {
119                                doFilterInternal(httpRequest, httpResponse, filterChain);
120                        }
121                        finally {
122                                // Remove the "already filtered" request attribute for this request.
123                                request.removeAttribute(alreadyFilteredAttributeName);
124                        }
125                }
126        }
127
128        private boolean skipDispatch(HttpServletRequest request) {
129                if (isAsyncDispatch(request) && shouldNotFilterAsyncDispatch()) {
130                        return true;
131                }
132                if (request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE) != null && shouldNotFilterErrorDispatch()) {
133                        return true;
134                }
135                return false;
136        }
137
138        /**
139         * The dispatcher type {@code javax.servlet.DispatcherType.ASYNC} introduced
140         * in Servlet 3.0 means a filter can be invoked in more than one thread over
141         * the course of a single request. This method returns {@code true} if the
142         * filter is currently executing within an asynchronous dispatch.
143         * @param request the current request
144         * @since 3.2
145         * @see WebAsyncManager#hasConcurrentResult()
146         */
147        protected boolean isAsyncDispatch(HttpServletRequest request) {
148                return WebAsyncUtils.getAsyncManager(request).hasConcurrentResult();
149        }
150
151        /**
152         * Whether request processing is in asynchronous mode meaning that the
153         * response will not be committed after the current thread is exited.
154         * @param request the current request
155         * @since 3.2
156         * @see WebAsyncManager#isConcurrentHandlingStarted()
157         */
158        protected boolean isAsyncStarted(HttpServletRequest request) {
159                return WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted();
160        }
161
162        /**
163         * Return the name of the request attribute that identifies that a request
164         * is already filtered.
165         * <p>The default implementation takes the configured name of the concrete filter
166         * instance and appends ".FILTERED". If the filter is not fully initialized,
167         * it falls back to its class name.
168         * @see #getFilterName
169         * @see #ALREADY_FILTERED_SUFFIX
170         */
171        protected String getAlreadyFilteredAttributeName() {
172                String name = getFilterName();
173                if (name == null) {
174                        name = getClass().getName();
175                }
176                return name + ALREADY_FILTERED_SUFFIX;
177        }
178
179        /**
180         * Can be overridden in subclasses for custom filtering control,
181         * returning {@code true} to avoid filtering of the given request.
182         * <p>The default implementation always returns {@code false}.
183         * @param request current HTTP request
184         * @return whether the given request should <i>not</i> be filtered
185         * @throws ServletException in case of errors
186         */
187        protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
188                return false;
189        }
190
191        /**
192         * The dispatcher type {@code javax.servlet.DispatcherType.ASYNC} introduced
193         * in Servlet 3.0 means a filter can be invoked in more than one thread
194         * over the course of a single request. Some filters only need to filter
195         * the initial thread (e.g. request wrapping) while others may need
196         * to be invoked at least once in each additional thread for example for
197         * setting up thread locals or to perform final processing at the very end.
198         * <p>Note that although a filter can be mapped to handle specific dispatcher
199         * types via {@code web.xml} or in Java through the {@code ServletContext},
200         * servlet containers may enforce different defaults with regards to
201         * dispatcher types. This flag enforces the design intent of the filter.
202         * <p>The default return value is "true", which means the filter will not be
203         * invoked during subsequent async dispatches. If "false", the filter will
204         * be invoked during async dispatches with the same guarantees of being
205         * invoked only once during a request within a single thread.
206         * @since 3.2
207         */
208        protected boolean shouldNotFilterAsyncDispatch() {
209                return true;
210        }
211
212        /**
213         * Whether to filter error dispatches such as when the servlet container
214         * processes and error mapped in {@code web.xml}. The default return value
215         * is "true", which means the filter will not be invoked in case of an error
216         * dispatch.
217         * @since 3.2
218         */
219        protected boolean shouldNotFilterErrorDispatch() {
220                return true;
221        }
222
223
224        /**
225         * Same contract as for {@code doFilter}, but guaranteed to be
226         * just invoked once per request within a single request thread.
227         * See {@link #shouldNotFilterAsyncDispatch()} for details.
228         * <p>Provides HttpServletRequest and HttpServletResponse arguments instead of the
229         * default ServletRequest and ServletResponse ones.
230         */
231        protected abstract void doFilterInternal(
232                        HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
233                        throws ServletException, IOException;
234
235        /**
236         * Typically an ERROR dispatch happens after the REQUEST dispatch completes,
237         * and the filter chain starts anew. On some servers however the ERROR
238         * dispatch may be nested within the REQUEST dispatch, e.g. as a result of
239         * calling {@code sendError} on the response. In that case we are still in
240         * the filter chain, on the same thread, but the request and response have
241         * been switched to the original, unwrapped ones.
242         * <p>Sub-classes may use this method to filter such nested ERROR dispatches
243         * and re-apply wrapping on the request or response. {@code ThreadLocal}
244         * context, if any, should still be active as we are still nested within
245         * the filter chain.
246         * @since 5.1.9
247         */
248        protected void doFilterNestedErrorDispatch(HttpServletRequest request, HttpServletResponse response,
249                        FilterChain filterChain) throws ServletException, IOException {
250
251                filterChain.doFilter(request, response);
252        }
253
254}