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
This commit is contained in:
Brian Clozel 2017-05-16 22:36:48 +02:00
parent 0557404715
commit cf1bc81199
18 changed files with 335 additions and 183 deletions

View File

@ -24,8 +24,8 @@ import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.support.HttpRequestPathHelper;
import org.springframework.web.util.pattern.ParsingPathMatcher;
import org.springframework.web.server.support.LookupPath;
/**
* Provide a per reactive request {@link CorsConfiguration} instance based on a
@ -43,8 +43,6 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
private PathMatcher pathMatcher = new ParsingPathMatcher();
private HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
/**
* Set the PathMatcher implementation to use for matching URL paths
@ -56,26 +54,6 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
this.pathMatcher = pathMatcher;
}
/**
* Set if context path and request URI should be URL-decoded. Both are returned
* <i>undecoded</i> by the Servlet API, in contrast to the servlet path.
* <p>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.
* <p>Use this to override the default UrlPathHelper with a custom subclass.
*/
public void setHttpRequestPathHelper(HttpRequestPathHelper pathHelper) {
Assert.notNull(pathHelper, "HttpRequestPathHelper must not be null");
this.pathHelper = pathHelper;
}
/**
* Set CORS configuration based on URL patterns.
*/
@ -102,7 +80,7 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
@Override
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
String lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
String lookupPath = exchange.<LookupPath>getAttribute(LookupPath.LOOKUP_PATH_ATTRIBUTE).get().getPath();
for (Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
if (this.pathMatcher.match(entry.getKey(), lookupPath)) {
return entry.getValue();

View File

@ -57,9 +57,14 @@ public class HttpRequestPathHelper {
}
public String getLookupPathForRequest(ServerWebExchange exchange) {
public LookupPath getLookupPathForRequest(ServerWebExchange exchange) {
String path = getPathWithinApplication(exchange.getRequest());
return (shouldUrlDecode() ? decode(exchange, path) : path);
path = (shouldUrlDecode() ? decode(exchange, path) : path);
int begin = path.lastIndexOf('/') + 1;
int end = path.length();
int paramIndex = path.indexOf(';', begin);
int extIndex = path.lastIndexOf('.', paramIndex != -1 ? paramIndex : end);
return new LookupPath(path, extIndex, paramIndex);
}
private String getPathWithinApplication(ServerHttpRequest request) {

View File

@ -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);
}
}

View File

@ -21,6 +21,8 @@ import org.junit.Test;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.support.HttpRequestPathHelper;
import org.springframework.web.server.support.LookupPath;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@ -39,6 +41,7 @@ public class UrlBasedCorsConfigurationSourceTests {
@Test
public void empty() {
ServerWebExchange exchange = MockServerHttpRequest.get("/bar/test.html").toExchange();
setLookupPathAttribute(exchange);
assertNull(this.configSource.getCorsConfiguration(exchange));
}
@ -48,9 +51,11 @@ public class UrlBasedCorsConfigurationSourceTests {
this.configSource.registerCorsConfiguration("/bar/**", config);
ServerWebExchange exchange = MockServerHttpRequest.get("/foo/test.html").toExchange();
setLookupPathAttribute(exchange);
assertNull(this.configSource.getCorsConfiguration(exchange));
exchange = MockServerHttpRequest.get("/bar/test.html").toExchange();
setLookupPathAttribute(exchange);
assertEquals(config, this.configSource.getCorsConfiguration(exchange));
}
@ -59,4 +64,10 @@ public class UrlBasedCorsConfigurationSourceTests {
this.configSource.getCorsConfigurations().put("/**", new CorsConfiguration());
}
public void setLookupPathAttribute(ServerWebExchange exchange) {
HttpRequestPathHelper helper = new HttpRequestPathHelper();
exchange.getAttributes().put(LookupPath.LOOKUP_PATH_ATTRIBUTE,
helper.getLookupPathForRequest(exchange));
}
}

View File

@ -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);
}
}

View File

@ -17,6 +17,7 @@
package org.springframework.web.reactive.handler;
import java.util.Map;
import java.util.Optional;
import reactor.core.publisher.Mono;
@ -32,6 +33,7 @@ import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.cors.reactive.DefaultCorsProcessor;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.server.support.LookupPath;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.support.HttpRequestPathHelper;
@ -43,6 +45,7 @@ import org.springframework.web.util.pattern.ParsingPathMatcher;
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Brian Clozel
* @since 5.0
*/
public abstract class AbstractHandlerMapping extends ApplicationObjectSupport implements HandlerMapping, Ordered {
@ -171,6 +174,19 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im
});
}
protected LookupPath getLookupPath(ServerWebExchange exchange) {
Optional<LookupPath> 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}.

View File

