Skip to content

Commit 1ff29b0

Browse files
committed
Merge branch '5.1.x'
2 parents dae4509 + 99c4a9e commit 1ff29b0

File tree

3 files changed

+228
-18
lines changed

3 files changed

+228
-18
lines changed

spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,12 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
158158
}
159159
}
160160

161+
@Override
162+
protected void doFilterNestedErrorDispatch(HttpServletRequest request, HttpServletResponse response,
163+
FilterChain filterChain) throws ServletException, IOException {
164+
165+
doFilterInternal(request, response, filterChain);
166+
}
161167

162168
/**
163169
* Hide "Forwarded" or "X-Forwarded-*" headers.

spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,19 @@ public final void doFilter(ServletRequest request, ServletResponse response, Fil
9494
HttpServletResponse httpResponse = (HttpServletResponse) response;
9595

9696
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
97-
alreadyFilteredAttributeName = updateForErrorDispatch(alreadyFilteredAttributeName, request);
9897
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
9998

100-
if (hasAlreadyFilteredAttribute || skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
99+
if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
100+
101+
// Proceed without invoking this filter...
102+
filterChain.doFilter(request, response);
103+
}
104+
else if (hasAlreadyFilteredAttribute) {
105+
106+
if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
107+
doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
108+
return;
109+
}
101110

102111
// Proceed without invoking this filter...
103112
filterChain.doFilter(request, response);
@@ -115,7 +124,6 @@ public final void doFilter(ServletRequest request, ServletResponse response, Fil
115124
}
116125
}
117126

118-
119127
private boolean skipDispatch(HttpServletRequest request) {
120128
if (isAsyncDispatch(request) && shouldNotFilterAsyncDispatch()) {
121129
return true;
@@ -167,21 +175,6 @@ protected String getAlreadyFilteredAttributeName() {
167175
return name + ALREADY_FILTERED_SUFFIX;
168176
}
169177

170-
private String updateForErrorDispatch(String alreadyFilteredAttributeName, ServletRequest request) {
171-
172-
// Jetty does ERROR dispatch within sendError, so request attribute is still present
173-
// Use a separate attribute for ERROR dispatches
174-
175-
if (DispatcherType.ERROR.equals(request.getDispatcherType()) && !shouldNotFilterErrorDispatch() &&
176-
request.getAttribute(alreadyFilteredAttributeName) != null) {
177-
178-
return alreadyFilteredAttributeName + ".ERROR";
179-
}
180-
181-
182-
return alreadyFilteredAttributeName;
183-
}
184-
185178
/**
186179
* Can be overridden in subclasses for custom filtering control,
187180
* returning {@code true} to avoid filtering of the given request.
@@ -238,4 +231,23 @@ protected abstract void doFilterInternal(
238231
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
239232
throws ServletException, IOException;
240233

234+
/**
235+
* Typically an ERROR dispatch happens after the REQUEST dispatch completes,
236+
* and the filter chain starts anew. On some servers however the ERROR
237+
* dispatch may be nested within the REQUEST dispatch, e.g. as a result of
238+
* calling {@code sendError} on the response. In that case we are still in
239+
* the filter chain, on the same thread, but the request and response have
240+
* been switched to the original, unwrapped ones.
241+
* <p>Sub-classes may use this method to filter such nested ERROR dispatches
242+
* and re-apply wrapping on the request or response. {@code ThreadLocal}
243+
* context, if any, should still be active as we are still nested within
244+
* the filter chain.
245+
* @since 5.1.9
246+
*/
247+
protected void doFilterNestedErrorDispatch(HttpServletRequest request, HttpServletResponse response,
248+
FilterChain filterChain) throws ServletException, IOException {
249+
250+
doFilter(request, response, filterChain);
251+
}
252+
241253
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/*
2+
* Copyright 2002-2019 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+
* https://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+
package org.springframework.web.filter;
17+
18+
import java.io.IOException;
19+
import javax.servlet.DispatcherType;
20+
import javax.servlet.FilterChain;
21+
import javax.servlet.ServletException;
22+
import javax.servlet.http.HttpServlet;
23+
import javax.servlet.http.HttpServletRequest;
24+
import javax.servlet.http.HttpServletResponse;
25+
26+
import org.junit.Before;
27+
import org.junit.Test;
28+
29+
import org.springframework.mock.web.test.MockFilterChain;
30+
import org.springframework.mock.web.test.MockHttpServletRequest;
31+
import org.springframework.mock.web.test.MockHttpServletResponse;
32+
import org.springframework.web.util.WebUtils;
33+
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
36+
37+
/**
38+
* Unit tests for {@link OncePerRequestFilter}.
39+
* @author Rossen Stoyanchev
40+
* @since 5.1.9
41+
*/
42+
public class OncePerRequestFilterTests {
43+
44+
private final TestOncePerRequestFilter filter = new TestOncePerRequestFilter();
45+
46+
private MockHttpServletRequest request;
47+
48+
private MockFilterChain filterChain;
49+
50+
51+
@Before
52+
@SuppressWarnings("serial")
53+
public void setup() throws Exception {
54+
this.request = new MockHttpServletRequest();
55+
this.request.setScheme("http");
56+
this.request.setServerName("localhost");
57+
this.request.setServerPort(80);
58+
this.filterChain = new MockFilterChain(new HttpServlet() {});
59+
}
60+
61+
62+
@Test
63+
public void filterOnce() throws ServletException, IOException {
64+
65+
// Already filtered
66+
this.request.setAttribute(this.filter.getAlreadyFilteredAttributeName(), Boolean.TRUE);
67+
68+
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
69+
assertThat(this.filter.didFilter).isFalse();
70+
assertThat(this.filter.didFilterNestedErrorDispatch).isFalse();
71+
72+
// Remove already filtered
73+
this.request.removeAttribute(this.filter.getAlreadyFilteredAttributeName());
74+
this.filter.reset();
75+
76+
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
77+
assertThat(this.filter.didFilter).isTrue();
78+
assertThat(this.filter.didFilterNestedErrorDispatch).isFalse();
79+
}
80+
81+
@Test
82+
public void shouldNotFilterErrorDispatch() throws ServletException, IOException {
83+
84+
initErrorDispatch();
85+
86+
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
87+
assertThat(this.filter.didFilter).isFalse();
88+
assertThat(this.filter.didFilterNestedErrorDispatch).isFalse();
89+
}
90+
91+
@Test
92+
public void shouldNotFilterNestedErrorDispatch() throws ServletException, IOException {
93+
94+
initErrorDispatch();
95+
this.request.setAttribute(this.filter.getAlreadyFilteredAttributeName(), Boolean.TRUE);
96+
97+
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
98+
assertThat(this.filter.didFilter).isFalse();
99+
assertThat(this.filter.didFilterNestedErrorDispatch).isFalse();
100+
}
101+
102+
@Test // gh-23196
103+
public void filterNestedErrorDispatch() throws ServletException, IOException {
104+
105+
// Opt in for ERROR dispatch
106+
this.filter.setShouldNotFilterErrorDispatch(false);
107+
108+
this.request.setAttribute(this.filter.getAlreadyFilteredAttributeName(), Boolean.TRUE);
109+
initErrorDispatch();
110+
111+
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
112+
assertThat(this.filter.didFilter).isFalse();
113+
assertThat(this.filter.didFilterNestedErrorDispatch).isTrue();
114+
}
115+
116+
private void initErrorDispatch() {
117+
this.request.setDispatcherType(DispatcherType.ERROR);
118+
this.request.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error");
119+
}
120+
121+
122+
private static class TestOncePerRequestFilter extends OncePerRequestFilter {
123+
124+
private boolean shouldNotFilter;
125+
126+
private boolean shouldNotFilterAsyncDispatch = true;
127+
128+
private boolean shouldNotFilterErrorDispatch = true;
129+
130+
private boolean didFilter;
131+
132+
private boolean didFilterNestedErrorDispatch;
133+
134+
135+
public void setShouldNotFilter(boolean shouldNotFilter) {
136+
this.shouldNotFilter = shouldNotFilter;
137+
}
138+
139+
public void setShouldNotFilterAsyncDispatch(boolean shouldNotFilterAsyncDispatch) {
140+
this.shouldNotFilterAsyncDispatch = shouldNotFilterAsyncDispatch;
141+
}
142+
143+
public void setShouldNotFilterErrorDispatch(boolean shouldNotFilterErrorDispatch) {
144+
this.shouldNotFilterErrorDispatch = shouldNotFilterErrorDispatch;
145+
}
146+
147+
148+
public boolean didFilter() {
149+
return this.didFilter;
150+
}
151+
152+
public boolean didFilterNestedErrorDispatch() {
153+
return this.didFilterNestedErrorDispatch;
154+
}
155+
156+
public void reset() {
157+
this.didFilter = false;
158+
this.didFilterNestedErrorDispatch = false;
159+
}
160+
161+
162+
@Override
163+
protected boolean shouldNotFilter(HttpServletRequest request) {
164+
return this.shouldNotFilter;
165+
}
166+
167+
@Override
168+
protected boolean shouldNotFilterAsyncDispatch() {
169+
return this.shouldNotFilterAsyncDispatch;
170+
}
171+
172+
@Override
173+
protected boolean shouldNotFilterErrorDispatch() {
174+
return this.shouldNotFilterErrorDispatch;
175+
}
176+
177+
@Override
178+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
179+
FilterChain filterChain) {
180+
181+
this.didFilter = true;
182+
}
183+
184+
@Override
185+
protected void doFilterNestedErrorDispatch(HttpServletRequest request, HttpServletResponse response,
186+
FilterChain filterChain) {
187+
188+
this.didFilterNestedErrorDispatch = true;
189+
}
190+
}
191+
192+
}

0 commit comments

Comments
 (0)