Add PathPatternRegistry
This commit adds the new `PathPatternRegistry`, which holds a sorted set of `PathPattern`s and allows for searching/adding patterns This registry is being used in `HandlerMapping` implementations and separates path pattern parsing/matching logic from the rest. Directly using `PathPattern` instances should improve the performance of those `HandlerMapping` implementations, since the parsing and generation of pattern variants (trailing slash, suffix patterns, etc) is done only once. Issue: SPR-14544
This commit is contained in:
parent
a4da313a0a
commit
18c04815a7
|
@ -19,13 +19,14 @@ package org.springframework.web.cors.reactive;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
|
||||||
import org.springframework.util.AntPathMatcher;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.PathMatcher;
|
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.support.HttpRequestPathHelper;
|
import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||||
|
import org.springframework.web.util.patterns.PathPattern;
|
||||||
|
import org.springframework.web.util.patterns.PathPatternRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a per reactive request {@link CorsConfiguration} instance based on a
|
* Provide a per reactive request {@link CorsConfiguration} instance based on a
|
||||||
|
@ -35,27 +36,18 @@ import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||||
* as well as Ant-style path patterns (such as {@code "/admin/**"}).
|
* as well as Ant-style path patterns (such as {@code "/admin/**"}).
|
||||||
*
|
*
|
||||||
* @author Sebastien Deleuze
|
* @author Sebastien Deleuze
|
||||||
|
* @author Brian Clozel
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource {
|
public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource {
|
||||||
|
|
||||||
private final Map<String, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
|
private final PathPatternRegistry patternRegistry = new PathPatternRegistry();
|
||||||
|
|
||||||
private PathMatcher pathMatcher = new AntPathMatcher();
|
private final Map<PathPattern, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
|
||||||
|
|
||||||
private HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
|
private HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the PathMatcher implementation to use for matching URL paths
|
|
||||||
* against registered URL patterns. Default is AntPathMatcher.
|
|
||||||
* @see AntPathMatcher
|
|
||||||
*/
|
|
||||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
|
||||||
Assert.notNull(pathMatcher, "PathMatcher must not be null");
|
|
||||||
this.pathMatcher = pathMatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set if context path and request URI should be URL-decoded. Both are returned
|
* 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.
|
* <i>undecoded</i> by the Servlet API, in contrast to the servlet path.
|
||||||
|
@ -79,9 +71,11 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
|
||||||
/**
|
/**
|
||||||
* Set CORS configuration based on URL patterns.
|
* Set CORS configuration based on URL patterns.
|
||||||
*/
|
*/
|
||||||
public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
|
public void setCorsConfigurations(Map<PathPattern, CorsConfiguration> corsConfigurations) {
|
||||||
|
this.patternRegistry.clear();
|
||||||
this.corsConfigurations.clear();
|
this.corsConfigurations.clear();
|
||||||
if (corsConfigurations != null) {
|
if (corsConfigurations != null) {
|
||||||
|
this.patternRegistry.addAll(corsConfigurations.keySet());
|
||||||
this.corsConfigurations.putAll(corsConfigurations);
|
this.corsConfigurations.putAll(corsConfigurations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +83,7 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
|
||||||
/**
|
/**
|
||||||
* Get the CORS configuration.
|
* Get the CORS configuration.
|
||||||
*/
|
*/
|
||||||
public Map<String, CorsConfiguration> getCorsConfigurations() {
|
public Map<PathPattern, CorsConfiguration> getCorsConfigurations() {
|
||||||
return Collections.unmodifiableMap(this.corsConfigurations);
|
return Collections.unmodifiableMap(this.corsConfigurations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,17 +91,18 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
|
||||||
* Register a {@link CorsConfiguration} for the specified path pattern.
|
* Register a {@link CorsConfiguration} for the specified path pattern.
|
||||||
*/
|
*/
|
||||||
public void registerCorsConfiguration(String path, CorsConfiguration config) {
|
public void registerCorsConfiguration(String path, CorsConfiguration config) {
|
||||||
this.corsConfigurations.put(path, config);
|
this.patternRegistry
|
||||||
|
.register(path)
|
||||||
|
.forEach(pattern -> this.corsConfigurations.put(pattern, config));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
|
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
|
||||||
String lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
|
String lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
|
||||||
for (Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
|
SortedSet<PathPattern> matches = this.patternRegistry.findMatches(lookupPath);
|
||||||
if (this.pathMatcher.match(entry.getKey(), lookupPath)) {
|
if(!matches.isEmpty()) {
|
||||||
return entry.getValue();
|
return this.corsConfigurations.get(matches.first());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,323 @@
|
||||||
|
/*
|
||||||
|
* 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.util.patterns;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry that holds {@code PathPattern}s instances
|
||||||
|
* sorted according to their specificity (most specific patterns first).
|
||||||
|
* <p>For a given path pattern string, {@code PathPattern} variants
|
||||||
|
* can be generated and registered automatically, depending
|
||||||
|
* on the {@code useTrailingSlashMatch}, {@code useSuffixPatternMatch}
|
||||||
|
* and {@code fileExtensions} properties.
|
||||||
|
*
|
||||||
|
* @author Brian Clozel
|
||||||
|
* @since 5.0
|
||||||
|
*/
|
||||||
|
public class PathPatternRegistry {
|
||||||
|
|
||||||
|
private final PathPatternParser pathPatternParser;
|
||||||
|
|
||||||
|
private final HashSet<PathPattern> patterns;
|
||||||
|
|
||||||
|
private boolean useSuffixPatternMatch = false;
|
||||||
|
|
||||||
|
private boolean useTrailingSlashMatch = false;
|
||||||
|
|
||||||
|
private Set<String> fileExtensions = Collections.emptySet();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@code PathPatternRegistry} with defaults options for
|
||||||
|
* pattern variants generation.
|
||||||
|
* <p>By default, no pattern variant will be generated.
|
||||||
|
*/
|
||||||
|
public PathPatternRegistry() {
|
||||||
|
this.pathPatternParser = new PathPatternParser();
|
||||||
|
this.patterns = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to match to paths irrespective of the presence of a trailing slash.
|
||||||
|
*/
|
||||||
|
public boolean useSuffixPatternMatch() {
|
||||||
|
return useSuffixPatternMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use suffix pattern match (".*") when matching patterns to
|
||||||
|
* requests. If enabled a path pattern such as "/users" will also
|
||||||
|
* generate the following pattern variant: "/users.*".
|
||||||
|
* <p>By default this is set to {@code false}.
|
||||||
|
*/
|
||||||
|
public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
|
||||||
|
this.useSuffixPatternMatch = useSuffixPatternMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to generate path pattern variants with a trailing slash.
|
||||||
|
*/
|
||||||
|
public boolean useTrailingSlashMatch() {
|
||||||
|
return useTrailingSlashMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to match to paths irrespective of the presence of a trailing slash.
|
||||||
|
* If enabled a path pattern such as "/users" will also generate the
|
||||||
|
* following pattern variant: "/users/".
|
||||||
|
* <p>The default value is {@code false}.
|
||||||
|
*/
|
||||||
|
public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch) {
|
||||||
|
this.useTrailingSlashMatch = useTrailingSlashMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the set of file extensions to use for suffix pattern matching.
|
||||||
|
*/
|
||||||
|
public Set<String> getFileExtensions() {
|
||||||
|
return fileExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the set of file extensions to use for suffix pattern matching.
|
||||||
|
* For a given path "/users", each file extension will be used to
|
||||||
|
* generate a path pattern variant such as "json" -> "/users.json".
|
||||||
|
* <p>The default value is an empty {@code Set}
|
||||||
|
*/
|
||||||
|
public void setFileExtensions(Set<String> fileExtensions) {
|
||||||
|
this.fileExtensions = fileExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a (read-only) set of all patterns, sorted according to their specificity.
|
||||||
|
*/
|
||||||
|
public Set<PathPattern> getPatterns() {
|
||||||
|
return Collections.unmodifiableSet(this.patterns);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a {@code SortedSet} of {@code PathPattern}s matching the given {@code lookupPath}.
|
||||||
|
*
|
||||||
|
* <p>The returned set sorted with the most specific
|
||||||
|
* patterns first, according to the given {@code lookupPath}.
|
||||||
|
* @param lookupPath the URL lookup path to be matched against
|
||||||
|
*/
|
||||||
|
public SortedSet<PathPattern> findMatches(String lookupPath) {
|
||||||
|
return this.patterns.stream()
|
||||||
|
.filter(pattern -> pattern.matches(lookupPath))
|
||||||
|
.collect(Collectors.toCollection(() ->
|
||||||
|
new TreeSet<>(new PatternSetComparator(lookupPath))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the path pattern data using the internal {@link PathPatternParser}
|
||||||
|
* instance, producing a {@link PathPattern} object that can be used for fast matching
|
||||||
|
* against paths.
|
||||||
|
*
|
||||||
|
* @param pathPattern the input path pattern, e.g. /foo/{bar}
|
||||||
|
* @return a PathPattern for quickly matching paths against the specified path pattern
|
||||||
|
*/
|
||||||
|
public PathPattern parsePattern(String pathPattern) {
|
||||||
|
return this.pathPatternParser.parse(pathPattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a {@link PathPattern} instance to this registry
|
||||||
|
* @return true if this registry did not already contain the specified {@code PathPattern}
|
||||||
|
*/
|
||||||
|
public boolean add(PathPattern pathPattern) {
|
||||||
|
return this.patterns.add(pathPattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all {@link PathPattern}s instance to this registry
|
||||||
|
* @return true if this registry did not already contain at least one of the given {@code PathPattern}s
|
||||||
|
*/
|
||||||
|
public boolean addAll(Collection<PathPattern> pathPatterns) {
|
||||||
|
return this.patterns.addAll(pathPatterns);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the given {@link PathPattern} from this registry
|
||||||
|
* @return true if this registry contained the given {@code PathPattern}
|
||||||
|
*/
|
||||||
|
public boolean remove(PathPattern pathPattern) {
|
||||||
|
return this.patterns.remove(pathPattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all {@link PathPattern}s from this registry
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
this.patterns.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the given {@code rawPattern} and adds it to this registry,
|
||||||
|
* as well as pattern variants, depending on the given options and
|
||||||
|
* the nature of the input pattern.
|
||||||
|
* <p>The following set of patterns will be added:
|
||||||
|
* <ul>
|
||||||
|
* <li>the pattern given as input, e.g. "/foo/{bar}"
|
||||||
|
* <li>if {@link #useSuffixPatternMatch()}, variants for each given
|
||||||
|
* {@link #getFileExtensions()}, such as "/foo/{bar}.pdf" or a variant for all extensions,
|
||||||
|
* such as "/foo/{bar}.*"
|
||||||
|
* <li>if {@link #useTrailingSlashMatch()}, a variant such as "/foo/{bar}/"
|
||||||
|
* </ul>
|
||||||
|
* @param rawPattern raw path pattern to parse and register
|
||||||
|
* @return the list of {@link PathPattern} that were registered as a result
|
||||||
|
*/
|
||||||
|
public List<PathPattern> register(String rawPattern) {
|
||||||
|
List<PathPattern> newPatterns = new ArrayList<>();
|
||||||
|
PathPattern pattern = this.pathPatternParser.parse(rawPattern);
|
||||||
|
newPatterns.add(pattern);
|
||||||
|
if (StringUtils.hasLength(rawPattern) && !pattern.isCatchAll()) {
|
||||||
|
if (this.useSuffixPatternMatch) {
|
||||||
|
if (this.fileExtensions != null && !this.fileExtensions.isEmpty()) {
|
||||||
|
for (String extension : this.fileExtensions) {
|
||||||
|
newPatterns.add(this.pathPatternParser.parse(rawPattern + "." + extension));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newPatterns.add(this.pathPatternParser.parse(rawPattern + ".*"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.useTrailingSlashMatch && !rawPattern.endsWith("/")) {
|
||||||
|
newPatterns.add(this.pathPatternParser.parse(rawPattern + "/"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.patterns.addAll(newPatterns);
|
||||||
|
return newPatterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine the patterns contained in the current registry
|
||||||
|
* with the ones in the other, into a new {@code PathPatternRegistry} instance.
|
||||||
|
* <p>Given the current registry contains "/prefix" and the other contains
|
||||||
|
* "/foo" and "/bar/{item}", the combined result will be: a new registry
|
||||||
|
* containing "/prefix/foo" and "/prefix/bar/{item}".
|
||||||
|
* @param other other {@code PathPatternRegistry} to combine with
|
||||||
|
* @return a new instance of {@code PathPatternRegistry} that combines both
|
||||||
|
* @see PathPattern#combine(String)
|
||||||
|
*/
|
||||||
|
public PathPatternRegistry combine(PathPatternRegistry other) {
|
||||||
|
PathPatternRegistry result = new PathPatternRegistry();
|
||||||
|
result.setUseSuffixPatternMatch(this.useSuffixPatternMatch);
|
||||||
|
result.setUseTrailingSlashMatch(this.useTrailingSlashMatch);
|
||||||
|
result.setFileExtensions(this.fileExtensions);
|
||||||
|
if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
|
||||||
|
for (PathPattern pattern1 : this.patterns) {
|
||||||
|
for (PathPattern pattern2 : other.patterns) {
|
||||||
|
String combined = pattern1.combine(pattern2.getPatternString());
|
||||||
|
result.register(combined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!this.patterns.isEmpty()) {
|
||||||
|
result.patterns.addAll(this.patterns);
|
||||||
|
}
|
||||||
|
else if (!other.patterns.isEmpty()) {
|
||||||
|
result.patterns.addAll(other.patterns);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result.register("");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a full path, returns a {@link Comparator} suitable for sorting pattern
|
||||||
|
* registries in order of explicitness for that path.
|
||||||
|
* <p>The returned {@code Comparator} will
|
||||||
|
* {@linkplain java.util.Collections#sort(java.util.List, java.util.Comparator) sort}
|
||||||
|
* a list so that more specific patterns registries come before generic ones.
|
||||||
|
* @param path the full path to use for comparison
|
||||||
|
* @return a comparator capable of sorting patterns in order of explicitness
|
||||||
|
*/
|
||||||
|
public Comparator<PathPatternRegistry> getComparator(final String path) {
|
||||||
|
return (r1, r2) -> {
|
||||||
|
PatternSetComparator comparator = new PatternSetComparator(path);
|
||||||
|
Iterator<PathPattern> it1 = r1.patterns.stream()
|
||||||
|
.sorted(comparator).collect(Collectors.toList()).iterator();
|
||||||
|
Iterator<PathPattern> it2 = r2.patterns.stream()
|
||||||
|
.sorted(comparator).collect(Collectors.toList()).iterator();
|
||||||
|
while (it1.hasNext() && it2.hasNext()) {
|
||||||
|
int result = comparator.compare(it1.next(), it2.next());
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (it1.hasNext()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (it2.hasNext()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PatternSetComparator implements Comparator<PathPattern> {
|
||||||
|
|
||||||
|
private final String path;
|
||||||
|
|
||||||
|
public PatternSetComparator(String path) {
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(PathPattern o1, PathPattern o2) {
|
||||||
|
// Nulls get sorted to the end
|
||||||
|
if (o1 == null) {
|
||||||
|
return (o2 == null ? 0 : +1);
|
||||||
|
}
|
||||||
|
else if (o2 == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// exact matches get sorted first
|
||||||
|
if (o1.getPatternString().equals(path)) {
|
||||||
|
return (o2.getPatternString().equals(path)) ? 0 : -1;
|
||||||
|
}
|
||||||
|
else if (o2.getPatternString().equals(path)) {
|
||||||
|
return +1;
|
||||||
|
}
|
||||||
|
// compare pattern specificity
|
||||||
|
int result = o1.compareTo(o2);
|
||||||
|
// if equal specificity, sort using pattern string
|
||||||
|
if (result == 0) {
|
||||||
|
return o1.getPatternString().compareTo(o2.getPatternString());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,9 +25,11 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||||
|
import org.springframework.web.util.patterns.PathPattern;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link UrlBasedCorsConfigurationSource}.
|
* Unit tests for {@link UrlBasedCorsConfigurationSource}.
|
||||||
|
@ -60,7 +62,7 @@ public class UrlBasedCorsConfigurationSourceTests {
|
||||||
|
|
||||||
@Test(expected = UnsupportedOperationException.class)
|
@Test(expected = UnsupportedOperationException.class)
|
||||||
public void unmodifiableConfigurationsMap() {
|
public void unmodifiableConfigurationsMap() {
|
||||||
this.configSource.getCorsConfigurations().put("/**", new CorsConfiguration());
|
this.configSource.getCorsConfigurations().put(mock(PathPattern.class), new CorsConfiguration());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServerWebExchange createExchange(HttpMethod httpMethod, String url) {
|
private ServerWebExchange createExchange(HttpMethod httpMethod, String url) {
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
* 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.util.patterns;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link PathPatternRegistry}
|
||||||
|
* @author Brian Clozel
|
||||||
|
*/
|
||||||
|
public class PathPatternRegistryTests {
|
||||||
|
|
||||||
|
private PathPatternRegistry registry;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
this.registry = new PathPatternRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotRegisterInvalidPatterns() {
|
||||||
|
this.thrown.expect(PatternParseException.class);
|
||||||
|
this.thrown.expectMessage(Matchers.containsString("Expected close capture character after variable name"));
|
||||||
|
this.registry.register("/{invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotRegisterPatternVariants() {
|
||||||
|
List<PathPattern> patterns = this.registry.register("/foo/{bar}");
|
||||||
|
assertPathPatternListContains(patterns, "/foo/{bar}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRegisterTrailingSlashVariants() {
|
||||||
|
this.registry.setUseTrailingSlashMatch(true);
|
||||||
|
List<PathPattern> patterns = this.registry.register("/foo/{bar}");
|
||||||
|
assertPathPatternListContains(patterns, "/foo/{bar}", "/foo/{bar}/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRegisterSuffixVariants() {
|
||||||
|
this.registry.setUseSuffixPatternMatch(true);
|
||||||
|
List<PathPattern> patterns = this.registry.register("/foo/{bar}");
|
||||||
|
assertPathPatternListContains(patterns, "/foo/{bar}", "/foo/{bar}.*");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRegisterExtensionsVariants() {
|
||||||
|
Set<String> fileExtensions = new HashSet<>();
|
||||||
|
fileExtensions.add("json");
|
||||||
|
fileExtensions.add("xml");
|
||||||
|
this.registry.setUseSuffixPatternMatch(true);
|
||||||
|
this.registry.setFileExtensions(fileExtensions);
|
||||||
|
List<PathPattern> patterns = this.registry.register("/foo/{bar}");
|
||||||
|
assertPathPatternListContains(patterns, "/foo/{bar}", "/foo/{bar}.xml", "/foo/{bar}.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRegisterAllVariants() {
|
||||||
|
Set<String> fileExtensions = new HashSet<>();
|
||||||
|
fileExtensions.add("json");
|
||||||
|
fileExtensions.add("xml");
|
||||||
|
this.registry.setUseSuffixPatternMatch(true);
|
||||||
|
this.registry.setUseTrailingSlashMatch(true);
|
||||||
|
this.registry.setFileExtensions(fileExtensions);
|
||||||
|
List<PathPattern> patterns = this.registry.register("/foo/{bar}");
|
||||||
|
assertPathPatternListContains(patterns, "/foo/{bar}",
|
||||||
|
"/foo/{bar}.xml", "/foo/{bar}.json", "/foo/{bar}/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void combineEmptyRegistries() {
|
||||||
|
PathPatternRegistry result = this.registry.combine(new PathPatternRegistry());
|
||||||
|
assertPathPatternListContains(result.getPatterns(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void combineWithEmptyRegistry() {
|
||||||
|
this.registry.register("/foo");
|
||||||
|
PathPatternRegistry result = this.registry.combine(new PathPatternRegistry());
|
||||||
|
assertPathPatternListContains(result.getPatterns(), "/foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void combineRegistries() {
|
||||||
|
this.registry.register("/foo");
|
||||||
|
PathPatternRegistry other = new PathPatternRegistry();
|
||||||
|
other.register("/bar");
|
||||||
|
other.register("/baz");
|
||||||
|
PathPatternRegistry result = this.registry.combine(other);
|
||||||
|
assertPathPatternListContains(result.getPatterns(), "/foo/bar", "/foo/baz");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void registerPatternsWithSameSpecificity() {
|
||||||
|
PathPattern fooOne = this.registry.parsePattern("/fo?");
|
||||||
|
PathPattern fooTwo = this.registry.parsePattern("/f?o");
|
||||||
|
assertThat(fooOne.compareTo(fooTwo), is(0));
|
||||||
|
|
||||||
|
this.registry.add(fooOne);
|
||||||
|
this.registry.add(fooTwo);
|
||||||
|
Set<PathPattern> matches = this.registry.findMatches("/foo");
|
||||||
|
assertPathPatternListContains(matches, "/f?o", "/fo?");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findNoMatch() {
|
||||||
|
this.registry.register("/foo/{bar}");
|
||||||
|
assertThat(this.registry.findMatches("/other"), hasSize(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void orderMatchesBySpecificity() {
|
||||||
|
this.registry.register("/foo/{*baz}");
|
||||||
|
this.registry.register("/foo/bar/baz");
|
||||||
|
this.registry.register("/foo/bar/{baz}");
|
||||||
|
Set<PathPattern> matches = this.registry.findMatches("/foo/bar/baz");
|
||||||
|
assertPathPatternListContains(matches, "/foo/bar/baz", "/foo/bar/{baz}",
|
||||||
|
"/foo/{*baz}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void assertPathPatternListContains(Collection<PathPattern> parsedPatterns, String... pathPatterns) {
|
||||||
|
List<String> patternList = parsedPatterns.
|
||||||
|
stream().map(pattern -> pattern.getPatternString()).collect(Collectors.toList());
|
||||||
|
assertThat(patternList, Matchers.contains(pathPatterns));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -105,6 +105,7 @@ public class PathMatchConfigurer {
|
||||||
return this.pathHelper;
|
return this.pathHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: remove
|
||||||
protected PathMatcher getPathMatcher() {
|
protected PathMatcher getPathMatcher() {
|
||||||
return this.pathMatcher;
|
return this.pathMatcher;
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,9 +151,6 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
|
||||||
if (configurer.isUseTrailingSlashMatch() != null) {
|
if (configurer.isUseTrailingSlashMatch() != null) {
|
||||||
mapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
|
mapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
|
||||||
}
|
}
|
||||||
if (configurer.getPathMatcher() != null) {
|
|
||||||
mapping.setPathMatcher(configurer.getPathMatcher());
|
|
||||||
}
|
|
||||||
if (configurer.getPathHelper() != null) {
|
if (configurer.getPathHelper() != null) {
|
||||||
mapping.setPathHelper(configurer.getPathHelper());
|
mapping.setPathHelper(configurer.getPathHelper());
|
||||||
}
|
}
|
||||||
|
@ -246,9 +243,6 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
|
||||||
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
|
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
|
||||||
if (handlerMapping != null) {
|
if (handlerMapping != null) {
|
||||||
PathMatchConfigurer pathMatchConfigurer = getPathMatchConfigurer();
|
PathMatchConfigurer pathMatchConfigurer = getPathMatchConfigurer();
|
||||||
if (pathMatchConfigurer.getPathMatcher() != null) {
|
|
||||||
handlerMapping.setPathMatcher(pathMatchConfigurer.getPathMatcher());
|
|
||||||
}
|
|
||||||
if (pathMatchConfigurer.getPathHelper() != null) {
|
if (pathMatchConfigurer.getPathHelper() != null) {
|
||||||
handlerMapping.setPathHelper(pathMatchConfigurer.getPathHelper());
|
handlerMapping.setPathHelper(pathMatchConfigurer.getPathHelper());
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ import reactor.core.publisher.Mono;
|
||||||
import org.springframework.context.support.ApplicationObjectSupport;
|
import org.springframework.context.support.ApplicationObjectSupport;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.PathMatcher;
|
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.cors.reactive.CorsConfigurationSource;
|
import org.springframework.web.cors.reactive.CorsConfigurationSource;
|
||||||
import org.springframework.web.cors.reactive.CorsProcessor;
|
import org.springframework.web.cors.reactive.CorsProcessor;
|
||||||
|
@ -34,7 +33,8 @@ import org.springframework.web.reactive.HandlerMapping;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.WebHandler;
|
import org.springframework.web.server.WebHandler;
|
||||||
import org.springframework.web.server.support.HttpRequestPathHelper;
|
import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||||
import org.springframework.web.util.ParsingPathMatcher;
|
import org.springframework.web.util.patterns.PathPattern;
|
||||||
|
import org.springframework.web.util.patterns.PathPatternRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for {@link org.springframework.web.reactive.HandlerMapping}
|
* Abstract base class for {@link org.springframework.web.reactive.HandlerMapping}
|
||||||
|
@ -53,12 +53,11 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im
|
||||||
|
|
||||||
private HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
|
private HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
|
||||||
|
|
||||||
private PathMatcher pathMatcher = new ParsingPathMatcher();
|
|
||||||
|
|
||||||
private final UrlBasedCorsConfigurationSource globalCorsConfigSource = new UrlBasedCorsConfigurationSource();
|
private final UrlBasedCorsConfigurationSource globalCorsConfigSource = new UrlBasedCorsConfigurationSource();
|
||||||
|
|
||||||
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
|
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
|
||||||
|
|
||||||
|
protected PathPatternRegistry patternRegistry = new PathPatternRegistry();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify the order value for this HandlerMapping bean.
|
* Specify the order value for this HandlerMapping bean.
|
||||||
|
@ -102,22 +101,19 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the PathMatcher implementation to use for matching URL paths
|
* Return the {@link PathPatternRegistry} instance to use for parsing
|
||||||
* against registered URL patterns. Default is AntPathMatcher.
|
* and matching path patterns.
|
||||||
* @see org.springframework.util.AntPathMatcher
|
|
||||||
*/
|
*/
|
||||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
public PathPatternRegistry getPatternRegistry() {
|
||||||
Assert.notNull(pathMatcher, "PathMatcher must not be null");
|
return patternRegistry;
|
||||||
this.pathMatcher = pathMatcher;
|
|
||||||
this.globalCorsConfigSource.setPathMatcher(pathMatcher);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the PathMatcher implementation to use for matching URL paths
|
* Set the {@link PathPatternRegistry} instance to use for parsing
|
||||||
* against registered URL patterns.
|
* and matching path patterns.
|
||||||
*/
|
*/
|
||||||
public PathMatcher getPathMatcher() {
|
public void setPatternRegistry(PathPatternRegistry patternRegistry) {
|
||||||
return this.pathMatcher;
|
this.patternRegistry = patternRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -126,13 +122,16 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im
|
||||||
* configuration if any.
|
* configuration if any.
|
||||||
*/
|
*/
|
||||||
public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
|
public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
|
||||||
this.globalCorsConfigSource.setCorsConfigurations(corsConfigurations);
|
for(String patternString : corsConfigurations.keySet()) {
|
||||||
|
this.globalCorsConfigSource
|
||||||
|
.registerCorsConfiguration(patternString, corsConfigurations.get(patternString));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the "global" CORS configuration.
|
* Return the "global" CORS configuration.
|
||||||
*/
|
*/
|
||||||
public Map<String, CorsConfiguration> getCorsConfigurations() {
|
public Map<PathPattern, CorsConfiguration> getCorsConfigurations() {
|
||||||
return this.globalCorsConfigSource.getCorsConfigurations();
|
return this.globalCorsConfigSource.getCorsConfigurations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,32 +16,32 @@
|
||||||
|
|
||||||
package org.springframework.web.reactive.handler;
|
package org.springframework.web.reactive.handler;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import org.springframework.web.util.patterns.PathPattern;
|
||||||
|
import org.springframework.web.util.patterns.PathPatternParser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for URL-mapped
|
* Abstract base class for URL-mapped
|
||||||
* {@link org.springframework.web.reactive.HandlerMapping} implementations.
|
* {@link org.springframework.web.reactive.HandlerMapping} implementations.
|
||||||
*
|
*
|
||||||
* <p>Supports direct matches, e.g. a registered "/test" matches "/test", and
|
* <p>Supports direct matches, e.g. a registered "/test" matches "/test", and
|
||||||
* various Ant-style pattern matches, e.g. a registered "/t*" pattern matches
|
* various path pattern matches, e.g. a registered "/t*" pattern matches
|
||||||
* both "/test" and "/team", "/test/*" matches all paths under "/test",
|
* both "/test" and "/team", "/test/*" matches all paths under "/test",
|
||||||
* "/test/**" matches all paths below "/test". For details, see the
|
* "/test/**" matches all paths below "/test". For details, see the
|
||||||
* {@link org.springframework.util.AntPathMatcher AntPathMatcher} javadoc.
|
* {@link PathPatternParser} javadoc.
|
||||||
*
|
*
|
||||||
* <p>Will search all path patterns to find the most exact match for the
|
* <p>Will search all path patterns to find the most specific match for the
|
||||||
* current request path. The most exact match is defined as the longest
|
* current request path. The most specific pattern is defined as the longest
|
||||||
* path pattern that matches the current request path.
|
* path pattern with the fewest captured variables and wildcards.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
@ -49,12 +49,9 @@ import org.springframework.web.server.ServerWebExchange;
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||||
|
|
||||||
private boolean useTrailingSlashMatch = false;
|
|
||||||
|
|
||||||
private boolean lazyInitHandlers = false;
|
private boolean lazyInitHandlers = false;
|
||||||
|
|
||||||
private final Map<String, Object> handlerMap = new LinkedHashMap<>();
|
private final Map<PathPattern, Object> handlerMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to match to URLs irrespective of the presence of a trailing slash.
|
* Whether to match to URLs irrespective of the presence of a trailing slash.
|
||||||
|
@ -62,14 +59,14 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||||
* <p>The default value is {@code false}.
|
* <p>The default value is {@code false}.
|
||||||
*/
|
*/
|
||||||
public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch) {
|
public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch) {
|
||||||
this.useTrailingSlashMatch = useTrailingSlashMatch;
|
this.patternRegistry.setUseTrailingSlashMatch(useTrailingSlashMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to match to URLs irrespective of the presence of a trailing slash.
|
* Whether to match to URLs irrespective of the presence of a trailing slash.
|
||||||
*/
|
*/
|
||||||
public boolean useTrailingSlashMatch() {
|
public boolean useTrailingSlashMatch() {
|
||||||
return this.useTrailingSlashMatch;
|
return this.patternRegistry.useTrailingSlashMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,7 +88,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||||
* as key and the handler object (or handler bean name in case of a lazy-init handler)
|
* as key and the handler object (or handler bean name in case of a lazy-init handler)
|
||||||
* as value.
|
* as value.
|
||||||
*/
|
*/
|
||||||
public final Map<String, Object> getHandlerMap() {
|
public final Map<PathPattern, Object> getHandlerMap() {
|
||||||
return Collections.unmodifiableMap(this.handlerMap);
|
return Collections.unmodifiableMap(this.handlerMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +119,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||||
*
|
*
|
||||||
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
|
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
|
||||||
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
|
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
|
||||||
* both "/test" and "/team". For details, see the AntPathMatcher class.
|
* both "/test" and "/team". For details, see the PathPattern class.
|
||||||
*
|
*
|
||||||
* <p>Looks for the most exact pattern, where most exact is defined as
|
* <p>Looks for the most exact pattern, where most exact is defined as
|
||||||
* the longest path pattern.
|
* the longest path pattern.
|
||||||
|
@ -133,54 +130,21 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||||
* @see org.springframework.util.AntPathMatcher
|
* @see org.springframework.util.AntPathMatcher
|
||||||
*/
|
*/
|
||||||
protected Object lookupHandler(String urlPath, ServerWebExchange exchange) throws Exception {
|
protected Object lookupHandler(String urlPath, ServerWebExchange exchange) throws Exception {
|
||||||
// Direct match?
|
SortedSet<PathPattern> matches = this.patternRegistry.findMatches(urlPath);
|
||||||
Object handler = this.handlerMap.get(urlPath);
|
|
||||||
if (handler != null) {
|
|
||||||
return handleMatch(handler, urlPath, urlPath, exchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pattern match?
|
|
||||||
List<String> matches = new ArrayList<>();
|
|
||||||
for (String pattern : this.handlerMap.keySet()) {
|
|
||||||
if (getPathMatcher().match(pattern, urlPath)) {
|
|
||||||
matches.add(pattern);
|
|
||||||
}
|
|
||||||
else if (useTrailingSlashMatch()) {
|
|
||||||
if (!pattern.endsWith("/") && getPathMatcher().match(pattern + "/", urlPath)) {
|
|
||||||
matches.add(pattern +"/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String bestMatch = null;
|
|
||||||
Comparator<String> comparator = getPathMatcher().getPatternComparator(urlPath);
|
|
||||||
if (!matches.isEmpty()) {
|
if (!matches.isEmpty()) {
|
||||||
Collections.sort(matches, comparator);
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Matching patterns for request [" + urlPath + "] are " + matches);
|
logger.debug("Matching patterns for request [" + urlPath + "] are " + matches);
|
||||||
}
|
}
|
||||||
bestMatch = matches.get(0);
|
PathPattern bestMatch = matches.first();
|
||||||
}
|
String pathWithinMapping = bestMatch.extractPathWithinPattern(urlPath);
|
||||||
if (bestMatch != null) {
|
return handleMatch(this.handlerMap.get(bestMatch), bestMatch, pathWithinMapping, exchange);
|
||||||
handler = this.handlerMap.get(bestMatch);
|
|
||||||
if (handler == null) {
|
|
||||||
if (bestMatch.endsWith("/")) {
|
|
||||||
handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
|
|
||||||
}
|
|
||||||
if (handler == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Could not find handler for best pattern match [" + bestMatch + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
|
|
||||||
return handleMatch(handler, bestMatch, pathWithinMapping, exchange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No handler found...
|
// No handler found...
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object handleMatch(Object handler, String bestMatch, String pathWithinMapping,
|
private Object handleMatch(Object handler, PathPattern bestMatch, String pathWithinMapping,
|
||||||
ServerWebExchange exchange) throws Exception {
|
ServerWebExchange exchange) throws Exception {
|
||||||
|
|
||||||
// Bean name or resolved handler?
|
// Bean name or resolved handler?
|
||||||
|
@ -243,8 +207,8 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||||
resolvedHandler = getApplicationContext().getBean(handlerName);
|
resolvedHandler = getApplicationContext().getBean(handlerName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (PathPattern newPattern : this.patternRegistry.register(urlPath)) {
|
||||||
Object mappedHandler = this.handlerMap.get(urlPath);
|
Object mappedHandler = this.handlerMap.get(newPattern);
|
||||||
if (mappedHandler != null) {
|
if (mappedHandler != null) {
|
||||||
if (mappedHandler != resolvedHandler) {
|
if (mappedHandler != resolvedHandler) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
|
@ -253,12 +217,14 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.handlerMap.put(urlPath, resolvedHandler);
|
this.handlerMap.put(newPattern, resolvedHandler);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
if (logger.isInfoEnabled()) {
|
if (logger.isInfoEnabled()) {
|
||||||
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
|
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private String getHandlerDescription(Object handler) {
|
private String getHandlerDescription(Object handler) {
|
||||||
return "handler " + (handler instanceof String ? "'" + handler + "'" : "of type [" + handler.getClass() + "]");
|
return "handler " + (handler instanceof String ? "'" + handler + "'" : "of type [" + handler.getClass() + "]");
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,11 +17,10 @@
|
||||||
package org.springframework.web.reactive.resource;
|
package org.springframework.web.reactive.resource;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
@ -33,11 +32,11 @@ import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.context.event.ContextRefreshedEvent;
|
import org.springframework.context.event.ContextRefreshedEvent;
|
||||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
import org.springframework.util.PathMatcher;
|
|
||||||
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
|
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.support.HttpRequestPathHelper;
|
import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||||
import org.springframework.web.util.ParsingPathMatcher;
|
import org.springframework.web.util.patterns.PathPattern;
|
||||||
|
import org.springframework.web.util.patterns.PathPatternRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A central component to use to obtain the public URL path that clients should
|
* A central component to use to obtain the public URL path that clients should
|
||||||
|
@ -56,9 +55,9 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||||
|
|
||||||
private HttpRequestPathHelper urlPathHelper = new HttpRequestPathHelper();
|
private HttpRequestPathHelper urlPathHelper = new HttpRequestPathHelper();
|
||||||
|
|
||||||
private PathMatcher pathMatcher = new ParsingPathMatcher();
|
private final PathPatternRegistry patternRegistry = new PathPatternRegistry();
|
||||||
|
|
||||||
private final Map<String, ResourceWebHandler> handlerMap = new LinkedHashMap<>();
|
private final Map<PathPattern, ResourceWebHandler> handlerMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
private boolean autodetect = true;
|
private boolean autodetect = true;
|
||||||
|
|
||||||
|
@ -79,29 +78,16 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||||
return this.urlPathHelper;
|
return this.urlPathHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure a {@code PathMatcher} to use when comparing target lookup path
|
|
||||||
* against resource mappings.
|
|
||||||
*/
|
|
||||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
|
||||||
this.pathMatcher = pathMatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the configured {@code PathMatcher}.
|
|
||||||
*/
|
|
||||||
public PathMatcher getPathMatcher() {
|
|
||||||
return this.pathMatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manually configure the resource mappings.
|
* Manually configure the resource mappings.
|
||||||
* <p><strong>Note:</strong> by default resource mappings are auto-detected
|
* <p><strong>Note:</strong> by default resource mappings are auto-detected
|
||||||
* from the Spring {@code ApplicationContext}. However if this property is
|
* from the Spring {@code ApplicationContext}. However if this property is
|
||||||
* used, the auto-detection is turned off.
|
* used, the auto-detection is turned off.
|
||||||
*/
|
*/
|
||||||
public void setHandlerMap(Map<String, ResourceWebHandler> handlerMap) {
|
public void setHandlerMap(Map<PathPattern, ResourceWebHandler> handlerMap) {
|
||||||
if (handlerMap != null) {
|
if (handlerMap != null) {
|
||||||
|
this.patternRegistry.clear();
|
||||||
|
this.patternRegistry.addAll(handlerMap.keySet());
|
||||||
this.handlerMap.clear();
|
this.handlerMap.clear();
|
||||||
this.handlerMap.putAll(handlerMap);
|
this.handlerMap.putAll(handlerMap);
|
||||||
this.autodetect = false;
|
this.autodetect = false;
|
||||||
|
@ -112,7 +98,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||||
* Return the resource mappings, either manually configured or auto-detected
|
* Return the resource mappings, either manually configured or auto-detected
|
||||||
* when the Spring {@code ApplicationContext} is refreshed.
|
* when the Spring {@code ApplicationContext} is refreshed.
|
||||||
*/
|
*/
|
||||||
public Map<String, ResourceWebHandler> getHandlerMap() {
|
public Map<PathPattern, ResourceWebHandler> getHandlerMap() {
|
||||||
return this.handlerMap;
|
return this.handlerMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,6 +113,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||||
@Override
|
@Override
|
||||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||||
if (isAutodetect()) {
|
if (isAutodetect()) {
|
||||||
|
this.patternRegistry.clear();
|
||||||
this.handlerMap.clear();
|
this.handlerMap.clear();
|
||||||
detectResourceHandlers(event.getApplicationContext());
|
detectResourceHandlers(event.getApplicationContext());
|
||||||
if (this.handlerMap.isEmpty() && logger.isDebugEnabled()) {
|
if (this.handlerMap.isEmpty() && logger.isDebugEnabled()) {
|
||||||
|
@ -147,7 +134,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||||
AnnotationAwareOrderComparator.sort(handlerMappings);
|
AnnotationAwareOrderComparator.sort(handlerMappings);
|
||||||
|
|
||||||
for (SimpleUrlHandlerMapping hm : handlerMappings) {
|
for (SimpleUrlHandlerMapping hm : handlerMappings) {
|
||||||
for (String pattern : hm.getHandlerMap().keySet()) {
|
for (PathPattern pattern : hm.getHandlerMap().keySet()) {
|
||||||
Object handler = hm.getHandlerMap().get(pattern);
|
Object handler = hm.getHandlerMap().get(pattern);
|
||||||
if (handler instanceof ResourceWebHandler) {
|
if (handler instanceof ResourceWebHandler) {
|
||||||
ResourceWebHandler resourceHandler = (ResourceWebHandler) handler;
|
ResourceWebHandler resourceHandler = (ResourceWebHandler) handler;
|
||||||
|
@ -156,6 +143,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||||
"locations=" + resourceHandler.getLocations() + ", " +
|
"locations=" + resourceHandler.getLocations() + ", " +
|
||||||
"resolvers=" + resourceHandler.getResourceResolvers());
|
"resolvers=" + resourceHandler.getResourceResolvers());
|
||||||
}
|
}
|
||||||
|
this.patternRegistry.add(pattern);
|
||||||
this.handlerMap.put(pattern, resourceHandler);
|
this.handlerMap.put(pattern, resourceHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,26 +206,18 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||||
logger.trace("Getting resource URL for lookup path \"" + lookupPath + "\"");
|
logger.trace("Getting resource URL for lookup path \"" + lookupPath + "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> matchingPatterns = new ArrayList<>();
|
SortedSet<PathPattern> matches = this.patternRegistry.findMatches(lookupPath);
|
||||||
for (String pattern : this.handlerMap.keySet()) {
|
if (matches.isEmpty()) {
|
||||||
if (getPathMatcher().match(pattern, lookupPath)) {
|
|
||||||
matchingPatterns.add(pattern);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchingPatterns.isEmpty()) {
|
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath);
|
return Flux.fromIterable(matches)
|
||||||
Collections.sort(matchingPatterns, patternComparator);
|
|
||||||
|
|
||||||
return Flux.fromIterable(matchingPatterns)
|
|
||||||
.concatMap(pattern -> {
|
.concatMap(pattern -> {
|
||||||
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(pattern, lookupPath);
|
String pathWithinMapping = pattern.extractPathWithinPattern(lookupPath);
|
||||||
String pathMapping = lookupPath.substring(0, lookupPath.indexOf(pathWithinMapping));
|
String pathMapping = lookupPath.substring(0, lookupPath.indexOf(pathWithinMapping));
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Invoking ResourceResolverChain for URL pattern \"" + pattern + "\"");
|
logger.trace("Invoking ResourceResolverChain for URL pattern \""
|
||||||
|
+ pattern.getPatternString() + "\"");
|
||||||
}
|
}
|
||||||
ResourceWebHandler handler = this.handlerMap.get(pattern);
|
ResourceWebHandler handler = this.handlerMap.get(pattern);
|
||||||
ResourceResolverChain chain = new DefaultResourceResolverChain(handler.getResourceResolvers());
|
ResourceResolverChain chain = new DefaultResourceResolverChain(handler.getResourceResolvers());
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,22 +16,21 @@
|
||||||
|
|
||||||
package org.springframework.web.reactive.result.condition;
|
package org.springframework.web.reactive.result.condition;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.util.PathMatcher;
|
import org.springframework.util.PathMatcher;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.support.HttpRequestPathHelper;
|
import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||||
import org.springframework.web.util.ParsingPathMatcher;
|
import org.springframework.web.util.patterns.PathPattern;
|
||||||
|
import org.springframework.web.util.patterns.PathPatternRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A logical disjunction (' || ') request condition that matches a request
|
* A logical disjunction (' || ') request condition that matches a request
|
||||||
|
@ -42,26 +41,17 @@ import org.springframework.web.util.ParsingPathMatcher;
|
||||||
*/
|
*/
|
||||||
public final class PatternsRequestCondition extends AbstractRequestCondition<PatternsRequestCondition> {
|
public final class PatternsRequestCondition extends AbstractRequestCondition<PatternsRequestCondition> {
|
||||||
|
|
||||||
private final Set<String> patterns;
|
private final PathPatternRegistry patternRegistry;
|
||||||
|
|
||||||
private final HttpRequestPathHelper pathHelper;
|
private final HttpRequestPathHelper pathHelper;
|
||||||
|
|
||||||
private final PathMatcher pathMatcher;
|
|
||||||
|
|
||||||
private final boolean useSuffixPatternMatch;
|
|
||||||
|
|
||||||
private final boolean useTrailingSlashMatch;
|
|
||||||
|
|
||||||
private final Set<String> fileExtensions = new HashSet<>();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance with the given URL patterns.
|
* Creates a new instance with the given URL patterns.
|
||||||
* Each pattern that is not empty and does not start with "/" is prepended with "/".
|
* Each pattern that is not empty and does not start with "/" is prepended with "/".
|
||||||
* @param patterns 0 or more URL patterns; if 0 the condition will match to every request.
|
* @param patterns 0 or more URL patterns; if 0 the condition will match to every request.
|
||||||
*/
|
*/
|
||||||
public PatternsRequestCondition(String... patterns) {
|
public PatternsRequestCondition(String... patterns) {
|
||||||
this(asList(patterns), null, null, true, true, null);
|
this(patterns, null, false, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,66 +59,58 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
||||||
* Each pattern that is not empty and does not start with "/" is pre-pended with "/".
|
* 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 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 pathHelper to determine the lookup path for a request
|
||||||
* @param pathMatcher for pattern path matching
|
|
||||||
* @param useSuffixPatternMatch whether to enable matching by suffix (".*")
|
* @param useSuffixPatternMatch whether to enable matching by suffix (".*")
|
||||||
* @param useTrailingSlashMatch whether to match irrespective of a trailing slash
|
* @param useTrailingSlashMatch whether to match irrespective of a trailing slash
|
||||||
* @param extensions file extensions to consider for path matching
|
* @param extensions file extensions to consider for path matching
|
||||||
*/
|
*/
|
||||||
public PatternsRequestCondition(String[] patterns, HttpRequestPathHelper pathHelper,
|
public PatternsRequestCondition(String[] patterns, HttpRequestPathHelper pathHelper,
|
||||||
PathMatcher pathMatcher, boolean useSuffixPatternMatch, boolean useTrailingSlashMatch,
|
boolean useSuffixPatternMatch, boolean useTrailingSlashMatch, Set<String> extensions) {
|
||||||
Set<String> extensions) {
|
|
||||||
|
|
||||||
this(asList(patterns), pathHelper, pathMatcher, useSuffixPatternMatch, useTrailingSlashMatch, extensions);
|
this(createPatternSet(patterns, useSuffixPatternMatch, useTrailingSlashMatch, extensions),
|
||||||
|
(pathHelper != null ? pathHelper : new HttpRequestPathHelper()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static PathPatternRegistry createPatternSet(String[] patterns,boolean useSuffixPatternMatch,
|
||||||
* Private constructor accepting a collection of patterns.
|
boolean useTrailingSlashMatch, Set<String> extensions) {
|
||||||
*/
|
Set<String> fixedFileExtensions = (extensions != null) ? extensions.stream()
|
||||||
private PatternsRequestCondition(Collection<String> patterns, HttpRequestPathHelper pathHelper,
|
.map(ext -> (ext.charAt(0) != '.') ? "." + ext : ext)
|
||||||
PathMatcher pathMatcher, boolean useSuffixPatternMatch, boolean useTrailingSlashMatch,
|
.collect(Collectors.toSet()) : Collections.emptySet();
|
||||||
Set<String> fileExtensions) {
|
PathPatternRegistry patternSet = new PathPatternRegistry();
|
||||||
|
patternSet.setUseSuffixPatternMatch(useSuffixPatternMatch);
|
||||||
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
|
patternSet.setUseTrailingSlashMatch(useTrailingSlashMatch);
|
||||||
this.pathHelper = (pathHelper != null ? pathHelper : new HttpRequestPathHelper());
|
patternSet.setFileExtensions(extensions);
|
||||||
this.pathMatcher = (pathMatcher != null ? pathMatcher : new ParsingPathMatcher());
|
if(patterns != null) {
|
||||||
this.useSuffixPatternMatch = useSuffixPatternMatch;
|
Arrays.asList(patterns).stream()
|
||||||
this.useTrailingSlashMatch = useTrailingSlashMatch;
|
.map(prependLeadingSlash())
|
||||||
if (fileExtensions != null) {
|
.forEach(p -> patternSet.register(p));
|
||||||
for (String fileExtension : fileExtensions) {
|
|
||||||
if (fileExtension.charAt(0) != '.') {
|
|
||||||
fileExtension = "." + fileExtension;
|
|
||||||
}
|
|
||||||
this.fileExtensions.add(fileExtension);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return patternSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Function<String, String> prependLeadingSlash() {
|
||||||
private static List<String> asList(String... patterns) {
|
return pattern -> {
|
||||||
return (patterns != null ? Arrays.asList(patterns) : Collections.emptyList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Set<String> prependLeadingSlash(Collection<String> patterns) {
|
|
||||||
if (patterns == null) {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
Set<String> result = new LinkedHashSet<>(patterns.size());
|
|
||||||
for (String pattern : patterns) {
|
|
||||||
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
|
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
|
||||||
pattern = "/" + pattern;
|
return "/" + pattern;
|
||||||
}
|
}
|
||||||
result.add(pattern);
|
else {
|
||||||
|
return pattern;
|
||||||
}
|
}
|
||||||
return result;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getPatterns() {
|
private PatternsRequestCondition(PathPatternRegistry patternRegistry, HttpRequestPathHelper pathHelper) {
|
||||||
return this.patterns;
|
this.patternRegistry = patternRegistry;
|
||||||
|
this.pathHelper = pathHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Set<PathPattern> getPatterns() {
|
||||||
|
return this.patternRegistry.getPatterns();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Collection<String> getContent() {
|
protected Collection<PathPattern> getContent() {
|
||||||
return this.patterns;
|
return this.patternRegistry.getPatterns();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -148,25 +130,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public PatternsRequestCondition combine(PatternsRequestCondition other) {
|
public PatternsRequestCondition combine(PatternsRequestCondition other) {
|
||||||
Set<String> result = new LinkedHashSet<>();
|
return new PatternsRequestCondition(this.patternRegistry.combine(other.patternRegistry), this.pathHelper);
|
||||||
if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
|
|
||||||
for (String pattern1 : this.patterns) {
|
|
||||||
for (String pattern2 : other.patterns) {
|
|
||||||
result.add(this.pathMatcher.combine(pattern1, pattern2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!this.patterns.isEmpty()) {
|
|
||||||
result.addAll(this.patterns);
|
|
||||||
}
|
|
||||||
else if (!other.patterns.isEmpty()) {
|
|
||||||
result.addAll(other.patterns);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
result.add("");
|
|
||||||
}
|
|
||||||
return new PatternsRequestCondition(result, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch,
|
|
||||||
this.useTrailingSlashMatch, this.fileExtensions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -187,16 +151,19 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public PatternsRequestCondition getMatchingCondition(ServerWebExchange exchange) {
|
public PatternsRequestCondition getMatchingCondition(ServerWebExchange exchange) {
|
||||||
if (this.patterns.isEmpty()) {
|
if (this.patternRegistry.getPatterns().isEmpty()) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
String lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
|
String lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
|
||||||
List<String> matches = getMatchingPatterns(lookupPath);
|
SortedSet<PathPattern> matches = getMatchingPatterns(lookupPath);
|
||||||
|
|
||||||
return matches.isEmpty() ? null :
|
if(!matches.isEmpty()) {
|
||||||
new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch,
|
PathPatternRegistry registry = new PathPatternRegistry();
|
||||||
this.useTrailingSlashMatch, this.fileExtensions);
|
registry.addAll(matches);
|
||||||
|
return new PatternsRequestCondition(registry, this.pathHelper);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -206,54 +173,16 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
||||||
* This method is provided as an alternative to be used if no request is available
|
* This method is provided as an alternative to be used if no request is available
|
||||||
* (e.g. introspection, tooling, etc).
|
* (e.g. introspection, tooling, etc).
|
||||||
* @param lookupPath the lookup path to match to existing patterns
|
* @param lookupPath the lookup path to match to existing patterns
|
||||||
* @return a collection of matching patterns sorted with the closest match at the top
|
* @return a sorted set of matching patterns sorted with the closest match first
|
||||||
*/
|
*/
|
||||||
public List<String> getMatchingPatterns(String lookupPath) {
|
public SortedSet<PathPattern> getMatchingPatterns(String lookupPath) {
|
||||||
List<String> matches = new ArrayList<>();
|
return this.patternRegistry.findMatches(lookupPath);
|
||||||
for (String pattern : this.patterns) {
|
|
||||||
String match = getMatchingPattern(pattern, lookupPath);
|
|
||||||
if (match != null) {
|
|
||||||
matches.add(match);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath));
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getMatchingPattern(String pattern, String lookupPath) {
|
|
||||||
if (pattern.equals(lookupPath)) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
boolean hasSuffix = pattern.indexOf('.') != -1;
|
|
||||||
if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
|
|
||||||
return pattern + ".*";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.pathMatcher.match(pattern, lookupPath)) {
|
|
||||||
return pattern;
|
|
||||||
}
|
|
||||||
if (this.useTrailingSlashMatch) {
|
|
||||||
if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
|
|
||||||
return pattern +"/";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare the two conditions based on the URL patterns they contain.
|
* Compare the two conditions based on the URL patterns they contain.
|
||||||
* Patterns are compared one at a time, from top to bottom via
|
* Patterns are compared one at a time, from top to bottom via
|
||||||
* {@link PathMatcher#getPatternComparator(String)}. If all compared
|
* {@link PathPatternRegistry#getComparator(String)}. If all compared
|
||||||
* patterns match equally, but one instance has more patterns, it is
|
* patterns match equally, but one instance has more patterns, it is
|
||||||
* considered a closer match.
|
* considered a closer match.
|
||||||
* <p>It is assumed that both instances have been obtained via
|
* <p>It is assumed that both instances have been obtained via
|
||||||
|
@ -264,24 +193,8 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(PatternsRequestCondition other, ServerWebExchange exchange) {
|
public int compareTo(PatternsRequestCondition other, ServerWebExchange exchange) {
|
||||||
String lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
|
String lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
|
||||||
Comparator<String> patternComparator = this.pathMatcher.getPatternComparator(lookupPath);
|
Comparator<PathPatternRegistry> comparator = this.patternRegistry.getComparator(lookupPath);
|
||||||
Iterator<String> iterator = this.patterns.iterator();
|
return comparator.compare(this.patternRegistry, other.patternRegistry);
|
||||||
Iterator<String> iteratorOther = other.patterns.iterator();
|
|
||||||
while (iterator.hasNext() && iteratorOther.hasNext()) {
|
|
||||||
int result = patternComparator.compare(iterator.next(), iteratorOther.next());
|
|
||||||
if (result != 0) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (iterator.hasNext()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (iteratorOther.hasNext()) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@ -44,6 +45,7 @@ import org.springframework.web.method.HandlerMethod;
|
||||||
import org.springframework.web.reactive.HandlerMapping;
|
import org.springframework.web.reactive.HandlerMapping;
|
||||||
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
|
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import org.springframework.web.util.patterns.PathPattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for {@link HandlerMapping} implementations that define
|
* Abstract base class for {@link HandlerMapping} implementations that define
|
||||||
|
@ -88,6 +90,9 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
ALLOW_CORS_CONFIG.setAllowCredentials(true);
|
ALLOW_CORS_CONFIG.setAllowCredentials(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final MultiValueMap<PathPattern, T> mappingLookup = new LinkedMultiValueMap<>();
|
||||||
|
|
||||||
|
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
private final MappingRegistry mappingRegistry = new MappingRegistry();
|
private final MappingRegistry mappingRegistry = new MappingRegistry();
|
||||||
|
|
||||||
|
@ -98,12 +103,12 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
* Return a (read-only) map with all mappings and HandlerMethod's.
|
* Return a (read-only) map with all mappings and HandlerMethod's.
|
||||||
*/
|
*/
|
||||||
public Map<T, HandlerMethod> getHandlerMethods() {
|
public Map<T, HandlerMethod> getHandlerMethods() {
|
||||||
this.mappingRegistry.acquireReadLock();
|
this.readWriteLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
return Collections.unmodifiableMap(this.mappingRegistry.getMappings());
|
return Collections.unmodifiableMap(this.mappingRegistry.getMappings());
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
this.mappingRegistry.releaseReadLock();
|
this.readWriteLock.readLock().unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,8 +127,19 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
* @param method the method
|
* @param method the method
|
||||||
*/
|
*/
|
||||||
public void registerMapping(T mapping, Object handler, Method method) {
|
public void registerMapping(T mapping, Object handler, Method method) {
|
||||||
|
this.readWriteLock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
Set<PathPattern> patterns = getMappingPathPatterns(mapping);
|
||||||
|
getPatternRegistry().addAll(patterns);
|
||||||
|
patterns.forEach(pathPattern -> {
|
||||||
|
this.mappingLookup.add(pathPattern, mapping);
|
||||||
|
});
|
||||||
this.mappingRegistry.register(mapping, handler, method);
|
this.mappingRegistry.register(mapping, handler, method);
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
this.readWriteLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Un-register the given mapping.
|
* Un-register the given mapping.
|
||||||
|
@ -131,8 +147,16 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
* @param mapping the mapping to unregister
|
* @param mapping the mapping to unregister
|
||||||
*/
|
*/
|
||||||
public void unregisterMapping(T mapping) {
|
public void unregisterMapping(T mapping) {
|
||||||
|
this.readWriteLock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
getMappingPathPatterns(mapping)
|
||||||
|
.forEach(pathPattern -> getPatternRegistry().remove(pathPattern));
|
||||||
this.mappingRegistry.unregister(mapping);
|
this.mappingRegistry.unregister(mapping);
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
this.readWriteLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Handler method detection
|
// Handler method detection
|
||||||
|
@ -195,23 +219,10 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
for (Map.Entry<Method, T> entry : methods.entrySet()) {
|
for (Map.Entry<Method, T> entry : methods.entrySet()) {
|
||||||
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
|
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
|
||||||
T mapping = entry.getValue();
|
T mapping = entry.getValue();
|
||||||
registerHandlerMethod(handler, invocableMethod, mapping);
|
registerMapping(mapping, handler, invocableMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a handler method and its unique mapping. Invoked at startup for
|
|
||||||
* each detected handler method.
|
|
||||||
* @param handler the bean name of the handler or the handler instance
|
|
||||||
* @param method the method to register
|
|
||||||
* @param mapping the mapping conditions associated with the handler method
|
|
||||||
* @throws IllegalStateException if another method was already registered
|
|
||||||
* under the same mapping
|
|
||||||
*/
|
|
||||||
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
|
|
||||||
this.mappingRegistry.register(mapping, handler, method);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the HandlerMethod instance.
|
* Create the HandlerMethod instance.
|
||||||
* @param handler either a bean name or an actual handler instance
|
* @param handler either a bean name or an actual handler instance
|
||||||
|
@ -258,9 +269,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Looking up handler method for path " + lookupPath);
|
logger.debug("Looking up handler method for path " + lookupPath);
|
||||||
}
|
}
|
||||||
this.mappingRegistry.acquireReadLock();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Ensure form data is parsed for "params" conditions...
|
// Ensure form data is parsed for "params" conditions...
|
||||||
return exchange.getRequestParams()
|
return exchange.getRequestParams()
|
||||||
.then(() -> {
|
.then(() -> {
|
||||||
|
@ -285,10 +294,6 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
return Mono.justOrEmpty(handlerMethod);
|
return Mono.justOrEmpty(handlerMethod);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
this.mappingRegistry.releaseReadLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look up the best-matching handler method for the current request.
|
* Look up the best-matching handler method for the current request.
|
||||||
|
@ -302,19 +307,11 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
protected HandlerMethod lookupHandlerMethod(String lookupPath, ServerWebExchange exchange)
|
protected HandlerMethod lookupHandlerMethod(String lookupPath, ServerWebExchange exchange)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
List<Match> matches = new ArrayList<Match>();
|
List<Match> matches = findMatchingMappings(lookupPath, exchange);
|
||||||
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
|
|
||||||
if (directPathMatches != null) {
|
|
||||||
addMatchingMappings(directPathMatches, matches, exchange);
|
|
||||||
}
|
|
||||||
if (matches.isEmpty()) {
|
|
||||||
// No choice but to go through all mappings...
|
|
||||||
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, exchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!matches.isEmpty()) {
|
if (!matches.isEmpty()) {
|
||||||
Comparator<Match> comparator = new MatchComparator(getMappingComparator(exchange));
|
Comparator<Match> comparator = new MatchComparator(getMappingComparator(exchange));
|
||||||
Collections.sort(matches, comparator);
|
matches.sort(comparator);
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
|
logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
|
||||||
lookupPath + "] : " + matches);
|
lookupPath + "] : " + matches);
|
||||||
|
@ -340,7 +337,16 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, ServerWebExchange exchange) {
|
private List<Match> findMatchingMappings(String lookupPath, ServerWebExchange exchange) {
|
||||||
|
List<Match> matches = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
this.readWriteLock.readLock().lock();
|
||||||
|
// Fast lookup of potential matching mappings given the current lookup path
|
||||||
|
Set<PathPattern> matchingPatterns = getPatternRegistry().findMatches(lookupPath);
|
||||||
|
Set<T> mappings = matchingPatterns.stream()
|
||||||
|
.flatMap(pattern -> this.mappingLookup.get(pattern).stream())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
for (T mapping : mappings) {
|
for (T mapping : mappings) {
|
||||||
T match = getMatchingMapping(mapping, exchange);
|
T match = getMatchingMapping(mapping, exchange);
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
|
@ -348,6 +354,11 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
this.readWriteLock.readLock().unlock();
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when a matching mapping is found.
|
* Invoked when a matching mapping is found.
|
||||||
|
@ -409,7 +420,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
/**
|
/**
|
||||||
* Extract and return the URL paths contained in a mapping.
|
* Extract and return the URL paths contained in a mapping.
|
||||||
*/
|
*/
|
||||||
protected abstract Set<String> getMappingPathPatterns(T mapping);
|
protected abstract Set<PathPattern> getMappingPathPatterns(T mapping);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a mapping matches the current request and return a (potentially
|
* Check if a mapping matches the current request and return a (potentially
|
||||||
|
@ -431,7 +442,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A registry that maintains all mappings to handler methods, exposing methods
|
* A registry that maintains all mappings to handler methods, exposing methods
|
||||||
* to perform lookups and providing concurrent access.
|
* to perform lookups.
|
||||||
*
|
*
|
||||||
* <p>Package-private for testing purposes.
|
* <p>Package-private for testing purposes.
|
||||||
*/
|
*/
|
||||||
|
@ -439,28 +450,15 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
|
|
||||||
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
|
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
|
||||||
|
|
||||||
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
|
private final Map<T, HandlerMethod> handlerMethodLookup = new LinkedHashMap<>();
|
||||||
|
|
||||||
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
|
|
||||||
|
|
||||||
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
|
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all mappings and handler methods. Not thread-safe.
|
* Return all mappings and handler methods. Not thread-safe.
|
||||||
* @see #acquireReadLock()
|
|
||||||
*/
|
*/
|
||||||
public Map<T, HandlerMethod> getMappings() {
|
public Map<T, HandlerMethod> getMappings() {
|
||||||
return this.mappingLookup;
|
return this.handlerMethodLookup;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return matches for the given URL path. Not thread-safe.
|
|
||||||
* @see #acquireReadLock()
|
|
||||||
*/
|
|
||||||
public List<T> getMappingsByUrl(String urlPath) {
|
|
||||||
return this.urlLookup.get(urlPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -471,50 +469,25 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
return this.corsLookup.get(original != null ? original : handlerMethod);
|
return this.corsLookup.get(original != null ? original : handlerMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Acquire the read lock when using getMappings and getMappingsByUrl.
|
|
||||||
*/
|
|
||||||
public void acquireReadLock() {
|
|
||||||
this.readWriteLock.readLock().lock();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Release the read lock after using getMappings and getMappingsByUrl.
|
|
||||||
*/
|
|
||||||
public void releaseReadLock() {
|
|
||||||
this.readWriteLock.readLock().unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void register(T mapping, Object handler, Method method) {
|
public void register(T mapping, Object handler, Method method) {
|
||||||
this.readWriteLock.writeLock().lock();
|
|
||||||
try {
|
|
||||||
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
|
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
|
||||||
assertUniqueMethodMapping(handlerMethod, mapping);
|
assertUniqueMethodMapping(handlerMethod, mapping);
|
||||||
|
|
||||||
if (logger.isInfoEnabled()) {
|
if (logger.isInfoEnabled()) {
|
||||||
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
|
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
|
||||||
}
|
}
|
||||||
this.mappingLookup.put(mapping, handlerMethod);
|
this.handlerMethodLookup.put(mapping, handlerMethod);
|
||||||
|
|
||||||
List<String> directUrls = getDirectUrls(mapping);
|
|
||||||
for (String url : directUrls) {
|
|
||||||
this.urlLookup.add(url, mapping);
|
|
||||||
}
|
|
||||||
|
|
||||||
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
|
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
|
||||||
if (corsConfig != null) {
|
if (corsConfig != null) {
|
||||||
this.corsLookup.put(handlerMethod, corsConfig);
|
this.corsLookup.put(handlerMethod, corsConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls));
|
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod));
|
||||||
}
|
|
||||||
finally {
|
|
||||||
this.readWriteLock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping) {
|
private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping) {
|
||||||
HandlerMethod handlerMethod = this.mappingLookup.get(mapping);
|
HandlerMethod handlerMethod = this.handlerMethodLookup.get(mapping);
|
||||||
if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {
|
if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Ambiguous mapping. Cannot map '" + newHandlerMethod.getBean() + "' method \n" +
|
"Ambiguous mapping. Cannot map '" + newHandlerMethod.getBean() + "' method \n" +
|
||||||
|
@ -523,41 +496,16 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getDirectUrls(T mapping) {
|
|
||||||
List<String> urls = new ArrayList<>(1);
|
|
||||||
for (String path : getMappingPathPatterns(mapping)) {
|
|
||||||
if (!getPathMatcher().isPattern(path)) {
|
|
||||||
urls.add(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return urls;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unregister(T mapping) {
|
public void unregister(T mapping) {
|
||||||
this.readWriteLock.writeLock().lock();
|
|
||||||
try {
|
|
||||||
MappingRegistration<T> definition = this.registry.remove(mapping);
|
MappingRegistration<T> definition = this.registry.remove(mapping);
|
||||||
if (definition == null) {
|
if (definition == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mappingLookup.remove(definition.getMapping());
|
this.handlerMethodLookup.remove(definition.getMapping());
|
||||||
|
|
||||||
for (String url : definition.getDirectUrls()) {
|
|
||||||
List<T> list = this.urlLookup.get(url);
|
|
||||||
if (list != null) {
|
|
||||||
list.remove(definition.getMapping());
|
|
||||||
if (list.isEmpty()) {
|
|
||||||
this.urlLookup.remove(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.corsLookup.remove(definition.getHandlerMethod());
|
this.corsLookup.remove(definition.getHandlerMethod());
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
this.readWriteLock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -567,14 +515,11 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
|
|
||||||
private final HandlerMethod handlerMethod;
|
private final HandlerMethod handlerMethod;
|
||||||
|
|
||||||
private final List<String> directUrls;
|
public MappingRegistration(T mapping, HandlerMethod handlerMethod) {
|
||||||
|
|
||||||
public MappingRegistration(T mapping, HandlerMethod handlerMethod, List<String> directUrls) {
|
|
||||||
Assert.notNull(mapping, "Mapping must not be null");
|
Assert.notNull(mapping, "Mapping must not be null");
|
||||||
Assert.notNull(handlerMethod, "HandlerMethod must not be null");
|
Assert.notNull(handlerMethod, "HandlerMethod must not be null");
|
||||||
this.mapping = mapping;
|
this.mapping = mapping;
|
||||||
this.handlerMethod = handlerMethod;
|
this.handlerMethod = handlerMethod;
|
||||||
this.directUrls = (directUrls != null ? directUrls : Collections.emptyList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public T getMapping() {
|
public T getMapping() {
|
||||||
|
@ -584,10 +529,6 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||||
public HandlerMethod getHandlerMethod() {
|
public HandlerMethod getHandlerMethod() {
|
||||||
return this.handlerMethod;
|
return this.handlerMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getDirectUrls() {
|
|
||||||
return this.directUrls;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,7 +18,6 @@ package org.springframework.web.reactive.result.method;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.util.PathMatcher;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.reactive.accept.MappingContentTypeResolver;
|
import org.springframework.web.reactive.accept.MappingContentTypeResolver;
|
||||||
|
@ -472,7 +471,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
|
||||||
RequestedContentTypeResolver contentTypeResolver = this.options.getContentTypeResolver();
|
RequestedContentTypeResolver contentTypeResolver = this.options.getContentTypeResolver();
|
||||||
|
|
||||||
PatternsRequestCondition patternsCondition = new PatternsRequestCondition(
|
PatternsRequestCondition patternsCondition = new PatternsRequestCondition(
|
||||||
this.paths, this.options.getPathHelper(), this.options.getPathMatcher(),
|
this.paths, this.options.getPathHelper(),
|
||||||
this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
|
this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
|
||||||
this.options.getFileExtensions());
|
this.options.getFileExtensions());
|
||||||
|
|
||||||
|
@ -497,11 +496,9 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
|
||||||
|
|
||||||
private HttpRequestPathHelper pathHelper;
|
private HttpRequestPathHelper pathHelper;
|
||||||
|
|
||||||
private PathMatcher pathMatcher;
|
|
||||||
|
|
||||||
private boolean trailingSlashMatch = true;
|
private boolean trailingSlashMatch = true;
|
||||||
|
|
||||||
private boolean suffixPatternMatch = true;
|
private boolean suffixPatternMatch = false;
|
||||||
|
|
||||||
private boolean registeredSuffixPatternMatch = false;
|
private boolean registeredSuffixPatternMatch = false;
|
||||||
|
|
||||||
|
@ -519,18 +516,6 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
|
||||||
return this.pathHelper;
|
return this.pathHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a custom PathMatcher to use for the PatternsRequestCondition.
|
|
||||||
* <p>By default this is not set.
|
|
||||||
*/
|
|
||||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
|
||||||
this.pathMatcher = pathMatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PathMatcher getPathMatcher() {
|
|
||||||
return this.pathMatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to apply trailing slash matching in PatternsRequestCondition.
|
* Whether to apply trailing slash matching in PatternsRequestCondition.
|
||||||
* <p>By default this is set to 'true'.
|
* <p>By default this is set to 'true'.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -26,6 +26,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -45,6 +46,7 @@ import org.springframework.web.server.NotAcceptableStatusException;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.ServerWebInputException;
|
import org.springframework.web.server.ServerWebInputException;
|
||||||
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
|
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
|
||||||
|
import org.springframework.web.util.patterns.PathPattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for classes for which {@link RequestMappingInfo} defines
|
* Abstract base class for classes for which {@link RequestMappingInfo} defines
|
||||||
|
@ -72,7 +74,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||||
* Get the URL path patterns associated with this {@link RequestMappingInfo}.
|
* Get the URL path patterns associated with this {@link RequestMappingInfo}.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Set<String> getMappingPathPatterns(RequestMappingInfo info) {
|
protected Set<PathPattern> getMappingPathPatterns(RequestMappingInfo info) {
|
||||||
return info.getPatternsCondition().getPatterns();
|
return info.getPatternsCondition().getPatterns();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,23 +107,22 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||||
protected void handleMatch(RequestMappingInfo info, String lookupPath, ServerWebExchange exchange) {
|
protected void handleMatch(RequestMappingInfo info, String lookupPath, ServerWebExchange exchange) {
|
||||||
super.handleMatch(info, lookupPath, exchange);
|
super.handleMatch(info, lookupPath, exchange);
|
||||||
|
|
||||||
String bestPattern;
|
|
||||||
Map<String, String> uriVariables;
|
Map<String, String> uriVariables;
|
||||||
Map<String, String> decodedUriVariables;
|
Map<String, String> decodedUriVariables;
|
||||||
|
|
||||||
Set<String> patterns = info.getPatternsCondition().getPatterns();
|
SortedSet<PathPattern> patterns = info.getPatternsCondition().getMatchingPatterns(lookupPath);
|
||||||
if (patterns.isEmpty()) {
|
if (patterns.isEmpty()) {
|
||||||
bestPattern = lookupPath;
|
|
||||||
uriVariables = Collections.emptyMap();
|
uriVariables = Collections.emptyMap();
|
||||||
decodedUriVariables = Collections.emptyMap();
|
decodedUriVariables = Collections.emptyMap();
|
||||||
|
exchange.getAttributes().put(BEST_MATCHING_PATTERN_ATTRIBUTE,
|
||||||
|
getPatternRegistry().parsePattern(lookupPath));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
bestPattern = patterns.iterator().next();
|
PathPattern bestPattern = patterns.first();
|
||||||
uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
|
uriVariables = bestPattern.matchAndExtract(lookupPath);
|
||||||
decodedUriVariables = getPathHelper().decodePathVariables(exchange, uriVariables);
|
decodedUriVariables = getPathHelper().decodePathVariables(exchange, uriVariables);
|
||||||
}
|
|
||||||
|
|
||||||
exchange.getAttributes().put(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
|
exchange.getAttributes().put(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
|
||||||
|
}
|
||||||
exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
|
exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
|
||||||
|
|
||||||
Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(exchange, uriVariables);
|
Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(exchange, uriVariables);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -48,9 +48,9 @@ import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerM
|
||||||
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
|
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
|
||||||
implements EmbeddedValueResolverAware {
|
implements EmbeddedValueResolverAware {
|
||||||
|
|
||||||
private boolean useSuffixPatternMatch = true;
|
private boolean useSuffixPatternMatch = false;
|
||||||
|
|
||||||
private boolean useRegisteredSuffixPatternMatch = true;
|
private boolean useRegisteredSuffixPatternMatch = false;
|
||||||
|
|
||||||
private boolean useTrailingSlashMatch = true;
|
private boolean useTrailingSlashMatch = true;
|
||||||
|
|
||||||
|
@ -113,7 +113,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
public void afterPropertiesSet() {
|
public void afterPropertiesSet() {
|
||||||
this.config = new RequestMappingInfo.BuilderConfiguration();
|
this.config = new RequestMappingInfo.BuilderConfiguration();
|
||||||
this.config.setPathHelper(getPathHelper());
|
this.config.setPathHelper(getPathHelper());
|
||||||
this.config.setPathMatcher(getPathMatcher());
|
|
||||||
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
|
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
|
||||||
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
|
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
|
||||||
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
|
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
|
||||||
|
|
|
@ -102,9 +102,9 @@ public class WebFluxConfigurationSupportTests {
|
||||||
|
|
||||||
assertEquals(0, mapping.getOrder());
|
assertEquals(0, mapping.getOrder());
|
||||||
|
|
||||||
assertTrue(mapping.useSuffixPatternMatch());
|
assertFalse(mapping.useSuffixPatternMatch());
|
||||||
|
assertFalse(mapping.useRegisteredSuffixPatternMatch());
|
||||||
assertTrue(mapping.useTrailingSlashMatch());
|
assertTrue(mapping.useTrailingSlashMatch());
|
||||||
assertTrue(mapping.useRegisteredSuffixPatternMatch());
|
|
||||||
|
|
||||||
name = "webFluxContentTypeResolver";
|
name = "webFluxContentTypeResolver";
|
||||||
RequestedContentTypeResolver resolver = context.getBean(name, RequestedContentTypeResolver.class);
|
RequestedContentTypeResolver resolver = context.getBean(name, RequestedContentTypeResolver.class);
|
||||||
|
@ -262,7 +262,6 @@ public class WebFluxConfigurationSupportTests {
|
||||||
assertEquals(Ordered.LOWEST_PRECEDENCE - 1, handlerMapping.getOrder());
|
assertEquals(Ordered.LOWEST_PRECEDENCE - 1, handlerMapping.getOrder());
|
||||||
|
|
||||||
assertNotNull(handlerMapping.getPathHelper());
|
assertNotNull(handlerMapping.getPathHelper());
|
||||||
assertNotNull(handlerMapping.getPathMatcher());
|
|
||||||
|
|
||||||
SimpleUrlHandlerMapping urlHandlerMapping = (SimpleUrlHandlerMapping) handlerMapping;
|
SimpleUrlHandlerMapping urlHandlerMapping = (SimpleUrlHandlerMapping) handlerMapping;
|
||||||
WebHandler webHandler = (WebHandler) urlHandlerMapping.getUrlMap().get("/images/**");
|
WebHandler webHandler = (WebHandler) urlHandlerMapping.getUrlMap().get("/images/**");
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -55,11 +55,12 @@ public class SimpleUrlHandlerMappingTests {
|
||||||
Object mainController = wac.getBean("mainController");
|
Object mainController = wac.getBean("mainController");
|
||||||
Object otherController = wac.getBean("otherController");
|
Object otherController = wac.getBean("otherController");
|
||||||
|
|
||||||
testUrl("/welcome.html", mainController, handlerMapping, "/welcome.html");
|
// TODO: direct matches have been removed, path within mapping is indeed ""
|
||||||
|
testUrl("/welcome.html", mainController, handlerMapping, "");
|
||||||
testUrl("/welcome.x", otherController, handlerMapping, "welcome.x");
|
testUrl("/welcome.x", otherController, handlerMapping, "welcome.x");
|
||||||
testUrl("/welcome/", otherController, handlerMapping, "welcome");
|
testUrl("/welcome/", otherController, handlerMapping, "welcome");
|
||||||
testUrl("/show.html", mainController, handlerMapping, "/show.html");
|
testUrl("/show.html", mainController, handlerMapping, "");
|
||||||
testUrl("/bookseats.html", mainController, handlerMapping, "/bookseats.html");
|
testUrl("/bookseats.html", mainController, handlerMapping, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -74,10 +75,10 @@ public class SimpleUrlHandlerMappingTests {
|
||||||
testUrl("welcome.html", null, handlerMapping, null);
|
testUrl("welcome.html", null, handlerMapping, null);
|
||||||
testUrl("/pathmatchingAA.html", mainController, handlerMapping, "pathmatchingAA.html");
|
testUrl("/pathmatchingAA.html", mainController, handlerMapping, "pathmatchingAA.html");
|
||||||
testUrl("/pathmatchingA.html", null, handlerMapping, null);
|
testUrl("/pathmatchingA.html", null, handlerMapping, null);
|
||||||
testUrl("/administrator/pathmatching.html", mainController, handlerMapping, "/administrator/pathmatching.html");
|
testUrl("/administrator/pathmatching.html", mainController, handlerMapping, "");
|
||||||
testUrl("/administrator/test/pathmatching.html", mainController, handlerMapping, "test/pathmatching.html");
|
testUrl("/administrator/test/pathmatching.html", mainController, handlerMapping, "test/pathmatching.html");
|
||||||
testUrl("/administratort/pathmatching.html", null, handlerMapping, null);
|
testUrl("/administratort/pathmatching.html", null, handlerMapping, null);
|
||||||
testUrl("/administrator/another/bla.xml", mainController, handlerMapping, "/administrator/another/bla.xml");
|
testUrl("/administrator/another/bla.xml", mainController, handlerMapping, "");
|
||||||
testUrl("/administrator/another/bla.gif", null, handlerMapping, null);
|
testUrl("/administrator/another/bla.gif", null, handlerMapping, null);
|
||||||
testUrl("/administrator/test/testlastbit", mainController, handlerMapping, "test/testlastbit");
|
testUrl("/administrator/test/testlastbit", mainController, handlerMapping, "test/testlastbit");
|
||||||
testUrl("/administrator/test/testla", null, handlerMapping, null);
|
testUrl("/administrator/test/testla", null, handlerMapping, null);
|
||||||
|
@ -89,7 +90,7 @@ public class SimpleUrlHandlerMappingTests {
|
||||||
testUrl("/XpathXXmatching.html", null, handlerMapping, null);
|
testUrl("/XpathXXmatching.html", null, handlerMapping, null);
|
||||||
testUrl("/XXpathmatching.html", null, handlerMapping, null);
|
testUrl("/XXpathmatching.html", null, handlerMapping, null);
|
||||||
testUrl("/show12.html", mainController, handlerMapping, "show12.html");
|
testUrl("/show12.html", mainController, handlerMapping, "show12.html");
|
||||||
testUrl("/show123.html", mainController, handlerMapping, "/show123.html");
|
testUrl("/show123.html", mainController, handlerMapping, "");
|
||||||
testUrl("/show1.html", mainController, handlerMapping, "show1.html");
|
testUrl("/show1.html", mainController, handlerMapping, "show1.html");
|
||||||
testUrl("/reallyGood-test-is-this.jpeg", mainController, handlerMapping, "reallyGood-test-is-this.jpeg");
|
testUrl("/reallyGood-test-is-this.jpeg", mainController, handlerMapping, "reallyGood-test-is-this.jpeg");
|
||||||
testUrl("/reallyGood-tst-is-this.jpeg", null, handlerMapping, null);
|
testUrl("/reallyGood-tst-is-this.jpeg", null, handlerMapping, null);
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse
|
||||||
import org.springframework.util.FileCopyUtils;
|
import org.springframework.util.FileCopyUtils;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||||
|
import org.springframework.web.util.patterns.PathPatternParser;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
@ -57,7 +58,8 @@ public class AppCacheManifestTransformerTests {
|
||||||
ClassPathResource allowedLocation = new ClassPathResource("test/", getClass());
|
ClassPathResource allowedLocation = new ClassPathResource("test/", getClass());
|
||||||
ResourceWebHandler resourceHandler = new ResourceWebHandler();
|
ResourceWebHandler resourceHandler = new ResourceWebHandler();
|
||||||
ResourceUrlProvider resourceUrlProvider = new ResourceUrlProvider();
|
ResourceUrlProvider resourceUrlProvider = new ResourceUrlProvider();
|
||||||
resourceUrlProvider.setHandlerMap(Collections.singletonMap("/static/**", resourceHandler));
|
PathPatternParser parser = new PathPatternParser();
|
||||||
|
resourceUrlProvider.setHandlerMap(Collections.singletonMap(parser.parse("/static/**"), resourceHandler));
|
||||||
|
|
||||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||||
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
|
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||||
|
import org.springframework.web.util.patterns.PathPatternParser;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertSame;
|
||||||
|
@ -58,7 +59,8 @@ public class CssLinkResourceTransformerTests {
|
||||||
ResourceWebHandler resourceHandler = new ResourceWebHandler();
|
ResourceWebHandler resourceHandler = new ResourceWebHandler();
|
||||||
|
|
||||||
ResourceUrlProvider resourceUrlProvider = new ResourceUrlProvider();
|
ResourceUrlProvider resourceUrlProvider = new ResourceUrlProvider();
|
||||||
resourceUrlProvider.setHandlerMap(Collections.singletonMap("/static/**", resourceHandler));
|
PathPatternParser parser = new PathPatternParser();
|
||||||
|
resourceUrlProvider.setHandlerMap(Collections.singletonMap(parser.parse("/static/**"), resourceHandler));
|
||||||
|
|
||||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||||
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
|
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||||
|
import org.springframework.web.util.patterns.PathPatternParser;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
@ -69,7 +70,8 @@ public class ResourceTransformerSupportTests {
|
||||||
handler.setLocations(Collections.singletonList(new ClassPathResource("test/", getClass())));
|
handler.setLocations(Collections.singletonList(new ClassPathResource("test/", getClass())));
|
||||||
handler.setResourceResolvers(resolvers);
|
handler.setResourceResolvers(resolvers);
|
||||||
ResourceUrlProvider urlProvider = new ResourceUrlProvider();
|
ResourceUrlProvider urlProvider = new ResourceUrlProvider();
|
||||||
urlProvider.setHandlerMap(Collections.singletonMap("/resources/**", handler));
|
PathPatternParser parser = new PathPatternParser();
|
||||||
|
urlProvider.setHandlerMap(Collections.singletonMap(parser.parse("/resources/**"), handler));
|
||||||
return urlProvider;
|
return urlProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -38,6 +38,8 @@ import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||||
import org.springframework.web.server.session.DefaultWebSessionManager;
|
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||||
import org.springframework.web.server.session.WebSessionManager;
|
import org.springframework.web.server.session.WebSessionManager;
|
||||||
|
import org.springframework.web.util.patterns.PathPattern;
|
||||||
|
import org.springframework.web.util.patterns.PathPatternParser;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
@ -55,7 +57,7 @@ public class ResourceUrlProviderTests {
|
||||||
|
|
||||||
private final ResourceWebHandler handler = new ResourceWebHandler();
|
private final ResourceWebHandler handler = new ResourceWebHandler();
|
||||||
|
|
||||||
private final Map<String, ResourceWebHandler> handlerMap = new HashMap<>();
|
private final Map<PathPattern, ResourceWebHandler> handlerMap = new HashMap<>();
|
||||||
|
|
||||||
private final ResourceUrlProvider urlProvider = new ResourceUrlProvider();
|
private final ResourceUrlProvider urlProvider = new ResourceUrlProvider();
|
||||||
|
|
||||||
|
@ -66,7 +68,8 @@ public class ResourceUrlProviderTests {
|
||||||
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
|
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
|
||||||
this.handler.setLocations(locations);
|
this.handler.setLocations(locations);
|
||||||
this.handler.afterPropertiesSet();
|
this.handler.afterPropertiesSet();
|
||||||
this.handlerMap.put("/resources/**", this.handler);
|
PathPattern staticResources = new PathPatternParser().parse("/resources/**");
|
||||||
|
this.handlerMap.put(staticResources, this.handler);
|
||||||
this.urlProvider.setHandlerMap(this.handlerMap);
|
this.urlProvider.setHandlerMap(this.handlerMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +125,8 @@ public class ResourceUrlProviderTests {
|
||||||
resolvers.add(new PathResourceResolver());
|
resolvers.add(new PathResourceResolver());
|
||||||
otherHandler.setResourceResolvers(resolvers);
|
otherHandler.setResourceResolvers(resolvers);
|
||||||
|
|
||||||
this.handlerMap.put("/resources/*.css", otherHandler);
|
PathPattern staticResources = new PathPatternParser().parse("/resources/*.css");
|
||||||
|
this.handlerMap.put(staticResources, otherHandler);
|
||||||
this.urlProvider.setHandlerMap(this.handlerMap);
|
this.urlProvider.setHandlerMap(this.handlerMap);
|
||||||
|
|
||||||
String url = this.urlProvider.getForLookupPath("/resources/foo.css").blockMillis(5000);
|
String url = this.urlProvider.getForLookupPath("/resources/foo.css").blockMillis(5000);
|
||||||
|
@ -137,7 +141,8 @@ public class ResourceUrlProviderTests {
|
||||||
context.refresh();
|
context.refresh();
|
||||||
|
|
||||||
ResourceUrlProvider urlProviderBean = context.getBean(ResourceUrlProvider.class);
|
ResourceUrlProvider urlProviderBean = context.getBean(ResourceUrlProvider.class);
|
||||||
assertThat(urlProviderBean.getHandlerMap(), Matchers.hasKey("/resources/**"));
|
assertThat(urlProviderBean.getHandlerMap(),
|
||||||
|
Matchers.hasKey(new PathPatternParser().parse("/resources/**")));
|
||||||
assertFalse(urlProviderBean.isAutodetect());
|
assertFalse(urlProviderBean.isAutodetect());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,8 +18,10 @@ package org.springframework.web.reactive.result.condition;
|
||||||
|
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
@ -27,6 +29,7 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||||
|
import org.springframework.web.util.patterns.PathPattern;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
@ -42,13 +45,14 @@ public class PatternsRequestConditionTests {
|
||||||
@Test
|
@Test
|
||||||
public void prependSlash() {
|
public void prependSlash() {
|
||||||
PatternsRequestCondition c = new PatternsRequestCondition("foo");
|
PatternsRequestCondition c = new PatternsRequestCondition("foo");
|
||||||
assertEquals("/foo", c.getPatterns().iterator().next());
|
assertEquals("/foo", c.getPatterns().iterator().next().getPatternString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void prependNonEmptyPatternsOnly() {
|
public void prependNonEmptyPatternsOnly() {
|
||||||
PatternsRequestCondition c = new PatternsRequestCondition("");
|
PatternsRequestCondition c = new PatternsRequestCondition("");
|
||||||
assertEquals("Do not prepend empty patterns (SPR-8255)", "", c.getPatterns().iterator().next());
|
assertEquals("Do not prepend empty patterns (SPR-8255)", "",
|
||||||
|
c.getPatterns().iterator().next().getPatternString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -113,13 +117,13 @@ public class PatternsRequestConditionTests {
|
||||||
PatternsRequestCondition match = condition.getMatchingCondition(exchange);
|
PatternsRequestCondition match = condition.getMatchingCondition(exchange);
|
||||||
|
|
||||||
assertNotNull(match);
|
assertNotNull(match);
|
||||||
assertEquals("/{foo}.*", match.getPatterns().iterator().next());
|
assertEquals("/{foo}", match.getPatterns().iterator().next().getPatternString());
|
||||||
|
|
||||||
condition = new PatternsRequestCondition(new String[] {"/{foo}"}, null, null, false, false, null);
|
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, true, false, null);
|
||||||
match = condition.getMatchingCondition(exchange);
|
match = condition.getMatchingCondition(exchange);
|
||||||
|
|
||||||
assertNotNull(match);
|
assertNotNull(match);
|
||||||
assertEquals("/{foo}", match.getPatterns().iterator().next());
|
assertEquals("/foo.*", match.getPatterns().iterator().next().getPatternString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPR-8410
|
// SPR-8410
|
||||||
|
@ -128,28 +132,30 @@ public class PatternsRequestConditionTests {
|
||||||
public void matchSuffixPatternUsingFileExtensions() throws Exception {
|
public void matchSuffixPatternUsingFileExtensions() throws Exception {
|
||||||
String[] patterns = new String[] {"/jobs/{jobName}"};
|
String[] patterns = new String[] {"/jobs/{jobName}"};
|
||||||
Set<String> extensions = Collections.singleton("json");
|
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);
|
||||||
|
|
||||||
ServerWebExchange exchange = createExchange("/jobs/my.job");
|
ServerWebExchange exchange = createExchange("/jobs/my.job");
|
||||||
PatternsRequestCondition match = condition.getMatchingCondition(exchange);
|
PatternsRequestCondition match = condition.getMatchingCondition(exchange);
|
||||||
|
|
||||||
assertNotNull(match);
|
assertNotNull(match);
|
||||||
assertEquals("/jobs/{jobName}", match.getPatterns().iterator().next());
|
assertEquals("/jobs/{jobName}", match.getPatterns().iterator().next().getPatternString());
|
||||||
|
|
||||||
exchange = createExchange("/jobs/my.job.json");
|
exchange = createExchange("/jobs/my.job.json");
|
||||||
match = condition.getMatchingCondition(exchange);
|
match = condition.getMatchingCondition(exchange);
|
||||||
|
|
||||||
assertNotNull(match);
|
assertNotNull(match);
|
||||||
assertEquals("/jobs/{jobName}.json", match.getPatterns().iterator().next());
|
Iterator<PathPattern> matchedPatterns = match.getPatterns().iterator();
|
||||||
|
assertEquals("/jobs/{jobName}", matchedPatterns.next().getPatternString());
|
||||||
|
assertEquals("/jobs/{jobName}.json", matchedPatterns.next().getPatternString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchSuffixPatternUsingFileExtensions2() throws Exception {
|
public void matchSuffixPatternUsingFileExtensions2() throws Exception {
|
||||||
PatternsRequestCondition condition1 = new PatternsRequestCondition(
|
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(
|
PatternsRequestCondition condition2 = new PatternsRequestCondition(
|
||||||
new String[] {"/suffix"}, null, null, true, false, null);
|
new String[] {"/suffix"}, null, true, false, null);
|
||||||
|
|
||||||
PatternsRequestCondition combined = condition1.combine(condition2);
|
PatternsRequestCondition combined = condition1.combine(condition2);
|
||||||
|
|
||||||
|
@ -166,20 +172,20 @@ public class PatternsRequestConditionTests {
|
||||||
PatternsRequestCondition condition = new PatternsRequestCondition("/foo");
|
PatternsRequestCondition condition = new PatternsRequestCondition("/foo");
|
||||||
PatternsRequestCondition match = condition.getMatchingCondition(exchange);
|
PatternsRequestCondition match = condition.getMatchingCondition(exchange);
|
||||||
|
|
||||||
assertNotNull(match);
|
assertNull("Should not match by default", 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);
|
match = condition.getMatchingCondition(exchange);
|
||||||
|
|
||||||
assertNotNull(match);
|
assertNotNull(match);
|
||||||
assertEquals("Trailing slash should be insensitive to useSuffixPatternMatch settings (SPR-6164, SPR-5636)",
|
assertEquals("Trailing slash should be insensitive to useSuffixPatternMatch settings (SPR-6164, SPR-5636)",
|
||||||
"/foo/", match.getPatterns().iterator().next());
|
"/foo/", match.getPatterns().iterator().next().getPatternString());
|
||||||
|
|
||||||
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, false, false, null);
|
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, true, true, null);
|
||||||
match = condition.getMatchingCondition(exchange);
|
match = condition.getMatchingCondition(exchange);
|
||||||
|
|
||||||
assertNull(match);
|
assertNotNull(match);
|
||||||
|
assertEquals("/foo/", match.getPatterns().iterator().next().getPatternString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -216,8 +222,8 @@ public class PatternsRequestConditionTests {
|
||||||
PatternsRequestCondition match1 = c1.getMatchingCondition(exchange);
|
PatternsRequestCondition match1 = c1.getMatchingCondition(exchange);
|
||||||
PatternsRequestCondition match2 = c2.getMatchingCondition(exchange);
|
PatternsRequestCondition match2 = c2.getMatchingCondition(exchange);
|
||||||
|
|
||||||
assertNotNull(match1);
|
assertNull(match1);
|
||||||
assertEquals(1, match1.compareTo(match2, exchange));
|
assertEquals("/*.html", match2.getPatterns().iterator().next().getPatternString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,10 +18,9 @@ package org.springframework.web.reactive.result.method;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.SortedSet;
|
||||||
import java.util.Set;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -33,14 +32,15 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.util.PathMatcher;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||||
import org.springframework.web.server.session.MockWebSessionManager;
|
import org.springframework.web.server.session.MockWebSessionManager;
|
||||||
import org.springframework.web.server.session.WebSessionManager;
|
import org.springframework.web.server.session.WebSessionManager;
|
||||||
import org.springframework.web.util.ParsingPathMatcher;
|
import org.springframework.web.util.patterns.PathPattern;
|
||||||
|
import org.springframework.web.util.patterns.PathPatternParser;
|
||||||
|
import org.springframework.web.util.patterns.PatternComparatorConsideringPath;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
@ -52,7 +52,9 @@ import static org.junit.Assert.assertNull;
|
||||||
*/
|
*/
|
||||||
public class HandlerMethodMappingTests {
|
public class HandlerMethodMappingTests {
|
||||||
|
|
||||||
private AbstractHandlerMethodMapping<String> mapping;
|
private PathPatternParser patternParser = new PathPatternParser();
|
||||||
|
|
||||||
|
private AbstractHandlerMethodMapping<PathPattern> mapping;
|
||||||
|
|
||||||
private MyHandler handler;
|
private MyHandler handler;
|
||||||
|
|
||||||
|
@ -72,14 +74,14 @@ public class HandlerMethodMappingTests {
|
||||||
|
|
||||||
@Test(expected = IllegalStateException.class)
|
@Test(expected = IllegalStateException.class)
|
||||||
public void registerDuplicates() {
|
public void registerDuplicates() {
|
||||||
this.mapping.registerMapping("foo", this.handler, this.method1);
|
this.mapping.registerMapping(this.patternParser.parse("/foo"), this.handler, this.method1);
|
||||||
this.mapping.registerMapping("foo", this.handler, this.method2);
|
this.mapping.registerMapping(this.patternParser.parse("/foo"), this.handler, this.method2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void directMatch() throws Exception {
|
public void directMatch() throws Exception {
|
||||||
String key = "foo";
|
String key = "foo";
|
||||||
this.mapping.registerMapping(key, this.handler, this.method1);
|
this.mapping.registerMapping(this.patternParser.parse(key), this.handler, this.method1);
|
||||||
Mono<Object> result = this.mapping.getHandler(createExchange(HttpMethod.GET, key));
|
Mono<Object> result = this.mapping.getHandler(createExchange(HttpMethod.GET, key));
|
||||||
|
|
||||||
assertEquals(this.method1, ((HandlerMethod) result.block()).getMethod());
|
assertEquals(this.method1, ((HandlerMethod) result.block()).getMethod());
|
||||||
|
@ -87,8 +89,8 @@ public class HandlerMethodMappingTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void patternMatch() throws Exception {
|
public void patternMatch() throws Exception {
|
||||||
this.mapping.registerMapping("/fo*", this.handler, this.method1);
|
this.mapping.registerMapping(this.patternParser.parse("/fo*"), this.handler, this.method1);
|
||||||
this.mapping.registerMapping("/f*", this.handler, this.method2);
|
this.mapping.registerMapping(this.patternParser.parse("/f*"), this.handler, this.method2);
|
||||||
|
|
||||||
Mono<Object> result = this.mapping.getHandler(createExchange(HttpMethod.GET, "/foo"));
|
Mono<Object> result = this.mapping.getHandler(createExchange(HttpMethod.GET, "/foo"));
|
||||||
assertEquals(this.method1, ((HandlerMethod) result.block()).getMethod());
|
assertEquals(this.method1, ((HandlerMethod) result.block()).getMethod());
|
||||||
|
@ -96,8 +98,8 @@ public class HandlerMethodMappingTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void ambiguousMatch() throws Exception {
|
public void ambiguousMatch() throws Exception {
|
||||||
this.mapping.registerMapping("/f?o", this.handler, this.method1);
|
this.mapping.registerMapping(this.patternParser.parse("/f?o"), this.handler, this.method1);
|
||||||
this.mapping.registerMapping("/fo?", this.handler, this.method2);
|
this.mapping.registerMapping(this.patternParser.parse("/fo?"), this.handler, this.method2);
|
||||||
Mono<Object> result = this.mapping.getHandler(createExchange(HttpMethod.GET, "/foo"));
|
Mono<Object> result = this.mapping.getHandler(createExchange(HttpMethod.GET, "/foo"));
|
||||||
|
|
||||||
StepVerifier.create(result).expectError(IllegalStateException.class).verify();
|
StepVerifier.create(result).expectError(IllegalStateException.class).verify();
|
||||||
|
@ -105,47 +107,41 @@ public class HandlerMethodMappingTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void registerMapping() throws Exception {
|
public void registerMapping() throws Exception {
|
||||||
String key1 = "/foo";
|
PathPattern key1 = this.patternParser.parse("/foo");
|
||||||
String key2 = "/foo*";
|
PathPattern key2 = this.patternParser.parse("/foo*");
|
||||||
this.mapping.registerMapping(key1, this.handler, this.method1);
|
this.mapping.registerMapping(key1, this.handler, this.method1);
|
||||||
this.mapping.registerMapping(key2, this.handler, this.method2);
|
this.mapping.registerMapping(key2, this.handler, this.method2);
|
||||||
|
|
||||||
List directUrlMatches = this.mapping.getMappingRegistry().getMappingsByUrl(key1);
|
HandlerMethod match = this.mapping.getMappingRegistry().getMappings().get(key1);
|
||||||
|
assertNotNull(match);
|
||||||
assertNotNull(directUrlMatches);
|
|
||||||
assertEquals(1, directUrlMatches.size());
|
|
||||||
assertEquals(key1, directUrlMatches.get(0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void registerMappingWithSameMethodAndTwoHandlerInstances() throws Exception {
|
public void registerMappingWithSameMethodAndTwoHandlerInstances() throws Exception {
|
||||||
String key1 = "foo";
|
PathPattern key1 = this.patternParser.parse("/foo");
|
||||||
String key2 = "bar";
|
PathPattern key2 = this.patternParser.parse("/bar");
|
||||||
MyHandler handler1 = new MyHandler();
|
MyHandler handler1 = new MyHandler();
|
||||||
MyHandler handler2 = new MyHandler();
|
MyHandler handler2 = new MyHandler();
|
||||||
this.mapping.registerMapping(key1, handler1, this.method1);
|
this.mapping.registerMapping(key1, handler1, this.method1);
|
||||||
this.mapping.registerMapping(key2, handler2, this.method1);
|
this.mapping.registerMapping(key2, handler2, this.method1);
|
||||||
|
|
||||||
List directUrlMatches = this.mapping.getMappingRegistry().getMappingsByUrl(key1);
|
HandlerMethod match = this.mapping.getMappingRegistry().getMappings().get(key1);
|
||||||
|
assertNotNull(match);
|
||||||
assertNotNull(directUrlMatches);
|
|
||||||
assertEquals(1, directUrlMatches.size());
|
|
||||||
assertEquals(key1, directUrlMatches.get(0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void unregisterMapping() throws Exception {
|
public void unregisterMapping() throws Exception {
|
||||||
String key = "foo";
|
String key = "foo";
|
||||||
this.mapping.registerMapping(key, this.handler, this.method1);
|
this.mapping.registerMapping(this.patternParser.parse(key), this.handler, this.method1);
|
||||||
Mono<Object> result = this.mapping.getHandler(createExchange(HttpMethod.GET, key));
|
Mono<Object> result = this.mapping.getHandler(createExchange(HttpMethod.GET, key));
|
||||||
|
|
||||||
assertNotNull(result.block());
|
assertNotNull(result.block());
|
||||||
|
|
||||||
this.mapping.unregisterMapping(key);
|
this.mapping.unregisterMapping(this.patternParser.parse(key));
|
||||||
result = this.mapping.getHandler(createExchange(HttpMethod.GET, key));
|
result = this.mapping.getHandler(createExchange(HttpMethod.GET, key));
|
||||||
|
|
||||||
assertNull(result.block());
|
assertNull(result.block());
|
||||||
assertNull(this.mapping.getMappingRegistry().getMappingsByUrl(key));
|
assertNull(this.mapping.getMappingRegistry().getMappings().get(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,9 +152,9 @@ public class HandlerMethodMappingTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class MyHandlerMethodMapping extends AbstractHandlerMethodMapping<String> {
|
private static class MyHandlerMethodMapping extends AbstractHandlerMethodMapping<PathPattern> {
|
||||||
|
|
||||||
private PathMatcher pathMatcher = new ParsingPathMatcher();
|
private PathPatternParser patternParser = new PathPatternParser();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isHandler(Class<?> beanType) {
|
protected boolean isHandler(Class<?> beanType) {
|
||||||
|
@ -166,26 +162,28 @@ public class HandlerMethodMappingTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getMappingForMethod(Method method, Class<?> handlerType) {
|
protected PathPattern getMappingForMethod(Method method, Class<?> handlerType) {
|
||||||
String methodName = method.getName();
|
String methodName = method.getName();
|
||||||
return methodName.startsWith("handler") ? methodName : null;
|
return methodName.startsWith("handler") ? this.patternParser.parse(methodName) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Set<String> getMappingPathPatterns(String key) {
|
protected SortedSet<PathPattern> getMappingPathPatterns(PathPattern key) {
|
||||||
return (this.pathMatcher.isPattern(key) ? Collections.emptySet() : Collections.singleton(key));
|
TreeSet<PathPattern> patterns = new TreeSet<>();
|
||||||
|
patterns.add(key);
|
||||||
|
return patterns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getMatchingMapping(String pattern, ServerWebExchange exchange) {
|
protected PathPattern getMatchingMapping(PathPattern pattern, ServerWebExchange exchange) {
|
||||||
String lookupPath = exchange.getRequest().getURI().getPath();
|
String lookupPath = exchange.getRequest().getURI().getPath();
|
||||||
return (this.pathMatcher.match(pattern, lookupPath) ? pattern : null);
|
return (pattern.matches(lookupPath) ? pattern : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Comparator<String> getMappingComparator(ServerWebExchange exchange) {
|
protected Comparator<PathPattern> getMappingComparator(ServerWebExchange exchange) {
|
||||||
String lookupPath = exchange.getRequest().getURI().getPath();
|
String lookupPath = exchange.getRequest().getURI().getPath();
|
||||||
return this.pathMatcher.getPatternComparator(lookupPath);
|
return new PatternComparatorConsideringPath(lookupPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -24,9 +24,13 @@ import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
|
@ -59,6 +63,7 @@ import org.springframework.web.server.ServerWebInputException;
|
||||||
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
|
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
|
||||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||||
import org.springframework.web.server.support.HttpRequestPathHelper;
|
import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||||
|
import org.springframework.web.util.patterns.PathPattern;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
@ -81,7 +86,6 @@ public class RequestMappingInfoHandlerMappingTests {
|
||||||
|
|
||||||
private ServerHttpRequest request;
|
private ServerHttpRequest request;
|
||||||
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
this.handlerMapping = new TestRequestMappingInfoHandlerMapping();
|
this.handlerMapping = new TestRequestMappingInfoHandlerMapping();
|
||||||
|
@ -93,9 +97,11 @@ public class RequestMappingInfoHandlerMappingTests {
|
||||||
public void getMappingPathPatterns() throws Exception {
|
public void getMappingPathPatterns() throws Exception {
|
||||||
String[] patterns = {"/foo/*", "/foo", "/bar/*", "/bar"};
|
String[] patterns = {"/foo/*", "/foo", "/bar/*", "/bar"};
|
||||||
RequestMappingInfo info = paths(patterns).build();
|
RequestMappingInfo info = paths(patterns).build();
|
||||||
Set<String> actual = this.handlerMapping.getMappingPathPatterns(info);
|
Set<PathPattern> actual = this.handlerMapping.getMappingPathPatterns(info);
|
||||||
|
|
||||||
assertEquals(new HashSet<>(Arrays.asList(patterns)), actual);
|
assertThat(actual.stream().map(PathPattern::getPatternString).collect(Collectors.toList()),
|
||||||
|
Matchers.containsInAnyOrder(new String[]{"/foo/*", "/foo", "/bar/*", "/bar",
|
||||||
|
"/foo/*/", "/foo/", "/bar/*/", "/bar/"}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -123,6 +129,9 @@ public class RequestMappingInfoHandlerMappingTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore
|
||||||
|
// TODO: for "" patterns, should we generate the "/" variant (and break SPR-8255)
|
||||||
|
// or handle matching in a different way? Here, setTrailingSlashMatch is set to false for tests
|
||||||
public void getHandlerEmptyPathMatch() throws Exception {
|
public void getHandlerEmptyPathMatch() throws Exception {
|
||||||
String[] patterns = new String[] {""};
|
String[] patterns = new String[] {""};
|
||||||
Method expected = resolveMethod(new TestController(), patterns, null, null);
|
Method expected = resolveMethod(new TestController(), patterns, null, null);
|
||||||
|
@ -274,7 +283,9 @@ public class RequestMappingInfoHandlerMappingTests {
|
||||||
ServerWebExchange exchange = createExchange();
|
ServerWebExchange exchange = createExchange();
|
||||||
this.handlerMapping.handleMatch(key, "/1/2", exchange);
|
this.handlerMapping.handleMatch(key, "/1/2", exchange);
|
||||||
|
|
||||||
assertEquals("/{path1}/2", exchange.getAttributes().get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
|
PathPattern pattern = (PathPattern) exchange.getAttributes()
|
||||||
|
.get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
|
||||||
|
assertEquals("/{path1}/2", pattern.getPatternString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -285,7 +296,9 @@ public class RequestMappingInfoHandlerMappingTests {
|
||||||
|
|
||||||
this.handlerMapping.handleMatch(key, "/1/2", exchange);
|
this.handlerMapping.handleMatch(key, "/1/2", exchange);
|
||||||
|
|
||||||
assertEquals("/1/2", exchange.getAttributes().get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
|
PathPattern pattern = (PathPattern) exchange.getAttributes()
|
||||||
|
.get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
|
||||||
|
assertEquals("/1/2", pattern.getPatternString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -534,7 +547,6 @@ public class RequestMappingInfoHandlerMappingTests {
|
||||||
if (annot != null) {
|
if (annot != null) {
|
||||||
BuilderConfiguration options = new BuilderConfiguration();
|
BuilderConfiguration options = new BuilderConfiguration();
|
||||||
options.setPathHelper(getPathHelper());
|
options.setPathHelper(getPathHelper());
|
||||||
options.setPathMatcher(getPathMatcher());
|
|
||||||
options.setSuffixPatternMatch(true);
|
options.setSuffixPatternMatch(true);
|
||||||
options.setTrailingSlashMatch(true);
|
options.setTrailingSlashMatch(true);
|
||||||
return paths(annot.value()).methods(annot.method())
|
return paths(annot.value()).methods(annot.method())
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -25,7 +25,10 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -42,12 +45,15 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||||
import org.springframework.web.reactive.accept.MappingContentTypeResolver;
|
import org.springframework.web.reactive.accept.MappingContentTypeResolver;
|
||||||
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
||||||
|
import org.springframework.web.util.patterns.PathPattern;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.contains;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ -71,12 +77,14 @@ public class RequestMappingHandlerMappingTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void useRegisteredSuffixPatternMatch() {
|
public void useRegisteredSuffixPatternMatch() {
|
||||||
assertTrue(this.handlerMapping.useSuffixPatternMatch());
|
assertFalse(this.handlerMapping.useSuffixPatternMatch());
|
||||||
assertTrue(this.handlerMapping.useRegisteredSuffixPatternMatch());
|
assertFalse(this.handlerMapping.useRegisteredSuffixPatternMatch());
|
||||||
|
|
||||||
MappingContentTypeResolver contentTypeResolver = mock(MappingContentTypeResolver.class);
|
MappingContentTypeResolver contentTypeResolver = mock(MappingContentTypeResolver.class);
|
||||||
when(contentTypeResolver.getKeys()).thenReturn(Collections.singleton("json"));
|
when(contentTypeResolver.getKeys()).thenReturn(Collections.singleton("json"));
|
||||||
|
|
||||||
|
this.handlerMapping.setUseSuffixPatternMatch(true);
|
||||||
|
this.handlerMapping.setUseRegisteredSuffixPatternMatch(true);
|
||||||
this.handlerMapping.setContentTypeResolver(contentTypeResolver);
|
this.handlerMapping.setContentTypeResolver(contentTypeResolver);
|
||||||
this.handlerMapping.afterPropertiesSet();
|
this.handlerMapping.afterPropertiesSet();
|
||||||
|
|
||||||
|
@ -111,15 +119,8 @@ public class RequestMappingHandlerMappingTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void useSuffixPatternMatch() {
|
public void useSuffixPatternMatch() {
|
||||||
assertTrue(this.handlerMapping.useSuffixPatternMatch());
|
|
||||||
assertTrue(this.handlerMapping.useRegisteredSuffixPatternMatch());
|
|
||||||
|
|
||||||
this.handlerMapping.setUseSuffixPatternMatch(false);
|
|
||||||
assertFalse(this.handlerMapping.useSuffixPatternMatch());
|
assertFalse(this.handlerMapping.useSuffixPatternMatch());
|
||||||
|
assertFalse(this.handlerMapping.useRegisteredSuffixPatternMatch());
|
||||||
this.handlerMapping.setUseRegisteredSuffixPatternMatch(false);
|
|
||||||
assertFalse("'false' registeredSuffixPatternMatch shouldn't impact suffixPatternMatch",
|
|
||||||
this.handlerMapping.useSuffixPatternMatch());
|
|
||||||
|
|
||||||
this.handlerMapping.setUseRegisteredSuffixPatternMatch(true);
|
this.handlerMapping.setUseRegisteredSuffixPatternMatch(true);
|
||||||
assertTrue("'true' registeredSuffixPatternMatch should enable suffixPatternMatch",
|
assertTrue("'true' registeredSuffixPatternMatch should enable suffixPatternMatch",
|
||||||
|
@ -197,9 +198,10 @@ public class RequestMappingHandlerMappingTests {
|
||||||
|
|
||||||
assertNotNull(info);
|
assertNotNull(info);
|
||||||
|
|
||||||
Set<String> paths = info.getPatternsCondition().getPatterns();
|
Set<String> paths = info.getPatternsCondition().getPatterns()
|
||||||
assertEquals(1, paths.size());
|
.stream().map(p -> p.getPatternString()).collect(Collectors.toSet());
|
||||||
assertEquals(path, paths.iterator().next());
|
assertEquals(2, paths.size());
|
||||||
|
assertThat(paths, Matchers.containsInAnyOrder(path, path + "/"));
|
||||||
|
|
||||||
Set<RequestMethod> methods = info.getMethodsCondition().getMethods();
|
Set<RequestMethod> methods = info.getMethodsCondition().getMethods();
|
||||||
assertEquals(1, methods.size());
|
assertEquals(1, methods.size());
|
||||||
|
|
Loading…
Reference in New Issue