@ -28,6 +28,7 @@ import reactor.core.publisher.Mono;
import org.springframework.beans.BeansException;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.server.support.LookupPath;
import org.springframework.web.server.ServerWebExchange;
/**
@ -46,6 +47,7 @@ import org.springframework.web.server.ServerWebExchange;
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Brian Clozel
* @since 5.0
*/
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
@ -99,7 +101,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
@Override
public Mono<Object> getHandlerInternal(ServerWebExchange exchange) {
String lookupPath = getPathHelper().getLookupPathForRequest(exchange);
LookupPath lookupPath = getLookupPath(exchange);
Object handler;
try {
handler = lookupHandler(lookupPath, exchange);
@ -109,30 +111,31 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
}
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Mapping [" + lookupPath + "] to " + handler);
logger.debug("Mapping [" + lookupPath.getPath() + "] to " + handler);
}
else if (handler == null && logger.isTraceEnabled()) {
logger.trace("No handler mapping found for [" + lookupPath + "]");
logger.trace("No handler mapping found for [" + lookupPath.getPath() + "]");
}
return Mono.justOrEmpty(handler);
}
/**
* Look up a handler instance for the given URL path.
* Look up a handler instance for the given URL lookup path.
*
* <p>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.
* <p>Looks for the most exact pattern, where most exact is defined as
* the longest path pattern.
* @param urlPath URL the bean is mapped to
* and various path pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the PathPattern class.
*
* @param lookupPath URL the handler is mapped to
* @param exchange the current exchange
* @return the associated handler instance, or {@code null} if not found
* @see org.springframework.web.util.pattern.ParsingPathMatcher
*/
@Nullable
protected Object lookupHandler(String urlPath, ServerWebExchange exchange) throws Exception {
protected Object lookupHandler(LookupPath lookupPath, ServerWebExchange exchange) throws Exception {
// Direct match?
String urlPath = lookupPath.getPath();
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
return handleMatch(handler, urlPath, urlPath, exchange);
@ -156,7 +159,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
if (!matches.isEmpty()) {
Collections.sort(matches, comparator);
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + urlPath + "] are " + matches);
logger.debug("Matching patterns for request [" + lookupPath + "] are " + matches);
}
bestMatch = matches.get(0);
}

View File

