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:
parent
0557404715
commit
cf1bc81199
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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}.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue