From cf1bc8119999e2bf3f41ae572f5b83536685ce5e Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 16 May 2017 22:36:48 +0200 Subject: [PATCH] Introduce LookupPath in WebFlux request routing This commit adds the `LookupPath` class that contains the full request path relative to the web context; the application can get from it various information, including the file extension and path parameters (if any). Since that operation is done multiple times for each request, this object is stored as an attribute at the `ServerWebExchange` level. Issue: SPR-15397 --- .../UrlBasedCorsConfigurationSource.java | 26 +----- .../server/support/HttpRequestPathHelper.java | 9 +- .../web/server/support/LookupPath.java | 84 +++++++++++++++++++ .../UrlBasedCorsConfigurationSourceTests.java | 11 +++ .../web/server/support/LookupPathTests.java | 61 ++++++++++++++ .../handler/AbstractHandlerMapping.java | 16 ++++ .../handler/AbstractUrlHandlerMapping.java | 25 +++--- .../resource/ResourceUrlProvider.java | 5 +- .../condition/PatternsRequestCondition.java | 57 ++++++------- .../method/AbstractHandlerMethodMapping.java | 22 ++--- .../result/method/RequestMappingInfo.java | 20 +---- .../RequestMappingInfoHandlerMapping.java | 46 ++-------- .../RequestMappingHandlerMapping.java | 10 ++- .../view/ViewResolutionResultHandler.java | 6 +- .../PatternsRequestConditionTests.java | 45 ++++++---- .../condition/RequestMappingInfoTests.java | 16 +++- ...RequestMappingInfoHandlerMappingTests.java | 45 +++++----- .../ViewResolutionResultHandlerTests.java | 14 +++- 18 files changed, 335 insertions(+), 183 deletions(-) create mode 100644 spring-web/src/main/java/org/springframework/web/server/support/LookupPath.java create mode 100644 spring-web/src/test/java/org/springframework/web/server/support/LookupPathTests.java diff --git a/spring-web/src/main/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSource.java b/spring-web/src/main/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSource.java index f1eaa47e25a..35268a886d6 100644 --- a/spring-web/src/main/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSource.java +++ b/spring-web/src/main/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSource.java @@ -24,8 +24,8 @@ import org.springframework.util.Assert; import org.springframework.util.PathMatcher; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.support.HttpRequestPathHelper; import org.springframework.web.util.pattern.ParsingPathMatcher; +import org.springframework.web.server.support.LookupPath; /** * Provide a per reactive request {@link CorsConfiguration} instance based on a @@ -43,8 +43,6 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource private PathMatcher pathMatcher = new ParsingPathMatcher(); - private HttpRequestPathHelper pathHelper = new HttpRequestPathHelper(); - /** * Set the PathMatcher implementation to use for matching URL paths @@ -56,26 +54,6 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource this.pathMatcher = pathMatcher; } - /** - * Set if context path and request URI should be URL-decoded. Both are returned - * undecoded by the Servlet API, in contrast to the servlet path. - *

Uses either the request encoding or the default encoding according - * to the Servlet spec (ISO-8859-1). - * @see HttpRequestPathHelper#setUrlDecode - */ - public void setUrlDecode(boolean urlDecode) { - this.pathHelper.setUrlDecode(urlDecode); - } - - /** - * Set the UrlPathHelper to use for resolution of lookup paths. - *

Use this to override the default UrlPathHelper with a custom subclass. - */ - public void setHttpRequestPathHelper(HttpRequestPathHelper pathHelper) { - Assert.notNull(pathHelper, "HttpRequestPathHelper must not be null"); - this.pathHelper = pathHelper; - } - /** * Set CORS configuration based on URL patterns. */ @@ -102,7 +80,7 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource @Override public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) { - String lookupPath = this.pathHelper.getLookupPathForRequest(exchange); + String lookupPath = exchange.getAttribute(LookupPath.LOOKUP_PATH_ATTRIBUTE).get().getPath(); for (Map.Entry entry : this.corsConfigurations.entrySet()) { if (this.pathMatcher.match(entry.getKey(), lookupPath)) { return entry.getValue(); diff --git a/spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java b/spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java index ac92db6a69a..3842184adec 100644 --- a/spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java +++ b/spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java @@ -57,9 +57,14 @@ public class HttpRequestPathHelper { } - public String getLookupPathForRequest(ServerWebExchange exchange) { + public LookupPath getLookupPathForRequest(ServerWebExchange exchange) { String path = getPathWithinApplication(exchange.getRequest()); - return (shouldUrlDecode() ? decode(exchange, path) : path); + path = (shouldUrlDecode() ? decode(exchange, path) : path); + int begin = path.lastIndexOf('/') + 1; + int end = path.length(); + int paramIndex = path.indexOf(';', begin); + int extIndex = path.lastIndexOf('.', paramIndex != -1 ? paramIndex : end); + return new LookupPath(path, extIndex, paramIndex); } private String getPathWithinApplication(ServerHttpRequest request) { diff --git a/spring-web/src/main/java/org/springframework/web/server/support/LookupPath.java b/spring-web/src/main/java/org/springframework/web/server/support/LookupPath.java new file mode 100644 index 00000000000..3fc00e0970c --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/server/support/LookupPath.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.server.support; + +import org.springframework.lang.Nullable; +import org.springframework.web.server.ServerWebExchange; + +/** + * Lookup path information of an incoming HTTP request. + * + * @author Brian Clozel + * @since 5.0 + * @see HttpRequestPathHelper + */ +public final class LookupPath { + + public static final String LOOKUP_PATH_ATTRIBUTE = LookupPath.class.getName(); + + private final String path; + + private final int fileExtensionIndex; + + private final int pathParametersIndex; + + public LookupPath(String path, int fileExtensionIndex, int pathParametersIndex) { + this.path = path; + this.fileExtensionIndex = fileExtensionIndex; + this.pathParametersIndex = pathParametersIndex; + } + + public String getPath() { + if (this.pathParametersIndex != -1) { + // TODO: variant without the path parameter information? + //return this.path.substring(0, this.pathParametersIndex); + return this.path; + } + else { + return this.path; + } + } + + public String getPathWithoutExtension() { + if (this.fileExtensionIndex != -1) { + return this.path.substring(0, this.fileExtensionIndex); + } + else { + return this.path; + } + } + + @Nullable + public String getFileExtension() { + if (this.fileExtensionIndex == -1) { + return null; + } + else if (this.pathParametersIndex == -1) { + return this.path.substring(this.fileExtensionIndex); + } + else { + return this.path.substring(this.fileExtensionIndex, this.pathParametersIndex); + } + } + + @Nullable + public String getPathParameters() { + return this.pathParametersIndex == -1 ? + null : this.path.substring(this.pathParametersIndex + 1); + } + +} diff --git a/spring-web/src/test/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSourceTests.java b/spring-web/src/test/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSourceTests.java index 75775e790e2..4fe628ed947 100644 --- a/spring-web/src/test/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSourceTests.java +++ b/spring-web/src/test/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSourceTests.java @@ -21,6 +21,8 @@ import org.junit.Test; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.support.HttpRequestPathHelper; +import org.springframework.web.server.support.LookupPath; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -39,6 +41,7 @@ public class UrlBasedCorsConfigurationSourceTests { @Test public void empty() { ServerWebExchange exchange = MockServerHttpRequest.get("/bar/test.html").toExchange(); + setLookupPathAttribute(exchange); assertNull(this.configSource.getCorsConfiguration(exchange)); } @@ -48,9 +51,11 @@ public class UrlBasedCorsConfigurationSourceTests { this.configSource.registerCorsConfiguration("/bar/**", config); ServerWebExchange exchange = MockServerHttpRequest.get("/foo/test.html").toExchange(); + setLookupPathAttribute(exchange); assertNull(this.configSource.getCorsConfiguration(exchange)); exchange = MockServerHttpRequest.get("/bar/test.html").toExchange(); + setLookupPathAttribute(exchange); assertEquals(config, this.configSource.getCorsConfiguration(exchange)); } @@ -59,4 +64,10 @@ public class UrlBasedCorsConfigurationSourceTests { this.configSource.getCorsConfigurations().put("/**", new CorsConfiguration()); } + public void setLookupPathAttribute(ServerWebExchange exchange) { + HttpRequestPathHelper helper = new HttpRequestPathHelper(); + exchange.getAttributes().put(LookupPath.LOOKUP_PATH_ATTRIBUTE, + helper.getLookupPathForRequest(exchange)); + } + } diff --git a/spring-web/src/test/java/org/springframework/web/server/support/LookupPathTests.java b/spring-web/src/test/java/org/springframework/web/server/support/LookupPathTests.java new file mode 100644 index 00000000000..11a46dd12a0 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/web/server/support/LookupPathTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.server.support; + +import org.junit.Test; + +import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; +import org.springframework.web.server.ServerWebExchange; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for {@link LookupPath} + * @author Brian Clozel + */ +public class LookupPathTests { + + @Test + public void parsePath() { + LookupPath path = create("/foo"); + assertEquals("/foo", path.getPath()); + assertEquals("/foo", path.getPathWithoutExtension()); + } + + @Test + public void parsePathWithExtension() { + LookupPath path = create("/foo.txt"); + assertEquals("/foo.txt", path.getPath()); + assertEquals("/foo", path.getPathWithoutExtension()); + assertEquals(".txt", path.getFileExtension()); + } + + @Test + public void parsePathWithParams() { + LookupPath path = create("/test/foo.txt;foo=bar?framework=spring"); + assertEquals("/test/foo.txt;foo=bar", path.getPath()); + assertEquals("/test/foo", path.getPathWithoutExtension()); + assertEquals(".txt", path.getFileExtension()); + assertEquals("foo=bar", path.getPathParameters()); + } + + private LookupPath create(String path) { + HttpRequestPathHelper helper = new HttpRequestPathHelper(); + ServerWebExchange exchange = MockServerHttpRequest.get(path).build().toExchange(); + return helper.getLookupPathForRequest(exchange); + } +} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java index d2a20b0befa..92d8937abb3 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java @@ -17,6 +17,7 @@ package org.springframework.web.reactive.handler; import java.util.Map; +import java.util.Optional; import reactor.core.publisher.Mono; @@ -32,6 +33,7 @@ import org.springframework.web.cors.reactive.CorsUtils; import org.springframework.web.cors.reactive.DefaultCorsProcessor; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; import org.springframework.web.reactive.HandlerMapping; +import org.springframework.web.server.support.LookupPath; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebHandler; import org.springframework.web.server.support.HttpRequestPathHelper; @@ -43,6 +45,7 @@ import org.springframework.web.util.pattern.ParsingPathMatcher; * * @author Rossen Stoyanchev * @author Juergen Hoeller + * @author Brian Clozel * @since 5.0 */ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport implements HandlerMapping, Ordered { @@ -171,6 +174,19 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im }); } + protected LookupPath getLookupPath(ServerWebExchange exchange) { + Optional attribute = exchange.getAttribute(LookupPath.LOOKUP_PATH_ATTRIBUTE); + return attribute.orElseGet(() -> { + LookupPath lookupPath = createLookupPath(exchange); + exchange.getAttributes().put(LookupPath.LOOKUP_PATH_ATTRIBUTE, lookupPath); + return lookupPath; + }); + } + + protected LookupPath createLookupPath(ServerWebExchange exchange) { + return getPathHelper().getLookupPathForRequest(exchange); + } + /** * Look up a handler for the given request, returning an empty {@code Mono} * if no specific one is found. This method is called by {@link #getHandler}. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java index 4458bb34c63..c353b4376a9 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java @@ -28,6 +28,7 @@ import reactor.core.publisher.Mono; import org.springframework.beans.BeansException; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.web.server.support.LookupPath; import org.springframework.web.server.ServerWebExchange; /** @@ -46,6 +47,7 @@ import org.springframework.web.server.ServerWebExchange; * * @author Rossen Stoyanchev * @author Juergen Hoeller + * @author Brian Clozel * @since 5.0 */ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { @@ -99,7 +101,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { @Override public Mono getHandlerInternal(ServerWebExchange exchange) { - String lookupPath = getPathHelper().getLookupPathForRequest(exchange); + LookupPath lookupPath = getLookupPath(exchange); Object handler; try { handler = lookupHandler(lookupPath, exchange); @@ -109,30 +111,31 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { } if (handler != null && logger.isDebugEnabled()) { - logger.debug("Mapping [" + lookupPath + "] to " + handler); + logger.debug("Mapping [" + lookupPath.getPath() + "] to " + handler); } else if (handler == null && logger.isTraceEnabled()) { - logger.trace("No handler mapping found for [" + lookupPath + "]"); + logger.trace("No handler mapping found for [" + lookupPath.getPath() + "]"); } return Mono.justOrEmpty(handler); } /** - * Look up a handler instance for the given URL path. + * Look up a handler instance for the given URL lookup path. + * *

Supports direct matches, e.g. a registered "/test" matches "/test", - * and various Ant-style pattern matches, e.g. a registered "/t*" matches - * both "/test" and "/team". For details, see the AntPathMatcher class. - *

Looks for the most exact pattern, where most exact is defined as - * the longest path pattern. - * @param urlPath URL the bean is mapped to + * and various path pattern matches, e.g. a registered "/t*" matches + * both "/test" and "/team". For details, see the PathPattern class. + * + * @param lookupPath URL the handler is mapped to * @param exchange the current exchange * @return the associated handler instance, or {@code null} if not found * @see org.springframework.web.util.pattern.ParsingPathMatcher */ @Nullable - protected Object lookupHandler(String urlPath, ServerWebExchange exchange) throws Exception { + protected Object lookupHandler(LookupPath lookupPath, ServerWebExchange exchange) throws Exception { // Direct match? + String urlPath = lookupPath.getPath(); Object handler = this.handlerMap.get(urlPath); if (handler != null) { return handleMatch(handler, urlPath, urlPath, exchange); @@ -156,7 +159,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { if (!matches.isEmpty()) { Collections.sort(matches, comparator); if (logger.isDebugEnabled()) { - logger.debug("Matching patterns for request [" + urlPath + "] are " + matches); + logger.debug("Matching patterns for request [" + lookupPath + "] are " + matches); } bestMatch = matches.get(0); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java index b3ef7234bd5..40c822c0a8e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java @@ -37,6 +37,7 @@ import org.springframework.util.PathMatcher; import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.support.HttpRequestPathHelper; +import org.springframework.web.server.support.LookupPath; import org.springframework.web.util.pattern.ParsingPathMatcher; /** @@ -184,8 +185,8 @@ public class ResourceUrlProvider implements ApplicationListener patterns; - private final HttpRequestPathHelper pathHelper; - private final PathMatcher pathMatcher; private final boolean useSuffixPatternMatch; @@ -61,35 +59,33 @@ public final class PatternsRequestCondition extends AbstractRequestCondition extensions) { - this(asList(patterns), pathHelper, pathMatcher, useSuffixPatternMatch, useTrailingSlashMatch, extensions); + this(asList(patterns), pathMatcher, useSuffixPatternMatch, useTrailingSlashMatch, extensions); } /** * Private constructor accepting a collection of patterns. */ - private PatternsRequestCondition(Collection patterns, HttpRequestPathHelper pathHelper, - PathMatcher pathMatcher, boolean useSuffixPatternMatch, boolean useTrailingSlashMatch, + private PatternsRequestCondition(Collection patterns, PathMatcher pathMatcher, + boolean useSuffixPatternMatch, boolean useTrailingSlashMatch, Set fileExtensions) { this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns)); - this.pathHelper = (pathHelper != null ? pathHelper : new HttpRequestPathHelper()); this.pathMatcher = (pathMatcher != null ? pathMatcher : new ParsingPathMatcher()); this.useSuffixPatternMatch = useSuffixPatternMatch; this.useTrailingSlashMatch = useTrailingSlashMatch; @@ -165,7 +161,7 @@ public final class PatternsRequestCondition extends AbstractRequestConditiongetAttribute(LookupPath.LOOKUP_PATH_ATTRIBUTE).get(); List matches = getMatchingPatterns(lookupPath); return matches.isEmpty() ? null : - new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch, - this.useTrailingSlashMatch, this.fileExtensions); + new PatternsRequestCondition(matches, this.pathMatcher, this.useSuffixPatternMatch, + this.useTrailingSlashMatch, this.fileExtensions); } /** @@ -208,7 +205,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition getMatchingPatterns(String lookupPath) { + public List getMatchingPatterns(LookupPath lookupPath) { List matches = new ArrayList<>(); for (String pattern : this.patterns) { String match = getMatchingPattern(pattern, lookupPath); @@ -216,34 +213,33 @@ public final class PatternsRequestCondition extends AbstractRequestCondition patternComparator = this.pathMatcher.getPatternComparator(lookupPath); + LookupPath lookupPath = exchange + .getAttribute(LookupPath.LOOKUP_PATH_ATTRIBUTE).get(); + Comparator patternComparator = this.pathMatcher.getPatternComparator(lookupPath.getPath()); Iterator iterator = this.patterns.iterator(); Iterator iteratorOther = other.patterns.iterator(); while (iterator.hasNext() && iteratorOther.hasNext()) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java index 9585e19b188..0b686282020 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java @@ -45,6 +45,7 @@ import org.springframework.web.method.HandlerMethod; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.handler.AbstractHandlerMapping; import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.support.LookupPath; /** * Abstract base class for {@link HandlerMapping} implementations that define @@ -54,6 +55,7 @@ import org.springframework.web.server.ServerWebExchange; * subclasses defining the details of the mapping type {@code }. * * @author Rossen Stoyanchev + * @author Brian Clozel * @since 5.0 * @param The mapping for a {@link HandlerMethod} containing the conditions * needed to match the handler method to incoming request. @@ -255,7 +257,7 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap */ @Override public Mono getHandlerInternal(ServerWebExchange exchange) { - String lookupPath = getPathHelper().getLookupPathForRequest(exchange); + LookupPath lookupPath = getLookupPath(exchange); if (logger.isDebugEnabled()) { logger.debug("Looking up handler method for path " + lookupPath); } @@ -289,18 +291,18 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap /** * Look up the best-matching handler method for the current request. * If multiple matches are found, the best match is selected. - * @param lookupPath mapping lookup path within the current servlet mapping + * @param lookupPath the lookup path within the current mapping * @param exchange the current exchange * @return the best-matching handler method, or {@code null} if no match - * @see #handleMatch(Object, String, ServerWebExchange) - * @see #handleNoMatch(Set, String, ServerWebExchange) + * @see #handleMatch(Object, LookupPath, ServerWebExchange) + * @see #handleNoMatch(Set, LookupPath, ServerWebExchange) */ @Nullable - protected HandlerMethod lookupHandlerMethod(String lookupPath, ServerWebExchange exchange) + protected HandlerMethod lookupHandlerMethod(LookupPath lookupPath, ServerWebExchange exchange) throws Exception { List matches = new ArrayList(); - List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); + List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath.getPath()); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, exchange); } @@ -349,22 +351,22 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap /** * Invoked when a matching mapping is found. * @param mapping the matching mapping - * @param lookupPath mapping lookup path within the current servlet mapping + * @param lookupPath the lookup path within the current mapping * @param exchange the current exchange */ - protected void handleMatch(T mapping, String lookupPath, ServerWebExchange exchange) { + protected void handleMatch(T mapping, LookupPath lookupPath, ServerWebExchange exchange) { } /** * Invoked when no matching mapping is not found. * @param mappings all registered mappings - * @param lookupPath mapping lookup path within the current servlet mapping + * @param lookupPath the lookup path within the current mapping * @param exchange the current exchange * @return an alternative HandlerMethod or {@code null} * @throws Exception provides details that can be translated into an error status code */ @Nullable - protected HandlerMethod handleNoMatch(Set mappings, String lookupPath, ServerWebExchange exchange) + protected HandlerMethod handleNoMatch(Set mappings, LookupPath lookupPath, ServerWebExchange exchange) throws Exception { return null; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfo.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfo.java index 1b0e5d61886..3b660e6228b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfo.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfo.java @@ -33,7 +33,6 @@ import org.springframework.web.reactive.result.condition.RequestCondition; import org.springframework.web.reactive.result.condition.RequestConditionHolder; import org.springframework.web.reactive.result.condition.RequestMethodsRequestCondition; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.support.HttpRequestPathHelper; /** * Encapsulates the following request mapping conditions: @@ -475,9 +474,8 @@ public final class RequestMappingInfo implements RequestConditionBy default this is not set. - */ - public void setPathHelper(HttpRequestPathHelper pathHelper) { - this.pathHelper = pathHelper; - } - - public HttpRequestPathHelper getPathHelper() { - return this.pathHelper; - } - /** * Set a custom PathMatcher to use for the PatternsRequestCondition. *

By default this is not set. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java index 0e258ccaaa9..3b6eed42b29 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.StringTokenizer; import java.util.stream.Collectors; import org.springframework.http.HttpHeaders; @@ -35,9 +34,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.result.condition.NameValueExpression; @@ -46,6 +43,8 @@ import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; import org.springframework.web.server.UnsupportedMediaTypeStatusException; +import org.springframework.web.server.support.LookupPath; +import org.springframework.web.util.WebUtils; /** * Abstract base class for classes for which {@link RequestMappingInfo} defines @@ -103,7 +102,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe * @see HandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE */ @Override - protected void handleMatch(RequestMappingInfo info, String lookupPath, ServerWebExchange exchange) { + protected void handleMatch(RequestMappingInfo info, LookupPath lookupPath, ServerWebExchange exchange) { super.handleMatch(info, lookupPath, exchange); String bestPattern; @@ -112,13 +111,13 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe Set patterns = info.getPatternsCondition().getPatterns(); if (patterns.isEmpty()) { - bestPattern = lookupPath; + bestPattern = lookupPath.getPath(); uriVariables = Collections.emptyMap(); decodedUriVariables = Collections.emptyMap(); } else { bestPattern = patterns.iterator().next(); - uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath); + uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath.getPath()); decodedUriVariables = getPathHelper().decodePathVariables(exchange, uriVariables); } @@ -157,43 +156,12 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe uriVariables.put(uriVar.getKey(), uriVarValue.substring(0, semicolonIndex)); } - MultiValueMap vars = parseMatrixVariables(matrixVariables); + MultiValueMap vars = WebUtils.parseMatrixVariables(matrixVariables); result.put(uriVar.getKey(), getPathHelper().decodeMatrixVariables(exchange, vars)); } return result; } - /** - * Parse the given string with matrix variables. An example string would look - * like this {@code "q1=a;q1=b;q2=a,b,c"}. The resulting map would contain - * keys {@code "q1"} and {@code "q2"} with values {@code ["a","b"]} and - * {@code ["a","b","c"]} respectively. - * @param matrixVariables the unparsed matrix variables string - * @return a map with matrix variable names and values (never {@code null}) - */ - private static MultiValueMap parseMatrixVariables(String matrixVariables) { - MultiValueMap result = new LinkedMultiValueMap<>(); - if (!StringUtils.hasText(matrixVariables)) { - return result; - } - StringTokenizer pairs = new StringTokenizer(matrixVariables, ";"); - while (pairs.hasMoreTokens()) { - String pair = pairs.nextToken(); - int index = pair.indexOf('='); - if (index != -1) { - String name = pair.substring(0, index); - String rawValue = pair.substring(index + 1); - for (String value : StringUtils.commaDelimitedListToStringArray(rawValue)) { - result.add(name, value); - } - } - else { - result.add(pair, ""); - } - } - return result; - } - /** * Iterate all RequestMappingInfos once again, look if any match by URL at * least and raise exceptions accordingly. @@ -206,7 +174,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe * method but not by query parameter conditions */ @Override - protected HandlerMethod handleNoMatch(Set infos, String lookupPath, + protected HandlerMethod handleNoMatch(Set infos, LookupPath lookupPath, ServerWebExchange exchange) throws Exception { PartialMatchHelper helper = new PartialMatchHelper(infos, exchange); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java index b3485ec8691..39b61b01751 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java @@ -37,6 +37,8 @@ import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.result.condition.RequestCondition; import org.springframework.web.reactive.result.method.RequestMappingInfo; import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping; +import org.springframework.web.server.support.LookupPath; +import org.springframework.web.server.ServerWebExchange; /** * An extension of {@link RequestMappingInfoHandlerMapping} that creates @@ -113,10 +115,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi @Override public void afterPropertiesSet() { this.config = new RequestMappingInfo.BuilderConfiguration(); - this.config.setPathHelper(getPathHelper()); this.config.setPathMatcher(getPathMatcher()); this.config.setSuffixPatternMatch(this.useSuffixPatternMatch); - this.config.setTrailingSlashMatch(this.useTrailingSlashMatch); this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch); this.config.setContentTypeResolver(getContentTypeResolver()); @@ -159,7 +159,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi return this.config.getFileExtensions(); } - /** * {@inheritDoc} * Expects a handler to have a type-level @{@link Controller} annotation. @@ -170,6 +169,11 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); } + @Override + protected LookupPath createLookupPath(ServerWebExchange exchange) { + return getPathHelper().getLookupPathForRequest(exchange); + } + /** * Uses method and type-level @{@link RequestMapping} annotations to create * the RequestMappingInfo. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java index c8b0b87bcf2..82fb55fec05 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java @@ -50,7 +50,7 @@ import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.result.HandlerResultHandlerSupport; import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.support.HttpRequestPathHelper; +import org.springframework.web.server.support.LookupPath; /** * {@code HandlerResultHandler} that encapsulates the view resolution algorithm @@ -91,8 +91,6 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport private final List defaultViews = new ArrayList<>(4); - private final HttpRequestPathHelper pathHelper = new HttpRequestPathHelper(); - /** * Basic constructor with a default {@link ReactiveAdapterRegistry}. @@ -259,7 +257,7 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport * Use the request path the leading and trailing slash stripped. */ private String getDefaultViewName(ServerWebExchange exchange) { - String path = this.pathHelper.getLookupPathForRequest(exchange); + String path = exchange.getAttribute(LookupPath.LOOKUP_PATH_ATTRIBUTE).get().getPath(); if (path.startsWith("/")) { path = path.substring(1); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/PatternsRequestConditionTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/PatternsRequestConditionTests.java index df36d448c20..b92fc911e3e 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/PatternsRequestConditionTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/PatternsRequestConditionTests.java @@ -22,6 +22,8 @@ import java.util.Set; import org.junit.Test; import org.springframework.mock.http.server.reactive.test.MockServerWebExchange; +import org.springframework.web.server.support.HttpRequestPathHelper; +import org.springframework.web.server.support.LookupPath; import org.springframework.web.server.ServerWebExchange; import static org.junit.Assert.assertEquals; @@ -80,7 +82,7 @@ public class PatternsRequestConditionTests { @Test public void matchDirectPath() throws Exception { PatternsRequestCondition condition = new PatternsRequestCondition("/foo"); - PatternsRequestCondition match = condition.getMatchingCondition(get("/foo").toExchange()); + PatternsRequestCondition match = condition.getMatchingCondition(createExchange("/foo")); assertNotNull(match); } @@ -88,7 +90,7 @@ public class PatternsRequestConditionTests { @Test public void matchPattern() throws Exception { PatternsRequestCondition condition = new PatternsRequestCondition("/foo/*"); - PatternsRequestCondition match = condition.getMatchingCondition(get("/foo/bar").toExchange()); + PatternsRequestCondition match = condition.getMatchingCondition(createExchange("/foo/bar")); assertNotNull(match); } @@ -96,7 +98,7 @@ public class PatternsRequestConditionTests { @Test public void matchSortPatterns() throws Exception { PatternsRequestCondition condition = new PatternsRequestCondition("/*/*", "/foo/bar", "/foo/*"); - PatternsRequestCondition match = condition.getMatchingCondition(get("/foo/bar").toExchange()); + PatternsRequestCondition match = condition.getMatchingCondition(createExchange("/foo/bar")); PatternsRequestCondition expected = new PatternsRequestCondition("/foo/bar", "/foo/*", "/*/*"); assertEquals(expected, match); @@ -104,7 +106,7 @@ public class PatternsRequestConditionTests { @Test public void matchSuffixPattern() throws Exception { - ServerWebExchange exchange = get("/foo.html").toExchange(); + ServerWebExchange exchange = createExchange("/foo.html"); PatternsRequestCondition condition = new PatternsRequestCondition("/{foo}"); PatternsRequestCondition match = condition.getMatchingCondition(exchange); @@ -112,7 +114,7 @@ public class PatternsRequestConditionTests { assertNotNull(match); assertEquals("/{foo}.*", match.getPatterns().iterator().next()); - condition = new PatternsRequestCondition(new String[] {"/{foo}"}, null, null, false, false, null); + condition = new PatternsRequestCondition(new String[] {"/{foo}"}, null,false, false, null); match = condition.getMatchingCondition(exchange); assertNotNull(match); @@ -125,15 +127,15 @@ public class PatternsRequestConditionTests { public void matchSuffixPatternUsingFileExtensions() throws Exception { String[] patterns = new String[] {"/jobs/{jobName}"}; Set extensions = Collections.singleton("json"); - PatternsRequestCondition condition = new PatternsRequestCondition(patterns, null, null, true, false, extensions); + PatternsRequestCondition condition = new PatternsRequestCondition(patterns, null, true, false, extensions); - MockServerWebExchange exchange = get("/jobs/my.job").toExchange(); + MockServerWebExchange exchange = createExchange("/jobs/my.job"); PatternsRequestCondition match = condition.getMatchingCondition(exchange); assertNotNull(match); assertEquals("/jobs/{jobName}", match.getPatterns().iterator().next()); - exchange = get("/jobs/my.job.json").toExchange(); + exchange = createExchange("/jobs/my.job.json"); match = condition.getMatchingCondition(exchange); assertNotNull(match); @@ -143,14 +145,14 @@ public class PatternsRequestConditionTests { @Test public void matchSuffixPatternUsingFileExtensions2() throws Exception { PatternsRequestCondition condition1 = new PatternsRequestCondition( - new String[] {"/prefix"}, null, null, true, false, Collections.singleton("json")); + new String[] {"/prefix"}, null, true, false, Collections.singleton("json")); PatternsRequestCondition condition2 = new PatternsRequestCondition( - new String[] {"/suffix"}, null, null, true, false, null); + new String[] {"/suffix"}, null, true, false, null); PatternsRequestCondition combined = condition1.combine(condition2); - MockServerWebExchange exchange = get("/prefix/suffix.json").toExchange(); + MockServerWebExchange exchange = createExchange("/prefix/suffix.json"); PatternsRequestCondition match = combined.getMatchingCondition(exchange); assertNotNull(match); @@ -158,7 +160,7 @@ public class PatternsRequestConditionTests { @Test public void matchTrailingSlash() throws Exception { - MockServerWebExchange exchange = get("/foo/").toExchange(); + MockServerWebExchange exchange = createExchange("/foo/"); PatternsRequestCondition condition = new PatternsRequestCondition("/foo"); PatternsRequestCondition match = condition.getMatchingCondition(exchange); @@ -166,14 +168,15 @@ public class PatternsRequestConditionTests { assertNotNull(match); assertEquals("Should match by default", "/foo/", match.getPatterns().iterator().next()); - condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, false, true, null); + condition = new PatternsRequestCondition(new String[] {"/foo"}, null, false, true, null); match = condition.getMatchingCondition(exchange); assertNotNull(match); assertEquals("Trailing slash should be insensitive to useSuffixPatternMatch settings (SPR-6164, SPR-5636)", "/foo/", match.getPatterns().iterator().next()); - condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, false, false, null); + exchange = createExchange("/foo/"); + condition = new PatternsRequestCondition(new String[] {"/foo"}, null, false, false, null); match = condition.getMatchingCondition(exchange); assertNull(match); @@ -182,7 +185,7 @@ public class PatternsRequestConditionTests { @Test public void matchPatternContainsExtension() throws Exception { PatternsRequestCondition condition = new PatternsRequestCondition("/foo.jpg"); - PatternsRequestCondition match = condition.getMatchingCondition(get("/foo.html").toExchange()); + PatternsRequestCondition match = condition.getMatchingCondition(createExchange("/foo.html")); assertNull(match); } @@ -192,7 +195,7 @@ public class PatternsRequestConditionTests { PatternsRequestCondition c1 = new PatternsRequestCondition("/foo*"); PatternsRequestCondition c2 = new PatternsRequestCondition("/foo*"); - assertEquals(0, c1.compareTo(c2, get("/foo").toExchange())); + assertEquals(0, c1.compareTo(c2, createExchange("/foo"))); } @Test @@ -200,12 +203,12 @@ public class PatternsRequestConditionTests { PatternsRequestCondition c1 = new PatternsRequestCondition("/fo*"); PatternsRequestCondition c2 = new PatternsRequestCondition("/foo"); - assertEquals(1, c1.compareTo(c2, get("/foo").toExchange())); + assertEquals(1, c1.compareTo(c2, createExchange("/foo"))); } @Test public void compareNumberOfMatchingPatterns() throws Exception { - ServerWebExchange exchange = get("/foo.html").toExchange(); + ServerWebExchange exchange = createExchange("/foo.html"); PatternsRequestCondition c1 = new PatternsRequestCondition("/foo", "*.jpeg"); PatternsRequestCondition c2 = new PatternsRequestCondition("/foo", "*.html"); @@ -217,5 +220,11 @@ public class PatternsRequestConditionTests { assertEquals(1, match1.compareTo(match2, exchange)); } + private MockServerWebExchange createExchange(String path) { + MockServerWebExchange exchange = get(path).toExchange(); + HttpRequestPathHelper helper = new HttpRequestPathHelper(); + exchange.getAttributes().put(LookupPath.LOOKUP_PATH_ATTRIBUTE, helper.getLookupPathForRequest(exchange)); + return exchange; + } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMappingInfoTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMappingInfoTests.java index 6e9c463a83a..ac32a58d28a 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMappingInfoTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMappingInfoTests.java @@ -29,6 +29,8 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.mock.http.server.reactive.test.MockServerWebExchange; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.reactive.result.method.RequestMappingInfo; +import org.springframework.web.server.support.HttpRequestPathHelper; +import org.springframework.web.server.support.LookupPath; import org.springframework.web.server.ServerWebExchange; import static java.util.Arrays.asList; @@ -65,6 +67,7 @@ public class RequestMappingInfoTests { @Test public void matchPatternsCondition() { MockServerWebExchange exchange = MockServerHttpRequest.get("/foo").toExchange(); + setLookupPathAttribute(exchange); RequestMappingInfo info = paths("/foo*", "/bar").build(); RequestMappingInfo expected = paths("/foo*").build(); @@ -80,6 +83,7 @@ public class RequestMappingInfoTests { @Test public void matchParamsCondition() { ServerWebExchange exchange = MockServerHttpRequest.get("/foo?foo=bar").toExchange(); + setLookupPathAttribute(exchange); RequestMappingInfo info = paths("/foo").params("foo=bar").build(); RequestMappingInfo match = info.getMatchingCondition(exchange); @@ -95,6 +99,7 @@ public class RequestMappingInfoTests { @Test public void matchHeadersCondition() { ServerWebExchange exchange = MockServerHttpRequest.get("/foo").header("foo", "bar").toExchange(); + setLookupPathAttribute(exchange); RequestMappingInfo info = paths("/foo").headers("foo=bar").build(); RequestMappingInfo match = info.getMatchingCondition(exchange); @@ -110,6 +115,7 @@ public class RequestMappingInfoTests { @Test public void matchConsumesCondition() { ServerWebExchange exchange = MockServerHttpRequest.post("/foo").contentType(MediaType.TEXT_PLAIN).toExchange(); + setLookupPathAttribute(exchange); RequestMappingInfo info = paths("/foo").consumes("text/plain").build(); RequestMappingInfo match = info.getMatchingCondition(exchange); @@ -125,6 +131,7 @@ public class RequestMappingInfoTests { @Test public void matchProducesCondition() { ServerWebExchange exchange = MockServerHttpRequest.get("/foo").accept(MediaType.TEXT_PLAIN).toExchange(); + setLookupPathAttribute(exchange); RequestMappingInfo info = paths("/foo").produces("text/plain").build(); RequestMappingInfo match = info.getMatchingCondition(exchange); @@ -140,7 +147,8 @@ public class RequestMappingInfoTests { @Test public void matchCustomCondition() { ServerWebExchange exchange = MockServerHttpRequest.get("/foo?foo=bar").toExchange(); - + setLookupPathAttribute(exchange); + RequestMappingInfo info = paths("/foo").params("foo=bar").build(); RequestMappingInfo match = info.getMatchingCondition(exchange); @@ -161,6 +169,7 @@ public class RequestMappingInfoTests { RequestMappingInfo oneMethodOneParam = paths().methods(RequestMethod.GET).params("foo").build(); ServerWebExchange exchange = MockServerHttpRequest.get("/foo").toExchange(); + setLookupPathAttribute(exchange); Comparator comparator = (info, otherInfo) -> info.compareTo(otherInfo, exchange); List list = asList(none, oneMethod, oneMethodOneParam); @@ -270,4 +279,9 @@ public class RequestMappingInfoTests { assertNull("Pre-flight should match the ACCESS_CONTROL_REQUEST_METHOD", match); } + public void setLookupPathAttribute(ServerWebExchange exchange) { + HttpRequestPathHelper helper = new HttpRequestPathHelper(); + exchange.getAttributes().put(LookupPath.LOOKUP_PATH_ATTRIBUTE, helper.getLookupPathForRequest(exchange)); + } + } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java index a48cb555555..0def9b7e1ef 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java @@ -54,6 +54,7 @@ import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; import org.springframework.web.server.UnsupportedMediaTypeStatusException; import org.springframework.web.server.support.HttpRequestPathHelper; +import org.springframework.web.server.support.LookupPath; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; @@ -72,11 +73,15 @@ public class RequestMappingInfoHandlerMappingTests { private TestRequestMappingInfoHandlerMapping handlerMapping; + private HttpRequestPathHelper pathHelper; + @Before public void setup() throws Exception { this.handlerMapping = new TestRequestMappingInfoHandlerMapping(); this.handlerMapping.registerHandler(new TestController()); + this.pathHelper = new HttpRequestPathHelper(); + this.handlerMapping.setPathHelper(this.pathHelper); } @@ -208,8 +213,8 @@ public class RequestMappingInfoHandlerMappingTests { @Test @SuppressWarnings("unchecked") public void handleMatchUriTemplateVariables() throws Exception { - String lookupPath = "/1/2"; - ServerWebExchange exchange = get(lookupPath).toExchange(); + ServerWebExchange exchange = get("/1/2").toExchange(); + LookupPath lookupPath = this.pathHelper.getLookupPathForRequest(exchange); RequestMappingInfo key = paths("/{path1}/{path2}").build(); this.handlerMapping.handleMatch(key, lookupPath, exchange); @@ -228,11 +233,10 @@ public class RequestMappingInfoHandlerMappingTests { URI url = URI.create("/group/a%2Fb"); ServerWebExchange exchange = MockServerHttpRequest.method(HttpMethod.GET, url).toExchange(); - HttpRequestPathHelper pathHelper = new HttpRequestPathHelper(); - pathHelper.setUrlDecode(false); - String lookupPath = pathHelper.getLookupPathForRequest(exchange); - - this.handlerMapping.setPathHelper(pathHelper); + this.pathHelper.setUrlDecode(false); + LookupPath lookupPath = this.pathHelper.getLookupPathForRequest(exchange); + this.handlerMapping.setPathHelper(this.pathHelper); + this.handlerMapping.handleMatch(key, lookupPath, exchange); String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; @@ -248,7 +252,8 @@ public class RequestMappingInfoHandlerMappingTests { public void handleMatchBestMatchingPatternAttribute() throws Exception { RequestMappingInfo key = paths("/{path1}/2", "/**").build(); ServerWebExchange exchange = get("/1/2").toExchange(); - this.handlerMapping.handleMatch(key, "/1/2", exchange); + LookupPath lookupPath = this.pathHelper.getLookupPathForRequest(exchange); + this.handlerMapping.handleMatch(key, lookupPath, exchange); assertEquals("/{path1}/2", exchange.getAttributes().get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)); } @@ -257,8 +262,8 @@ public class RequestMappingInfoHandlerMappingTests { public void handleMatchBestMatchingPatternAttributeNoPatternsDefined() throws Exception { RequestMappingInfo key = paths().build(); ServerWebExchange exchange = get("/1/2").toExchange(); - - this.handlerMapping.handleMatch(key, "/1/2", exchange); + LookupPath lookupPath = this.pathHelper.getLookupPathForRequest(exchange); + this.handlerMapping.handleMatch(key, lookupPath, exchange); assertEquals("/1/2", exchange.getAttributes().get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)); } @@ -268,8 +273,8 @@ public class RequestMappingInfoHandlerMappingTests { MultiValueMap matrixVariables; Map uriVariables; - ServerWebExchange exchange = get("/").toExchange(); - handleMatch(exchange, "/{cars}", "/cars;colors=red,blue,green;year=2012"); + ServerWebExchange exchange = get("/cars;colors=red,blue,green;year=2012").toExchange(); + handleMatch(exchange, "/{cars}"); matrixVariables = getMatrixVariables(exchange, "cars"); uriVariables = getUriTemplateVariables(exchange); @@ -279,8 +284,8 @@ public class RequestMappingInfoHandlerMappingTests { assertEquals("2012", matrixVariables.getFirst("year")); assertEquals("cars", uriVariables.get("cars")); - exchange = get("/").toExchange(); - handleMatch(exchange, "/{cars:[^;]+}{params}", "/cars;colors=red,blue,green;year=2012"); + exchange = get("/cars;colors=red,blue,green;year=2012").toExchange(); + handleMatch(exchange, "/{cars:[^;]+}{params}"); matrixVariables = getMatrixVariables(exchange, "params"); uriVariables = getUriTemplateVariables(exchange); @@ -291,8 +296,8 @@ public class RequestMappingInfoHandlerMappingTests { assertEquals("cars", uriVariables.get("cars")); assertEquals(";colors=red,blue,green;year=2012", uriVariables.get("params")); - exchange = get("/").toExchange(); - handleMatch(exchange, "/{cars:[^;]+}{params}", "/cars"); + exchange = get("/cars").toExchange(); + handleMatch(exchange, "/{cars:[^;]+}{params}"); matrixVariables = getMatrixVariables(exchange, "params"); uriVariables = getUriTemplateVariables(exchange); @@ -308,8 +313,8 @@ public class RequestMappingInfoHandlerMappingTests { urlPathHelper.setUrlDecode(false); this.handlerMapping.setPathHelper(urlPathHelper); - ServerWebExchange exchange = get("/").toExchange(); - handleMatch(exchange, "/path{filter}", "/path;mvar=a%2fb"); + ServerWebExchange exchange = get("/path;mvar=a%2fb").toExchange(); + handleMatch(exchange, "/path{filter}"); MultiValueMap matrixVariables = getMatrixVariables(exchange, "filter"); Map uriVariables = getUriTemplateVariables(exchange); @@ -368,8 +373,9 @@ public class RequestMappingInfoHandlerMappingTests { ex.getSupportedMediaTypes())); } - private void handleMatch(ServerWebExchange exchange, String pattern, String lookupPath) { + private void handleMatch(ServerWebExchange exchange, String pattern) { RequestMappingInfo info = paths(pattern).build(); + LookupPath lookupPath = this.pathHelper.getLookupPathForRequest(exchange); this.handlerMapping.handleMatch(info, lookupPath, exchange); } @@ -474,7 +480,6 @@ public class RequestMappingInfoHandlerMappingTests { RequestMapping annot = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class); if (annot != null) { BuilderConfiguration options = new BuilderConfiguration(); - options.setPathHelper(getPathHelper()); options.setPathMatcher(getPathMatcher()); options.setSuffixPatternMatch(true); options.setTrailingSlashMatch(true); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java index 7d11620c3eb..3fcf341e827 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java @@ -55,6 +55,8 @@ import org.springframework.web.reactive.accept.HeaderContentTypeResolver; import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.support.HttpRequestPathHelper; +import org.springframework.web.server.support.LookupPath; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; @@ -217,14 +219,17 @@ public class ViewResolutionResultHandlerTests { ViewResolutionResultHandler handler = resultHandler(new TestViewResolver("account")); MockServerWebExchange exchange = get("/account").toExchange(); + addLookupPathAttribute(exchange); handler.handleResult(exchange, result).block(Duration.ofMillis(5000)); assertResponseBody(exchange, "account: {id=123}"); exchange = get("/account/").toExchange(); + addLookupPathAttribute(exchange); handler.handleResult(exchange, result).block(Duration.ofMillis(5000)); assertResponseBody(exchange, "account: {id=123}"); exchange = get("/account.123").toExchange(); + addLookupPathAttribute(exchange); handler.handleResult(exchange, result).block(Duration.ofMillis(5000)); assertResponseBody(exchange, "account: {id=123}"); } @@ -251,7 +256,8 @@ public class ViewResolutionResultHandlerTests { HandlerResult handlerResult = new HandlerResult(new Object(), value, returnType, this.bindingContext); MockServerWebExchange exchange = get("/account").accept(APPLICATION_JSON).toExchange(); - + addLookupPathAttribute(exchange); + TestView defaultView = new TestView("jsonView", APPLICATION_JSON); resultHandler(Collections.singletonList(defaultView), new TestViewResolver("account")) @@ -301,6 +307,11 @@ public class ViewResolutionResultHandlerTests { assertEquals("/", response.getHeaders().getLocation().toString()); } + private void addLookupPathAttribute(ServerWebExchange exchange) { + HttpRequestPathHelper helper = new HttpRequestPathHelper(); + exchange.getAttributes().put(LookupPath.LOOKUP_PATH_ATTRIBUTE, helper.getLookupPathForRequest(exchange)); + } + private ViewResolutionResultHandler resultHandler(ViewResolver... resolvers) { return resultHandler(Collections.emptyList(), resolvers); @@ -322,6 +333,7 @@ public class ViewResolutionResultHandlerTests { model.addAttribute("id", "123"); HandlerResult result = new HandlerResult(new Object(), returnValue, returnType, this.bindingContext); MockServerWebExchange exchange = get(path).toExchange(); + addLookupPathAttribute(exchange); resultHandler(resolvers).handleResult(exchange, result).block(Duration.ofSeconds(5)); assertResponseBody(exchange, responseBody); return exchange;