@ -37,6 +37,7 @@ import org.springframework.util.PathMatcher;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.support.HttpRequestPathHelper;
import org.springframework.web.server.support.LookupPath;
import org.springframework.web.util.pattern.ParsingPathMatcher;
/**
@ -184,8 +185,8 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
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) {

View File

@ -29,8 +29,8 @@ import java.util.Set;
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<Pat
private final Set<String> patterns;
private final HttpRequestPathHelper pathHelper;
private final PathMatcher pathMatcher;
private final boolean useSuffixPatternMatch;
@ -61,35 +59,33 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
* @param patterns 0 or more URL patterns; if 0 the condition will match to every request.
*/
public PatternsRequestCondition(String... patterns) {
this(asList(patterns), null, null, true, true, null);
this(asList(patterns), null, true, true, null);
}
/**
* Creates a new instance with the given URL patterns.
* Each pattern that is not empty and does not start with "/" is pre-pended with "/".
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
* @param pathHelper to determine the lookup path for a request
* @param pathMatcher for pattern path matching
* @param useSuffixPatternMatch whether to enable matching by suffix (".*")
* @param useTrailingSlashMatch whether to match irrespective of a trailing slash
* @param extensions file extensions to consider for path matching
*/
public PatternsRequestCondition(String[] patterns, HttpRequestPathHelper pathHelper,
PathMatcher pathMatcher, boolean useSuffixPatternMatch, boolean useTrailingSlashMatch,
public PatternsRequestCondition(String[] patterns, PathMatcher pathMatcher,
boolean useSuffixPatternMatch, boolean useTrailingSlashMatch,
Set<String> 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<String> patterns, HttpRequestPathHelper pathHelper,
PathMatcher pathMatcher, boolean useSuffixPatternMatch, boolean useTrailingSlashMatch,
private PatternsRequestCondition(Collection<String> patterns, PathMatcher pathMatcher,
boolean useSuffixPatternMatch, boolean useTrailingSlashMatch,
Set<String> fileExtensions) {
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
this.pathHelper = (pathHelper != null ? pathHelper : new HttpRequestPathHelper());
this.pathMatcher = (pathMatcher != null ? pathMatcher : new ParsingPathMatcher());
this.useSuffixPatternMatch = useSuffixPatternMatch;
this.useTrailingSlashMatch = useTrailingSlashMatch;
@ -165,7 +161,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
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 final class PatternsRequestCondition extends AbstractRequestCondition<Pat
return this;
}
String lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
LookupPath lookupPath = exchange
.<LookupPath>getAttribute(LookupPath.LOOKUP_PATH_ATTRIBUTE).get();
List<String> matches = getMatchingPatterns(lookupPath);
return matches.isEmpty() ? null :
new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch,
this.useTrailingSlashMatch, this.fileExtensions);
new PatternsRequestCondition(matches, this.pathMatcher, this.useSuffixPatternMatch,
this.useTrailingSlashMatch, this.fileExtensions);
}
/**
@ -208,7 +205,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
* @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<String> getMatchingPatterns(String lookupPath) {
public List<String> getMatchingPatterns(LookupPath lookupPath) {
List<String> matches = new ArrayList<>();
for (String pattern : this.patterns) {
String match = getMatchingPattern(pattern, lookupPath);
@ -216,34 +213,33 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
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 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
*/
@Override
public int compareTo(PatternsRequestCondition other, ServerWebExchange exchange) {
String lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
Comparator<String> patternComparator = this.pathMatcher.getPatternComparator(lookupPath);
LookupPath lookupPath = exchange
.<LookupPath>getAttribute(LookupPath.LOOKUP_PATH_ATTRIBUTE).get();
Comparator<String> patternComparator = this.pathMatcher.getPatternComparator(lookupPath.getPath());
Iterator<String> iterator = this.patterns.iterator();
Iterator<String> iteratorOther = other.patterns.iterator();
while (iterator.hasNext() && iteratorOther.hasNext()) {

View File

@ -45,6 +45,7 @@ import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.support.LookupPath;
/**
* Abstract base class for {@link HandlerMapping} implementations that define
@ -54,6 +55,7 @@ import org.springframework.web.server.ServerWebExchange;
* subclasses defining the details of the mapping type {@code <T>}.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @since 5.0
* @param <T> The mapping for a {@link HandlerMethod} containing the conditions
* needed to match the handler method to incoming request.
@ -255,7 +257,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
*/
@Override
public Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange) {
String lookupPath = getPathHelper().getLookupPathForRequest(exchange);
LookupPath lookupPath = getLookupPath(exchange);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
@ -289,18 +291,18 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
/**
* Look up the best-matching handler method for the current request.
* If multiple matches are found, the best match is selected.
* @param lookupPath mapping lookup path within the current servlet mapping
* @param lookupPath the lookup path within the current mapping
* @param exchange the current exchange
* @return the best-matching handler method, or {@code null} if no match
* @see #handleMatch(Object, String, ServerWebExchange)
* @see #handleNoMatch(Set, String, ServerWebExchange)
* @see #handleMatch(Object, LookupPath, ServerWebExchange)
* @see #handleNoMatch(Set, LookupPath, ServerWebExchange)
*/
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, ServerWebExchange exchange)
protected HandlerMethod lookupHandlerMethod(LookupPath lookupPath, ServerWebExchange exchange)
throws Exception {
List<Match> matches = new ArrayList<Match>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath.getPath());
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, exchange);
}
@ -349,22 +351,22 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
/**
* Invoked when a matching mapping is found.
* @param mapping the matching mapping
* @param lookupPath mapping lookup path within the current servlet mapping
* @param lookupPath the lookup path within the current mapping
* @param exchange the current exchange
*/
protected void handleMatch(T mapping, String lookupPath, ServerWebExchange exchange) {
protected void handleMatch(T mapping, LookupPath lookupPath, ServerWebExchange exchange) {
}
/**
* Invoked when no matching mapping is not found.
* @param mappings all registered mappings
* @param lookupPath mapping lookup path within the current servlet mapping
* @param lookupPath the lookup path within the current mapping
* @param exchange the current exchange
* @return an alternative HandlerMethod or {@code null}
* @throws Exception provides details that can be translated into an error status code
*/
@Nullable
protected HandlerMethod handleNoMatch(Set<T> mappings, String lookupPath, ServerWebExchange exchange)
protected HandlerMethod handleNoMatch(Set<T> mappings, LookupPath lookupPath, ServerWebExchange exchange)
throws Exception {
return null;

View File

@ -33,7 +33,6 @@ import org.springframework.web.reactive.result.condition.RequestCondition;
import org.springframework.web.reactive.result.condition.RequestConditionHolder;
import org.springframework.web.reactive.result.condition.RequestMethodsRequestCondition;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.support.HttpRequestPathHelper;
/**
* Encapsulates the following request mapping conditions:
@ -475,9 +474,8 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
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 final class RequestMappingInfo implements RequestCondition<RequestMapping
*/
public static class BuilderConfiguration {
private HttpRequestPathHelper pathHelper;
private PathMatcher pathMatcher;
private boolean trailingSlashMatch = true;
@ -510,18 +506,6 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
private RequestedContentTypeResolver contentTypeResolver;
/**
* Set a custom UrlPathHelper to use for the PatternsRequestCondition.
* <p>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.
* <p>By default this is not set.

View File

@ -27,7 +27,6 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import org.springframework.http.HttpHeaders;
@ -35,9 +34,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.result.condition.NameValueExpression;
@ -46,6 +43,8 @@ import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import org.springframework.web.server.support.LookupPath;
import org.springframework.web.util.WebUtils;
/**
* Abstract base class for classes for which {@link RequestMappingInfo} defines
@ -103,7 +102,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
* @see HandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
*/
@Override
protected void handleMatch(RequestMappingInfo info, String lookupPath, ServerWebExchange exchange) {
protected void handleMatch(RequestMappingInfo info, LookupPath lookupPath, ServerWebExchange exchange) {
super.handleMatch(info, lookupPath, exchange);
String bestPattern;
@ -112,13 +111,13 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
Set<String> patterns = info.getPatternsCondition().getPatterns();
if (patterns.isEmpty()) {
bestPattern = lookupPath;
bestPattern = lookupPath.getPath();
uriVariables = Collections.emptyMap();
decodedUriVariables = Collections.emptyMap();
}
else {
bestPattern = patterns.iterator().next();
uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath.getPath());
decodedUriVariables = getPathHelper().decodePathVariables(exchange, uriVariables);
}
@ -157,43 +156,12 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
uriVariables.put(uriVar.getKey(), uriVarValue.substring(0, semicolonIndex));
}
MultiValueMap<String, String> vars = parseMatrixVariables(matrixVariables);
MultiValueMap<String, String> 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<String, String> parseMatrixVariables(String matrixVariables) {
MultiValueMap<String, String> result = new LinkedMultiValueMap<>();
if (!StringUtils.hasText(matrixVariables)) {
return result;
}
StringTokenizer pairs = new StringTokenizer(matrixVariables, ";");
while (pairs.hasMoreTokens()) {
String pair = pairs.nextToken();
int index = pair.indexOf('=');
if (index != -1) {
String name = pair.substring(0, index);
String rawValue = pair.substring(index + 1);
for (String value : StringUtils.commaDelimitedListToStringArray(rawValue)) {
result.add(name, value);
}
}
else {
result.add(pair, "");
}
}
return result;
}
/**
* Iterate all RequestMappingInfos once again, look if any match by URL at
* least and raise exceptions accordingly.
@ -206,7 +174,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
* method but not by query parameter conditions
*/
@Override
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> infos, String lookupPath,
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> infos, LookupPath lookupPath,
ServerWebExchange exchange) throws Exception {
PartialMatchHelper helper = new PartialMatchHelper(infos, exchange);

View File

@ -37,6 +37,8 @@ import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.result.condition.RequestCondition;
import org.springframework.web.reactive.result.method.RequestMappingInfo;
import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping;
import org.springframework.web.server.support.LookupPath;
import org.springframework.web.server.ServerWebExchange;
/**
* An extension of {@link RequestMappingInfoHandlerMapping} that creates
@ -113,10 +115,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
@Override
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setPathHelper(getPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
this.config.setContentTypeResolver(getContentTypeResolver());
@ -159,7 +159,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
return this.config.getFileExtensions();
}
/**
* {@inheritDoc}
* Expects a handler to have a type-level @{@link Controller} annotation.
@ -170,6 +169,11 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
@Override
protected LookupPath createLookupPath(ServerWebExchange exchange) {
return getPathHelper().getLookupPathForRequest(exchange);
}
/**
* Uses method and type-level @{@link RequestMapping} annotations to create
* the RequestMappingInfo.

View File

@ -50,7 +50,7 @@ import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.result.HandlerResultHandlerSupport;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.support.HttpRequestPathHelper;
import org.springframework.web.server.support.LookupPath;
/**
* {@code HandlerResultHandler} that encapsulates the view resolution algorithm
@ -91,8 +91,6 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport
private final List<View> defaultViews = new ArrayList<>(4);
private final HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
/**
* Basic constructor with a default {@link ReactiveAdapterRegistry}.
@ -259,7 +257,7 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport
* Use the request path the leading and trailing slash stripped.
*/
private String getDefaultViewName(ServerWebExchange exchange) {
String path = this.pathHelper.getLookupPathForRequest(exchange);
String path = exchange.<LookupPath>getAttribute(LookupPath.LOOKUP_PATH_ATTRIBUTE).get().getPath();
if (path.startsWith("/")) {
path = path.substring(1);
}

View File

@ -22,6 +22,8 @@ import java.util.Set;
import org.junit.Test;
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
import org.springframework.web.server.support.HttpRequestPathHelper;
import org.springframework.web.server.support.LookupPath;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
@ -80,7 +82,7 @@ public class PatternsRequestConditionTests {
@Test
public void matchDirectPath() throws Exception {
PatternsRequestCondition condition = new PatternsRequestCondition("/foo");
PatternsRequestCondition match = condition.getMatchingCondition(get("/foo").toExchange());
PatternsRequestCondition match = condition.getMatchingCondition(createExchange("/foo"));
assertNotNull(match);
}
@ -88,7 +90,7 @@ public class PatternsRequestConditionTests {
@Test
public void matchPattern() throws Exception {
PatternsRequestCondition condition = new PatternsRequestCondition("/foo/*");
PatternsRequestCondition match = condition.getMatchingCondition(get("/foo/bar").toExchange());
PatternsRequestCondition match = condition.getMatchingCondition(createExchange("/foo/bar"));
assertNotNull(match);
}
@ -96,7 +98,7 @@ public class PatternsRequestConditionTests {
@Test
public void matchSortPatterns() throws Exception {
PatternsRequestCondition condition = new PatternsRequestCondition("/*/*", "/foo/bar", "/foo/*");
PatternsRequestCondition match = condition.getMatchingCondition(get("/foo/bar").toExchange());
PatternsRequestCondition match = condition.getMatchingCondition(createExchange("/foo/bar"));
PatternsRequestCondition expected = new PatternsRequestCondition("/foo/bar", "/foo/*", "/*/*");
assertEquals(expected, match);
@ -104,7 +106,7 @@ public class PatternsRequestConditionTests {
@Test
public void matchSuffixPattern() throws Exception {
ServerWebExchange exchange = get("/foo.html").toExchange();
ServerWebExchange exchange = createExchange("/foo.html");
PatternsRequestCondition condition = new PatternsRequestCondition("/{foo}");
PatternsRequestCondition match = condition.getMatchingCondition(exchange);
@ -112,7 +114,7 @@ public class PatternsRequestConditionTests {
assertNotNull(match);
assertEquals("/{foo}.*", match.getPatterns().iterator().next());
condition = new PatternsRequestCondition(new String[] {"/{foo}"}, null, null, false, false, null);
condition = new PatternsRequestCondition(new String[] {"/{foo}"}, null,false, false, null);
match = condition.getMatchingCondition(exchange);
assertNotNull(match);
@ -125,15 +127,15 @@ public class PatternsRequestConditionTests {
public void matchSuffixPatternUsingFileExtensions() throws Exception {
String[] patterns = new String[] {"/jobs/{jobName}"};
Set<String> extensions = Collections.singleton("json");
PatternsRequestCondition condition = new PatternsRequestCondition(patterns, null, null, true, false, extensions);
PatternsRequestCondition condition = new PatternsRequestCondition(patterns, null, true, false, extensions);
MockServerWebExchange exchange = get("/jobs/my.job").toExchange();
MockServerWebExchange exchange = createExchange("/jobs/my.job");
PatternsRequestCondition match = condition.getMatchingCondition(exchange);
assertNotNull(match);
assertEquals("/jobs/{jobName}", match.getPatterns().iterator().next());
exchange = get("/jobs/my.job.json").toExchange();
exchange = createExchange("/jobs/my.job.json");
match = condition.getMatchingCondition(exchange);
assertNotNull(match);
@ -143,14 +145,14 @@ public class PatternsRequestConditionTests {
@Test
public void matchSuffixPatternUsingFileExtensions2() throws Exception {
PatternsRequestCondition condition1 = new PatternsRequestCondition(
new String[] {"/prefix"}, null, null, true, false, Collections.singleton("json"));
new String[] {"/prefix"}, null, true, false, Collections.singleton("json"));
PatternsRequestCondition condition2 = new PatternsRequestCondition(
new String[] {"/suffix"}, null, null, true, false, null);
new String[] {"/suffix"}, null, true, false, null);
PatternsRequestCondition combined = condition1.combine(condition2);
MockServerWebExchange exchange = get("/prefix/suffix.json").toExchange();
MockServerWebExchange exchange = createExchange("/prefix/suffix.json");
PatternsRequestCondition match = combined.getMatchingCondition(exchange);
assertNotNull(match);
@ -158,7 +160,7 @@ public class PatternsRequestConditionTests {
@Test
public void matchTrailingSlash() throws Exception {
MockServerWebExchange exchange = get("/foo/").toExchange();
MockServerWebExchange exchange = createExchange("/foo/");
PatternsRequestCondition condition = new PatternsRequestCondition("/foo");
PatternsRequestCondition match = condition.getMatchingCondition(exchange);
@ -166,14 +168,15 @@ public class PatternsRequestConditionTests {
assertNotNull(match);
assertEquals("Should match by default", "/foo/", match.getPatterns().iterator().next());
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, false, true, null);
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, false, true, null);
match = condition.getMatchingCondition(exchange);
assertNotNull(match);
assertEquals("Trailing slash should be insensitive to useSuffixPatternMatch settings (SPR-6164, SPR-5636)",
"/foo/", match.getPatterns().iterator().next());
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, false, false, null);
exchange = createExchange("/foo/");
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, false, false, null);
match = condition.getMatchingCondition(exchange);
assertNull(match);
@ -182,7 +185,7 @@ public class PatternsRequestConditionTests {
@Test
public void matchPatternContainsExtension() throws Exception {
PatternsRequestCondition condition = new PatternsRequestCondition("/foo.jpg");
PatternsRequestCondition match = condition.getMatchingCondition(get("/foo.html").toExchange());
PatternsRequestCondition match = condition.getMatchingCondition(createExchange("/foo.html"));
assertNull(match);
}
@ -192,7 +195,7 @@ public class PatternsRequestConditionTests {
PatternsRequestCondition c1 = new PatternsRequestCondition("/foo*");
PatternsRequestCondition c2 = new PatternsRequestCondition("/foo*");
assertEquals(0, c1.compareTo(c2, get("/foo").toExchange()));
assertEquals(0, c1.compareTo(c2, createExchange("/foo")));
}
@Test
@ -200,12 +203,12 @@ public class PatternsRequestConditionTests {
PatternsRequestCondition c1 = new PatternsRequestCondition("/fo*");
PatternsRequestCondition c2 = new PatternsRequestCondition("/foo");
assertEquals(1, c1.compareTo(c2, get("/foo").toExchange()));
assertEquals(1, c1.compareTo(c2, createExchange("/foo")));
}
@Test
public void compareNumberOfMatchingPatterns() throws Exception {
ServerWebExchange exchange = get("/foo.html").toExchange();
ServerWebExchange exchange = createExchange("/foo.html");
PatternsRequestCondition c1 = new PatternsRequestCondition("/foo", "*.jpeg");
PatternsRequestCondition c2 = new PatternsRequestCondition("/foo", "*.html");
@ -217,5 +220,11 @@ public class PatternsRequestConditionTests {
assertEquals(1, match1.compareTo(match2, exchange));
}
private MockServerWebExchange createExchange(String path) {
MockServerWebExchange exchange = get(path).toExchange();
HttpRequestPathHelper helper = new HttpRequestPathHelper();
exchange.getAttributes().put(LookupPath.LOOKUP_PATH_ATTRIBUTE, helper.getLookupPathForRequest(exchange));
return exchange;
}
}

View File

@ -29,6 +29,8 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.reactive.result.method.RequestMappingInfo;
import org.springframework.web.server.support.HttpRequestPathHelper;
import org.springframework.web.server.support.LookupPath;
import org.springframework.web.server.ServerWebExchange;
import static java.util.Arrays.asList;
@ -65,6 +67,7 @@ public class RequestMappingInfoTests {
@Test
public void matchPatternsCondition() {
MockServerWebExchange exchange = MockServerHttpRequest.get("/foo").toExchange();
setLookupPathAttribute(exchange);
RequestMappingInfo info = paths("/foo*", "/bar").build();
RequestMappingInfo expected = paths("/foo*").build();
@ -80,6 +83,7 @@ public class RequestMappingInfoTests {
@Test
public void matchParamsCondition() {
ServerWebExchange exchange = MockServerHttpRequest.get("/foo?foo=bar").toExchange();
setLookupPathAttribute(exchange);
RequestMappingInfo info = paths("/foo").params("foo=bar").build();
RequestMappingInfo match = info.getMatchingCondition(exchange);
@ -95,6 +99,7 @@ public class RequestMappingInfoTests {
@Test
public void matchHeadersCondition() {
ServerWebExchange exchange = MockServerHttpRequest.get("/foo").header("foo", "bar").toExchange();
setLookupPathAttribute(exchange);
RequestMappingInfo info = paths("/foo").headers("foo=bar").build();
RequestMappingInfo match = info.getMatchingCondition(exchange);
@ -110,6 +115,7 @@ public class RequestMappingInfoTests {
@Test
public void matchConsumesCondition() {
ServerWebExchange exchange = MockServerHttpRequest.post("/foo").contentType(MediaType.TEXT_PLAIN).toExchange();
setLookupPathAttribute(exchange);
RequestMappingInfo info = paths("/foo").consumes("text/plain").build();
RequestMappingInfo match = info.getMatchingCondition(exchange);
@ -125,6 +131,7 @@ public class RequestMappingInfoTests {
@Test
public void matchProducesCondition() {
ServerWebExchange exchange = MockServerHttpRequest.get("/foo").accept(MediaType.TEXT_PLAIN).toExchange();
setLookupPathAttribute(exchange);
RequestMappingInfo info = paths("/foo").produces("text/plain").build();
RequestMappingInfo match = info.getMatchingCondition(exchange);
@ -140,7 +147,8 @@ public class RequestMappingInfoTests {
@Test
public void matchCustomCondition() {
ServerWebExchange exchange = MockServerHttpRequest.get("/foo?foo=bar").toExchange();
setLookupPathAttribute(exchange);
RequestMappingInfo info = paths("/foo").params("foo=bar").build();
RequestMappingInfo match = info.getMatchingCondition(exchange);
@ -161,6 +169,7 @@ public class RequestMappingInfoTests {
RequestMappingInfo oneMethodOneParam = paths().methods(RequestMethod.GET).params("foo").build();
ServerWebExchange exchange = MockServerHttpRequest.get("/foo").toExchange();
setLookupPathAttribute(exchange);
Comparator<RequestMappingInfo> comparator = (info, otherInfo) -> info.compareTo(otherInfo, exchange);
List<RequestMappingInfo> list = asList(none, oneMethod, oneMethodOneParam);
@ -270,4 +279,9 @@ public class RequestMappingInfoTests {
assertNull("Pre-flight should match the ACCESS_CONTROL_REQUEST_METHOD", match);
}
public void setLookupPathAttribute(ServerWebExchange exchange) {
HttpRequestPathHelper helper = new HttpRequestPathHelper();
exchange.getAttributes().put(LookupPath.LOOKUP_PATH_ATTRIBUTE, helper.getLookupPathForRequest(exchange));
}
}

View File

@ -54,6 +54,7 @@ import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import org.springframework.web.server.support.HttpRequestPathHelper;
import org.springframework.web.server.support.LookupPath;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
@ -72,11 +73,15 @@ public class RequestMappingInfoHandlerMappingTests {
private TestRequestMappingInfoHandlerMapping handlerMapping;
private HttpRequestPathHelper pathHelper;
@Before
public void setup() throws Exception {
this.handlerMapping = new TestRequestMappingInfoHandlerMapping();
this.handlerMapping.registerHandler(new TestController());
this.pathHelper = new HttpRequestPathHelper();
this.handlerMapping.setPathHelper(this.pathHelper);
}
@ -208,8 +213,8 @@ public class RequestMappingInfoHandlerMappingTests {
@Test
@SuppressWarnings("unchecked")
public void handleMatchUriTemplateVariables() throws Exception {
String lookupPath = "/1/2";
ServerWebExchange exchange = get(lookupPath).toExchange();
ServerWebExchange exchange = get("/1/2").toExchange();
LookupPath lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
RequestMappingInfo key = paths("/{path1}/{path2}").build();
this.handlerMapping.handleMatch(key, lookupPath, exchange);
@ -228,11 +233,10 @@ public class RequestMappingInfoHandlerMappingTests {
URI url = URI.create("/group/a%2Fb");
ServerWebExchange exchange = MockServerHttpRequest.method(HttpMethod.GET, url).toExchange();
HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
pathHelper.setUrlDecode(false);
String lookupPath = pathHelper.getLookupPathForRequest(exchange);
this.handlerMapping.setPathHelper(pathHelper);
this.pathHelper.setUrlDecode(false);
LookupPath lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
this.handlerMapping.setPathHelper(this.pathHelper);
this.handlerMapping.handleMatch(key, lookupPath, exchange);
String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
@ -248,7 +252,8 @@ public class RequestMappingInfoHandlerMappingTests {
public void handleMatchBestMatchingPatternAttribute() throws Exception {
RequestMappingInfo key = paths("/{path1}/2", "/**").build();
ServerWebExchange exchange = get("/1/2").toExchange();
this.handlerMapping.handleMatch(key, "/1/2", exchange);
LookupPath lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
this.handlerMapping.handleMatch(key, lookupPath, exchange);
assertEquals("/{path1}/2", exchange.getAttributes().get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
}
@ -257,8 +262,8 @@ public class RequestMappingInfoHandlerMappingTests {
public void handleMatchBestMatchingPatternAttributeNoPatternsDefined() throws Exception {
RequestMappingInfo key = paths().build();
ServerWebExchange exchange = get("/1/2").toExchange();
this.handlerMapping.handleMatch(key, "/1/2", exchange);
LookupPath lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
this.handlerMapping.handleMatch(key, lookupPath, exchange);
assertEquals("/1/2", exchange.getAttributes().get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
}
@ -268,8 +273,8 @@ public class RequestMappingInfoHandlerMappingTests {
MultiValueMap<String, String> matrixVariables;
Map<String, String> uriVariables;
ServerWebExchange exchange = get("/").toExchange();
handleMatch(exchange, "/{cars}", "/cars;colors=red,blue,green;year=2012");
ServerWebExchange exchange = get("/cars;colors=red,blue,green;year=2012").toExchange();
handleMatch(exchange, "/{cars}");
matrixVariables = getMatrixVariables(exchange, "cars");
uriVariables = getUriTemplateVariables(exchange);
@ -279,8 +284,8 @@ public class RequestMappingInfoHandlerMappingTests {
assertEquals("2012", matrixVariables.getFirst("year"));
assertEquals("cars", uriVariables.get("cars"));
exchange = get("/").toExchange();
handleMatch(exchange, "/{cars:[^;]+}{params}", "/cars;colors=red,blue,green;year=2012");
exchange = get("/cars;colors=red,blue,green;year=2012").toExchange();
handleMatch(exchange, "/{cars:[^;]+}{params}");
matrixVariables = getMatrixVariables(exchange, "params");
uriVariables = getUriTemplateVariables(exchange);
@ -291,8 +296,8 @@ public class RequestMappingInfoHandlerMappingTests {
assertEquals("cars", uriVariables.get("cars"));
assertEquals(";colors=red,blue,green;year=2012", uriVariables.get("params"));
exchange = get("/").toExchange();
handleMatch(exchange, "/{cars:[^;]+}{params}", "/cars");
exchange = get("/cars").toExchange();
handleMatch(exchange, "/{cars:[^;]+}{params}");
matrixVariables = getMatrixVariables(exchange, "params");
uriVariables = getUriTemplateVariables(exchange);
@ -308,8 +313,8 @@ public class RequestMappingInfoHandlerMappingTests {
urlPathHelper.setUrlDecode(false);
this.handlerMapping.setPathHelper(urlPathHelper);
ServerWebExchange exchange = get("/").toExchange();
handleMatch(exchange, "/path{filter}", "/path;mvar=a%2fb");
ServerWebExchange exchange = get("/path;mvar=a%2fb").toExchange();
handleMatch(exchange, "/path{filter}");
MultiValueMap<String, String> matrixVariables = getMatrixVariables(exchange, "filter");
Map<String, String> uriVariables = getUriTemplateVariables(exchange);
@ -368,8 +373,9 @@ public class RequestMappingInfoHandlerMappingTests {
ex.getSupportedMediaTypes()));
}
private void handleMatch(ServerWebExchange exchange, String pattern, String lookupPath) {
private void handleMatch(ServerWebExchange exchange, String pattern) {
RequestMappingInfo info = paths(pattern).build();
LookupPath lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
this.handlerMapping.handleMatch(info, lookupPath, exchange);
}
@ -474,7 +480,6 @@ public class RequestMappingInfoHandlerMappingTests {
RequestMapping annot = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
if (annot != null) {
BuilderConfiguration options = new BuilderConfiguration();
options.setPathHelper(getPathHelper());
options.setPathMatcher(getPathMatcher());
options.setSuffixPatternMatch(true);
options.setTrailingSlashMatch(true);

View File

@ -55,6 +55,8 @@ import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.support.HttpRequestPathHelper;
import org.springframework.web.server.support.LookupPath;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
@ -217,14 +219,17 @@ public class ViewResolutionResultHandlerTests {
ViewResolutionResultHandler handler = resultHandler(new TestViewResolver("account"));
MockServerWebExchange exchange = get("/account").toExchange();
addLookupPathAttribute(exchange);
handler.handleResult(exchange, result).block(Duration.ofMillis(5000));
assertResponseBody(exchange, "account: {id=123}");
exchange = get("/account/").toExchange();
addLookupPathAttribute(exchange);
handler.handleResult(exchange, result).block(Duration.ofMillis(5000));
assertResponseBody(exchange, "account: {id=123}");
exchange = get("/account.123").toExchange();
addLookupPathAttribute(exchange);
handler.handleResult(exchange, result).block(Duration.ofMillis(5000));
assertResponseBody(exchange, "account: {id=123}");
}
@ -251,7 +256,8 @@ public class ViewResolutionResultHandlerTests {
HandlerResult handlerResult = new HandlerResult(new Object(), value, returnType, this.bindingContext);
MockServerWebExchange exchange = get("/account").accept(APPLICATION_JSON).toExchange();
addLookupPathAttribute(exchange);
TestView defaultView = new TestView("jsonView", APPLICATION_JSON);
resultHandler(Collections.singletonList(defaultView), new TestViewResolver("account"))
@ -301,6 +307,11 @@ public class ViewResolutionResultHandlerTests {
assertEquals("/", response.getHeaders().getLocation().toString());
}
private void addLookupPathAttribute(ServerWebExchange exchange) {
HttpRequestPathHelper helper = new HttpRequestPathHelper();
exchange.getAttributes().put(LookupPath.LOOKUP_PATH_ATTRIBUTE, helper.getLookupPathForRequest(exchange));
}
private ViewResolutionResultHandler resultHandler(ViewResolver... resolvers) {
return resultHandler(Collections.emptyList(), resolvers);
@ -322,6 +333,7 @@ public class ViewResolutionResultHandlerTests {
model.addAttribute("id", "123");
HandlerResult result = new HandlerResult(new Object(), returnValue, returnType, this.bindingContext);
MockServerWebExchange exchange = get(path).toExchange();
addLookupPathAttribute(exchange);
resultHandler(resolvers).handleResult(exchange, result).block(Duration.ofSeconds(5));
assertResponseBody(exchange, responseBody);
return exchange;