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 f1eaa47e25a8..35268a886d66 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.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 void setPathMatcher(PathMatcher pathMatcher) { 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 void registerCorsConfiguration(String path, CorsConfiguration config) { @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 ac92db6a69a6..3842184adece 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 boolean shouldUrlDecode() { } - 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 000000000000..3fc00e0970c6 --- /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 75775e790e2d..4fe628ed9473 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.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 void registerAndMatch() { 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 void unmodifiableConfigurationsMap() { 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 000000000000..11a46dd12a07 --- /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 d2a20b0befab..92d8937abb30 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.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 @@ * * @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 Mono getHandler(ServerWebExchange exchange) { }); } + 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 4458bb34c631..c353b4376a90 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 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 @@ * * @author Rossen Stoyanchev * @author Juergen Hoeller + * @author Brian Clozel * @since 5.0 */ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { @@ -99,7 +101,7 @@ public final Map getHandlerMap() { @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 Mono getHandlerInternal(ServerWebExchange exchange) { } 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 @@ else if (useTrailingSlashMatch()) { 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 b3ef7234bd57..40c822c0a8e3 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.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 final Mono getForRequestUrl(ServerWebExchange exchange, String re private int getLookupPathIndex(ServerWebExchange exchange) { ServerHttpRequest request = exchange.getRequest(); String requestPath = request.getURI().getPath(); - String lookupPath = getPathHelper().getLookupPathForRequest(exchange); - return requestPath.indexOf(lookupPath); + LookupPath lookupPath = getPathHelper().getLookupPathForRequest(exchange); + return requestPath.indexOf(lookupPath.getPath()); } private int getEndPathIndex(String lookupPath) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/PatternsRequestCondition.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/PatternsRequestCondition.java index 3814cec17cb8..6274e96ee2c7 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/PatternsRequestCondition.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/PatternsRequestCondition.java @@ -29,8 +29,8 @@ import org.springframework.util.PathMatcher; import org.springframework.util.StringUtils; +import org.springframework.web.server.support.LookupPath; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.support.HttpRequestPathHelper; import org.springframework.web.util.pattern.ParsingPathMatcher; /** @@ -44,8 +44,6 @@ public final class PatternsRequestCondition extends AbstractRequestCondition 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 @@ else if (!other.patterns.isEmpty()) { else { result.add(""); } - return new PatternsRequestCondition(result, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch, + return new PatternsRequestCondition(result, this.pathMatcher, this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions); } @@ -191,12 +187,13 @@ public PatternsRequestCondition getMatchingCondition(ServerWebExchange exchange) return this; } - String lookupPath = this.pathHelper.getLookupPathForRequest(exchange); + LookupPath lookupPath = exchange + .getAttribute(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 PatternsRequestCondition getMatchingCondition(ServerWebExchange exchange) * @param lookupPath the lookup path to match to existing patterns * @return a collection of matching patterns sorted with the closest match at the top */ - public List 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 List getMatchingPatterns(String lookupPath) { matches.add(match); } } - Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath)); + Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath.getPath())); return matches; } - private String getMatchingPattern(String pattern, String lookupPath) { - if (pattern.equals(lookupPath)) { + private String getMatchingPattern(String pattern, LookupPath lookupPath) { + if (pattern.equals(lookupPath.getPath())) { return pattern; } if (this.useSuffixPatternMatch) { - if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) { - for (String extension : this.fileExtensions) { - if (this.pathMatcher.match(pattern + extension, lookupPath)) { - return pattern + extension; - } + if (!this.fileExtensions.isEmpty() && lookupPath.getFileExtension() != null) { + if (this.fileExtensions.contains(lookupPath.getFileExtension()) && + this.pathMatcher.match(pattern, lookupPath.getPathWithoutExtension())) { + return pattern + lookupPath.getFileExtension(); } } else { - boolean hasSuffix = pattern.indexOf('.') != -1; - if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) { + if (lookupPath.getFileExtension() != null + && this.pathMatcher.match(pattern , lookupPath.getPathWithoutExtension())) { return pattern + ".*"; } } } - if (this.pathMatcher.match(pattern, lookupPath)) { + if (this.pathMatcher.match(pattern, lookupPath.getPath())) { return pattern; } if (this.useTrailingSlashMatch) { - if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) { + if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath.getPath())) { return pattern +"/"; } } @@ -263,8 +259,9 @@ private String getMatchingPattern(String pattern, String lookupPath) { */ @Override public int compareTo(PatternsRequestCondition other, ServerWebExchange exchange) { - String lookupPath = this.pathHelper.getLookupPathForRequest(exchange); - Comparator 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 9585e19b1884..0b6862820203 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.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 @@ * 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 @@ protected void handlerMethodsInitialized(Map handlerMethods) { */ @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 Mono getHandlerInternal(ServerWebExchange exchange) { /** * 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 @@ private void addMatchingMappings(Collection mappings, List matches, Se /** * 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 1b0e5d618869..3b660e6228b3 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.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 RequestMappingInfo build() { RequestedContentTypeResolver contentTypeResolver = this.options.getContentTypeResolver(); PatternsRequestCondition patternsCondition = new PatternsRequestCondition( - this.paths, this.options.getPathHelper(), this.options.getPathMatcher(), - this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(), - this.options.getFileExtensions()); + this.paths, this.options.getPathMatcher(), this.options.useSuffixPatternMatch(), + this.options.useTrailingSlashMatch(), this.options.getFileExtensions()); return new RequestMappingInfo(this.mappingName, patternsCondition, new RequestMethodsRequestCondition(methods), @@ -498,8 +496,6 @@ public RequestMappingInfo build() { */ public static class BuilderConfiguration { - private HttpRequestPathHelper pathHelper; - private PathMatcher pathMatcher; private boolean trailingSlashMatch = true; @@ -510,18 +506,6 @@ public static class BuilderConfiguration { private RequestedContentTypeResolver contentTypeResolver; - /** - * Set a custom UrlPathHelper to use for the PatternsRequestCondition. - *

By 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 0e258ccaaa9d..3b6eed42b295 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.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.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.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 @@ protected Comparator getMappingComparator(final ServerWebExc * @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 @@ protected void handleMatch(RequestMappingInfo info, String lookupPath, ServerWeb 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 @@ private Map> extractMatrixVariables( 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 @@ private static MultiValueMap parseMatrixVariables(String matrixV * 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 b3485ec86913..39b61b017513 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.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 void setEmbeddedValueResolver(StringValueResolver resolver) { @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 Set getFileExtensions() { return this.config.getFileExtensions(); } - /** * {@inheritDoc} * Expects a handler to have a type-level @{@link Controller} annotation. @@ -170,6 +169,11 @@ protected boolean isHandler(Class beanType) { 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 c8b0b87bcf29..82fb55fec055 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.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 @@ else if (View.class.isAssignableFrom(clazz)) { * 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 df36d448c20e..b92fc911e3e8 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 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 void combineMultiplePatterns() { @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 void matchDirectPath() throws Exception { @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 void matchPattern() throws Exception { @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 void matchSortPatterns() throws Exception { @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 void matchSuffixPattern() throws Exception { 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 void matchSuffixPattern() throws Exception { 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 void matchSuffixPatternUsingFileExtensions() throws Exception { @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 void matchSuffixPatternUsingFileExtensions2() throws Exception { @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 void matchTrailingSlash() throws Exception { 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 void matchTrailingSlash() throws Exception { @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 void compareEqualPatterns() throws Exception { 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 void comparePatternSpecificity() throws Exception { 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 void compareNumberOfMatchingPatterns() throws Exception { 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 6e9c463a83a6..ac32a58d28a5 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.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 void createEmpty() { @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 void matchPatternsCondition() { @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 void matchParamsCondition() { @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 void matchHeadersCondition() { @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 void matchConsumesCondition() { @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 void matchProducesCondition() { @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 void compareTwoHttpMethodsOneParam() { 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 void preFlightRequest() throws Exception { 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 a48cb5555552..0def9b7e1eff 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.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 void getHandlerProducibleMediaTypesAttribute() throws Exception { @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 void handleMatchUriTemplateVariablesDecode() throws Exception { 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 void handleMatchUriTemplateVariablesDecode() throws Exception { 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 void handleMatchBestMatchingPatternAttribute() throws Exception { 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 void handleMatchMatrixVariables() throws Exception { 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 void handleMatchMatrixVariables() throws Exception { 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 void handleMatchMatrixVariables() throws Exception { 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 void handleMatchMatrixVariablesDecoding() throws Exception { 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 @@ private void testMediaTypeNotAcceptable(String url) throws Exception { 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 @@ protected RequestMappingInfo getMappingForMethod(Method method, Class handler 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 7d11620c3eb7..3fcf341e8274 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.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 @@ private void testDefaultViewName(Object returnValue, MethodParameter returnType) 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 void contentNegotiation() throws Exception { 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 void contentNegotiationWithRedirect() throws Exception { 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 @@ private ServerWebExchange testHandle(String path, MethodParameter returnType, Ob 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;