Skip to content

Commit 86123de

Browse files
committed
HandlerMappingIntrospector handles attribute changes properly
Closes gh-26833
1 parent 7c3a184 commit 86123de

File tree

3 files changed

+113
-57
lines changed

3 files changed

+113
-57
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.io.IOException;
2020
import java.util.ArrayList;
2121
import java.util.Collections;
22+
import java.util.Enumeration;
23+
import java.util.HashMap;
2224
import java.util.List;
2325
import java.util.Map;
2426
import java.util.Properties;
@@ -138,16 +140,17 @@ public void afterPropertiesSet() {
138140
*/
139141
@Nullable
140142
public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
141-
HttpServletRequest wrappedRequest = new RequestAttributeChangeIgnoringWrapper(request);
143+
HttpServletRequest wrappedRequest = new AttributesPreservingRequest(request);
142144
return doWithMatchingMapping(wrappedRequest, false, (matchedMapping, executionChain) -> {
143145
if (matchedMapping instanceof MatchableHandlerMapping) {
144146
PathPatternMatchableHandlerMapping mapping = this.pathPatternHandlerMappings.get(matchedMapping);
145147
if (mapping != null) {
146-
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request);
147-
return mapping.decorate(requestPath);
148+
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(wrappedRequest);
149+
return new PathSettingHandlerMapping(mapping, requestPath);
148150
}
149151
else {
150-
return (MatchableHandlerMapping) matchedMapping;
152+
String lookupPath = (String) wrappedRequest.getAttribute(UrlPathHelper.PATH_ATTRIBUTE);
153+
return new PathSettingHandlerMapping((MatchableHandlerMapping) matchedMapping, lookupPath);
151154
}
152155
}
153156
throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
@@ -157,7 +160,7 @@ public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest req
157160
@Override
158161
@Nullable
159162
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
160-
RequestAttributeChangeIgnoringWrapper wrappedRequest = new RequestAttributeChangeIgnoringWrapper(request);
163+
AttributesPreservingRequest wrappedRequest = new AttributesPreservingRequest(request);
161164
return doWithMatchingMappingIgnoringException(wrappedRequest, (handlerMapping, executionChain) -> {
162165
for (HandlerInterceptor interceptor : executionChain.getInterceptorList()) {
163166
if (interceptor instanceof CorsConfigurationSource) {
@@ -272,19 +275,83 @@ private static Map<HandlerMapping, PathPatternMatchableHandlerMapping> initPathP
272275

273276

274277
/**
275-
* Request wrapper that ignores request attribute changes.
278+
* Request wrapper that buffers request attributes in order protect the
279+
* underlying request from attribute changes.
276280
*/
277-
private static class RequestAttributeChangeIgnoringWrapper extends HttpServletRequestWrapper {
281+
private static class AttributesPreservingRequest extends HttpServletRequestWrapper {
278282

279-
RequestAttributeChangeIgnoringWrapper(HttpServletRequest request) {
283+
private final Map<String, Object> attributes;
284+
285+
AttributesPreservingRequest(HttpServletRequest request) {
280286
super(request);
287+
this.attributes = initAttributes(request);
288+
}
289+
290+
private Map<String, Object> initAttributes(HttpServletRequest request) {
291+
Map<String, Object> map = new HashMap<>();
292+
Enumeration<String> names = request.getAttributeNames();
293+
while (names.hasMoreElements()) {
294+
String name = names.nextElement();
295+
map.put(name, request.getAttribute(name));
296+
}
297+
return map;
281298
}
282299

283300
@Override
284301
public void setAttribute(String name, Object value) {
285-
if (name.equals(ServletRequestPathUtils.PATH_ATTRIBUTE) || name.equals(UrlPathHelper.PATH_ATTRIBUTE)) {
286-
super.setAttribute(name, value);
302+
this.attributes.put(name, value);
303+
}
304+
305+
@Override
306+
public Object getAttribute(String name) {
307+
return this.attributes.get(name);
308+
}
309+
310+
@Override
311+
public Enumeration<String> getAttributeNames() {
312+
return Collections.enumeration(this.attributes.keySet());
313+
}
314+
315+
@Override
316+
public void removeAttribute(String name) {
317+
this.attributes.remove(name);
318+
}
319+
}
320+
321+
322+
private static class PathSettingHandlerMapping implements MatchableHandlerMapping {
323+
324+
private final MatchableHandlerMapping delegate;
325+
326+
private final Object path;
327+
328+
private final String pathAttributeName;
329+
330+
PathSettingHandlerMapping(MatchableHandlerMapping delegate, Object path) {
331+
this.delegate = delegate;
332+
this.path = path;
333+
this.pathAttributeName = (path instanceof RequestPath ?
334+
ServletRequestPathUtils.PATH_ATTRIBUTE : UrlPathHelper.PATH_ATTRIBUTE);
335+
}
336+
337+
@Nullable
338+
@Override
339+
public RequestMatchResult match(HttpServletRequest request, String pattern) {
340+
Object previousPath = request.getAttribute(this.pathAttributeName);
341+
request.setAttribute(this.pathAttributeName, this.path);
342+
try {
343+
return this.delegate.match(request, pattern);
344+
}
345+
finally {
346+
request.setAttribute(this.pathAttributeName, previousPath);
287347
}
288348
}
349+
350+
@Nullable
351+
@Override
352+
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
353+
return this.delegate.getHandler(request);
354+
}
289355
}
356+
290357
}

spring-webmvc/src/main/java/org/springframework/web/servlet/handler/PathPatternMatchableHandlerMapping.java

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import javax.servlet.http.HttpServletRequest;
2222

2323
import org.springframework.http.server.PathContainer;
24-
import org.springframework.http.server.RequestPath;
2524
import org.springframework.lang.Nullable;
2625
import org.springframework.util.Assert;
2726
import org.springframework.web.servlet.HandlerExecutionChain;
@@ -72,38 +71,4 @@ public HandlerExecutionChain getHandler(HttpServletRequest request) throws Excep
7271
return this.delegate.getHandler(request);
7372
}
7473

75-
MatchableHandlerMapping decorate(RequestPath requestPath) {
76-
return new MatchableHandlerMapping() {
77-
78-
@Nullable
79-
@Override
80-
public RequestMatchResult match(HttpServletRequest request, String pattern) {
81-
RequestPath previousPath = setRequestPathAttribute(request);
82-
try {
83-
return PathPatternMatchableHandlerMapping.this.match(request, pattern);
84-
}
85-
finally {
86-
ServletRequestPathUtils.setParsedRequestPath(previousPath, request);
87-
}
88-
}
89-
90-
@Nullable
91-
@Override
92-
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
93-
RequestPath previousPath = setRequestPathAttribute(request);
94-
try {
95-
return PathPatternMatchableHandlerMapping.this.getHandler(request);
96-
}
97-
finally {
98-
ServletRequestPathUtils.setParsedRequestPath(previousPath, request);
99-
}
100-
}
101-
102-
private RequestPath setRequestPathAttribute(HttpServletRequest request) {
103-
RequestPath previous = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
104-
ServletRequestPathUtils.setParsedRequestPath(requestPath, request);
105-
return previous;
106-
}
107-
};
108-
}
10974
}

spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMappingIntrospectorTests.java

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -40,6 +40,10 @@
4040
import org.springframework.web.cors.CorsConfiguration;
4141
import org.springframework.web.servlet.HandlerExecutionChain;
4242
import org.springframework.web.servlet.HandlerMapping;
43+
import org.springframework.web.servlet.function.RouterFunction;
44+
import org.springframework.web.servlet.function.RouterFunctions;
45+
import org.springframework.web.servlet.function.ServerResponse;
46+
import org.springframework.web.servlet.function.support.RouterFunctionMapping;
4347
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
4448
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
4549
import org.springframework.web.util.ServletRequestPathUtils;
@@ -99,16 +103,6 @@ void detectHandlerMappingsOrdered() {
99103
assertThat(actual).isEqualTo(expected);
100104
}
101105

102-
void defaultHandlerMappings() {
103-
StaticWebApplicationContext context = new StaticWebApplicationContext();
104-
context.refresh();
105-
List<HandlerMapping> actual = initIntrospector(context).getHandlerMappings();
106-
107-
assertThat(actual.size()).isEqualTo(2);
108-
assertThat(actual.get(0).getClass()).isEqualTo(BeanNameUrlHandlerMapping.class);
109-
assertThat(actual.get(1).getClass()).isEqualTo(RequestMappingHandlerMapping.class);
110-
}
111-
112106
@ParameterizedTest
113107
@ValueSource(booleans = {true, false})
114108
void getMatchable(boolean usePathPatterns) throws Exception {
@@ -151,6 +145,22 @@ void getMatchableWhereHandlerMappingDoesNotImplementMatchableInterface() {
151145
assertThatIllegalStateException().isThrownBy(() -> initIntrospector(cxt).getMatchableHandlerMapping(request));
152146
}
153147

148+
@Test // gh-26833
149+
void getMatchablePreservesRequestAttributes() throws Exception {
150+
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
151+
context.register(TestConfig.class);
152+
context.refresh();
153+
154+
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/path");
155+
request.setAttribute("name", "value");
156+
157+
MatchableHandlerMapping matchable = initIntrospector(context).getMatchableHandlerMapping(request);
158+
assertThat(matchable).isNotNull();
159+
160+
// RequestPredicates.restoreAttributes clears and re-adds attributes
161+
assertThat(request.getAttribute("name")).isEqualTo("value");
162+
}
163+
154164
@Test
155165
void getCorsConfigurationPreFlight() {
156166
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
@@ -204,15 +214,29 @@ public HandlerExecutionChain getHandler(HttpServletRequest request) {
204214
@Configuration
205215
static class TestConfig {
206216

217+
@Bean
218+
public RouterFunctionMapping routerFunctionMapping() {
219+
RouterFunctionMapping mapping = new RouterFunctionMapping();
220+
mapping.setOrder(1);
221+
return mapping;
222+
}
223+
207224
@Bean
208225
public RequestMappingHandlerMapping handlerMapping() {
209-
return new RequestMappingHandlerMapping();
226+
RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
227+
mapping.setOrder(2);
228+
return mapping;
210229
}
211230

212231
@Bean
213232
public TestController testController() {
214233
return new TestController();
215234
}
235+
236+
@Bean
237+
public RouterFunction<?> routerFunction() {
238+
return RouterFunctions.route().GET("/fn-path", request -> ServerResponse.ok().build()).build();
239+
}
216240
}
217241

218242

0 commit comments

Comments
 (0)