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;