Support for parsed PathPatterns in Spring MVC

See gh-24945
This commit is contained in:
Rossen Stoyanchev
2020-06-15 11:20:32 +01:00
parent a0f4d81db7
commit 5225a57411
82 changed files with 5324 additions and 3011 deletions

View File

@@ -68,6 +68,7 @@ import org.springframework.web.servlet.support.SessionFlashMapManager;
import org.springframework.web.servlet.theme.FixedThemeResolver;
import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* A {@code MockMvcBuilder} that accepts {@code @Controller} registrations
@@ -126,6 +127,9 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
@Nullable
private FlashMapManager flashMapManager;
@Nullable
private PathPatternParser patternParser;
private boolean useSuffixPatternMatch = true;
private boolean useTrailingSlashPatternMatch = true;
@@ -213,7 +217,7 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
@Nullable String[] pathPatterns, HandlerInterceptor... interceptors) {
for (HandlerInterceptor interceptor : interceptors) {
this.mappedInterceptors.add(new MappedInterceptor(pathPatterns, interceptor));
this.mappedInterceptors.add(new MappedInterceptor(pathPatterns, null, interceptor));
}
return this;
}
@@ -306,6 +310,17 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
return this;
}
/**
* Enable URL path matching with parsed
* {@link org.springframework.web.util.pattern.PathPattern PathPatterns}
* instead of String pattern matching with a {@link org.springframework.util.PathMatcher}.
* @param parser the parser to use
* @since 5.3
*/
public void setPatternParser(PathPatternParser parser) {
this.patternParser = parser;
}
/**
* Whether to use suffix pattern match (".*") when matching patterns to
* requests. If enabled a method mapped to "/users" also matches to "/users.*".
@@ -450,15 +465,21 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
public RequestMappingHandlerMapping getHandlerMapping(
FormattingConversionService mvcConversionService,
ResourceUrlProvider mvcResourceUrlProvider) {
RequestMappingHandlerMapping handlerMapping = handlerMappingFactory.get();
handlerMapping.setEmbeddedValueResolver(new StaticStringValueResolver(placeholderValues));
handlerMapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
if (patternParser != null) {
handlerMapping.setPatternParser(patternParser);
}
else {
handlerMapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
if (removeSemicolonContent != null) {
handlerMapping.setRemoveSemicolonContent(removeSemicolonContent);
}
}
handlerMapping.setUseTrailingSlashMatch(useTrailingSlashPatternMatch);
handlerMapping.setOrder(0);
handlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
if (removeSemicolonContent != null) {
handlerMapping.setRemoveSemicolonContent(removeSemicolonContent);
}
return handlerMapping;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -16,7 +16,6 @@
package org.springframework.http.server;
import java.net.URI;
import java.util.List;
import org.springframework.lang.Nullable;
@@ -37,8 +36,8 @@ class DefaultRequestPath implements RequestPath {
private final PathContainer pathWithinApplication;
DefaultRequestPath(URI uri, @Nullable String contextPath) {
this.fullPath = PathContainer.parsePath(uri.getRawPath());
DefaultRequestPath(String rawPath, @Nullable String contextPath) {
this.fullPath = PathContainer.parsePath(rawPath);
this.contextPath = initContextPath(this.fullPath, contextPath);
this.pathWithinApplication = extractPathWithinApplication(this.fullPath, this.contextPath);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2020 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.
@@ -20,7 +20,11 @@ import java.net.URI;
import org.springframework.lang.Nullable;
/**
* Represents the complete path for a request.
* Specialization of {@link PathContainer} that sub-divides the path into a
* {@link #contextPath()} and the remaining {@link #pathWithinApplication()}.
* The lattery is typically used for request mapping within the application
* while the former is useful when preparing external links that point back to
* the application.
*
* @author Rossen Stoyanchev
* @since 5.0
@@ -40,7 +44,8 @@ public interface RequestPath extends PathContainer {
PathContainer contextPath();
/**
* The portion of the request path after the context path.
* The portion of the request path after the context path which is typically
* used for request mapping within the application .
*/
PathContainer pathWithinApplication();
@@ -54,10 +59,23 @@ public interface RequestPath extends PathContainer {
/**
* Create a new {@code RequestPath} with the given parameters.
* Parse the URI for a request into a {@code RequestPath}.
* @param uri the URI of the request
* @param contextPath the contextPath portion of the URI path
*/
static RequestPath parse(URI uri, @Nullable String contextPath) {
return new DefaultRequestPath(uri, contextPath);
return parse(uri.getRawPath(), contextPath);
}
/**
* Variant of {@link #parse(URI, String)} with the encoded
* {@link URI#getRawPath() raw path}.
* @param rawPath the path
* @param contextPath the contextPath portion of the URI path
* @since 5.3
*/
static RequestPath parse(String rawPath, @Nullable String contextPath) {
return new DefaultRequestPath(rawPath, contextPath);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -17,87 +17,131 @@
package org.springframework.web.cors;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.server.PathContainer;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Provide a per request {@link CorsConfiguration} instance based on a
* collection of {@link CorsConfiguration} mapped on path patterns.
* {@code CorsConfigurationSource} that uses URL path patterns to select the
* {@code CorsConfiguration} for a request.
*
* <p>Exact path mapping URIs (such as {@code "/admin"}) are supported
* as well as Ant-style path patterns (such as {@code "/admin/**"}).
* <p>Pattern matching can be done with a {@link PathMatcher} or with pre-parsed
* {@link PathPattern}s. The syntax is largely the same with the latter being more
* tailored for web usage and more efficient. The choice depends on the presence of a
* {@link UrlPathHelper#resolveAndCacheLookupPath resolved} String lookupPath or a
* {@link ServletRequestPathUtils#parseAndCache parsed} {@code RequestPath}
* with a fallback on {@link PathMatcher} but the fallback can be disabled.
* For more details, please see {@link #setAllowInitLookupPath(boolean)}.
*
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
* @since 4.2
* @see PathPattern
* @see AntPathMatcher
*/
public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource {
private final Map<String, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
private static PathMatcher defaultPathMatcher = new AntPathMatcher();
private PathMatcher pathMatcher = new AntPathMatcher();
private UrlPathHelper urlPathHelper = new UrlPathHelper();
private final PathPatternParser patternParser;
private UrlPathHelper urlPathHelper = UrlPathHelper.defaultInstance;
private PathMatcher pathMatcher = defaultPathMatcher;
@Nullable
private String lookupPathAttributeName;
private boolean allowInitLookupPath = true;
private final Map<PathPattern, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
/**
* Set the PathMatcher implementation to use for matching URL paths
* against registered URL patterns. Default is AntPathMatcher.
* @see org.springframework.util.AntPathMatcher
* Default constructor with {@link PathPatternParser#defaultInstance}.
*/
public void setPathMatcher(PathMatcher pathMatcher) {
Assert.notNull(pathMatcher, "PathMatcher must not be null");
this.pathMatcher = pathMatcher;
public UrlBasedCorsConfigurationSource() {
this(PathPatternParser.defaultInstance);
}
/**
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
* Constructor with a {@link PathPatternParser} to parse patterns with.
* @param parser the parser to use
* @since 5.3
*/
public UrlBasedCorsConfigurationSource(PathPatternParser parser) {
Assert.notNull(parser, "PathPatternParser must not be null");
this.patternParser = parser;
}
/**
* Shortcut to the
* {@link org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
* same property} on the configured {@code UrlPathHelper}.
* @deprecated as of 5.3 in favor of using
* {@link #setUrlPathHelper(UrlPathHelper)}, if at all. For further details,
* please see {@link #setAllowInitLookupPath(boolean)}.
*/
@Deprecated
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
initUrlPathHelper();
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
}
/**
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
* Shortcut to the
* {@link org.springframework.web.util.UrlPathHelper#setUrlDecode same property}
* on the configured {@code UrlPathHelper}.
* @deprecated as of 5.3 in favor of using
* {@link #setUrlPathHelper(UrlPathHelper)}, if at all. For further details,
* please see {@link #setAllowInitLookupPath(boolean)}.
*/
@Deprecated
public void setUrlDecode(boolean urlDecode) {
initUrlPathHelper();
this.urlPathHelper.setUrlDecode(urlDecode);
}
/**
* Optionally configure the name of the attribute that caches the lookupPath.
* This is used to make the call to
* {@link UrlPathHelper#getLookupPathForRequest(HttpServletRequest, String)}
* @param lookupPathAttributeName the request attribute to check
* @since 5.2
*/
public void setLookupPathAttributeName(@Nullable String lookupPathAttributeName) {
this.lookupPathAttributeName = lookupPathAttributeName;
}
/**
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
* @see org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean)
* Shortcut to the
* {@link org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent
* same property} on the configured {@code UrlPathHelper}.
* @deprecated as of 5.3 in favor of using
* {@link #setUrlPathHelper(UrlPathHelper)}, if at all. For further details,
* please see {@link #setAllowInitLookupPath(boolean)}.
*/
@Deprecated
public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
initUrlPathHelper();
this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
}
private void initUrlPathHelper() {
if (this.urlPathHelper == UrlPathHelper.defaultInstance) {
this.urlPathHelper = new UrlPathHelper();
}
}
/**
* Set the UrlPathHelper to use for resolution of lookup paths.
* <p>Use this to override the default UrlPathHelper with a custom subclass.
* Configure the {@code UrlPathHelper} to resolve the lookupPath. This may
* not be necessary if the lookupPath is expected to be pre-resolved or if
* parsed {@code PathPatterns} are used instead.
* For further details on that, see {@link #setAllowInitLookupPath(boolean)}.
* <p>By default this is {@link UrlPathHelper#defaultInstance}.
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
@@ -105,40 +149,127 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
}
/**
* Set CORS configuration based on URL patterns.
* When enabled, if there is neither a
* {@link UrlPathHelper#resolveAndCacheLookupPath esolved} String lookupPath nor a
* {@link ServletRequestPathUtils#parseAndCache parsed} {@code RequestPath}
* then use the {@link #setUrlPathHelper configured} {@code UrlPathHelper}
* to resolve a String lookupPath. This in turn determines use of URL
* pattern matching with {@link PathMatcher} or with parsed {@link PathPattern}s.
* <p>In Spring MVC, either a resolved String lookupPath or a parsed
* {@code RequestPath} is always available within {@code DispatcherServlet}
* processing. However in a Servlet {@code Filter} such as {@code CorsFilter}
* that may or may not be the case.
* <p>By default this is set to {@code true} in which case lazy lookupPath
* initialization is allowed. Set this to {@code false} when an
* application is using parsed {@code PathPatterns} in which case the
* {@code RequestPath} can be parsed earlier via
* {@link org.springframework.web.filter.ServletRequestPathFilter
* ServletRequestPathFilter}.
* @param allowInitLookupPath whether to disable lazy initialization
* and fail if not already resolved
* @since 5.3
*/
public void setAllowInitLookupPath(boolean allowInitLookupPath) {
this.allowInitLookupPath = allowInitLookupPath;
}
/**
* Configure the name of the attribute that holds the lookupPath extracted
* via {@link UrlPathHelper#getLookupPathForRequest(HttpServletRequest)}.
* <p>By default this is {@link UrlPathHelper#PATH_ATTRIBUTE}.
* @param name the request attribute to check
* @since 5.2
* @deprecated as of 5.3 in favor of {@link UrlPathHelper#PATH_ATTRIBUTE}.
*/
@Deprecated
public void setLookupPathAttributeName(String name) {
this.lookupPathAttributeName = name;
}
/**
* Configure a {@code PathMatcher} to use for pattern matching.
* <p>This is an advanced property that should be used only when a
* customized {@link AntPathMatcher} or a custom PathMatcher is required.
* <p>By default this is {@link AntPathMatcher}.
* <p><strong>Note:</strong> Setting {@code PathMatcher} enforces use of
* String pattern matching even when a
* {@link ServletRequestPathUtils#parseAndCache parsed} {@code RequestPath}
* is available.
*/
public void setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
}
/**
* Set the CORS configuration mappings.
* <p>For pattern syntax see {@link AntPathMatcher} and {@link PathPattern}
* as well as class-level Javadoc for details on which one may in use.
* Generally the syntax is largely the same with {@link PathPattern} more
* tailored for web usage.
* @param corsConfigurations the mappings to use
* @see PathPattern
* @see AntPathMatcher
*/
public void setCorsConfigurations(@Nullable Map<String, CorsConfiguration> corsConfigurations) {
this.corsConfigurations.clear();
if (corsConfigurations != null) {
this.corsConfigurations.putAll(corsConfigurations);
corsConfigurations.forEach(this::registerCorsConfiguration);
}
}
/**
* Get the CORS configuration.
* Variant of {@link #setCorsConfigurations(Map)} to register one mapping at a time.
* @param pattern the mapping pattern
* @param config the CORS configuration to use for the pattern
* @see PathPattern
* @see AntPathMatcher
*/
public Map<String, CorsConfiguration> getCorsConfigurations() {
return Collections.unmodifiableMap(this.corsConfigurations);
public void registerCorsConfiguration(String pattern, CorsConfiguration config) {
this.corsConfigurations.put(this.patternParser.parse(pattern), config);
}
/**
* Register a {@link CorsConfiguration} for the specified path pattern.
* Return all configured CORS mappings.
*/
public void registerCorsConfiguration(String path, CorsConfiguration config) {
this.corsConfigurations.put(path, config);
public Map<String, CorsConfiguration> getCorsConfigurations() {
Map<String, CorsConfiguration> result = new HashMap<>(this.corsConfigurations.size());
this.corsConfigurations.forEach((pattern, config) -> result.put(pattern.getPatternString(), config));
return Collections.unmodifiableMap(result);
}
@Override
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, this.lookupPathAttributeName);
for (Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
if (this.pathMatcher.match(entry.getKey(), lookupPath)) {
Object path = resolvePath(request);
boolean isPathContainer = (path instanceof PathContainer);
for (Map.Entry<PathPattern, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
if (match(path, isPathContainer, entry.getKey())) {
return entry.getValue();
}
}
return null;
}
@SuppressWarnings("deprecation")
private Object resolvePath(HttpServletRequest request) {
if (this.allowInitLookupPath && !ServletRequestPathUtils.hasCachedPath(request)) {
return (this.lookupPathAttributeName != null ?
this.urlPathHelper.getLookupPathForRequest(request, this.lookupPathAttributeName) :
this.urlPathHelper.getLookupPathForRequest(request));
}
Object lookupPath = ServletRequestPathUtils.getCachedPath(request);
if (this.pathMatcher != defaultPathMatcher) {
lookupPath = lookupPath.toString();
}
return lookupPath;
}
private boolean match(Object path, boolean isPathContainer, PathPattern pattern) {
return (isPathContainer ?
pattern.matches((PathContainer) path) :
this.pathMatcher.match(pattern.getPatternString(), (String) path));
}
}

View File

@@ -27,22 +27,20 @@ import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Provide a per reactive request {@link CorsConfiguration} instance based on a
* collection of {@link CorsConfiguration} mapped on path patterns.
*
* <p>Exact path mapping URIs (such as {@code "/admin"}) are supported
* as well as Ant-style path patterns (such as {@code "/admin/**"}).
* {@code CorsConfigurationSource} that uses URL patterns to select the
* {@code CorsConfiguration} for a request.
*
* @author Sebastien Deleuze
* @author Brian Clozel
* @since 5.0
* @see PathPattern
*/
public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource {
private final Map<PathPattern, CorsConfiguration> corsConfigurations;
private final PathPatternParser patternParser;
private final Map<PathPattern, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
/**
* Construct a new {@code UrlBasedCorsConfigurationSource} instance with default
@@ -58,7 +56,6 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
* {@code PathPatternParser}.
*/
public UrlBasedCorsConfigurationSource(PathPatternParser patternParser) {
this.corsConfigurations = new LinkedHashMap<>();
this.patternParser = patternParser;
}
@@ -66,10 +63,10 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
/**
* Set CORS configuration based on URL patterns.
*/
public void setCorsConfigurations(@Nullable Map<String, CorsConfiguration> corsConfigurations) {
public void setCorsConfigurations(@Nullable Map<String, CorsConfiguration> configMap) {
this.corsConfigurations.clear();
if (corsConfigurations != null) {
corsConfigurations.forEach(this::registerCorsConfiguration);
if (configMap != null) {
configMap.forEach(this::registerCorsConfiguration);
}
}
@@ -83,12 +80,13 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
@Override
@Nullable
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
PathContainer lookupPath = exchange.getRequest().getPath().pathWithinApplication();
return this.corsConfigurations.entrySet().stream()
.filter(entry -> entry.getKey().matches(lookupPath))
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);
PathContainer path = exchange.getRequest().getPath().pathWithinApplication();
for (Map.Entry<PathPattern, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
if (entry.getKey().matches(path)) {
return entry.getValue();
}
}
return null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -32,24 +32,23 @@ import org.springframework.web.cors.DefaultCorsProcessor;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
/**
* {@link javax.servlet.Filter} that handles CORS preflight requests and intercepts
* CORS simple and actual requests thanks to a {@link CorsProcessor} implementation
* ({@link DefaultCorsProcessor} by default) in order to add the relevant CORS
* response headers (like {@code Access-Control-Allow-Origin}) using the provided
* {@link CorsConfigurationSource} (for example an {@link UrlBasedCorsConfigurationSource}
* instance.
* {@link javax.servlet.Filter} to handle CORS pre-flight requests and intercept
* CORS simple and actual requests with a {@link CorsProcessor}, and to update
* the response, e.g. with CORS response headers, based on the policy matched
* through the provided {@link CorsConfigurationSource}.
*
* <p>This is an alternative to Spring MVC Java config and XML namespace CORS configuration,
* useful for applications depending only on spring-web (not on spring-webmvc) or for
* security constraints requiring CORS checks to be performed at {@link javax.servlet.Filter}
* level.
* <p>This is an alternative to configuring CORS in the Spring MVC Java config
* and the Spring MVC XML namespace. It is useful for applications depending
* only on spring-web (not on spring-webmvc) or for security constraints that
* require CORS checks to be performed at {@link javax.servlet.Filter} level.
*
* <p>This filter could be used in conjunction with {@link DelegatingFilterProxy} in order
* to help with its initialization.
* <p>This filter could be used in conjunction with {@link DelegatingFilterProxy}
* in order to help with its initialization.
*
* @author Sebastien Deleuze
* @since 4.2
* @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
* @see UrlBasedCorsConfigurationSource
*/
public class CorsFilter extends OncePerRequestFilter {

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2002-2020 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
*
* https://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.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.util.ServletRequestPathUtils;
/**
* A {@code Filter} to {@link ServletRequestPathUtils#parseAndCache parse}
* and cache a {@link org.springframework.http.server.RequestPath} for further
* {@link ServletRequestPathUtils#getParsedRequestPath access} throughout the
* filter chain. This is useful when parsed
* {@link org.springframework.web.util.pattern.PathPattern}s are in use anywhere
* in an application instead of String pattern matching with
* {@link org.springframework.util.PathMatcher}.
* <p>Note that in Spring MVC, the {@code DispatcherServlet} will also parse and
* cache the {@code RequestPath} if it detects that parsed {@code PathPatterns}
* are enabled for any {@code HandlerMapping} but it will skip doing that if it
* finds the {@link ServletRequestPathUtils#PATH_ATTRIBUTE} already exists.
*
* @author Rossen Stoyanchev
* @since 5.3
*/
public class ServletRequestPathFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
ServletRequestPathUtils.parseAndCache((HttpServletRequest) request);
try {
chain.doFilter(request, response);
}
finally {
ServletRequestPathUtils.clearParsedRequestPath(request);
}
}
}

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2002-2020 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
*
* https://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;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.RequestPath;
import org.springframework.util.Assert;
/**
* Utility class to parse the path of an {@link HttpServletRequest} to a
* {@link RequestPath} and cache it in a request attribute for further access.
* This can then be used for URL path matching with
* {@link org.springframework.web.util.pattern.PathPattern PathPattern}s.
*
* <p>Also includes helper methods to return either a previously
* {@link UrlPathHelper#resolveAndCacheLookupPath resolved} String lookupPath
* or a previously {@link #parseAndCache parsed} {@code RequestPath} depending
* on which is cached in request attributes.
*
* @author Rossen Stoyanchev
* @since 5.3
*/
public abstract class ServletRequestPathUtils {
/** Name of Servlet request attribute that holds the parsed {@link RequestPath}. */
public static final String PATH_ATTRIBUTE = ServletRequestPathUtils.class.getName() + ".path";
/**
* Parse the {@link HttpServletRequest#getRequestURI() requestURI} of the
* request and its {@code contextPath} to create a {@link RequestPath} and
* cache it in the request attribute {@link #PATH_ATTRIBUTE}.
*
* <p>This method ignores the {@link HttpServletRequest#getServletPath()
* servletPath} and the {@link HttpServletRequest#getPathInfo() pathInfo}.
* Therefore in case of a Servlet mapping by prefix, the
* {@link RequestPath#pathWithinApplication()} will always include the
* Servlet prefix.
*/
public static RequestPath parseAndCache(HttpServletRequest request) {
String requestUri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
requestUri = (requestUri != null ? requestUri : request.getRequestURI());
RequestPath requestPath = RequestPath.parse(requestUri, request.getContextPath());
request.setAttribute(PATH_ATTRIBUTE, requestPath);
return requestPath;
}
/**
* Return a {@link #parseAndCache previously} parsed and cached {@code RequestPath}.
* @throws IllegalArgumentException if not found
*/
public static RequestPath getParsedRequestPath(ServletRequest request) {
RequestPath path = (RequestPath) request.getAttribute(PATH_ATTRIBUTE);
Assert.notNull(path, "Expected parsed RequestPath in request attribute \"" + PATH_ATTRIBUTE + "\".");
return path;
}
/**
* Check for a {@link #parseAndCache previously} parsed and cached {@code RequestPath}.
*/
public static boolean hasParsedRequestPath(ServletRequest request) {
return (request.getAttribute(PATH_ATTRIBUTE) != null);
}
/**
* Remove the request attribute {@link #PATH_ATTRIBUTE} that holds a
* {@link #parseAndCache previously} parsed and cached {@code RequestPath}.
*/
public static void clearParsedRequestPath(ServletRequest request) {
request.removeAttribute(PATH_ATTRIBUTE);
}
// Methods to select either parsed RequestPath or resolved String lookupPath
/**
* Return the {@link UrlPathHelper#resolveAndCacheLookupPath pre-resolved}
* String lookupPath or the {@link #parseAndCache(HttpServletRequest)
* pre-parsed} {@code RequestPath}.
* <p>In Spring MVC, when at least one {@code HandlerMapping} has parsed
* {@code PathPatterns} enabled, the {@code DispatcherServlet} eagerly parses
* and caches the {@code RequestPath} and the same can be also done earlier with
* {@link org.springframework.web.filter.ServletRequestPathFilter
* ServletRequestPathFilter}. In other cases where {@code HandlerMapping}s
* use String pattern matching with {@code PathMatcher}, the String
* lookupPath is resolved separately by each {@code HandlerMapping}.
* @param request the current request
* @return a String lookupPath or a {@code RequestPath}
* @throws IllegalArgumentException if neither is available
*/
public static Object getCachedPath(ServletRequest request) {
// The RequestPath is pre-parsed if any HandlerMapping uses PathPatterns.
// The lookupPath is re-resolved or cleared per HandlerMapping.
// So check for lookupPath first.
String lookupPath = (String) request.getAttribute(UrlPathHelper.PATH_ATTRIBUTE);
if (lookupPath != null) {
return lookupPath;
}
RequestPath requestPath = (RequestPath) request.getAttribute(PATH_ATTRIBUTE);
if (requestPath != null) {
return requestPath.pathWithinApplication();
}
throw new IllegalArgumentException(
"Neither a pre-parsed RequestPath nor a pre-resolved String lookupPath is available.");
}
/**
* Variant of {@link #getCachedPath(ServletRequest)} that returns the path
* for request mapping as a String.
* <p>If the cached path is a {@link #parseAndCache(HttpServletRequest)
* pre-parsed} {@code RequestPath} then the returned String path value is
* encoded and with path parameters removed.
* <p>If the cached path is a {@link UrlPathHelper#resolveAndCacheLookupPath
* pre-resolved} String lookupPath, then the returned String path value
* depends on how {@link UrlPathHelper} that resolved is configured.
* @param request the current request
* @return the full request mapping path as a String
*/
public static String getCachedPathValue(ServletRequest request) {
Object path = getCachedPath(request);
if (path instanceof PathContainer) {
String value = ((PathContainer) path).value();
path = UrlPathHelper.defaultInstance.removeSemicolonContent(value);
}
return (String) path;
}
/**
* Check for a previously {@link UrlPathHelper#resolveAndCacheLookupPath
* resolved} String lookupPath or a previously {@link #parseAndCache parsed}
* {@code RequestPath}.
* @param request the current request
* @return whether a pre-resolved or pre-parsed path is available
*/
public static boolean hasCachedPath(ServletRequest request) {
return (request.getAttribute(PATH_ATTRIBUTE) != null ||
request.getAttribute(UrlPathHelper.PATH_ATTRIBUTE) != null);
}
}

View File

@@ -22,6 +22,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.MappingMatch;
@@ -29,6 +30,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -51,6 +53,13 @@ import org.springframework.util.StringUtils;
*/
public class UrlPathHelper {
/**
* Name of Servlet request attribute that holds a
* {@link #getLookupPathForRequest resolved} lookupPath.
* @since 5.3
*/
public static final String PATH_ATTRIBUTE = UrlPathHelper.class.getName() + ".path";
private static boolean isServlet4Present =
ClassUtils.isPresent("javax.servlet.http.HttpServletMapping",
UrlPathHelper.class.getClassLoader());
@@ -160,24 +169,52 @@ public class UrlPathHelper {
}
/**
* {@link #getLookupPathForRequest Resolve} the lookupPath and cache it in a
* a request attribute with the key {@link #PATH_ATTRIBUTE} for subsequent
* access via {@link #getResolvedLookupPath(ServletRequest)}.
* @param request the current request
* @return the resolved path
* @since 5.3
*/
public String resolveAndCacheLookupPath(HttpServletRequest request) {
String lookupPath = getLookupPathForRequest(request);
request.setAttribute(PATH_ATTRIBUTE, lookupPath);
return lookupPath;
}
/**
* Return a previously {@link #getLookupPathForRequest resolved} lookupPath.
* @param request the current request
* @return the previously resolved lookupPath
* @throws IllegalArgumentException if the not found
* @since 5.3
*/
public static String getResolvedLookupPath(ServletRequest request) {
String lookupPath = (String) request.getAttribute(PATH_ATTRIBUTE);
Assert.notNull(lookupPath, "Expected lookupPath in request attribute \"" + PATH_ATTRIBUTE + "\".");
return lookupPath;
}
/**
* Variant of {@link #getLookupPathForRequest(HttpServletRequest)} that
* automates checking for a previously computed lookupPath saved as a
* request attribute. The attribute is only used for lookup purposes.
* @param request current HTTP request
* @param lookupPathAttributeName the request attribute to check
* @param name the request attribute that holds the lookupPath
* @return the lookup path
* @since 5.2
* @see org.springframework.web.servlet.HandlerMapping#LOOKUP_PATH
* @deprecated as of 5.3 in favor of using
* {@link #resolveAndCacheLookupPath(HttpServletRequest)} and
* {@link #getResolvedLookupPath(ServletRequest)}.
*/
public String getLookupPathForRequest(HttpServletRequest request, @Nullable String lookupPathAttributeName) {
if (lookupPathAttributeName != null) {
String result = (String) request.getAttribute(lookupPathAttributeName);
if (result != null) {
return result;
}
@Deprecated
public String getLookupPathForRequest(HttpServletRequest request, @Nullable String name) {
String result = null;
if (name != null) {
result = (String) request.getAttribute(name);
}
return getLookupPathForRequest(request);
return (result != null ? result : getLookupPathForRequest(request));
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -15,8 +15,6 @@
*/
package org.springframework.http.server;
import java.net.URI;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -25,10 +23,10 @@ import static org.assertj.core.api.Assertions.assertThat;
* Unit tests for {@link DefaultRequestPath}.
* @author Rossen Stoyanchev
*/
public class DefaultRequestPathTests {
class DefaultRequestPathTests {
@Test
public void requestPath() throws Exception {
void requestPath() {
// basic
testRequestPath("/app/a/b/c", "/app", "/a/b/c");
@@ -52,8 +50,7 @@ public class DefaultRequestPathTests {
private void testRequestPath(String fullPath, String contextPath, String pathWithinApplication) {
URI uri = URI.create("http://localhost:8080" + fullPath);
RequestPath requestPath = RequestPath.parse(uri, contextPath);
RequestPath requestPath = RequestPath.parse(fullPath, contextPath);
Object expected = contextPath.equals("/") ? "" : contextPath;
assertThat(requestPath.contextPath().value()).isEqualTo(expected);
@@ -61,10 +58,9 @@ public class DefaultRequestPathTests {
}
@Test
public void updateRequestPath() throws Exception {
void updateRequestPath() {
URI uri = URI.create("http://localhost:8080/aA/bB/cC");
RequestPath requestPath = RequestPath.parse(uri, null);
RequestPath requestPath = RequestPath.parse("/aA/bB/cC", null);
assertThat(requestPath.contextPath().value()).isEqualTo("");
assertThat(requestPath.pathWithinApplication().value()).isEqualTo("/aA/bB/cC");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -16,44 +16,96 @@
package org.springframework.web.cors;
import org.junit.jupiter.api.Test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Function;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.http.HttpMethod;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UrlPathHelper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Unit tests for {@link UrlBasedCorsConfigurationSource}.
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
*/
public class UrlBasedCorsConfigurationSourceTests {
class UrlBasedCorsConfigurationSourceTests {
private final UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
@Test
public void empty() {
MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/bar/test.html");
assertThat(this.configSource.getCorsConfiguration(request)).isNull();
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ParameterizedTest
@MethodSource("pathPatternsArguments")
@interface PathPatternsParameterizedTest {
}
@Test
public void registerAndMatch() {
@SuppressWarnings("unused")
private static Stream<Function<String, MockHttpServletRequest>> pathPatternsArguments() {
return Stream.of(
requestUri -> {
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
ServletRequestPathUtils.parseAndCache(request);
return request;
},
requestUri -> {
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
UrlPathHelper.defaultInstance.getLookupPathForRequest(request);
return request;
}
);
}
@PathPatternsParameterizedTest
void empty(Function<String, MockHttpServletRequest> requestFactory) {
CorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
assertThat(source.getCorsConfiguration(requestFactory.apply("/bar/test.html"))).isNull();
}
@PathPatternsParameterizedTest
void registerAndMatch(Function<String, MockHttpServletRequest> requestFactory) {
CorsConfiguration config = new CorsConfiguration();
this.configSource.registerCorsConfiguration("/bar/**", config);
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/bar/**", config);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/test.html");
assertThat(this.configSource.getCorsConfiguration(request)).isNull();
MockHttpServletRequest request = requestFactory.apply("/foo/test.html");
assertThat(configSource.getCorsConfiguration(request)).isNull();
request.setRequestURI("/bar/test.html");
assertThat(this.configSource.getCorsConfiguration(request)).isEqualTo(config);
request = requestFactory.apply("/bar/test.html");
assertThat(configSource.getCorsConfiguration(request)).isEqualTo(config);
}
@Test
public void unmodifiableConfigurationsMap() {
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
this.configSource.getCorsConfigurations().put("/**", new CorsConfiguration()));
void unmodifiableConfigurationsMap() {
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.getCorsConfigurations().put("/**", new CorsConfiguration());
});
}
@Test
void allowInitLookupPath() {
CorsConfiguration config = new CorsConfiguration();
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
assertThat(source.getCorsConfiguration(request))
.as("The path should be resolved lazily by default")
.isSameAs(config);
source.setAllowInitLookupPath(false);
assertThatIllegalArgumentException().isThrownBy(() -> source.getCorsConfiguration(request));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -30,20 +30,20 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
*/
public class UrlBasedCorsConfigurationSourceTests {
class UrlBasedCorsConfigurationSourceTests {
private final UrlBasedCorsConfigurationSource configSource
= new UrlBasedCorsConfigurationSource();
@Test
public void empty() {
void empty() {
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/bar/test.html"));
assertThat(this.configSource.getCorsConfiguration(exchange)).isNull();
}
@Test
public void registerAndMatch() {
void registerAndMatch() {
CorsConfiguration config = new CorsConfiguration();
this.configSource.registerCorsConfiguration("/bar/**", config);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
@@ -44,19 +45,24 @@ public class CorsFilterTests {
private final CorsConfiguration config = new CorsConfiguration();
@BeforeEach
public void setup() throws Exception {
void setup() {
config.setAllowedOrigins(Arrays.asList("https://domain1.com", "https://domain2.com"));
config.setAllowedMethods(Arrays.asList("GET", "POST"));
config.setAllowedHeaders(Arrays.asList("header1", "header2"));
config.setExposedHeaders(Arrays.asList("header3", "header4"));
config.setMaxAge(123L);
config.setAllowCredentials(false);
filter = new CorsFilter(r -> config);
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
filter = new CorsFilter(configSource);
}
@Test
public void nonCorsRequest() throws ServletException, IOException {
void nonCorsRequest() throws ServletException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/test.html");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -69,7 +75,7 @@ public class CorsFilterTests {
}
@Test
public void sameOriginRequest() throws ServletException, IOException {
void sameOriginRequest() throws ServletException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "https://domain1.com/test.html");
request.addHeader(HttpHeaders.ORIGIN, "https://domain1.com");
@@ -86,7 +92,7 @@ public class CorsFilterTests {
}
@Test
public void validActualRequest() throws ServletException, IOException {
void validActualRequest() throws ServletException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/test.html");
request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
@@ -101,7 +107,7 @@ public class CorsFilterTests {
}
@Test
public void invalidActualRequest() throws ServletException, IOException {
void invalidActualRequest() throws ServletException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.DELETE.name(), "/test.html");
request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
@@ -115,7 +121,7 @@ public class CorsFilterTests {
}
@Test
public void validPreFlightRequest() throws ServletException, IOException {
void validPreFlightRequest() throws ServletException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.OPTIONS.name(), "/test.html");
request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
@@ -134,7 +140,7 @@ public class CorsFilterTests {
}
@Test
public void invalidPreFlightRequest() throws ServletException, IOException {
void invalidPreFlightRequest() throws ServletException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.OPTIONS.name(), "/test.html");
request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -16,11 +16,11 @@
package org.springframework.web.reactive.handler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import reactor.core.publisher.Mono;
@@ -107,15 +107,16 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
*/
@Nullable
protected Object lookupHandler(PathContainer lookupPath, ServerWebExchange exchange) throws Exception {
List<PathPattern> matches = this.handlerMap.keySet().stream()
.filter(key -> key.matches(lookupPath))
.collect(Collectors.toList());
if (matches.isEmpty()) {
List<PathPattern> matches = null;
for (PathPattern pattern : this.handlerMap.keySet()) {
if (pattern.matches(lookupPath)) {
matches = (matches != null ? matches : new ArrayList<>());
matches.add(pattern);
}
}
if (matches == null) {
return null;
}
if (matches.size() > 1) {
matches.sort(PathPattern.SPECIFICITY_COMPARATOR);
if (logger.isTraceEnabled()) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -43,7 +43,7 @@ public class SimpleUrlHandlerMappingTests {
@Test
@SuppressWarnings("resource")
public void handlerMappingJavaConfig() throws Exception {
void handlerMappingJavaConfig() {
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
wac.register(WebConfig.class);
wac.refresh();
@@ -61,7 +61,7 @@ public class SimpleUrlHandlerMappingTests {
@Test
@SuppressWarnings("resource")
public void handlerMappingXmlConfig() throws Exception {
void handlerMappingXmlConfig() {
ClassPathXmlApplicationContext wac = new ClassPathXmlApplicationContext("map.xml", getClass());
wac.refresh();
@@ -98,7 +98,7 @@ public class SimpleUrlHandlerMappingTests {
testUrl("outofpattern*ye", null, handlerMapping, null);
}
private void testUrl(String url, Object bean, HandlerMapping handlerMapping, String pathWithinMapping) {
void testUrl(String url, Object bean, HandlerMapping handlerMapping, String pathWithinMapping) {
MockServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.GET, URI.create(url)).build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
Object actual = handlerMapping.getHandler(exchange).block();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -40,6 +40,8 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@@ -50,6 +52,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.ui.context.ThemeSource;
@@ -62,7 +65,9 @@ import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.util.NestedServletException;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.WebUtils;
/**
@@ -347,6 +352,8 @@ public class DispatcherServlet extends FrameworkServlet {
@Nullable
private List<ViewResolver> viewResolvers;
private boolean parseRequestPath;
/**
* Create a new {@code DispatcherServlet} that will create its own internal web
@@ -622,6 +629,27 @@ public class DispatcherServlet extends FrameworkServlet {
"': using default strategies from DispatcherServlet.properties");
}
}
this.parseRequestPath = initRequestPathParsing(this.handlerMappings);
}
private boolean initRequestPathParsing(List<HandlerMapping> mappings) {
for (HandlerMapping mapping : mappings) {
if (mapping instanceof AbstractHandlerMapping) {
if (((AbstractHandlerMapping) mapping).getPatternParser() != null) {
return true;
}
}
if (AopUtils.isAopProxy(mapping)) {
Object target = AopProxyUtils.getSingletonTarget(mapping);
if (target instanceof AbstractHandlerMapping) {
if (((AbstractHandlerMapping) target).getPatternParser() != null) {
return true;
}
}
}
}
return false;
}
/**
@@ -939,6 +967,11 @@ public class DispatcherServlet extends FrameworkServlet {
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
RequestPath requestPath = null;
if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
requestPath = ServletRequestPathUtils.parseAndCache(request);
}
try {
doDispatch(request, response);
}
@@ -949,6 +982,9 @@ public class DispatcherServlet extends FrameworkServlet {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (requestPath != null) {
ServletRequestPathUtils.clearParsedRequestPath(request);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -16,6 +16,7 @@
package org.springframework.web.servlet;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import org.springframework.lang.Nullable;
@@ -68,7 +69,13 @@ public interface HandlerMapping {
* {@link org.springframework.web.util.UrlPathHelper} could be the full path
* or without the context path, decoded or not, etc.
* @since 5.2
* @deprecated as of 5.3 in favor of
* {@link org.springframework.web.util.UrlPathHelper#PATH_ATTRIBUTE} and
* {@link org.springframework.web.util.ServletRequestPathUtils#PATH_ATTRIBUTE}.
* To access the cached path used for request mapping, use
* {@link org.springframework.web.util.ServletRequestPathUtils#getCachedPathValue(ServletRequest)}.
*/
@Deprecated
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -21,11 +21,14 @@ import java.util.Arrays;
import java.util.List;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.pattern.PathPattern;
/**
* Assists with the creation of a {@link MappedInterceptor}.
@@ -38,9 +41,11 @@ public class InterceptorRegistration {
private final HandlerInterceptor interceptor;
private final List<String> includePatterns = new ArrayList<>();
@Nullable
private List<String> includePatterns;
private final List<String> excludePatterns = new ArrayList<>();
@Nullable
private List<String> excludePatterns;
@Nullable
private PathMatcher pathMatcher;
@@ -58,7 +63,11 @@ public class InterceptorRegistration {
/**
* Add URL patterns to which the registered interceptor should apply to.
* Add patterns for URLs the interceptor should be included in.
* <p>For pattern syntax see {@link PathPattern} when parsed patterns
* are {@link PathMatchConfigurer#setPatternParser enabled} or
* {@link AntPathMatcher} otherwise. The syntax is largely the same with
* {@link PathPattern} more tailored for web usage and more efficient.
*/
public InterceptorRegistration addPathPatterns(String... patterns) {
return addPathPatterns(Arrays.asList(patterns));
@@ -69,12 +78,18 @@ public class InterceptorRegistration {
* @since 5.0.3
*/
public InterceptorRegistration addPathPatterns(List<String> patterns) {
this.includePatterns = (this.includePatterns != null ?
this.includePatterns : new ArrayList<>(patterns.size()));
this.includePatterns.addAll(patterns);
return this;
}
/**
* Add URL patterns to which the registered interceptor should not apply to.
* Add patterns for URLs the interceptor should be excluded from.
* <p>For pattern syntax see {@link PathPattern} when parsed patterns
* are {@link PathMatchConfigurer#setPatternParser enabled} or
* {@link AntPathMatcher} otherwise. The syntax is largely the same with
* {@link PathPattern} more tailored for web usage and more efficient.
*/
public InterceptorRegistration excludePathPatterns(String... patterns) {
return excludePathPatterns(Arrays.asList(patterns));
@@ -85,15 +100,22 @@ public class InterceptorRegistration {
* @since 5.0.3
*/
public InterceptorRegistration excludePathPatterns(List<String> patterns) {
this.excludePatterns = (this.excludePatterns != null ?
this.excludePatterns : new ArrayList<>(patterns.size()));
this.excludePatterns.addAll(patterns);
return this;
}
/**
* A PathMatcher implementation to use with this interceptor. This is an optional,
* advanced property required only if using custom PathMatcher implementations
* that support mapping metadata other than the Ant path patterns supported
* by default.
* Configure the PathMatcher to use to match URL paths with against include
* and exclude patterns.
* <p>This is an advanced property that should be used only when a
* customized {@link AntPathMatcher} or a custom PathMatcher is required.
* <p>By default this is {@link AntPathMatcher}.
* <p><strong>Note:</strong> Setting {@code PathMatcher} enforces use of
* String pattern matching even when a
* {@link ServletRequestPathUtils#parseAndCache parsed} {@code RequestPath}
* is available.
*/
public InterceptorRegistration pathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
@@ -121,16 +143,20 @@ public class InterceptorRegistration {
* type is {@link MappedInterceptor}; otherwise {@link HandlerInterceptor}.
*/
protected Object getInterceptor() {
if (this.includePatterns.isEmpty() && this.excludePatterns.isEmpty()) {
if (this.includePatterns == null && this.excludePatterns == null) {
return this.interceptor;
}
String[] include = StringUtils.toStringArray(this.includePatterns);
String[] exclude = StringUtils.toStringArray(this.excludePatterns);
MappedInterceptor mappedInterceptor = new MappedInterceptor(include, exclude, this.interceptor);
MappedInterceptor mappedInterceptor = new MappedInterceptor(
StringUtils.toStringArray(this.includePatterns),
StringUtils.toStringArray(this.excludePatterns),
this.interceptor);
if (this.pathMatcher != null) {
mappedInterceptor.setPathMatcher(this.pathMatcher);
}
return mappedInterceptor;
}

View File

@@ -21,37 +21,42 @@ import java.util.Map;
import java.util.function.Predicate;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Helps with configuring HandlerMappings path matching options such as trailing
* slash match, suffix registration, path matcher and path helper.
*
* <p>Configured path matcher and path helper instances are shared for:
* Configure path matching options. The options are applied to the following:
* <ul>
* <li>RequestMappings</li>
* <li>ViewControllerMappings</li>
* <li>ResourcesMappings</li>
* <li>{@link WebMvcConfigurationSupport#requestMappingHandlerMapping}</li>
* <li>{@link WebMvcConfigurationSupport#viewControllerHandlerMapping}</li>
* <li>{@link WebMvcConfigurationSupport#resourceHandlerMapping}</li>
* </ul>
*
* @author Brian Clozel
* @since 4.0.3
* @see RequestMappingHandlerMapping
* @see org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
*/
public class PathMatchConfigurer {
@Nullable
private PathPatternParser patternParser;
@Nullable
private Boolean trailingSlashMatch;
@Nullable
private Map<String, Predicate<Class<?>>> pathPrefixes;
@Nullable
private Boolean suffixPatternMatch;
@Nullable
private Boolean registeredSuffixPatternMatch;
@Nullable
private Boolean trailingSlashMatch;
@Nullable
private UrlPathHelper urlPathHelper;
@@ -59,41 +64,24 @@ public class PathMatchConfigurer {
private PathMatcher pathMatcher;
@Nullable
private Map<String, Predicate<Class<?>>> pathPrefixes;
private UrlPathHelper defaultUrlPathHelper;
@Nullable
private PathMatcher defaultPathMatcher;
/**
* Whether to use suffix pattern match (".*") when matching patterns to
* requests. If enabled a method mapped to "/users" also matches to "/users.*".
* <p>By default this is set to {@code true}.
* @see #registeredSuffixPatternMatch
* @deprecated as of 5.2.4. See class-level note in
* {@link RequestMappingHandlerMapping} on the deprecation of path extension
* config options. As there is no replacement for this method, in 5.2.x it is
* necessary to set it to {@code false}. In 5.3 the default changes to
* {@code false} and use of this property becomes unnecessary.
* Enable use of parsed {@link PathPattern}s as described in
* {@link AbstractHandlerMapping#setPatternParser(PathPatternParser)}.
* <p><strong>Note:</strong> This is mutually exclusive with use of
* {@link #setUrlPathHelper(UrlPathHelper)} and
* {@link #setPathMatcher(PathMatcher)}.
* <p>By default this is not enabled.
* @param patternParser the parser to pre-parse patterns with
* @since 5.3
*/
@Deprecated
public PathMatchConfigurer setUseSuffixPatternMatch(Boolean suffixPatternMatch) {
this.suffixPatternMatch = suffixPatternMatch;
return this;
}
/**
* Whether suffix pattern matching should work only against path extensions
* explicitly registered when you
* {@link WebMvcConfigurer#configureContentNegotiation configure content
* negotiation}. This is generally recommended to reduce ambiguity and to
* avoid issues such as when a "." appears in the path for other reasons.
* <p>By default this is set to "false".
* @see WebMvcConfigurer#configureContentNegotiation
* @deprecated as of 5.2.4. See class-level note in
* {@link RequestMappingHandlerMapping} on the deprecation of path extension
* config options.
*/
@Deprecated
public PathMatchConfigurer setUseRegisteredSuffixPatternMatch(Boolean registeredSuffixPatternMatch) {
this.registeredSuffixPatternMatch = registeredSuffixPatternMatch;
public PathMatchConfigurer setPatternParser(PathPatternParser patternParser) {
this.patternParser = patternParser;
return this;
}
@@ -107,27 +95,6 @@ public class PathMatchConfigurer {
return this;
}
/**
* Set the UrlPathHelper to use for resolution of lookup paths.
* <p>Use this to override the default UrlPathHelper with a custom subclass,
* or to share common UrlPathHelper settings across multiple HandlerMappings
* and MethodNameResolvers.
*/
public PathMatchConfigurer setUrlPathHelper(UrlPathHelper urlPathHelper) {
this.urlPathHelper = urlPathHelper;
return this;
}
/**
* Set the PathMatcher implementation to use for matching URL paths
* against registered URL patterns. Default is AntPathMatcher.
* @see org.springframework.util.AntPathMatcher
*/
public PathMatchConfigurer setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
return this;
}
/**
* Configure a path prefix to apply to matching controller methods.
* <p>Prefixes are used to enrich the mappings of every {@code @RequestMapping}
@@ -147,16 +114,83 @@ public class PathMatchConfigurer {
return this;
}
/**
* Whether to use suffix pattern match (".*") when matching patterns to
* requests. If enabled a method mapped to "/users" also matches to "/users.*".
* <p>By default this is set to {@code true}.
* <p><strong>Note:</strong> This property is mutually exclusive with and
* ignored when {@link #setPatternParser(PathPatternParser)} is set.
* @deprecated as of 5.2.4. See class-level note in
* {@link RequestMappingHandlerMapping} on the deprecation of path extension
* config options. As there is no replacement for this method, in 5.2.x it is
* necessary to set it to {@code false}. In 5.3 the default changes to
* {@code false} and use of this property becomes unnecessary.
*/
@Deprecated
public PathMatchConfigurer setUseSuffixPatternMatch(Boolean suffixPatternMatch) {
this.suffixPatternMatch = suffixPatternMatch;
return this;
}
/**
* Whether to use registered suffixes for pattern matching.
* @deprecated as of 5.2.4, see deprecation note on
* {@link #setUseSuffixPatternMatch(Boolean)}.
* Whether suffix pattern matching should work only against path extensions
* explicitly registered when you
* {@link WebMvcConfigurer#configureContentNegotiation configure content
* negotiation}. This is generally recommended to reduce ambiguity and to
* avoid issues such as when a "." appears in the path for other reasons.
* <p>By default this is set to "false".
* <p><strong>Note:</strong> This property is mutually exclusive with and
* ignored when {@link #setPatternParser(PathPatternParser)} is set.
* @deprecated as of 5.2.4. See class-level note in
* {@link RequestMappingHandlerMapping} on the deprecation of path extension
* config options.
*/
@Deprecated
public PathMatchConfigurer setUseRegisteredSuffixPatternMatch(Boolean registeredSuffixPatternMatch) {
this.registeredSuffixPatternMatch = registeredSuffixPatternMatch;
return this;
}
/**
* Set the UrlPathHelper to use to resolve the mapping path for the application.
* <p><strong>Note:</strong> This property is mutually exclusive with and
* ignored when {@link #setPatternParser(PathPatternParser)} is set.
*/
public PathMatchConfigurer setUrlPathHelper(UrlPathHelper urlPathHelper) {
this.urlPathHelper = urlPathHelper;
return this;
}
/**
* Set the PathMatcher to use for String pattern matching.
* <p>By default this is {@link AntPathMatcher}.
* <p><strong>Note:</strong> This property is mutually exclusive with and
* ignored when {@link #setPatternParser(PathPatternParser)} is set.
*/
public PathMatchConfigurer setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
return this;
}
/**
* Return the {@link PathPatternParser} to use, if configured.
* @since 5.3
*/
@Nullable
public PathPatternParser getPatternParser() {
return this.patternParser;
}
@Nullable
@Deprecated
public Boolean isUseSuffixPatternMatch() {
return this.suffixPatternMatch;
public Boolean isUseTrailingSlashMatch() {
return this.trailingSlashMatch;
}
@Nullable
protected Map<String, Predicate<Class<?>>> getPathPrefixes() {
return this.pathPrefixes;
}
/**
@@ -170,9 +204,15 @@ public class PathMatchConfigurer {
return this.registeredSuffixPatternMatch;
}
/**
* Whether to use registered suffixes for pattern matching.
* @deprecated as of 5.2.4, see deprecation note on
* {@link #setUseSuffixPatternMatch(Boolean)}.
*/
@Nullable
public Boolean isUseTrailingSlashMatch() {
return this.trailingSlashMatch;
@Deprecated
public Boolean isUseSuffixPatternMatch() {
return this.suffixPatternMatch;
}
@Nullable
@@ -185,8 +225,32 @@ public class PathMatchConfigurer {
return this.pathMatcher;
}
@Nullable
protected Map<String, Predicate<Class<?>>> getPathPrefixes() {
return this.pathPrefixes;
/**
* Return the configure UrlPathHelper instance or a default (shared) instance.
* @since 5.3
*/
protected UrlPathHelper getUrlPathHelperOrDefault() {
if (this.urlPathHelper != null) {
return this.urlPathHelper;
}
if (this.defaultUrlPathHelper == null) {
this.defaultUrlPathHelper = new UrlPathHelper();
}
return this.defaultUrlPathHelper;
}
/**
* Return the configure PathMatcher instance or a default (shared) instance.
* @since 5.3
*/
protected PathMatcher getPathMatcherOrDefault() {
if (this.pathMatcher != null) {
return this.pathMatcher;
}
if (this.defaultPathMatcher == null) {
this.defaultPathMatcher = new AntPathMatcher();
}
return this.defaultPathMatcher;
}
}

View File

@@ -28,6 +28,7 @@ import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.accept.ContentNegotiationManager;
@@ -36,18 +37,24 @@ import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern;
/**
* Stores registrations of resource handlers for serving static resources such as images, css files and others
* through Spring MVC including setting cache headers optimized for efficient loading in a web browser.
* Resources can be served out of locations under web application root, from the classpath, and others.
* Stores registrations of resource handlers for serving static resources such
* as images, css files and others through Spring MVC including setting cache
* headers optimized for efficient loading in a web browser. Resources can be
* served out of locations under web application root, from the classpath, and
* others.
*
* <p>To create a resource handler, use {@link #addResourceHandler(String...)} providing the URL path patterns
* for which the handler should be invoked to serve static resources (e.g. {@code "/resources/**"}).
* <p>To create a resource handler, use {@link #addResourceHandler(String...)}
* providing the URL path patterns for which the handler should be invoked to
* serve static resources (e.g. {@code "/resources/**"}).
*
* <p>Then use additional methods on the returned {@link ResourceHandlerRegistration} to add one or more
* locations from which to serve static content from (e.g. {{@code "/"},
* {@code "classpath:/META-INF/public-web-resources/"}}) or to specify a cache period for served resources.
* <p>Then use additional methods on the returned
* {@link ResourceHandlerRegistration} to add one or more locations from which
* to serve static content from (e.g. {{@code "/"},
* {@code "classpath:/META-INF/public-web-resources/"}}) or to specify a cache
* period for served resources.
*
* @author Rossen Stoyanchev
* @since 3.1
@@ -110,13 +117,14 @@ public class ResourceHandlerRegistry {
/**
* Add a resource handler for serving static resources based on the specified URL path patterns.
* The handler will be invoked for every incoming request that matches to one of the specified
* path patterns.
* <p>Patterns like {@code "/static/**"} or {@code "/css/{filename:\\w+\\.css}"} are allowed.
* See {@link org.springframework.util.AntPathMatcher} for more details on the syntax.
* @return a {@link ResourceHandlerRegistration} to use to further configure the
* registered resource handler
* Add a resource handler to serve static resources. The handler is invoked
* for requests that match one of the specified URL path patterns.
* <p>Patterns such as {@code "/static/**"} or {@code "/css/{filename:\\w+\\.css}"}
* are supported.
* <p>For pattern syntax see {@link PathPattern} when parsed patterns
* are {@link PathMatchConfigurer#setPatternParser enabled} or
* {@link AntPathMatcher} otherwise. The syntax is largely the same with
* {@link PathPattern} more tailored for web usage and more efficient.
*/
public ResourceHandlerRegistration addResourceHandler(String... pathPatterns) {
ResourceHandlerRegistration registration = new ResourceHandlerRegistration(pathPatterns);

View File

@@ -24,7 +24,9 @@ import java.util.Map;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;
/**
* Assists with the registration of simple automated controllers pre-configured
@@ -56,27 +58,34 @@ public class ViewControllerRegistry {
/**
* Map a view controller to the given URL path (or pattern) in order to render
* a response with a pre-configured status code and view.
* <p>Patterns like {@code "/admin/**"} or {@code "/articles/{articlename:\\w+}"}
* are allowed. See {@link org.springframework.util.AntPathMatcher} for more details on the
* syntax.
* Map a URL path or pattern to a view controller to render a response with
* the configured status code and view.
* <p>Patterns such as {@code "/admin/**"} or {@code "/articles/{articlename:\\w+}"}
* are supported. For pattern syntax see {@link PathPattern} when parsed
* patterns are {@link PathMatchConfigurer#setPatternParser enabled} or
* {@link AntPathMatcher} otherwise. The syntax is largely the same with
* {@link PathPattern} more tailored for web usage and more efficient.
* <p><strong>Note:</strong> If an {@code @RequestMapping} method is mapped
* to a URL for any HTTP method then a view controller cannot handle the
* same URL. For this reason it is recommended to avoid splitting URL
* handling across an annotated controller and a view controller.
*/
public ViewControllerRegistration addViewController(String urlPath) {
ViewControllerRegistration registration = new ViewControllerRegistration(urlPath);
public ViewControllerRegistration addViewController(String urlPathOrPattern) {
ViewControllerRegistration registration = new ViewControllerRegistration(urlPathOrPattern);
registration.setApplicationContext(this.applicationContext);
this.registrations.add(registration);
return registration;
}
/**
* Map a view controller to the given URL path (or pattern) in order to redirect
* to another URL. By default the redirect URL is expected to be relative to
* the current ServletContext, i.e. as relative to the web application root.
* Map a view controller to the given URL path or pattern in order to redirect
* to another URL.
* <p>For pattern syntax see {@link PathPattern} when parsed patterns
* are {@link PathMatchConfigurer#setPatternParser enabled} or
* {@link AntPathMatcher} otherwise. The syntax is largely the same with
* {@link PathPattern} more tailored for web usage and more efficient.
* <p>By default the redirect URL is expected to be relative to the current
* ServletContext, i.e. as relative to the web application root.
* @since 4.1
*/
public RedirectViewControllerRegistration addRedirectViewController(String urlPath, String redirectUrl) {
@@ -89,6 +98,10 @@ public class ViewControllerRegistry {
/**
* Map a simple controller to the given URL path (or pattern) in order to
* set the response status to the given code without rendering a body.
* <p>For pattern syntax see {@link PathPattern} when parsed patterns
* are {@link PathMatchConfigurer#setPatternParser enabled} or
* {@link AntPathMatcher} otherwise. The syntax is largely the same with
* {@link PathPattern} more tailored for web usage and more efficient.
* @since 4.1
*/
public void addStatusController(String urlPath, HttpStatus statusCode) {

View File

@@ -22,7 +22,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
import javax.servlet.ServletContext;
@@ -287,32 +286,29 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
mapping.setContentNegotiationManager(contentNegotiationManager);
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
PathMatchConfigurer pathConfig = getPathMatchConfigurer();
if (pathConfig.getPatternParser() != null) {
mapping.setPatternParser(pathConfig.getPatternParser());
}
else {
mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
if (useSuffixPatternMatch != null) {
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
Boolean useSuffixPatternMatch = pathConfig.isUseSuffixPatternMatch();
if (useSuffixPatternMatch != null) {
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
}
Boolean useRegisteredSuffixPatternMatch = pathConfig.isUseRegisteredSuffixPatternMatch();
if (useRegisteredSuffixPatternMatch != null) {
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
}
}
Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
if (useRegisteredSuffixPatternMatch != null) {
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
}
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
Boolean useTrailingSlashMatch = pathConfig.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
UrlPathHelper pathHelper = configurer.getUrlPathHelper();
if (pathHelper != null) {
mapping.setUrlPathHelper(pathHelper);
}
PathMatcher pathMatcher = configurer.getPathMatcher();
if (pathMatcher != null) {
mapping.setPathMatcher(pathMatcher);
}
Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
if (pathPrefixes != null) {
mapping.setPathPrefixes(pathPrefixes);
if (pathConfig.getPathPrefixes() != null) {
mapping.setPathPrefixes(pathConfig.getPathPrefixes());
}
return mapping;
@@ -335,6 +331,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
protected final Object[] getInterceptors(
FormattingConversionService mvcConversionService,
ResourceUrlProvider mvcResourceUrlProvider) {
if (this.interceptors == null) {
InterceptorRegistry registry = new InterceptorRegistry();
addInterceptors(registry);
@@ -374,30 +371,31 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
protected void configurePathMatch(PathMatchConfigurer configurer) {
}
/**
* Return a global {@link PathMatcher} instance for path matching
* patterns in {@link HandlerMapping HandlerMappings}.
* This instance can be configured using the {@link PathMatchConfigurer}
* in {@link #configurePathMatch(PathMatchConfigurer)}.
* @since 4.1
*/
@Bean
public PathMatcher mvcPathMatcher() {
PathMatcher pathMatcher = getPathMatchConfigurer().getPathMatcher();
return (pathMatcher != null ? pathMatcher : new AntPathMatcher());
}
/**
* Return a global {@link UrlPathHelper} instance for path matching
* patterns in {@link HandlerMapping HandlerMappings}.
* This instance can be configured using the {@link PathMatchConfigurer}
* in {@link #configurePathMatch(PathMatchConfigurer)}.
* Return a global {@link UrlPathHelper} instance which is used to resolve
* the request mapping path for an application. The instance can be
* configured via {@link #configurePathMatch(PathMatchConfigurer)}.
* <p><b>Note:</b> This is only used when parsed patterns are not
* {@link PathMatchConfigurer#setPatternParser enabled}.
* @since 4.1
*/
@Bean
public UrlPathHelper mvcUrlPathHelper() {
UrlPathHelper pathHelper = getPathMatchConfigurer().getUrlPathHelper();
return (pathHelper != null ? pathHelper : new UrlPathHelper());
return getPathMatchConfigurer().getUrlPathHelperOrDefault();
}
/**
* Return a global {@link PathMatcher} instance which is used for URL path
* matching with String patterns. The returned instance can be configured
* using {@link #configurePathMatch(PathMatchConfigurer)}.
* <p><b>Note:</b> This is only used when parsed patterns are not
* {@link PathMatchConfigurer#setPatternParser enabled}.
* @since 4.1
*/
@Bean
public PathMatcher mvcPathMatcher() {
return getPathMatchConfigurer().getPathMatcherOrDefault();
}
/**
@@ -451,10 +449,9 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
@Bean
@Nullable
public HandlerMapping viewControllerHandlerMapping(
@Qualifier("mvcPathMatcher") PathMatcher pathMatcher,
@Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext);
addViewControllers(registry);
@@ -462,8 +459,14 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
if (handlerMapping == null) {
return null;
}
handlerMapping.setPathMatcher(pathMatcher);
handlerMapping.setUrlPathHelper(urlPathHelper);
PathMatchConfigurer pathConfig = getPathMatchConfigurer();
if (pathConfig.getPatternParser() != null) {
handlerMapping.setPatternParser(pathConfig.getPatternParser());
}
else {
handlerMapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
handlerMapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
}
handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
handlerMapping.setCorsConfigurations(getCorsConfigurations());
return handlerMapping;
@@ -524,8 +527,6 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
@Bean
@Nullable
public HandlerMapping resourceHandlerMapping(
@Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper,
@Qualifier("mvcPathMatcher") PathMatcher pathMatcher,
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
@@ -533,16 +534,23 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
Assert.state(this.applicationContext != null, "No ApplicationContext set");
Assert.state(this.servletContext != null, "No ServletContext set");
PathMatchConfigurer pathConfig = getPathMatchConfigurer();
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
this.servletContext, contentNegotiationManager, urlPathHelper);
this.servletContext, contentNegotiationManager, pathConfig.getUrlPathHelper());
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
if (handlerMapping == null) {
return null;
}
handlerMapping.setPathMatcher(pathMatcher);
handlerMapping.setUrlPathHelper(urlPathHelper);
if (pathConfig.getPatternParser() != null) {
handlerMapping.setPatternParser(pathConfig.getPatternParser());
}
else {
handlerMapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
handlerMapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
}
handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
handlerMapping.setCorsConfigurations(getCorsConfigurations());
return handlerMapping;
@@ -562,14 +570,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
@Bean
public ResourceUrlProvider mvcResourceUrlProvider() {
ResourceUrlProvider urlProvider = new ResourceUrlProvider();
UrlPathHelper pathHelper = getPathMatchConfigurer().getUrlPathHelper();
if (pathHelper != null) {
urlProvider.setUrlPathHelper(pathHelper);
}
PathMatcher pathMatcher = getPathMatchConfigurer().getPathMatcher();
if (pathMatcher != null) {
urlProvider.setPathMatcher(pathMatcher);
}
urlProvider.setUrlPathHelper(getPathMatchConfigurer().getUrlPathHelperOrDefault());
urlProvider.setPathMatcher(getPathMatchConfigurer().getPathMatcherOrDefault());
return urlProvider;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -29,6 +29,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
/**
@@ -47,15 +48,11 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
public interface WebMvcConfigurer {
/**
* Helps with configuring HandlerMappings path matching options such as trailing slash match,
* suffix registration, path matcher and path helper.
* Configured path matcher and path helper instances are shared for:
* <ul>
* <li>RequestMappings</li>
* <li>ViewControllerMappings</li>
* <li>ResourcesMappings</li>
* </ul>
* Help with configuring {@link HandlerMapping} path matching options such as
* whether to use parsed {@code PathPatterns} or String pattern matching
* with {@code PathMatcher}, whether to match trailing slashes, and more.
* @since 4.0.3
* @see PathMatchConfigurer
*/
default void configurePathMatch(PathMatchConfigurer configurer) {
}
@@ -101,6 +98,7 @@ public interface WebMvcConfigurer {
* Add handlers to serve static resources such as images, js, and, css
* files from specific locations under web application root, the classpath,
* and others.
* @see ResourceHandlerRegistry
*/
default void addResourceHandlers(ResourceHandlerRegistry registry) {
}
@@ -118,6 +116,7 @@ public interface WebMvcConfigurer {
* cases where there is no need for custom controller logic -- e.g. render a
* home page, perform simple site URL redirects, return a 404 status with
* HTML content, a 204 with no content, and more.
* @see ViewControllerRegistry
*/
default void addViewControllers(ViewControllerRegistry registry) {
}

View File

@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.function;
import java.io.IOException;
@@ -53,6 +52,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
@@ -62,10 +62,9 @@ import org.springframework.util.ObjectUtils;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UrlPathHelper;
/**
* {@code ServerRequest} implementation based on a {@link HttpServletRequest}.
@@ -77,7 +76,7 @@ class DefaultServerRequest implements ServerRequest {
private final ServletServerHttpRequest serverHttpRequest;
private final PathContainer pathContainer;
private final RequestPath requestPath;
private final Headers headers;
@@ -102,7 +101,7 @@ class DefaultServerRequest implements ServerRequest {
this.params = CollectionUtils.toMultiValueMap(new ServletParametersMap(servletRequest));
this.attributes = new ServletAttributesMap(servletRequest);
this.pathContainer = PathContainer.parsePath(path());
this.requestPath = ServletRequestPathUtils.getParsedRequestPath(servletRequest);
}
private static List<MediaType> allSupportedMediaTypes(List<HttpMessageConverter<?>> messageConverters) {
@@ -130,16 +129,12 @@ class DefaultServerRequest implements ServerRequest {
@Override
public String path() {
String path = (String) servletRequest().getAttribute(HandlerMapping.LOOKUP_PATH);
if (path == null) {
path = UrlPathHelper.defaultInstance.getLookupPathForRequest(servletRequest());
}
return path;
return pathContainer().value();
}
@Override
public PathContainer pathContainer() {
return this.pathContainer;
return this.requestPath.pathWithinApplication();
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -166,8 +166,6 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
@Nullable
@Override
protected Object getHandlerInternal(HttpServletRequest servletRequest) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(servletRequest);
servletRequest.setAttribute(LOOKUP_PATH, lookupPath);
if (this.routerFunction != null) {
ServerRequest request = ServerRequest.create(servletRequest, this.messageConverters);
servletRequest.setAttribute(RouterFunctions.REQUEST_ATTRIBUTE, request);

View File

@@ -30,9 +30,11 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.core.Ordered;
import org.springframework.http.server.RequestPath;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.context.request.WebRequestInterceptor;
@@ -48,7 +50,10 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Abstract base class for {@link org.springframework.web.servlet.HandlerMapping}
@@ -64,9 +69,6 @@ import org.springframework.web.util.UrlPathHelper;
* @since 07.04.2003
* @see #getHandlerInternal
* @see #setDefaultHandler
* @see #setAlwaysUseFullPath
* @see #setUrlDecode
* @see org.springframework.util.AntPathMatcher
* @see #setInterceptors
* @see org.springframework.web.servlet.HandlerInterceptor
*/
@@ -76,6 +78,9 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
@Nullable
private Object defaultHandler;
@Nullable
private PathPatternParser patternParser;
private UrlPathHelper urlPathHelper = new UrlPathHelper();
private PathMatcher pathMatcher = new AntPathMatcher();
@@ -114,9 +119,57 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
/**
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
* Enable use of pre-parsed {@link PathPattern}s as an alternative to
* String pattern matching with {@link AntPathMatcher}. The syntax is
* largely the same but the {@code PathPattern} syntax is more tailored for
* web applications, and its implementation is more efficient.
* <p>This property is mutually exclusive with the following others which
* are effectively ignored when this is set:
* <ul>
* <li>{@link #setAlwaysUseFullPath} -- {@code PathPatterns} always use the
* full path and ignore the servletPath/pathInfo which are decoded and
* partially normalized and therefore not comparable against the
* {@link HttpServletRequest#getRequestURI() requestURI}.
* <li>{@link #setRemoveSemicolonContent} -- {@code PathPatterns} always
* ignore semicolon content for path matching purposes, but path parameters
* remain available for use in controllers via {@code @MatrixVariable}.
* <li>{@link #setUrlDecode} -- {@code PathPatterns} match one decoded path
* segment at a time and never need the full decoded path which can cause
* issues due to decoded reserved characters.
* <li>{@link #setUrlPathHelper} -- the request path is pre-parsed globally
* by the {@link org.springframework.web.servlet.DispatcherServlet
* DispatcherServlet} or by
* {@link org.springframework.web.filter.ServletRequestPathFilter
* ServletRequestPathFilter} using {@link ServletRequestPathUtils} and saved
* in a request attribute for re-use.
* <li>{@link #setPathMatcher} -- patterns are parsed to {@code PathPatterns}
* and used instead of String matching with {@code PathMatcher}.
* </ul>
* <p>By default this is not set.
* @param patternParser the parser to use
* @since 5.3
*/
public void setPatternParser(PathPatternParser patternParser) {
this.patternParser = patternParser;
}
/**
* Return the {@link #setPatternParser(PathPatternParser) configured}
* {@code PathPatternParser}, or {@code null}.
* @since 5.3
*/
@Nullable
public PathPatternParser getPatternParser() {
return this.patternParser;
}
/**
* Shortcut to same property on the configured {@code UrlPathHelper}.
* <p><strong>Note:</strong> This property is mutually exclusive with and
* ignored when {@link #setPatternParser(PathPatternParser)} is set.
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath(boolean)
*/
@SuppressWarnings("deprecation")
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) {
@@ -125,9 +178,12 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
/**
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
* Shortcut to same property on the underlying {@code UrlPathHelper}.
* <p><strong>Note:</strong> This property is mutually exclusive with and
* ignored when {@link #setPatternParser(PathPatternParser)} is set.
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode(boolean)
*/
@SuppressWarnings("deprecation")
public void setUrlDecode(boolean urlDecode) {
this.urlPathHelper.setUrlDecode(urlDecode);
if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) {
@@ -136,9 +192,12 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
/**
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
* Shortcut to same property on the underlying {@code UrlPathHelper}.
* <p><strong>Note:</strong> This property is mutually exclusive with and
* ignored when {@link #setPatternParser(PathPatternParser)} is set.
* @see org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean)
*/
@SuppressWarnings("deprecation")
public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) {
@@ -147,11 +206,11 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
/**
* Set the UrlPathHelper to use for resolution of lookup paths.
* <p>Use this to override the default UrlPathHelper with a custom subclass,
* or to share common UrlPathHelper settings across multiple HandlerMappings
* and MethodNameResolvers.
* Configure the UrlPathHelper to use for resolution of lookup paths.
* <p><strong>Note:</strong> This property is mutually exclusive with and
* ignored when {@link #setPatternParser(PathPatternParser)} is set.
*/
@SuppressWarnings("deprecation")
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
this.urlPathHelper = urlPathHelper;
@@ -161,15 +220,17 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
/**
* Return the UrlPathHelper implementation to use for resolution of lookup paths.
* Return the {@link #setUrlPathHelper configured} {@code UrlPathHelper}.
*/
public UrlPathHelper getUrlPathHelper() {
return this.urlPathHelper;
}
/**
* Set the PathMatcher implementation to use for matching URL paths
* against registered URL patterns. Default is AntPathMatcher.
* Configure the PathMatcher to use.
* <p><strong>Note:</strong> This property is mutually exclusive with and
* ignored when {@link #setPatternParser(PathPatternParser)} is set.
* <p>By default this is {@link AntPathMatcher}.
* @see org.springframework.util.AntPathMatcher
*/
public void setPathMatcher(PathMatcher pathMatcher) {
@@ -181,8 +242,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
/**
* Return the PathMatcher implementation to use for matching URL paths
* against registered URL patterns.
* Return the {@link #setPathMatcher configured} {@code PathMatcher}.
*/
public PathMatcher getPathMatcher() {
return this.pathMatcher;
@@ -190,48 +250,74 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
/**
* Set the interceptors to apply for all handlers mapped by this handler mapping.
* <p>Supported interceptor types are HandlerInterceptor, WebRequestInterceptor, and MappedInterceptor.
* <p>Supported interceptor types are {@link HandlerInterceptor},
* {@link WebRequestInterceptor}, and {@link MappedInterceptor}.
* Mapped interceptors apply only to request URLs that match its path patterns.
* Mapped interceptor beans are also detected by type during initialization.
* @param interceptors array of handler interceptors
* @see #adaptInterceptor
* @see org.springframework.web.servlet.HandlerInterceptor
* @see org.springframework.web.context.request.WebRequestInterceptor
* @see MappedInterceptor
*/
public void setInterceptors(Object... interceptors) {
this.interceptors.addAll(Arrays.asList(interceptors));
}
/**
* Set the "global" CORS configurations based on URL patterns. By default the first
* matching URL pattern is combined with the CORS configuration for the handler, if any.
* Set "global" CORS configuration mappings. The first matching URL pattern
* determines the {@code CorsConfiguration} to use which is then further
* {@link CorsConfiguration#combine(CorsConfiguration) combined} with the
* {@code CorsConfiguration} for the selected handler.
* <p>This is mutually exclusie with
* {@link #setCorsConfigurationSource(CorsConfigurationSource)}.
* @since 4.2
* @see #setCorsConfigurationSource(CorsConfigurationSource)
* @see #setCorsProcessor(CorsProcessor)
*/
public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
Assert.notNull(corsConfigurations, "corsConfigurations must not be null");
if (!corsConfigurations.isEmpty()) {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
if (CollectionUtils.isEmpty(corsConfigurations)) {
this.corsConfigurationSource = null;
return;
}
UrlBasedCorsConfigurationSource source;
if (getPatternParser() != null) {
source = new UrlBasedCorsConfigurationSource(getPatternParser());
source.setCorsConfigurations(corsConfigurations);
}
else {
source = new UrlBasedCorsConfigurationSource();
source.setCorsConfigurations(corsConfigurations);
source.setPathMatcher(this.pathMatcher);
source.setUrlPathHelper(this.urlPathHelper);
source.setLookupPathAttributeName(LOOKUP_PATH);
this.corsConfigurationSource = source;
}
else {
this.corsConfigurationSource = null;
setCorsConfigurationSource(source);
}
/**
* Set a {@code CorsConfigurationSource} for "global" CORS config. The
* {@code CorsConfiguration} determined by the source is
* {@link CorsConfiguration#combine(CorsConfiguration) combined} with the
* {@code CorsConfiguration} for the selected handler.
* <p>This is mutually exclusie with {@link #setCorsConfigurations(Map)}.
* @since 5.1
* @see #setCorsProcessor(CorsProcessor)
*/
public void setCorsConfigurationSource(CorsConfigurationSource source) {
Assert.notNull(source, "CorsConfigurationSource must not be null");
this.corsConfigurationSource = source;
if (source instanceof UrlBasedCorsConfigurationSource) {
((UrlBasedCorsConfigurationSource) source).setAllowInitLookupPath(false);
}
}
/**
* Set the "global" CORS configuration source. By default the first matching URL
* pattern is combined with the CORS configuration for the handler, if any.
* @since 5.1
* @see #setCorsConfigurations(Map)
* Return the {@link #setCorsConfigurationSource(CorsConfigurationSource)
* configured} {@code CorsConfigurationSource}, if any.
* @since 5.3
*/
public void setCorsConfigurationSource(CorsConfigurationSource corsConfigurationSource) {
Assert.notNull(corsConfigurationSource, "corsConfigurationSource must not be null");
this.corsConfigurationSource = corsConfigurationSource;
@Nullable
public CorsConfigurationSource getCorsConfigurationSource() {
return this.corsConfigurationSource;
}
/**
@@ -301,22 +387,22 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
/**
* Detect beans of type {@link MappedInterceptor} and add them to the list of mapped interceptors.
* <p>This is called in addition to any {@link MappedInterceptor MappedInterceptors} that may have been provided
* via {@link #setInterceptors}, by default adding all beans of type {@link MappedInterceptor}
* from the current context and its ancestors. Subclasses can override and refine this policy.
* @param mappedInterceptors an empty list to add {@link MappedInterceptor} instances to
* Detect beans of type {@link MappedInterceptor} and add them to the list
* of mapped interceptors.
* <p>This is called in addition to any {@link MappedInterceptor}s that may
* have been provided via {@link #setInterceptors}, by default adding all
* beans of type {@link MappedInterceptor} from the current context and its
* ancestors. Subclasses can override and refine this policy.
* @param mappedInterceptors an empty list to add to
*/
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
mappedInterceptors.addAll(
BeanFactoryUtils.beansOfTypeIncludingAncestors(
obtainApplicationContext(), MappedInterceptor.class, true, false).values());
mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(
obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}
/**
* Initialize the specified interceptors, checking for {@link MappedInterceptor MappedInterceptors} and
* adapting {@link HandlerInterceptor}s and {@link WebRequestInterceptor HandlerInterceptor}s and
* {@link WebRequestInterceptor}s if necessary.
* Initialize the specified interceptors adapting
* {@link WebRequestInterceptor}s to {@link HandlerInterceptor}.
* @see #setInterceptors
* @see #adaptInterceptor
*/
@@ -333,13 +419,13 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
/**
* Adapt the given interceptor object to the {@link HandlerInterceptor} interface.
* <p>By default, the supported interceptor types are {@link HandlerInterceptor}
* and {@link WebRequestInterceptor}. Each given {@link WebRequestInterceptor}
* will be wrapped in a {@link WebRequestHandlerInterceptorAdapter}.
* Can be overridden in subclasses.
* @param interceptor the specified interceptor object
* @return the interceptor wrapped as HandlerInterceptor
* Adapt the given interceptor object to {@link HandlerInterceptor}.
* <p>By default, the supported interceptor types are
* {@link HandlerInterceptor} and {@link WebRequestInterceptor}. Each given
* {@link WebRequestInterceptor} is wrapped with
* {@link WebRequestHandlerInterceptorAdapter}.
* @param interceptor the interceptor
* @return the interceptor downcast or adapted to HandlerInterceptor
* @see org.springframework.web.servlet.HandlerInterceptor
* @see org.springframework.web.context.request.WebRequestInterceptor
* @see WebRequestHandlerInterceptorAdapter
@@ -358,7 +444,8 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
/**
* Return the adapted interceptors as {@link HandlerInterceptor} array.
* @return the array of {@link HandlerInterceptor HandlerInterceptors}, or {@code null} if none
* @return the array of {@link HandlerInterceptor HandlerInterceptors}, or
* {@code null} if none
*/
@Nullable
protected final HandlerInterceptor[] getAdaptedInterceptors() {
@@ -367,8 +454,8 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
/**
* Return all configured {@link MappedInterceptor MappedInterceptors} as an array.
* @return the array of {@link MappedInterceptor MappedInterceptors}, or {@code null} if none
* Return all configured {@link MappedInterceptor}s as an array.
* @return the array of {@link MappedInterceptor}s, or {@code null} if none
*/
@Nullable
protected final MappedInterceptor[] getMappedInterceptors() {
@@ -415,9 +502,11 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
CorsConfiguration config = getCorsConfiguration(handler, request);
if (getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
config = (globalConfig != null ? globalConfig.combine(config) : config);
}
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
@@ -443,11 +532,35 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
@Nullable
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
/**
* Initialize the path to use for request mapping.
* <p>When parsed patterns are {@link #setPatternParser(PathPatternParser)
* enabled} a parsed {@code RequestPath} is expected to have been
* {@link ServletRequestPathUtils#parseAndCache(HttpServletRequest) parsed}
* externally by the {@link org.springframework.web.servlet.DispatcherServlet}
* or {@link org.springframework.web.filter.ServletRequestPathFilter}.
* <p>Otherwise for String pattern matching via {@code PathMatcher} the
* path is {@link UrlPathHelper#resolveAndCacheLookupPath resolved} by this
* method.
* @since 5.3
*/
protected String initLookupPath(HttpServletRequest request) {
if (getPatternParser() != null) {
request.removeAttribute(UrlPathHelper.PATH_ATTRIBUTE);
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request);
String lookupPath = requestPath.pathWithinApplication().value();
return UrlPathHelper.defaultInstance.removeSemicolonContent(lookupPath);
}
else {
return getUrlPathHelper().resolveAndCacheLookupPath(request);
}
}
/**
* Build a {@link HandlerExecutionChain} for the given handler, including
* applicable interceptors.
* <p>The default implementation builds a standard {@link HandlerExecutionChain}
* with the given handler, the handler mapping's common interceptors, and any
* with the given handler, the handler mappings common interceptors, and any
* {@link MappedInterceptor MappedInterceptors} matching to the current request URL. Interceptors
* are added in the order they were registered. Subclasses may override this
* in order to extend/rearrange the list of interceptors.
@@ -467,11 +580,10 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}

View File

@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -357,8 +358,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
*/
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
String lookupPath = initLookupPath(request);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
@@ -456,7 +456,8 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
@Override
protected boolean hasCorsConfigurationSource(Object handler) {
return super.hasCorsConfigurationSource(handler) ||
(handler instanceof HandlerMethod && this.mappingRegistry.getCorsConfiguration((HandlerMethod) handler) != null);
(handler instanceof HandlerMethod &&
this.mappingRegistry.getCorsConfiguration((HandlerMethod) handler) != null);
}
@Override
@@ -498,9 +499,27 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
/**
* Extract and return the URL paths contained in the supplied mapping.
* @deprecated as of 5.3 in favor of providing non-pattern mappings via
* {@link #getDirectPaths(Object)} instead
*/
@Deprecated
protected abstract Set<String> getMappingPathPatterns(T mapping);
/**
* Return the request mapping paths that are not patterns.
* @since 5.3
*/
protected Set<String> getDirectPaths(T mapping) {
Set<String> urls = Collections.emptySet();
for (String path : getMappingPathPatterns(mapping)) {
if (!getPathMatcher().isPattern(path)) {
urls = (urls.isEmpty() ? new HashSet<>(1) : urls);
urls.add(path);
}
}
return urls;
}
/**
* Check if a mapping matches the current request and return a (potentially
* new) mapping with conditions relevant to the current request.
@@ -589,8 +608,8 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
public void register(T mapping, Object handler, Method method) {
// Assert that the handler method is not a suspending one.
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
Class<?>[] parameterTypes = method.getParameterTypes();
if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
Class<?>[] types = method.getParameterTypes();
if ((types.length > 0) && "kotlin.coroutines.Continuation".equals(types[types.length - 1].getName())) {
throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
}
}
@@ -600,7 +619,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
validateMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
Set<String> directUrls = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
@@ -634,16 +653,6 @@ 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;
}
private void addMappingName(String name, HandlerMethod handlerMethod) {
List<HandlerMethod> oldList = this.nameLookup.get(name);
if (oldList == null) {
@@ -722,19 +731,19 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
private final HandlerMethod handlerMethod;
private final List<String> directUrls;
private final Set<String> directUrls;
@Nullable
private final String mappingName;
public MappingRegistration(T mapping, HandlerMethod handlerMethod,
@Nullable List<String> directUrls, @Nullable String mappingName) {
@Nullable Set<String> directUrls, @Nullable String mappingName) {
Assert.notNull(mapping, "Mapping must not be null");
Assert.notNull(handlerMethod, "HandlerMethod must not be null");
this.mapping = mapping;
this.handlerMethod = handlerMethod;
this.directUrls = (directUrls != null ? directUrls : Collections.emptyList());
this.directUrls = (directUrls != null ? directUrls : Collections.emptySet());
this.mappingName = mappingName;
}
@@ -746,7 +755,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
return this.handlerMethod;
}
public List<String> getDirectUrls() {
public Set<String> getDirectUrls() {
return this.directUrls;
}

View File

@@ -28,26 +28,33 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.RequestPath;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Abstract base class for URL-mapped {@link org.springframework.web.servlet.HandlerMapping}
* implementations. Provides infrastructure for mapping handlers to URLs and configurable
* URL lookup. For information on the latter, see "alwaysUseFullPath" property.
* Abstract base class for URL-mapped {@link HandlerMapping} implementations.
*
* <p>Supports direct matches, e.g. a registered "/test" matches "/test", and
* various Ant-style pattern matches, e.g. a registered "/t*" pattern matches
* both "/test" and "/team", "/test/*" matches all paths in the "/test" directory,
* "/test/**" matches all paths below "/test". For details, see the
* {@link org.springframework.util.AntPathMatcher AntPathMatcher} javadoc.
* <p>Supports literal matches and pattern matches such as "/test/*", "/test/**",
* and others. For details on pattern syntax refer to {@link PathPattern} when
* parsed patterns are {@link #setPatternParser(PathPatternParser) enabled} or
* see {@link AntPathMatcher} otherwise. The syntax is largely the same but the
* {@code PathPattern} syntax is more tailored for web applications, and its
* implementation is more efficient.
*
* <p>Will search all path patterns to find the most exact match for the
* current request path. The most exact match is defined as the longest
* path pattern that matches the current request path.
* <p>All path patterns are checked in order to find the most exact match for the
* current request path where the "most exact" is the longest path pattern that
* matches the current request path.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
@@ -64,6 +71,8 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
private final Map<String, Object> handlerMap = new LinkedHashMap<>();
private final Map<PathPattern, Object> pathPatternHandlerMap = new LinkedHashMap<>();
/**
* Set the root handler for this handler mapping, that is,
@@ -90,6 +99,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
*/
public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch) {
this.useTrailingSlashMatch = useTrailingSlashMatch;
if (getPatternParser() != null) {
getPatternParser().setMatchOptionalTrailingSeparator(useTrailingSlashMatch);
}
}
/**
@@ -121,9 +133,15 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
@Override
@Nullable
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
Object handler = lookupHandler(lookupPath, request);
String lookupPath = initLookupPath(request);
Object handler;
if (getPatternParser() != null) {
RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);
handler = lookupHandler(path, lookupPath, request);
}
else {
handler = lookupHandler(lookupPath, request);
}
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
@@ -148,47 +166,83 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
}
/**
* Look up a handler instance for the given URL path.
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher class.
* <p>Looks for the most exact pattern, where most exact is defined as
* the longest path pattern.
* @param urlPath the URL the bean is mapped to
* @param request current HTTP request (to expose the path within the mapping to)
* @return the associated handler instance, or {@code null} if not found
* @see #exposePathWithinMapping
* @see org.springframework.util.AntPathMatcher
* Look up a handler instance for the given URL path. This method is used
* when parsed {@code PathPattern}s are
* {@link #setPatternParser(PathPatternParser) enabled}.
* @param path the parsed RequestPath
* @param lookupPath the String lookupPath for checking direct hits
* @param request current HTTP request
* @return a matching handler, or {@code null} if not found
* @since 5.3
*/
@Nullable
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
Object handler = this.handlerMap.get(urlPath);
protected Object lookupHandler(
RequestPath path, String lookupPath, HttpServletRequest request) throws Exception {
Object handler = getDirectMatch(lookupPath, request);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
return handler;
}
// Pattern match?
List<PathPattern> matches = null;
for (PathPattern pattern : this.pathPatternHandlerMap.keySet()) {
if (pattern.matches(path)) {
matches = (matches != null ? matches : new ArrayList<>());
matches.add(pattern);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
if (matches == null) {
return null;
}
if (matches.size() > 1) {
matches.sort(PathPattern.SPECIFICITY_COMPARATOR);
if (logger.isTraceEnabled()) {
logger.debug("Matching patterns " + matches);
}
}
PathPattern pattern = matches.get(0);
handler = this.pathPatternHandlerMap.get(pattern);
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
PathContainer pathWithinMapping = pattern.extractPathWithinPattern(path);
return buildPathExposingHandler(handler, pattern.getPatternString(), pathWithinMapping.value(), null);
}
/**
* Look up a handler instance for the given URL path. This method is used
* when String pattern matching with {@code PathMatcher} is in use.
* @param lookupPath the path to match patterns against
* @param request current HTTP request
* @return a matching handler, or {@code null} if not found
* @see #exposePathWithinMapping
* @see AntPathMatcher
*/
@Nullable
protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception {
Object handler = getDirectMatch(lookupPath, request);
if (handler != null) {
return handler;
}
// Pattern match?
List<String> matchingPatterns = new ArrayList<>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
if (getPathMatcher().match(registeredPattern, lookupPath)) {
matchingPatterns.add(registeredPattern);
}
else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", lookupPath)) {
matchingPatterns.add(registeredPattern + "/");
}
}
}
String bestMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath);
if (!matchingPatterns.isEmpty()) {
matchingPatterns.sort(patternComparator);
if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
@@ -213,14 +267,14 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, lookupPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, lookupPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
@@ -235,6 +289,21 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
return null;
}
@Nullable
private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception {
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
return null;
}
/**
* Validate the given handler against the current request.
* <p>The default implementation is empty. Can be overridden in subclasses,
@@ -251,7 +320,8 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
* handler, the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}, as well as
* the {@link #URI_TEMPLATE_VARIABLES_ATTRIBUTE} before executing the handler.
* <p>The default implementation builds a {@link HandlerExecutionChain}
* with a special interceptor that exposes the path attribute and uri template variables
* with a special interceptor that exposes the path attribute and URI
* template variables
* @param rawHandler the raw handler to expose
* @param pathWithinMapping the path to expose before executing the handler
* @param uriTemplateVariables the URI template variables, can be {@code null} if no variables found
@@ -294,7 +364,8 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
@Override
@Nullable
public RequestMatchResult match(HttpServletRequest request, String pattern) {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request, LOOKUP_PATH);
Assert.isNull(getPatternParser(), "This HandlerMapping uses PathPatterns.");
String lookupPath = UrlPathHelper.getResolvedLookupPath(request);
if (getPathMatcher().match(pattern, lookupPath)) {
return new RequestMatchResult(pattern, lookupPath, getPathMatcher());
}
@@ -365,6 +436,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
}
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (getPatternParser() != null) {
this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);
}
if (logger.isTraceEnabled()) {
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
}
@@ -378,15 +452,25 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
/**
* Return the registered handlers as an unmodifiable Map, with the registered path
* as key and the handler object (or handler bean name in case of a lazy-init handler)
* as value.
* Return the handler mappings as a read-only Map, with the registered path
* or pattern as key and the handler object (or handler bean name in case of
* a lazy-init handler), as value.
* @see #getDefaultHandler()
*/
public final Map<String, Object> getHandlerMap() {
return Collections.unmodifiableMap(this.handlerMap);
}
/**
* Identical to {@link #getHandlerMap()} but with parsed patterns when
* {@link #setPatternParser(PathPatternParser)} is set, or otherwise empty.
* @since 5.3
*/
public final Map<PathPattern, Object> getPathPatternHandlerMap() {
return (this.pathPatternHandlerMap.isEmpty() ?
Collections.emptyMap() : Collections.unmodifiableMap(this.pathPatternHandlerMap));
}
/**
* Indicates whether this handler mapping support type-level mappings. Default to {@code false}.
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -44,6 +44,7 @@ import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.UrlPathHelper;
/**
* Helper class to get information from the {@code HandlerMapping} that would
@@ -57,6 +58,12 @@ import org.springframework.web.servlet.HandlerMapping;
* request.
* </ul>
*
* <p><strong>Note:</strong> This is primarily an SPI to allow Spring Security
* to align its pattern matching with the same pattern matching that would be
* used in Spring MVC for a given request, in order to avoid security issues.
* Use of this introspector should be avoided for other purposes because it
* incurs the overhead of resolving the handler for a request.
*
* @author Rossen Stoyanchev
* @since 4.3.1
*/
@@ -89,7 +96,7 @@ public class HandlerMappingIntrospector
/**
* Return the configured HandlerMapping's.
* Return the configured or detected HandlerMapping's.
*/
public List<HandlerMapping> getHandlerMappings() {
return (this.handlerMappings != null ? this.handlerMappings : Collections.emptyList());
@@ -141,7 +148,7 @@ public class HandlerMappingIntrospector
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
Assert.notNull(this.handlerMappings, "Handler mappings not initialized");
HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request);
RequestAttributeChangeIgnoringWrapper wrapper = new RequestAttributeChangeIgnoringWrapper(request);
for (HandlerMapping handlerMapping : this.handlerMappings) {
HandlerExecutionChain handler = null;
try {
@@ -189,7 +196,6 @@ public class HandlerMappingIntrospector
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + path + "': " + ex.getMessage());
}
String value = props.getProperty(HandlerMapping.class.getName());
String[] names = StringUtils.commaDelimitedListToStringArray(value);
List<HandlerMapping> result = new ArrayList<>(names.length);
@@ -212,14 +218,16 @@ public class HandlerMappingIntrospector
*/
private static class RequestAttributeChangeIgnoringWrapper extends HttpServletRequestWrapper {
public RequestAttributeChangeIgnoringWrapper(HttpServletRequest request) {
RequestAttributeChangeIgnoringWrapper(HttpServletRequest request) {
super(request);
}
@Override
public void setAttribute(String name, Object value) {
// Ignore attribute change...
// Allow UrlPathHelper-resolved lookupPath to be saved for efficiency
if (name.equals(UrlPathHelper.PATH_ATTRIBUTE)) {
super.setAttribute(name, value);
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -16,26 +16,42 @@
package org.springframework.web.servlet.handler;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.server.PathContainer;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Contains and delegates calls to a {@link HandlerInterceptor} along with
* include (and optionally exclude) path patterns to which the interceptor should apply.
* Also provides matching logic to test if the interceptor applies to a given request path.
* Wraps a {@link HandlerInterceptor} and uses URL patterns to determine whether
* it applies to a given request.
*
* <p>A MappedInterceptor can be registered directly with any
* {@link org.springframework.web.servlet.handler.AbstractHandlerMethodMapping}.
* Furthermore, beans of type {@code MappedInterceptor} are automatically detected by
* {@code AbstractHandlerMethodMapping} (including ancestor ApplicationContext's) which
* effectively means the interceptor is registered "globally" with all handler mappings.
* <p>Pattern matching can be done with {@link PathMatcher} or with parsed
* {@link PathPattern}. The syntax is largely the same with the latter being more
* tailored for web usage and more efficient. The choice is driven by the
* presence of a {@link UrlPathHelper#resolveAndCacheLookupPath resolved}
* {@code String} lookupPath or a {@link ServletRequestPathUtils#parseAndCache
* parsed} {@code RequestPath} which in turn depends on the
* {@link HandlerMapping} that matched the current request.
*
* <p>{@code MappedInterceptor} is supported by sub-classes of
* {@link org.springframework.web.servlet.handler.AbstractHandlerMethodMapping
* AbstractHandlerMethodMapping} which detect beans of type
* {@code MappedInterceptor} and also check if interceptors directly registered
* with it are of this type.
*
* @author Keith Donald
* @author Rossen Stoyanchev
@@ -44,56 +60,83 @@ import org.springframework.web.servlet.ModelAndView;
*/
public final class MappedInterceptor implements HandlerInterceptor {
@Nullable
private final String[] includePatterns;
private static PathMatcher defaultPathMatcher = new AntPathMatcher();
@Nullable
private final String[] excludePatterns;
private final PathPattern[] includePatterns;
@Nullable
private final PathPattern[] excludePatterns;
private PathMatcher pathMatcher = defaultPathMatcher;
private final HandlerInterceptor interceptor;
@Nullable
private PathMatcher pathMatcher;
/**
* Create a new MappedInterceptor instance.
* @param includePatterns the path patterns to map (empty for matching to all paths)
* @param interceptor the HandlerInterceptor instance to map to the given patterns
* Create an instance with the given include and exclude patterns along with
* the target interceptor for the mappings.
* @param includePatterns patterns to which requests must match, or null to
* match all paths
* @param excludePatterns patterns to which requests must not match
* @param interceptor the target interceptor
* @param parser a parser to use to pre-parse patterns into {@link PathPattern};
* when not provided, {@link PathPatternParser#defaultInstance} is used.
* @since 5.3
*/
public MappedInterceptor(@Nullable String[] includePatterns, @Nullable String[] excludePatterns,
HandlerInterceptor interceptor, @Nullable PathPatternParser parser) {
this.includePatterns = initPatterns(includePatterns, parser);
this.excludePatterns = initPatterns(excludePatterns, parser);
this.interceptor = interceptor;
}
@Nullable
private static PathPattern[] initPatterns(
@Nullable String[] patterns, @Nullable PathPatternParser parser) {
if (ObjectUtils.isEmpty(patterns)) {
return null;
}
parser = (parser != null ? parser : PathPatternParser.defaultInstance);
return Arrays.stream(patterns).map(parser::parse).toArray(PathPattern[]::new);
}
/**
* Variant of
* {@link #MappedInterceptor(String[], String[], HandlerInterceptor, PathPatternParser)}
* with include patterns only.
*/
public MappedInterceptor(@Nullable String[] includePatterns, HandlerInterceptor interceptor) {
this(includePatterns, null, interceptor);
}
/**
* Create a new MappedInterceptor instance.
* @param includePatterns the path patterns to map (empty for matching to all paths)
* @param excludePatterns the path patterns to exclude (empty for no specific excludes)
* @param interceptor the HandlerInterceptor instance to map to the given patterns
* Variant of
* {@link #MappedInterceptor(String[], String[], HandlerInterceptor, PathPatternParser)}
* without a provided parser.
*/
public MappedInterceptor(@Nullable String[] includePatterns, @Nullable String[] excludePatterns,
HandlerInterceptor interceptor) {
this.includePatterns = includePatterns;
this.excludePatterns = excludePatterns;
this.interceptor = interceptor;
this(includePatterns, excludePatterns, interceptor, null);
}
/**
* Create a new MappedInterceptor instance.
* @param includePatterns the path patterns to map (empty for matching to all paths)
* @param interceptor the WebRequestInterceptor instance to map to the given patterns
* Variant of
* {@link #MappedInterceptor(String[], String[], HandlerInterceptor, PathPatternParser)}
* with a {@link WebRequestInterceptor} as the target.
*/
public MappedInterceptor(@Nullable String[] includePatterns, WebRequestInterceptor interceptor) {
this(includePatterns, null, interceptor);
}
/**
* Create a new MappedInterceptor instance.
* @param includePatterns the path patterns to map (empty for matching to all paths)
* @param excludePatterns the path patterns to exclude (empty for no specific excludes)
* @param interceptor the WebRequestInterceptor instance to map to the given patterns
* Variant of
* {@link #MappedInterceptor(String[], String[], HandlerInterceptor, PathPatternParser)}
* with a {@link WebRequestInterceptor} as the target.
*/
public MappedInterceptor(@Nullable String[] includePatterns, @Nullable String[] excludePatterns,
WebRequestInterceptor interceptor) {
@@ -103,51 +146,61 @@ public final class MappedInterceptor implements HandlerInterceptor {
/**
* Configure a PathMatcher to use with this MappedInterceptor instead of the one passed
* by default to the {@link #matches(String, org.springframework.util.PathMatcher)} method.
* <p>This is an advanced property that is only required when using custom PathMatcher
* implementations that support mapping metadata other than the Ant-style path patterns
* supported by default.
*/
public void setPathMatcher(@Nullable PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
}
/**
* The configured PathMatcher, or {@code null} if none.
*/
@Nullable
public PathMatcher getPathMatcher() {
return this.pathMatcher;
}
/**
* The path into the application the interceptor is mapped to.
* Return the patterns this interceptor is mapped to.
*/
@Nullable
public String[] getPathPatterns() {
return this.includePatterns;
return (!ObjectUtils.isEmpty(this.includePatterns) ?
Arrays.stream(this.includePatterns).map(PathPattern::getPatternString).toArray(String[]::new) :
null);
}
/**
* The actual {@link HandlerInterceptor} reference.
* The target {@link HandlerInterceptor} to invoke in case of a match.
*/
public HandlerInterceptor getInterceptor() {
return this.interceptor;
}
/**
* Configure the PathMatcher to use to match URL paths with against include
* and exclude patterns.
* <p>This is an advanced property that should be used only when a
* customized {@link AntPathMatcher} or a custom PathMatcher is required.
* <p>By default this is {@link AntPathMatcher}.
* <p><strong>Note:</strong> Setting {@code PathMatcher} enforces use of
* String pattern matching even when a
* {@link ServletRequestPathUtils#parseAndCache parsed} {@code RequestPath}
* is available.
*/
public void setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
}
/**
* Determine a match for the given lookup path.
* @param lookupPath the current request path
* @param pathMatcher a path matcher for path pattern matching
* @return {@code true} if the interceptor applies to the given request path
* The {@link #setPathMatcher(PathMatcher) configured} PathMatcher.
*/
public boolean matches(String lookupPath, PathMatcher pathMatcher) {
PathMatcher pathMatcherToUse = (this.pathMatcher != null ? this.pathMatcher : pathMatcher);
public PathMatcher getPathMatcher() {
return this.pathMatcher;
}
/**
* Check whether this interceptor is mapped to the request.
* <p>The request mapping path is expected to have been resolved externally.
* See also class-level Javadoc.
* @param request the request to match to
* @return {@code true} if the interceptor should be applied to the request
*/
public boolean matches(HttpServletRequest request) {
Object path = ServletRequestPathUtils.getCachedPath(request);
if (this.pathMatcher != defaultPathMatcher) {
path = path.toString();
}
boolean isPathContainer = (path instanceof PathContainer);
if (!ObjectUtils.isEmpty(this.excludePatterns)) {
for (String pattern : this.excludePatterns) {
if (pathMatcherToUse.match(pattern, lookupPath)) {
for (PathPattern pattern : this.excludePatterns) {
if (matchPattern(path, isPathContainer, pattern)) {
return false;
}
}
@@ -155,14 +208,51 @@ public final class MappedInterceptor implements HandlerInterceptor {
if (ObjectUtils.isEmpty(this.includePatterns)) {
return true;
}
for (String pattern : this.includePatterns) {
if (pathMatcherToUse.match(pattern, lookupPath)) {
for (PathPattern pattern : this.includePatterns) {
if (matchPattern(path, isPathContainer, pattern)) {
return true;
}
}
return false;
}
private boolean matchPattern(Object path, boolean isPathContainer, PathPattern pattern) {
return (isPathContainer ?
pattern.matches((PathContainer) path) :
this.pathMatcher.match(pattern.getPatternString(), (String) path));
}
/**
* Determine a match for the given lookup path.
* @param lookupPath the current request path
* @param pathMatcher a path matcher for path pattern matching
* @return {@code true} if the interceptor applies to the given request path
* @deprecated as of 5.3 in favor of {@link #matches(HttpServletRequest)}
*/
@Deprecated
public boolean matches(String lookupPath, PathMatcher pathMatcher) {
pathMatcher = (this.pathMatcher != defaultPathMatcher ? this.pathMatcher : pathMatcher);
if (!ObjectUtils.isEmpty(this.excludePatterns)) {
for (PathPattern pattern : this.excludePatterns) {
if (pathMatcher.match(pattern.getPatternString(), lookupPath)) {
return false;
}
}
}
if (ObjectUtils.isEmpty(this.includePatterns)) {
return true;
}
for (PathPattern pattern : this.includePatterns) {
if (pathMatcher.match(pattern.getPatternString(), lookupPath)) {
return true;
}
}
return false;
}
// HandlerInterceptor delegation
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2020 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.
@@ -18,8 +18,12 @@ package org.springframework.web.servlet.handler;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.server.PathContainer;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Additional interface that a {@link HandlerMapping} can implement to expose
@@ -33,7 +37,32 @@ import org.springframework.web.servlet.HandlerMapping;
public interface MatchableHandlerMapping extends HandlerMapping {
/**
* Determine whether the given request matches the request criteria.
* Return the parser of this {@code HandlerMapping}, if configured in which
* case pre-parsed patterns are used.
* @since 5.3
*/
@Nullable
PathPatternParser getPatternParser();
/**
* Determine whether the request matches the given pattern. Use this method
* when {@link #getPatternParser()} is not {@code null} which means that the
* {@code HandlerMapping} has pre-parsed patterns enabled.
* @param request the current request
* @param pattern the pattern to match
* @return the result from request matching, or {@code null} if none
* @since 5.3
*/
@Nullable
default RequestMatchResult match(HttpServletRequest request, PathPattern pattern) {
PathContainer path = ServletRequestPathUtils.getParsedRequestPath(request).pathWithinApplication();
return (pattern.matches(path) ? new RequestMatchResult(pattern, path) : null);
}
/**
* Determine whether the request matches the given pattern. Use this method
* when {@link #getPatternParser()} returns {@code null} which means that the
* {@code HandlerMapping} is uses String pattern matching.
* @param request the current request
* @param pattern the pattern to match
* @return the result from request matching, or {@code null} if none

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2020 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.
@@ -18,8 +18,11 @@ package org.springframework.web.servlet.handler;
import java.util.Map;
import org.springframework.http.server.PathContainer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
import org.springframework.web.util.pattern.PathPattern;
/**
* Container for the result from request pattern matching via
@@ -31,37 +34,70 @@ import org.springframework.util.PathMatcher;
*/
public class RequestMatchResult {
private final String matchingPattern;
@Nullable
private final PathPattern pathPattern;
@Nullable
private final PathContainer lookupPathContainer;
@Nullable
private final String pattern;
@Nullable
private final String lookupPath;
@Nullable
private final PathMatcher pathMatcher;
/**
* Create an instance with a matching pattern.
* @param matchingPattern the matching pattern, possibly not the same as the
* input pattern, e.g. inputPattern="/foo" and matchingPattern="/foo/".
* @param lookupPath the lookup path extracted from the request
* @param pathMatcher the PathMatcher used
* Create an instance with the matched {@code PathPattern}.
* @param pathPattern the pattern that was matched
* @param lookupPath the mapping path
* @since 5.3
*/
public RequestMatchResult(String matchingPattern, String lookupPath, PathMatcher pathMatcher) {
Assert.hasText(matchingPattern, "'matchingPattern' is required");
Assert.hasText(lookupPath, "'lookupPath' is required");
Assert.notNull(pathMatcher, "'pathMatcher' is required");
this.matchingPattern = matchingPattern;
this.lookupPath = lookupPath;
this.pathMatcher = pathMatcher;
public RequestMatchResult(PathPattern pathPattern, PathContainer lookupPath) {
Assert.notNull(pathPattern, "PathPattern is required");
Assert.notNull(pathPattern, "PathContainer is required");
this.pattern = null;
this.lookupPath = null;
this.pathMatcher = null;
this.pathPattern = pathPattern;
this.lookupPathContainer = lookupPath;
}
/**
* Create an instance with the matched String pattern.
* @param pattern the pattern that was matched, possibly with a '/' appended
* @param lookupPath the mapping path
* @param pathMatcher the PathMatcher instance used for the match
*/
public RequestMatchResult(String pattern, String lookupPath, PathMatcher pathMatcher) {
Assert.hasText(pattern, "'matchingPattern' is required");
Assert.hasText(lookupPath, "'lookupPath' is required");
Assert.notNull(pathMatcher, "PathMatcher is required");
this.pattern = pattern;
this.lookupPath = lookupPath;
this.pathMatcher = pathMatcher;
this.pathPattern = null;
this.lookupPathContainer = null;
}
/**
* Extract URI template variables from the matching pattern as defined in
* {@link PathMatcher#extractUriTemplateVariables}.
* @return a map with URI template variables
*/
@SuppressWarnings("ConstantConditions")
public Map<String, String> extractUriTemplateVariables() {
return this.pathMatcher.extractUriTemplateVariables(this.matchingPattern, this.lookupPath);
return (this.pathPattern != null ?
this.pathPattern.matchAndExtract(this.lookupPathContainer).getUriVariables() :
this.pathMatcher.extractUriTemplateVariables(this.pattern, this.lookupPath));
}
}

View File

@@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.ServletRequestPathUtils;
/**
* Simple {@code Controller} implementation that transforms the virtual
@@ -111,7 +112,7 @@ public class UrlFilenameViewController extends AbstractUrlViewController {
protected String extractOperableUrl(HttpServletRequest request) {
String urlPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
if (!StringUtils.hasText(urlPath)) {
urlPath = getUrlPathHelper().getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
urlPath = ServletRequestPathUtils.getCachedPathValue(request);
}
return urlPath;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -22,25 +22,43 @@ import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.CacheControl;
import org.springframework.http.server.PathContainer;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Handler interceptor that checks the request and prepares the response.
* Checks for supported methods and a required session, and applies the
* specified {@link org.springframework.http.CacheControl} builder.
* See superclass bean properties for configuration options.
* Handler interceptor that checks the request for supported methods and a
* required session and prepares the response by applying the configured
* cache settings.
*
* <p>Cache settings may be configured for specific URLs via path pattern with
* {@link #addCacheMapping(CacheControl, String...)} and
* {@link #setCacheMappings(Properties)}, along with a fallback on default
* settings for all URLs via {@link #setCacheControl(CacheControl)}.
*
* <p>Pattern matching can be done with {@link PathMatcher} or with parsed
* {@link PathPattern}s. The syntax is largely the same with the latter being
* more tailored for web usage and more efficient. The choice depends on the
* presence of a {@link UrlPathHelper#resolveAndCacheLookupPath resolved}
* {@code String} lookupPath or a {@link ServletRequestPathUtils#parseAndCache}
* parsed} {@code RequestPath} which in turn depends on the
* {@link HandlerMapping} that matched the current request.
*
* <p>All the settings supported by this interceptor can also be set on
* {@link AbstractController}. This interceptor is mainly intended for applying
@@ -48,113 +66,85 @@ import org.springframework.web.util.UrlPathHelper;
*
* @author Juergen Hoeller
* @author Brian Clozel
* @author Rossen Stoyanchev
* @since 27.11.2003
* @see AbstractController
* @see PathMatcher
* @see AntPathMatcher
*/
public class WebContentInterceptor extends WebContentGenerator implements HandlerInterceptor {
private UrlPathHelper urlPathHelper = new UrlPathHelper();
private PathMatcher pathMatcher = new AntPathMatcher();
private Map<String, Integer> cacheMappings = new HashMap<>();
private Map<String, CacheControl> cacheControlMappings = new HashMap<>();
private static PathMatcher defaultPathMatcher = new AntPathMatcher();
private final PathPatternParser patternParser;
private PathMatcher pathMatcher = defaultPathMatcher;
private Map<PathPattern, Integer> cacheMappings = new HashMap<>();
private Map<PathPattern, CacheControl> cacheControlMappings = new HashMap<>();
/**
* Default constructor with {@link PathPatternParser#defaultInstance}.
*/
public WebContentInterceptor() {
this(PathPatternParser.defaultInstance);
}
/**
* Constructor with a {@link PathPatternParser} to parse patterns with.
* @since 5.3
*/
public WebContentInterceptor(PathPatternParser parser) {
// No restriction of HTTP methods by default,
// in particular for use with annotated controllers...
super(false);
this.patternParser = parser;
}
/**
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
* <p>Only relevant for the "cacheMappings" setting.
* @see #setCacheMappings
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
* Shortcut to the
* {@link org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
* same property} on the configured {@code UrlPathHelper}.
* @deprecated as of 5.3, the path is resolved externally and obtained with
* {@link ServletRequestPathUtils#getCachedPathValue(ServletRequest)}
*/
@Deprecated
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
}
/**
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
* <p>Only relevant for the "cacheMappings" setting.
* @see #setCacheMappings
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
* Shortcut to the
* {@link org.springframework.web.util.UrlPathHelper#setUrlDecode
* same property} on the configured {@code UrlPathHelper}.
* @deprecated as of 5.3, the path is resolved externally and obtained with
* {@link ServletRequestPathUtils#getCachedPathValue(ServletRequest)}
*/
@Deprecated
public void setUrlDecode(boolean urlDecode) {
this.urlPathHelper.setUrlDecode(urlDecode);
}
/**
* Set the UrlPathHelper to use for resolution of lookup paths.
* <p>Use this to override the default UrlPathHelper with a custom subclass,
* or to share common UrlPathHelper settings across multiple HandlerMappings
* and MethodNameResolvers.
* <p>Only relevant for the "cacheMappings" setting.
* @see #setCacheMappings
* @see org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#setUrlPathHelper
* @deprecated as of 5.3, the path is resolved externally and obtained with
* {@link ServletRequestPathUtils#getCachedPathValue(ServletRequest)}
*/
@Deprecated
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
this.urlPathHelper = urlPathHelper;
}
/**
* Map specific URL paths to specific cache seconds.
* <p>Overrides the default cache seconds setting of this interceptor.
* Can specify "-1" to exclude a URL path from default caching.
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and a various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher javadoc.
* <p><b>NOTE:</b> Path patterns are not supposed to overlap. If a request
* matches several mappings, it is effectively undefined which one will apply
* (due to the lack of key ordering in {@code java.util.Properties}).
* @param cacheMappings a mapping between URL paths (as keys) and
* cache seconds (as values, need to be integer-parsable)
* @see #setCacheSeconds
* @see org.springframework.util.AntPathMatcher
*/
public void setCacheMappings(Properties cacheMappings) {
this.cacheMappings.clear();
Enumeration<?> propNames = cacheMappings.propertyNames();
while (propNames.hasMoreElements()) {
String path = (String) propNames.nextElement();
int cacheSeconds = Integer.parseInt(cacheMappings.getProperty(path));
this.cacheMappings.put(path, cacheSeconds);
}
}
/**
* Map specific URL paths to a specific {@link org.springframework.http.CacheControl}.
* <p>Overrides the default cache seconds setting of this interceptor.
* Can specify a empty {@link org.springframework.http.CacheControl} instance
* to exclude a URL path from default caching.
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and a various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher javadoc.
* <p><b>NOTE:</b> Path patterns are not supposed to overlap. If a request
* matches several mappings, it is effectively undefined which one will apply
* (due to the lack of key ordering in the underlying {@code java.util.HashMap}).
* @param cacheControl the {@code CacheControl} to use
* @param paths the URL paths that will map to the given {@code CacheControl}
* @since 4.2
* @see #setCacheSeconds
* @see org.springframework.util.AntPathMatcher
*/
public void addCacheMapping(CacheControl cacheControl, String... paths) {
for (String path : paths) {
this.cacheControlMappings.put(path, cacheControl);
}
}
/**
* Set the PathMatcher implementation to use for matching URL paths
* against registered URL patterns, for determining cache mappings.
* Default is AntPathMatcher.
* Configure the PathMatcher to use to match URL paths against registered
* URL patterns to select the cache settings for a request.
* <p>This is an advanced property that should be used only when a
* customized {@link AntPathMatcher} or a custom PathMatcher is required.
* <p>By default this is {@link AntPathMatcher}.
* <p><strong>Note:</strong> Setting {@code PathMatcher} enforces use of
* String pattern matching even when a
* {@link ServletRequestPathUtils#parseAndCache parsed} {@code RequestPath}
* is available.
* @see #addCacheMapping
* @see #setCacheMappings
* @see org.springframework.util.AntPathMatcher
@@ -164,6 +154,54 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
this.pathMatcher = pathMatcher;
}
/**
* Map settings for cache seconds to specific URL paths via patterns.
* <p>Overrides the default cache seconds setting of this interceptor.
* Can specify "-1" to exclude a URL path from default caching.
* <p>For pattern syntax see {@link AntPathMatcher} and {@link PathPattern}
* as well as the class-level Javadoc for details for when each is used.
* The syntax is largely the same with {@link PathPattern} more tailored for
* web usage.
* <p><b>NOTE:</b> Path patterns are not supposed to overlap. If a request
* matches several mappings, it is effectively undefined which one will apply
* (due to the lack of key ordering in {@code java.util.Properties}).
* @param cacheMappings a mapping between URL paths (as keys) and
* cache seconds (as values, need to be integer-parsable)
* @see #setCacheSeconds
*/
public void setCacheMappings(Properties cacheMappings) {
this.cacheMappings.clear();
Enumeration<?> propNames = cacheMappings.propertyNames();
while (propNames.hasMoreElements()) {
String path = (String) propNames.nextElement();
int cacheSeconds = Integer.parseInt(cacheMappings.getProperty(path));
this.cacheMappings.put(this.patternParser.parse(path), cacheSeconds);
}
}
/**
* Map specific URL paths to a specific {@link org.springframework.http.CacheControl}.
* <p>Overrides the default cache seconds setting of this interceptor.
* Can specify a empty {@link org.springframework.http.CacheControl} instance
* to exclude a URL path from default caching.
* <p>For pattern syntax see {@link AntPathMatcher} and {@link PathPattern}
* as well as the class-level Javadoc for details for when each is used.
* The syntax is largely the same with {@link PathPattern} more tailored for
* web usage.
* <p><b>NOTE:</b> Path patterns are not supposed to overlap. If a request
* matches several mappings, it is effectively undefined which one will apply
* (due to the lack of key ordering in the underlying {@code java.util.HashMap}).
* @param cacheControl the {@code CacheControl} to use
* @param paths the URL paths that will map to the given {@code CacheControl}
* @since 4.2
* @see #setCacheSeconds
*/
public void addCacheMapping(CacheControl cacheControl, String... paths) {
for (String path : paths) {
this.cacheControlMappings.put(this.patternParser.parse(path), cacheControl);
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
@@ -171,51 +209,51 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
checkRequest(request);
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
CacheControl cacheControl = lookupCacheControl(lookupPath);
Integer cacheSeconds = lookupCacheSeconds(lookupPath);
if (cacheControl != null) {
if (logger.isTraceEnabled()) {
logger.trace("Applying " + cacheControl);
}
applyCacheControl(response, cacheControl);
}
else if (cacheSeconds != null) {
if (logger.isTraceEnabled()) {
logger.trace("Applying cacheSeconds " + cacheSeconds);
}
applyCacheSeconds(response, cacheSeconds);
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Applying default cacheSeconds");
}
prepareResponse(response);
Object path = ServletRequestPathUtils.getCachedPath(request);
if (this.pathMatcher != defaultPathMatcher) {
path = path.toString();
}
if (!ObjectUtils.isEmpty(this.cacheControlMappings)) {
CacheControl control = (path instanceof PathContainer ?
lookupCacheControl((PathContainer) path) : lookupCacheControl((String) path));
if (control != null) {
if (logger.isTraceEnabled()) {
logger.trace("Applying " + control);
}
applyCacheControl(response, control);
return true;
}
}
if (!ObjectUtils.isEmpty(this.cacheMappings)) {
Integer cacheSeconds = (path instanceof PathContainer ?
lookupCacheSeconds((PathContainer) path) : lookupCacheSeconds((String) path));
if (cacheSeconds != null) {
if (logger.isTraceEnabled()) {
logger.trace("Applying cacheSeconds " + cacheSeconds);
}
applyCacheSeconds(response, cacheSeconds);
return true;
}
}
prepareResponse(response);
return true;
}
/**
* Look up a {@link org.springframework.http.CacheControl} instance for the given URL path.
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher class.
* @param urlPath the URL the bean is mapped to
* @return the associated {@code CacheControl}, or {@code null} if not found
* @see org.springframework.util.AntPathMatcher
* Find a {@link org.springframework.http.CacheControl} instance for the
* given parsed {@link PathContainer path}. This is used when the
* {@code HandlerMapping} uses parsed {@code PathPatterns}.
* @param path the path to match to
* @return the matched {@code CacheControl}, or {@code null} if no match
* @since 5.3
*/
@Nullable
protected CacheControl lookupCacheControl(String urlPath) {
// Direct match?
CacheControl cacheControl = this.cacheControlMappings.get(urlPath);
if (cacheControl != null) {
return cacheControl;
}
// Pattern match?
for (Map.Entry<String, CacheControl> entry : this.cacheControlMappings.entrySet()) {
if (this.pathMatcher.match(entry.getKey(), urlPath)) {
protected CacheControl lookupCacheControl(PathContainer path) {
for (Map.Entry<PathPattern, CacheControl> entry : this.cacheControlMappings.entrySet()) {
if (entry.getKey().matches(path)) {
return entry.getValue();
}
}
@@ -223,30 +261,55 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
}
/**
* Look up a cacheSeconds integer value for the given URL path.
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher class.
* @param urlPath the URL the bean is mapped to
* @return the cacheSeconds integer value, or {@code null} if not found
* @see org.springframework.util.AntPathMatcher
* Find a {@link org.springframework.http.CacheControl} instance for the
* given String lookupPath. This is used when the {@code HandlerMapping}
* relies on String pattern matching with {@link PathMatcher}.
* @param lookupPath the path to match to
* @return the matched {@code CacheControl}, or {@code null} if no match
*/
@Nullable
protected Integer lookupCacheSeconds(String urlPath) {
// Direct match?
Integer cacheSeconds = this.cacheMappings.get(urlPath);
if (cacheSeconds != null) {
return cacheSeconds;
}
// Pattern match?
for (Map.Entry<String, Integer> entry : this.cacheMappings.entrySet()) {
if (this.pathMatcher.match(entry.getKey(), urlPath)) {
protected CacheControl lookupCacheControl(String lookupPath) {
for (Map.Entry<PathPattern, CacheControl> entry : this.cacheControlMappings.entrySet()) {
if (this.pathMatcher.match(entry.getKey().getPatternString(), lookupPath)) {
return entry.getValue();
}
}
return null;
}
/**
* Find a cacheSeconds value for the given parsed {@link PathContainer path}.
* This is used when the {@code HandlerMapping} uses parsed {@code PathPatterns}.
* @param path the path to match to
* @return the matched cacheSeconds, or {@code null} if there is no match
* @since 5.3
*/
@Nullable
protected Integer lookupCacheSeconds(PathContainer path) {
for (Map.Entry<PathPattern, Integer> entry : this.cacheMappings.entrySet()) {
if (entry.getKey().matches(path)) {
return entry.getValue();
}
}
return null;
}
/**
* Find a cacheSeconds instance for the given String lookupPath.
* This is used when the {@code HandlerMapping} relies on String pattern
* matching with {@link PathMatcher}.
* @param lookupPath the path to match to
* @return the matched cacheSeconds, or {@code null} if there is no match
*/
@Nullable
protected Integer lookupCacheSeconds(String lookupPath) {
for (Map.Entry<PathPattern, Integer> entry : this.cacheMappings.entrySet()) {
if (this.pathMatcher.match(entry.getKey().getPatternString(), lookupPath)) {
return entry.getValue();
}
}
return null;
}
/**
* This implementation is empty.

View File

@@ -0,0 +1,240 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.mvc.condition;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.server.PathContainer;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* A logical disjunction (' || ') request condition that matches a request
* against a set of URL path patterns.
*
* <p>In contrast to {@link PatternsRequestCondition}, this condition uses
* parsed {@link PathPattern}s instead of String pattern matching with
* {@link org.springframework.util.AntPathMatcher AntPathMatcher}.
*
* @author Rossen Stoyanchev
* @since 5.3
*/
public final class PathPatternsRequestCondition extends AbstractRequestCondition<PathPatternsRequestCondition> {
private static final SortedSet<PathPattern> EMPTY_PATH_PATTERN =
new TreeSet<>(Collections.singleton(new PathPatternParser().parse("")));
private static final Set<String> EMPTY_PATH = Collections.singleton("");
private final SortedSet<PathPattern> patterns;
/**
* Default constructor resulting in an {@code ""} (empty path) mapping.
*/
public PathPatternsRequestCondition() {
this(EMPTY_PATH_PATTERN);
}
/**
* Constructor with patterns to use.
*/
public PathPatternsRequestCondition(PathPatternParser parser, String... patterns) {
this(parse(parser, patterns));
}
private static SortedSet<PathPattern> parse(PathPatternParser parser, String... patterns) {
if (patterns.length == 0 || (patterns.length == 1 && !StringUtils.hasText(patterns[0]))) {
return EMPTY_PATH_PATTERN;
}
SortedSet<PathPattern> result = new TreeSet<>();
for (String path : patterns) {
if (StringUtils.hasText(path) && !path.startsWith("/")) {
path = "/" + path;
}
result.add(parser.parse(path));
}
return result;
}
private PathPatternsRequestCondition(SortedSet<PathPattern> patterns) {
this.patterns = patterns;
}
/**
* Return the patterns in this condition. If only the first (top) pattern
* is needed use {@link #getFirstPattern()}.
*/
public Set<PathPattern> getPatterns() {
return this.patterns;
}
@Override
protected Collection<PathPattern> getContent() {
return this.patterns;
}
@Override
protected String getToStringInfix() {
return " || ";
}
/**
* Return the first pattern.
*/
public PathPattern getFirstPattern() {
return this.patterns.first();
}
/**
* Whether the condition is the "" (empty path) mapping.
*/
public boolean isEmptyPathMapping() {
return this.patterns == EMPTY_PATH_PATTERN;
}
/**
* Return the mapping paths that are not patterns.
*/
public Set<String> getDirectPaths() {
if (isEmptyPathMapping()) {
return EMPTY_PATH;
}
Set<String> result = Collections.emptySet();
for (PathPattern pattern : this.patterns) {
if (pattern.hasPatternSyntax()) {
result = (result.isEmpty() ? new HashSet<>(1) : result);
result.add(pattern.getPatternString());
}
}
return result;
}
/**
* Return the {@link #getPatterns()} mapped to Strings.
*/
public Set<String> getPatternValues() {
return (isEmptyPathMapping() ? EMPTY_PATH :
getPatterns().stream().map(PathPattern::getPatternString).collect(Collectors.toSet()));
}
/**
* Returns a new instance with URL patterns from the current instance
* ("this") and the "other" instance as follows:
* <ul>
* <li>If there are patterns in both instances, combine the patterns in
* "this" with the patterns in "other" using
* {@link PathPattern#combine(PathPattern)}.
* <li>If only one instance has patterns, use them.
* <li>If neither instance has patterns, use an empty String (i.e. "").
* </ul>
*/
@Override
public PathPatternsRequestCondition combine(PathPatternsRequestCondition other) {
if (isEmptyPathMapping() && other.isEmptyPathMapping()) {
return this;
}
else if (other.isEmptyPathMapping()) {
return this;
}
else if (isEmptyPathMapping()) {
return other;
}
else {
SortedSet<PathPattern> combined = new TreeSet<>();
for (PathPattern pattern1 : this.patterns) {
for (PathPattern pattern2 : other.patterns) {
combined.add(pattern1.combine(pattern2));
}
}
return new PathPatternsRequestCondition(combined);
}
}
/**
* Checks if any of the patterns match the given request and returns an
* instance that is guaranteed to contain matching patterns, sorted.
* @param request the current request
* @return the same instance if the condition contains no patterns;
* or a new condition with sorted matching patterns;
* or {@code null} if no patterns match.
*/
@Override
@Nullable
public PathPatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
PathContainer path = ServletRequestPathUtils.getParsedRequestPath(request).pathWithinApplication();
SortedSet<PathPattern> matches = getMatchingPatterns(path);
return (matches != null ? new PathPatternsRequestCondition(matches) : null);
}
@Nullable
private SortedSet<PathPattern> getMatchingPatterns(PathContainer path) {
TreeSet<PathPattern> result = null;
for (PathPattern pattern : this.patterns) {
if (pattern.matches(path)) {
result = (result != null ? result : new TreeSet<>());
result.add(pattern);
}
}
return result;
}
/**
* Compare the two conditions based on the URL patterns they contain.
* Patterns are compared one at a time, from top to bottom. If all compared
* patterns match equally, but one instance has more patterns, it is
* considered a closer match.
* <p>It is assumed that both instances have been obtained via
* {@link #getMatchingCondition(HttpServletRequest)} to ensure they
* contain only patterns that match the request and are sorted with
* the best matches on top.
*/
@Override
public int compareTo(PathPatternsRequestCondition other, HttpServletRequest request) {
Iterator<PathPattern> iterator = this.patterns.iterator();
Iterator<PathPattern> iteratorOther = other.getPatterns().iterator();
while (iterator.hasNext() && iteratorOther.hasNext()) {
int result = PathPattern.SPECIFICITY_COMPARATOR.compare(iterator.next(), iteratorOther.next());
if (result != 0) {
return result;
}
}
if (iterator.hasNext()) {
return -1;
}
else if (iteratorOther.hasNext()) {
return 1;
}
else {
return 0;
}
}
}

View File

@@ -20,6 +20,7 @@ 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.LinkedHashSet;
import java.util.List;
@@ -32,13 +33,17 @@ import org.springframework.util.AntPathMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern;
/**
* A logical disjunction (' || ') request condition that matches a request
* against a set of URL path patterns.
*
* <p>In contrast to {@link PathPatternsRequestCondition} which uses parsed
* {@link PathPattern}s, this condition does String pattern matching via
* {@link org.springframework.util.AntPathMatcher AntPathMatcher}.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
@@ -49,8 +54,6 @@ public class PatternsRequestCondition extends AbstractRequestCondition<PatternsR
private final Set<String> patterns;
private final UrlPathHelper pathHelper;
private final PathMatcher pathMatcher;
private final boolean useSuffixPatternMatch;
@@ -61,37 +64,50 @@ public class PatternsRequestCondition extends AbstractRequestCondition<PatternsR
/**
* Creates a new instance with the given URL patterns. 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.
* Constructor with URL patterns which are prepended with "/" if necessary.
* @param patterns 0 or more URL patterns; no patterns results in an empty
* path {@code ""} mapping which matches all requests.
*/
public PatternsRequestCondition(String... patterns) {
this(patterns, null, null, true, true, null);
this(patterns, true, null);
}
/**
* Alternative constructor with additional, optional {@link UrlPathHelper},
* {@link PathMatcher}, and whether to automatically match trailing slashes.
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
* @param urlPathHelper a {@link UrlPathHelper} for determining the lookup path for a request
* @param pathMatcher a {@link PathMatcher} for pattern path matching
* @param useTrailingSlashMatch whether to match irrespective of a trailing slash
* @since 5.2.4
* Variant of {@link #PatternsRequestCondition(String...)} with a
* {@link PathMatcher} and flag for matching trailing slashes.
* @since 5.3
*/
public PatternsRequestCondition(String[] patterns, boolean useTrailingSlashMatch,
@Nullable PathMatcher pathMatcher) {
this(patterns, null, pathMatcher, useTrailingSlashMatch);
}
/**
* Variant of {@link #PatternsRequestCondition(String...)} with a
* {@link UrlPathHelper} and a {@link PathMatcher}, and whether to match
* trailing slashes.
* <p>As of 5.3 the the path is obtained through the static method
* {@link UrlPathHelper#getResolvedLookupPath} and a {@code UrlPathHelper}
* does not need to be passed in.
* @since 5.2.4
* @deprecated as of 5.3 in favor of
* {@link #PatternsRequestCondition(String[], boolean, PathMatcher)}.
*/
@Deprecated
public PatternsRequestCondition(String[] patterns, @Nullable UrlPathHelper urlPathHelper,
@Nullable PathMatcher pathMatcher, boolean useTrailingSlashMatch) {
this(patterns, urlPathHelper, pathMatcher, false, useTrailingSlashMatch, null);
this(patterns, urlPathHelper, pathMatcher, false, useTrailingSlashMatch);
}
/**
* Alternative constructor with additional optional parameters.
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
* @param urlPathHelper for determining the lookup path of a request
* @param pathMatcher for path matching with patterns
* @param useSuffixPatternMatch whether to enable matching by suffix (".*")
* @param useTrailingSlashMatch whether to match irrespective of a trailing slash
* Variant of {@link #PatternsRequestCondition(String...)} with a
* {@link UrlPathHelper} and a {@link PathMatcher}, and flags for matching
* with suffixes and trailing slashes.
* <p>As of 5.3 the the path is obtained through the static method
* {@link UrlPathHelper#getResolvedLookupPath} and a {@code UrlPathHelper}
* does not need to be passed in.
* @deprecated as of 5.2.4. See class-level note in
* {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping}
* on the deprecation of path extension config options.
@@ -104,13 +120,12 @@ public class PatternsRequestCondition extends AbstractRequestCondition<PatternsR
}
/**
* Alternative constructor with additional optional parameters.
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
* @param urlPathHelper a {@link UrlPathHelper} for determining the lookup path for a request
* @param pathMatcher a {@link PathMatcher} for pattern path matching
* @param useSuffixPatternMatch whether to enable matching by suffix (".*")
* @param useTrailingSlashMatch whether to match irrespective of a trailing slash
* @param fileExtensions a list of file extensions to consider for path matching
* Variant of {@link #PatternsRequestCondition(String...)} with a
* {@link UrlPathHelper} and a {@link PathMatcher}, and flags for matching
* with suffixes and trailing slashes, along with specific extensions.
* <p>As of 5.3 the the path is obtained through the static method
* {@link UrlPathHelper#getResolvedLookupPath} and a {@code UrlPathHelper}
* does not need to be passed in.
* @deprecated as of 5.2.4. See class-level note in
* {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping}
* on the deprecation of path extension config options.
@@ -121,7 +136,6 @@ public class PatternsRequestCondition extends AbstractRequestCondition<PatternsR
boolean useTrailingSlashMatch, @Nullable List<String> fileExtensions) {
this.patterns = initPatterns(patterns);
this.pathHelper = urlPathHelper != null ? urlPathHelper : UrlPathHelper.defaultInstance;
this.pathMatcher = pathMatcher != null ? pathMatcher : new AntPathMatcher();
this.useSuffixPatternMatch = useSuffixPatternMatch;
this.useTrailingSlashMatch = useTrailingSlashMatch;
@@ -166,7 +180,6 @@ public class PatternsRequestCondition extends AbstractRequestCondition<PatternsR
*/
private PatternsRequestCondition(Set<String> patterns, PatternsRequestCondition other) {
this.patterns = patterns;
this.pathHelper = other.pathHelper;
this.pathMatcher = other.pathMatcher;
this.useSuffixPatternMatch = other.useSuffixPatternMatch;
this.useTrailingSlashMatch = other.useTrailingSlashMatch;
@@ -188,6 +201,31 @@ public class PatternsRequestCondition extends AbstractRequestCondition<PatternsR
return " || ";
}
/**
* Whether the condition is the "" (empty path) mapping.
*/
public boolean isEmptyPathMapping() {
return this.patterns == EMPTY_PATH_PATTERN;
}
/**
* Return the mapping paths that are not patterns.
* @since 5.3
*/
public Set<String> getDirectPaths() {
if (isEmptyPathMapping()) {
return EMPTY_PATH_PATTERN;
}
Set<String> result = Collections.emptySet();
for (String pattern : this.patterns) {
if (this.pathMatcher.isPattern(pattern)) {
result = (result.isEmpty() ? new HashSet<>(1) : result);
result.add(pattern);
}
}
return result;
}
/**
* Returns a new instance with URL patterns from the current instance ("this") and
* the "other" instance as follows:
@@ -200,13 +238,13 @@ public class PatternsRequestCondition extends AbstractRequestCondition<PatternsR
*/
@Override
public PatternsRequestCondition combine(PatternsRequestCondition other) {
if (isEmptyPathPattern() && other.isEmptyPathPattern()) {
if (isEmptyPathMapping() && other.isEmptyPathMapping()) {
return this;
}
else if (other.isEmptyPathPattern()) {
else if (other.isEmptyPathMapping()) {
return this;
}
else if (isEmptyPathPattern()) {
else if (isEmptyPathMapping()) {
return other;
}
Set<String> result = new LinkedHashSet<>();
@@ -220,10 +258,6 @@ public class PatternsRequestCondition extends AbstractRequestCondition<PatternsR
return new PatternsRequestCondition(result, this);
}
private boolean isEmptyPathPattern() {
return this.patterns == EMPTY_PATH_PATTERN;
}
/**
* Checks if any of the patterns match the given request and returns an instance
* that is guaranteed to contain matching patterns, sorted via
@@ -243,7 +277,7 @@ public class PatternsRequestCondition extends AbstractRequestCondition<PatternsR
@Override
@Nullable
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
String lookupPath = this.pathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
String lookupPath = UrlPathHelper.getResolvedLookupPath(request);
List<String> matches = getMatchingPatterns(lookupPath);
return !matches.isEmpty() ? new PatternsRequestCondition(new LinkedHashSet<>(matches), this) : null;
}
@@ -318,7 +352,7 @@ public class PatternsRequestCondition extends AbstractRequestCondition<PatternsR
*/
@Override
public int compareTo(PatternsRequestCondition other, HttpServletRequest request) {
String lookupPath = this.pathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
String lookupPath = UrlPathHelper.getResolvedLookupPath(request);
Comparator<String> patternComparator = this.pathMatcher.getPatternComparator(lookupPath);
Iterator<String> iterator = this.patterns.iterator();
Iterator<String> iteratorOther = other.patterns.iterator();

View File

@@ -19,30 +19,38 @@ package org.springframework.web.servlet.mvc.method;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestConditionHolder;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Request mapping information. Encapsulates the following request mapping conditions:
* Request mapping information. A composite for the the following conditions:
* <ol>
* <li>{@link PatternsRequestCondition}
* <li>{@link PathPatternsRequestCondition} with parsed {@code PathPatterns} or
* {@link PatternsRequestCondition} with String patterns via {@code PathMatcher}
* <li>{@link RequestMethodsRequestCondition}
* <li>{@link ParamsRequestCondition}
* <li>{@link HeadersRequestCondition}
@@ -57,6 +65,8 @@ import org.springframework.web.util.UrlPathHelper;
*/
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
private static final PathPatternsRequestCondition EMPTY_PATH_PATTERNS = new PathPatternsRequestCondition();
private static final PatternsRequestCondition EMPTY_PATTERNS = new PatternsRequestCondition();
private static final RequestMethodsRequestCondition EMPTY_REQUEST_METHODS = new RequestMethodsRequestCondition();
@@ -75,6 +85,10 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
@Nullable
private final String name;
@Nullable
private final PathPatternsRequestCondition pathPatternsCondition;
@Nullable
private final PatternsRequestCondition patternsCondition;
private final RequestMethodsRequestCondition methodsCondition;
@@ -92,28 +106,33 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
private final int hashCode;
/**
* Full constructor with a mapping name.
* @deprecated as of 5.3 in favor using {@link RequestMappingInfo.Builder} via
* {@link #paths(String...)}.
*/
@Deprecated
public RequestMappingInfo(@Nullable String name, @Nullable PatternsRequestCondition patterns,
@Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params,
@Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes,
@Nullable ProducesRequestCondition produces, @Nullable RequestCondition<?> custom) {
this.name = (StringUtils.hasText(name) ? name : null);
this.patternsCondition = (patterns != null ? patterns : EMPTY_PATTERNS);
this.methodsCondition = (methods != null ? methods : EMPTY_REQUEST_METHODS);
this.paramsCondition = (params != null ? params : EMPTY_PARAMS);
this.headersCondition = (headers != null ? headers : EMPTY_HEADERS);
this.consumesCondition = (consumes != null ? consumes : EMPTY_CONSUMES);
this.producesCondition = (produces != null ? produces : EMPTY_PRODUCES);
this.customConditionHolder = (custom != null ? new RequestConditionHolder(custom) : EMPTY_CUSTOM);
this.hashCode = calculateHashCode(
this.patternsCondition, this.methodsCondition, this.paramsCondition, this.headersCondition,
this.consumesCondition, this.producesCondition, this.customConditionHolder);
this(name, null,
(patterns != null ? patterns : EMPTY_PATTERNS),
(methods != null ? methods : EMPTY_REQUEST_METHODS),
(params != null ? params : EMPTY_PARAMS),
(headers != null ? headers : EMPTY_HEADERS),
(consumes != null ? consumes : EMPTY_CONSUMES),
(produces != null ? produces : EMPTY_PRODUCES),
(custom != null ? new RequestConditionHolder(custom) : EMPTY_CUSTOM));
}
/**
* Creates a new instance with the given request conditions.
* Create an instance with the given conditions.
* @deprecated as of 5.3 in favor using {@link RequestMappingInfo.Builder} via
* {@link #paths(String...)}.
*/
@Deprecated
public RequestMappingInfo(@Nullable PatternsRequestCondition patterns,
@Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params,
@Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes,
@@ -124,12 +143,41 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
/**
* Re-create a RequestMappingInfo with the given custom request condition.
* @deprecated since 5.3 in favor of using {@link #addCustomCondition(RequestCondition)}.
*/
@Deprecated
public RequestMappingInfo(RequestMappingInfo info, @Nullable RequestCondition<?> customRequestCondition) {
this(info.name, info.patternsCondition, info.methodsCondition, info.paramsCondition, info.headersCondition,
info.consumesCondition, info.producesCondition, customRequestCondition);
}
private RequestMappingInfo(@Nullable String name,
@Nullable PathPatternsRequestCondition pathPatternsCondition,
@Nullable PatternsRequestCondition patternsCondition,
RequestMethodsRequestCondition methodsCondition, ParamsRequestCondition paramsCondition,
HeadersRequestCondition headersCondition, ConsumesRequestCondition consumesCondition,
ProducesRequestCondition producesCondition, RequestConditionHolder customCondition) {
Assert.isTrue(pathPatternsCondition != null || patternsCondition != null,
"Neither PathPatterns nor String patterns condition");
this.name = (StringUtils.hasText(name) ? name : null);
this.pathPatternsCondition = pathPatternsCondition;
this.patternsCondition = patternsCondition;
this.methodsCondition = methodsCondition;
this.paramsCondition = paramsCondition;
this.headersCondition = headersCondition;
this.consumesCondition = consumesCondition;
this.producesCondition = producesCondition;
this.customConditionHolder = customCondition;
this.hashCode = calculateHashCode(
this.pathPatternsCondition, this.patternsCondition,
this.methodsCondition, this.paramsCondition, this.headersCondition,
this.consumesCondition, this.producesCondition, this.customConditionHolder);
}
/**
* Return the name for this mapping, or {@code null}.
*/
@@ -139,13 +187,71 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
}
/**
* Return the URL patterns of this {@link RequestMappingInfo};
* or instance with 0 patterns (never {@code null}).
* Return the patterns condition in use when parsed patterns are enabled via
* {@link org.springframework.web.servlet.handler.AbstractHandlerMapping#setPatternParser(PathPatternParser)}.
* <p>This is mutually exclusive with {@link #getPatternsCondition()} such
* that when one returns {@code null} the other one returns an instance.
* @since 5.3
* @see #getActivePatternsCondition()
*/
@Nullable
public PathPatternsRequestCondition getPathPatternsCondition() {
return this.pathPatternsCondition;
}
/**
* Return the patterns condition when String pattern matching via
* {@link PathMatcher} is in use.
* <p>This is mutually exclusive with {@link #getPathPatternsCondition()}
* such that when one returns {@code null} the other one returns an instance.
*/
@Nullable
public PatternsRequestCondition getPatternsCondition() {
return this.patternsCondition;
}
/**
* Returns either {@link #getPathPatternsCondition()} or
* {@link #getPatternsCondition()} depending on which is not null.
* @since 5.3
*/
@SuppressWarnings("unchecked")
public <T> RequestCondition<T> getActivePatternsCondition() {
if (this.pathPatternsCondition != null) {
return (RequestCondition<T>) this.pathPatternsCondition;
}
else if (this.patternsCondition != null) {
return (RequestCondition<T>) this.patternsCondition;
}
else {
// Already checked in the constructor...
throw new IllegalStateException();
}
}
/**
* Return the mapping paths that are not patterns.
* @since 5.3
*/
public Set<String> getDirectPaths() {
RequestCondition<?> condition = getActivePatternsCondition();
return (condition instanceof PathPatternsRequestCondition ?
((PathPatternsRequestCondition) condition).getDirectPaths() :
((PatternsRequestCondition) condition).getDirectPaths());
}
/**
* Return the patterns for the {@link #getActivePatternsCondition() active}
* patterns condition as Strings.
* @since 5.3
*/
public Set<String> getPatternValues() {
RequestCondition<?> condition = getActivePatternsCondition();
return (condition instanceof PathPatternsRequestCondition ?
((PathPatternsRequestCondition) condition).getPatternValues() :
((PatternsRequestCondition) condition).getPatterns());
}
/**
* Return the HTTP request methods of this {@link RequestMappingInfo};
* or instance with 0 request methods (never {@code null}).
@@ -194,16 +300,36 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
return this.customConditionHolder.getCondition();
}
/**
* Create a new instance based on the current one, also adding the given
* custom condition.
* @param customCondition the custom condition to add
* @since 5.3
*/
public RequestMappingInfo addCustomCondition(RequestCondition<?> customCondition) {
return new RequestMappingInfo(this.name, this.pathPatternsCondition, this.patternsCondition,
this.methodsCondition, this.paramsCondition, this.headersCondition,
this.consumesCondition, this.producesCondition, new RequestConditionHolder(customCondition));
}
/**
* Combine "this" request mapping info (i.e. the current instance) with another request mapping info instance.
* Combine "this" request mapping info (i.e. the current instance) with
* another request mapping info instance.
* <p>Example: combine type- and method-level request mappings.
* @return a new request mapping info instance; never {@code null}
*/
@Override
public RequestMappingInfo combine(RequestMappingInfo other) {
String name = combineNames(other);
PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
PathPatternsRequestCondition pathPatterns =
(this.pathPatternsCondition != null && other.pathPatternsCondition != null ?
this.pathPatternsCondition.combine(other.pathPatternsCondition) : null);
PatternsRequestCondition patterns =
(this.patternsCondition != null && other.patternsCondition != null ?
this.patternsCondition.combine(other.patternsCondition) : null);
RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
@@ -211,8 +337,8 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);
return new RequestMappingInfo(name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
return new RequestMappingInfo(
name, pathPatterns, patterns, methods, params, headers, consumes, produces, custom);
}
@Nullable
@@ -230,11 +356,13 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
}
/**
* Checks if all conditions in this request mapping info match the provided request and returns
* a potentially new request mapping info with conditions tailored to the current request.
* <p>For example the returned instance may contain the subset of URL patterns that match to
* the current request, sorted with best matching patterns on top.
* @return a new instance in case all conditions match; or {@code null} otherwise
* Checks if all conditions in this request mapping info match the provided
* request and returns a potentially new request mapping info with conditions
* tailored to the current request.
* <p>For example the returned instance may contain the subset of URL
* patterns that match to the current request, sorted with best matching
* patterns on top.
* @return a new instance in case of a match; or {@code null} otherwise
*/
@Override
@Nullable
@@ -259,24 +387,34 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
if (produces == null) {
return null;
}
PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
PathPatternsRequestCondition pathPatterns = null;
if (this.pathPatternsCondition != null) {
pathPatterns = this.pathPatternsCondition.getMatchingCondition(request);
if (pathPatterns == null) {
return null;
}
}
PatternsRequestCondition patterns = null;
if (this.patternsCondition != null) {
patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}
}
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}
return new RequestMappingInfo(this.name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
return new RequestMappingInfo(
this.name, pathPatterns, patterns, methods, params, headers, consumes, produces, custom);
}
/**
* Compares "this" info (i.e. the current instance) with another info in the context of a request.
* Compares "this" info (i.e. the current instance) with another info in the
* context of a request.
* <p>Note: It is assumed both instances have been obtained via
* {@link #getMatchingCondition(HttpServletRequest)} to ensure they have conditions with
* content relevant to current request.
* {@link #getMatchingCondition(HttpServletRequest)} to ensure they have
* conditions with content relevant to current request.
*/
@Override
public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
@@ -288,7 +426,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
return result;
}
}
result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);
result = getActivePatternsCondition().compareTo(other.getActivePatternsCondition(), request);
if (result != 0) {
return result;
}
@@ -329,7 +467,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
return false;
}
RequestMappingInfo otherInfo = (RequestMappingInfo) other;
return (this.patternsCondition.equals(otherInfo.patternsCondition) &&
return (getActivePatternsCondition().equals(otherInfo.getActivePatternsCondition()) &&
this.methodsCondition.equals(otherInfo.methodsCondition) &&
this.paramsCondition.equals(otherInfo.paramsCondition) &&
this.headersCondition.equals(otherInfo.headersCondition) &&
@@ -343,14 +481,16 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
return this.hashCode;
}
@SuppressWarnings("ConstantConditions")
private static int calculateHashCode(
PatternsRequestCondition patterns, RequestMethodsRequestCondition methods,
ParamsRequestCondition params, HeadersRequestCondition headers,
ConsumesRequestCondition consumes, ProducesRequestCondition produces,
RequestConditionHolder custom) {
@Nullable PathPatternsRequestCondition pathPatterns, @Nullable PatternsRequestCondition patterns,
RequestMethodsRequestCondition methods, ParamsRequestCondition params, HeadersRequestCondition headers,
ConsumesRequestCondition consumes, ProducesRequestCondition produces, RequestConditionHolder custom) {
return patterns.hashCode() * 31 + methods.hashCode() + params.hashCode() +
headers.hashCode() + consumes.hashCode() + produces.hashCode() + custom.hashCode();
return (pathPatterns != null ? pathPatterns : patterns).hashCode() * 31 +
methods.hashCode() + params.hashCode() +
headers.hashCode() + consumes.hashCode() + produces.hashCode() +
custom.hashCode();
}
@Override
@@ -360,10 +500,10 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
Set<RequestMethod> httpMethods = this.methodsCondition.getMethods();
builder.append(httpMethods.size() == 1 ? httpMethods.iterator().next() : httpMethods);
}
if (!this.patternsCondition.isEmpty()) {
Set<String> patterns = this.patternsCondition.getPatterns();
builder.append(" ").append(patterns.size() == 1 ? patterns.iterator().next() : patterns);
}
// Patterns conditions are never empty and have "" (empty path) at least.
builder.append(" ").append(getActivePatternsCondition());
if (!this.paramsCondition.isEmpty()) {
builder.append(", params ").append(this.paramsCondition);
}
@@ -401,7 +541,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
public interface Builder {
/**
* Set the path patterns.
* Set the URL path patterns.
*/
Builder paths(String... paths);
@@ -547,26 +687,39 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
@SuppressWarnings("deprecation")
public RequestMappingInfo build() {
PatternsRequestCondition patternsCondition = ObjectUtils.isEmpty(this.paths) ? null :
new PatternsRequestCondition(
this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(),
this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
this.options.getFileExtensions());
PathPatternsRequestCondition pathPatterns = null;
PatternsRequestCondition patterns = null;
if (this.options.patternParser != null) {
pathPatterns = (ObjectUtils.isEmpty(this.paths) ?
EMPTY_PATH_PATTERNS :
new PathPatternsRequestCondition(this.options.patternParser, this.paths));
}
else {
patterns = (ObjectUtils.isEmpty(this.paths) ?
EMPTY_PATTERNS :
new PatternsRequestCondition(
this.paths, null, this.options.getPathMatcher(),
this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
this.options.getFileExtensions()));
}
ContentNegotiationManager manager = this.options.getContentNegotiationManager();
return new RequestMappingInfo(this.mappingName, patternsCondition,
return new RequestMappingInfo(
this.mappingName, pathPatterns, patterns,
ObjectUtils.isEmpty(this.methods) ?
null : new RequestMethodsRequestCondition(this.methods),
EMPTY_REQUEST_METHODS : new RequestMethodsRequestCondition(this.methods),
ObjectUtils.isEmpty(this.params) ?
null : new ParamsRequestCondition(this.params),
EMPTY_PARAMS : new ParamsRequestCondition(this.params),
ObjectUtils.isEmpty(this.headers) ?
null : new HeadersRequestCondition(this.headers),
EMPTY_HEADERS : new HeadersRequestCondition(this.headers),
ObjectUtils.isEmpty(this.consumes) && !this.hasContentType ?
null : new ConsumesRequestCondition(this.consumes, this.headers),
EMPTY_CONSUMES : new ConsumesRequestCondition(this.consumes, this.headers),
ObjectUtils.isEmpty(this.produces) && !this.hasAccept ?
null : new ProducesRequestCondition(this.produces, this.headers, manager),
this.customCondition);
EMPTY_PRODUCES : new ProducesRequestCondition(this.produces, this.headers, manager),
this.customCondition != null ?
new RequestConditionHolder(this.customCondition) : EMPTY_CUSTOM);
}
}
@@ -581,7 +734,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
public static class BuilderConfiguration {
@Nullable
private UrlPathHelper urlPathHelper;
private PathPatternParser patternParser;
@Nullable
private PathMatcher pathMatcher;
@@ -595,21 +748,50 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
@Nullable
private ContentNegotiationManager contentNegotiationManager;
/**
* Set a custom UrlPathHelper to use for the PatternsRequestCondition.
* <p>By default this is not set.
* @since 4.2.8
* Enable use of parsed {@link PathPattern} as described in
* {@link AbstractHandlerMapping#setPatternParser(PathPatternParser)}.
* <p><strong>Note:</strong> This property is mutually exclusive with
* {@link #setPathMatcher(PathMatcher)}.
* <p>By default this is not enabled.
* @since 5.3
*/
public void setUrlPathHelper(@Nullable UrlPathHelper urlPathHelper) {
this.urlPathHelper = urlPathHelper;
public void setPatternParser(@Nullable PathPatternParser patternParser) {
this.patternParser = patternParser;
}
/**
* Return a custom UrlPathHelper to use for the PatternsRequestCondition, if any.
* Return the {@link #setPatternParser(PathPatternParser) configured}
* {@code PathPatternParser}, or {@code null}.
* @since 5.3
*/
@Nullable
public PathPatternParser getPatternParser() {
return this.patternParser;
}
/**
* Set a custom UrlPathHelper to use for the PatternsRequestCondition.
* <p>By default this is not set.
* @deprecated as of 5.3, the path is resolved externally and obtained with
* {@link ServletRequestPathUtils#getCachedPathValue(ServletRequest)}
* @since 4.2.8
*/
@Deprecated
public void setUrlPathHelper(@Nullable UrlPathHelper urlPathHelper) {
}
/**
* Return the configured UrlPathHelper.
* @deprecated as of 5.3, the path is resolved externally and obtained with
* {@link ServletRequestPathUtils#getCachedPathValue(ServletRequest)};
* this method always returns {@link UrlPathHelper#defaultInstance}.
*/
@Nullable
@Deprecated
public UrlPathHelper getUrlPathHelper() {
return this.urlPathHelper;
return UrlPathHelper.defaultInstance;
}
/**

View File

@@ -33,6 +33,8 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.http.server.PathContainer;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
@@ -45,8 +47,13 @@ import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
import org.springframework.web.servlet.mvc.condition.NameValueExpression;
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.WebUtils;
import org.springframework.web.util.pattern.PathPattern;
/**
* Abstract base class for classes for which {@link RequestMappingInfo} defines
@@ -80,8 +87,14 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
* Get the URL path patterns associated with the supplied {@link RequestMappingInfo}.
*/
@Override
@SuppressWarnings("deprecation")
protected Set<String> getMappingPathPatterns(RequestMappingInfo info) {
return info.getPatternsCondition().getPatterns();
return info.getPatternValues();
}
@Override
protected Set<String> getDirectPaths(RequestMappingInfo info) {
return info.getDirectPaths();
}
/**
@@ -124,37 +137,61 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
super.handleMatch(info, lookupPath, request);
String bestPattern;
Map<String, String> uriVariables;
Set<String> patterns = info.getPatternsCondition().getPatterns();
if (patterns.isEmpty()) {
bestPattern = lookupPath;
uriVariables = Collections.emptyMap();
RequestCondition<?> condition = info.getActivePatternsCondition();
if (condition instanceof PathPatternsRequestCondition) {
extractMatchDetails((PathPatternsRequestCondition) condition, lookupPath, request);
}
else {
bestPattern = patterns.iterator().next();
uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
extractMatchDetails((PatternsRequestCondition) condition, lookupPath, request);
}
request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
if (isMatrixVariableContentAvailable()) {
Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
}
Map<String, String> decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
}
}
private boolean isMatrixVariableContentAvailable() {
return !getUrlPathHelper().shouldRemoveSemicolonContent();
private void extractMatchDetails(
PathPatternsRequestCondition condition, String lookupPath, HttpServletRequest request) {
PathPattern bestPattern;
Map<String, String> uriVariables;
if (condition.isEmptyPathMapping()) {
bestPattern = condition.getFirstPattern();
uriVariables = Collections.emptyMap();
}
else {
PathContainer path = ServletRequestPathUtils.getParsedRequestPath(request).pathWithinApplication();
bestPattern = condition.getFirstPattern();
PathPattern.PathMatchInfo result = bestPattern.matchAndExtract(path);
Assert.notNull(result, () ->
"Expected bestPattern: " + bestPattern + " to match lookupPath " + path);
uriVariables = result.getUriVariables();
request.setAttribute(MATRIX_VARIABLES_ATTRIBUTE, result.getMatrixVariables());
}
request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern.getPatternString());
request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
}
private void extractMatchDetails(
PatternsRequestCondition condition, String lookupPath, HttpServletRequest request) {
String bestPattern;
Map<String, String> uriVariables;
if (condition.isEmptyPathMapping()) {
bestPattern = lookupPath;
uriVariables = Collections.emptyMap();
}
else {
bestPattern = condition.getPatterns().iterator().next();
uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
if (!getUrlPathHelper().shouldRemoveSemicolonContent()) {
request.setAttribute(MATRIX_VARIABLES_ATTRIBUTE, extractMatrixVariables(request, uriVariables));
}
uriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
}
request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
}
private Map<String, MultiValueMap<String, String>> extractMatrixVariables(
@@ -250,7 +287,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
public PartialMatchHelper(Set<RequestMappingInfo> infos, HttpServletRequest request) {
for (RequestMappingInfo info : infos) {
if (info.getPatternsCondition().getMatchingCondition(request) != null) {
if (info.getActivePatternsCondition().getMatchingCondition(request) != null) {
this.partialMatches.add(new PartialMatch(info, request));
}
}

View File

@@ -97,9 +97,7 @@ import org.springframework.web.util.UriComponentsBuilder;
*/
public class MvcUriComponentsBuilder {
/**
* Well-known name for the {@link CompositeUriComponentsContributor} object in the bean factory.
*/
/** Well-known name for the {@link CompositeUriComponentsContributor} object in the bean factory. */
public static final String MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME = "mvcUriComponentsContributor";

View File

@@ -23,7 +23,6 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import javax.servlet.http.HttpServletRequest;
@@ -52,6 +51,8 @@ import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Creates {@link RequestMappingInfo} instances from type and method-level
@@ -81,7 +82,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
private boolean useTrailingSlashMatch = true;
private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();
private Map<String, Predicate<Class<?>>> pathPrefixes = Collections.emptyMap();
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
@@ -97,6 +98,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
* <p>By default value this is set to {@code false}.
* <p>Also see {@link #setUseRegisteredSuffixPatternMatch(boolean)} for
* more fine-grained control over specific suffixes to allow.
* <p><strong>Note:</strong> This property is ignored when
* {@link #setPatternParser(PathPatternParser)} is configured.
* @deprecated as of 5.2.4. See class level note on the deprecation of
* path extension config options. As there is no replacement for this method,
* in 5.2.x it is necessary to set it to {@code false}. In 5.3 the default
@@ -113,6 +116,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
* is generally recommended to reduce ambiguity and to avoid issues such as
* when a "." appears in the path for other reasons.
* <p>By default this is set to "false".
* <p><strong>Note:</strong> This property is ignored when
* {@link #setPatternParser(PathPatternParser)} is configured.
* @deprecated as of 5.2.4. See class level note on the deprecation of
* path extension config options.
*/
@@ -129,6 +134,9 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
*/
public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch) {
this.useTrailingSlashMatch = useTrailingSlashMatch;
if (getPatternParser() != null) {
getPatternParser().setMatchOptionalTrailingSeparator(useTrailingSlashMatch);
}
}
/**
@@ -142,7 +150,9 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
* @since 5.1
*/
public void setPathPrefixes(Map<String, Predicate<Class<?>>> prefixes) {
this.pathPrefixes = Collections.unmodifiableMap(new LinkedHashMap<>(prefixes));
this.pathPrefixes = (!prefixes.isEmpty() ?
Collections.unmodifiableMap(new LinkedHashMap<>(prefixes)) :
Collections.emptyMap());
}
/**
@@ -177,14 +187,22 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
@Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(useSuffixPatternMatch());
this.config.setTrailingSlashMatch(useTrailingSlashMatch());
this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
this.config.setContentNegotiationManager(getContentNegotiationManager());
if (getPatternParser() != null) {
this.config.setPatternParser(getPatternParser());
Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,
"Suffix pattern matching not supported with PathPatternParser.");
}
else {
this.config.setSuffixPatternMatch(useSuffixPatternMatch());
this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
this.config.setPathMatcher(getPathMatcher());
}
super.afterPropertiesSet();
}
@@ -393,14 +411,14 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
@Override
public RequestMatchResult match(HttpServletRequest request, String pattern) {
Assert.isNull(getPatternParser(), "This HandlerMapping requires a PathPattern.");
RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(this.config).build();
RequestMappingInfo matchingInfo = info.getMatchingCondition(request);
if (matchingInfo == null) {
return null;
}
Set<String> patterns = matchingInfo.getPatternsCondition().getPatterns();
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request, LOOKUP_PATH);
return new RequestMatchResult(patterns.iterator().next(), lookupPath, getPathMatcher());
RequestMappingInfo match = info.getMatchingCondition(request);
return (match != null ?
new RequestMatchResult(
match.getPatternsCondition().getPatterns().iterator().next(),
UrlPathHelper.getResolvedLookupPath(request),
getPathMatcher()) : null);
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -57,10 +57,12 @@ public class ResourceUrlEncodingFilter extends GenericFilterBean {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
throw new ServletException("ResourceUrlEncodingFilter only supports HTTP requests");
}
ResourceUrlEncodingRequestWrapper wrappedRequest =
new ResourceUrlEncodingRequestWrapper((HttpServletRequest) request);
ResourceUrlEncodingResponseWrapper wrappedResponse =
new ResourceUrlEncodingResponseWrapper(wrappedRequest, (HttpServletResponse) response);
filterChain.doFilter(wrappedRequest, wrappedResponse);
}

View File

@@ -35,7 +35,6 @@ import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.util.UrlPathHelper;
@@ -181,8 +180,11 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
private int getLookupPathIndex(HttpServletRequest request) {
UrlPathHelper pathHelper = getUrlPathHelper();
if (request.getAttribute(UrlPathHelper.PATH_ATTRIBUTE) == null) {
pathHelper.resolveAndCacheLookupPath(request);
}
String requestUri = pathHelper.getRequestUri(request);
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
String lookupPath = UrlPathHelper.getResolvedLookupPath(request);
return requestUri.indexOf(lookupPath);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -395,9 +395,15 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
*/
protected final void prepareResponse(HttpServletResponse response) {
if (this.cacheControl != null) {
if (logger.isTraceEnabled()) {
logger.trace("Applying default " + getCacheControl());
}
applyCacheControl(response, this.cacheControl);
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Applying default cacheSeconds=" + this.cacheSeconds);
}
applyCacheSeconds(response, this.cacheSeconds);
}
if (this.varyByRequestHeaders != null) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -16,13 +16,13 @@
package org.springframework.web.servlet.view;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.RequestToViewNameTranslator;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UrlPathHelper;
/**
@@ -72,8 +72,6 @@ public class DefaultRequestToViewNameTranslator implements RequestToViewNameTran
private boolean stripExtension = true;
private UrlPathHelper urlPathHelper = new UrlPathHelper();
/**
* Set the prefix to prepend to generated view names.
@@ -127,25 +125,31 @@ public class DefaultRequestToViewNameTranslator implements RequestToViewNameTran
/**
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
* @deprecated as of 5.3, the path is resolved externally and obtained with
* {@link ServletRequestPathUtils#getCachedPathValue(ServletRequest)}
*/
@Deprecated
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
}
/**
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
* @deprecated as of 5.3, the path is resolved externally and obtained with
* {@link ServletRequestPathUtils#getCachedPathValue(ServletRequest)}
*/
@Deprecated
public void setUrlDecode(boolean urlDecode) {
this.urlPathHelper.setUrlDecode(urlDecode);
}
/**
* Set if ";" (semicolon) content should be stripped from the request URI.
* @see org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean)
* @deprecated as of 5.3, the path is resolved externally and obtained with
* {@link ServletRequestPathUtils#getCachedPathValue(ServletRequest)}
*/
@Deprecated
public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
}
/**
@@ -153,23 +157,26 @@ public class DefaultRequestToViewNameTranslator implements RequestToViewNameTran
* the resolution of lookup paths.
* <p>Use this to override the default UrlPathHelper with a custom subclass,
* or to share common UrlPathHelper settings across multiple web components.
* @deprecated as of 5.3, the path is resolved externally and obtained with
* {@link ServletRequestPathUtils#getCachedPathValue(ServletRequest)}
*/
@Deprecated
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
this.urlPathHelper = urlPathHelper;
}
/**
* Translates the request URI of the incoming {@link HttpServletRequest}
* into the view name based on the configured parameters.
* @see org.springframework.web.util.UrlPathHelper#getLookupPathForRequest
* @see ServletRequestPathUtils#getCachedPath(ServletRequest)
* @see #transformPath
* @throws IllegalArgumentException if neither a parsed RequestPath, nor a
* String lookupPath have been resolved and cached as a request attribute.
*/
@Override
public String getViewName(HttpServletRequest request) {
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
return (this.prefix + transformPath(lookupPath) + this.suffix);
String path = ServletRequestPathUtils.getCachedPathValue(request);
return (this.prefix + transformPath(path) + this.suffix);
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -16,9 +16,9 @@
package org.springframework.web.servlet.config.annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -34,16 +34,20 @@ import org.springframework.util.PathMatcher;
import org.springframework.validation.DefaultMessageCodesResolver;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import org.springframework.web.testfixture.servlet.MockServletContext;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@@ -82,15 +86,16 @@ public class DelegatingWebMvcConfigurationTests {
@Captor
private ArgumentCaptor<List<HandlerExceptionResolver>> exceptionResolvers;
private final DelegatingWebMvcConfiguration delegatingConfig = new DelegatingWebMvcConfiguration();
private final DelegatingWebMvcConfiguration webMvcConfig = new DelegatingWebMvcConfiguration();
@Test
public void requestMappingHandlerAdapter() throws Exception {
delegatingConfig.setConfigurers(Collections.singletonList(webMvcConfigurer));
RequestMappingHandlerAdapter adapter = this.delegatingConfig.requestMappingHandlerAdapter(
this.delegatingConfig.mvcContentNegotiationManager(), this.delegatingConfig.mvcConversionService(),
this.delegatingConfig.mvcValidator());
public void requestMappingHandlerAdapter() {
webMvcConfig.setConfigurers(Collections.singletonList(webMvcConfigurer));
RequestMappingHandlerAdapter adapter = this.webMvcConfig.requestMappingHandlerAdapter(
this.webMvcConfig.mvcContentNegotiationManager(),
this.webMvcConfig.mvcConversionService(),
this.webMvcConfig.mvcValidator());
ConfigurableWebBindingInitializer initializer =
(ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer();
@@ -114,10 +119,9 @@ public class DelegatingWebMvcConfigurationTests {
@Test
public void configureMessageConverters() {
final HttpMessageConverter<?> customConverter = mock(HttpMessageConverter.class);
final StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
List<WebMvcConfigurer> configurers = new ArrayList<>();
configurers.add(new WebMvcConfigurer() {
HttpMessageConverter<?> customConverter = mock(HttpMessageConverter.class);
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
WebMvcConfigurer configurer = new WebMvcConfigurer() {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(stringConverter);
@@ -127,13 +131,15 @@ public class DelegatingWebMvcConfigurationTests {
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, customConverter);
}
});
delegatingConfig.setConfigurers(configurers);
};
webMvcConfig.setConfigurers(Collections.singletonList(configurer));
RequestMappingHandlerAdapter adapter = delegatingConfig.requestMappingHandlerAdapter(
this.delegatingConfig.mvcContentNegotiationManager(), this.delegatingConfig.mvcConversionService(),
this.delegatingConfig.mvcValidator());
assertThat(adapter.getMessageConverters().size()).as("Only one custom converter should be registered").isEqualTo(2);
RequestMappingHandlerAdapter adapter = webMvcConfig.requestMappingHandlerAdapter(
this.webMvcConfig.mvcContentNegotiationManager(),
this.webMvcConfig.mvcConversionService(),
this.webMvcConfig.mvcValidator());
assertThat(adapter.getMessageConverters().size()).as("One custom converter expected").isEqualTo(2);
assertThat(adapter.getMessageConverters().get(0)).isSameAs(customConverter);
assertThat(adapter.getMessageConverters().get(1)).isSameAs(stringConverter);
}
@@ -142,8 +148,8 @@ public class DelegatingWebMvcConfigurationTests {
public void getCustomValidator() {
given(webMvcConfigurer.getValidator()).willReturn(new LocalValidatorFactoryBean());
delegatingConfig.setConfigurers(Collections.singletonList(webMvcConfigurer));
delegatingConfig.mvcValidator();
webMvcConfig.setConfigurers(Collections.singletonList(webMvcConfigurer));
webMvcConfig.mvcValidator();
verify(webMvcConfigurer).getValidator();
}
@@ -152,16 +158,16 @@ public class DelegatingWebMvcConfigurationTests {
public void getCustomMessageCodesResolver() {
given(webMvcConfigurer.getMessageCodesResolver()).willReturn(new DefaultMessageCodesResolver());
delegatingConfig.setConfigurers(Collections.singletonList(webMvcConfigurer));
delegatingConfig.getMessageCodesResolver();
webMvcConfig.setConfigurers(Collections.singletonList(webMvcConfigurer));
webMvcConfig.getMessageCodesResolver();
verify(webMvcConfigurer).getMessageCodesResolver();
}
@Test
public void handlerExceptionResolver() throws Exception {
delegatingConfig.setConfigurers(Collections.singletonList(webMvcConfigurer));
delegatingConfig.handlerExceptionResolver(delegatingConfig.mvcContentNegotiationManager());
public void handlerExceptionResolver() {
webMvcConfig.setConfigurers(Collections.singletonList(webMvcConfigurer));
webMvcConfig.handlerExceptionResolver(webMvcConfig.mvcContentNegotiationManager());
verify(webMvcConfigurer).configureMessageConverters(converters.capture());
verify(webMvcConfigurer).configureContentNegotiation(contentNegotiationConfigurer.capture());
@@ -178,29 +184,30 @@ public class DelegatingWebMvcConfigurationTests {
}
@Test
public void configureExceptionResolvers() throws Exception {
List<WebMvcConfigurer> configurers = new ArrayList<>();
configurers.add(new WebMvcConfigurer() {
public void configureExceptionResolvers() {
WebMvcConfigurer configurer = new WebMvcConfigurer() {
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(new DefaultHandlerExceptionResolver());
}
});
delegatingConfig.setConfigurers(configurers);
};
webMvcConfig.setConfigurers(Collections.singletonList(configurer));
HandlerExceptionResolverComposite composite =
(HandlerExceptionResolverComposite) delegatingConfig
.handlerExceptionResolver(delegatingConfig.mvcContentNegotiationManager());
assertThat(composite.getExceptionResolvers().size()).as("Only one custom converter is expected").isEqualTo(1);
(HandlerExceptionResolverComposite) webMvcConfig
.handlerExceptionResolver(webMvcConfig.mvcContentNegotiationManager());
assertThat(composite.getExceptionResolvers().size())
.as("Only one custom converter is expected")
.isEqualTo(1);
}
@Test
public void configurePathMatch() throws Exception {
final PathMatcher pathMatcher = mock(PathMatcher.class);
final UrlPathHelper pathHelper = mock(UrlPathHelper.class);
public void configurePathMatcher() {
PathMatcher pathMatcher = mock(PathMatcher.class);
UrlPathHelper pathHelper = mock(UrlPathHelper.class);
List<WebMvcConfigurer> configurers = new ArrayList<>();
configurers.add(new WebMvcConfigurer() {
WebMvcConfigurer configurer = new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseRegisteredSuffixPatternMatch(true)
@@ -208,18 +215,122 @@ public class DelegatingWebMvcConfigurationTests {
.setUrlPathHelper(pathHelper)
.setPathMatcher(pathMatcher);
}
});
delegatingConfig.setConfigurers(configurers);
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/");
}
};
RequestMappingHandlerMapping handlerMapping = delegatingConfig.requestMappingHandlerMapping(
delegatingConfig.mvcContentNegotiationManager(), delegatingConfig.mvcConversionService(),
delegatingConfig.mvcResourceUrlProvider());
assertThat(handlerMapping).isNotNull();
assertThat(handlerMapping.useRegisteredSuffixPatternMatch()).as("PathMatchConfigurer should configure RegisteredSuffixPatternMatch").isEqualTo(true);
assertThat(handlerMapping.useSuffixPatternMatch()).as("PathMatchConfigurer should configure SuffixPatternMatch").isEqualTo(true);
assertThat(handlerMapping.useTrailingSlashMatch()).as("PathMatchConfigurer should configure TrailingSlashMatch").isEqualTo(false);
assertThat(handlerMapping.getUrlPathHelper()).as("PathMatchConfigurer should configure UrlPathHelper").isEqualTo(pathHelper);
assertThat(handlerMapping.getPathMatcher()).as("PathMatchConfigurer should configure PathMatcher").isEqualTo(pathMatcher);
MockServletContext servletContext = new MockServletContext();
webMvcConfig.setConfigurers(Collections.singletonList(configurer));
webMvcConfig.setServletContext(servletContext);
webMvcConfig.setApplicationContext(new GenericWebApplicationContext(servletContext));
BiConsumer<UrlPathHelper, PathMatcher> configAssertion = (helper, matcher) -> {
assertThat(helper).isSameAs(pathHelper);
assertThat(matcher).isSameAs(pathMatcher);
};
RequestMappingHandlerMapping annotationsMapping = webMvcConfig.requestMappingHandlerMapping(
webMvcConfig.mvcContentNegotiationManager(),
webMvcConfig.mvcConversionService(),
webMvcConfig.mvcResourceUrlProvider());
assertThat(annotationsMapping).isNotNull();
assertThat(annotationsMapping.useRegisteredSuffixPatternMatch()).isEqualTo(true);
assertThat(annotationsMapping.useSuffixPatternMatch()).isEqualTo(true);
assertThat(annotationsMapping.useTrailingSlashMatch()).isEqualTo(false);
configAssertion.accept(annotationsMapping.getUrlPathHelper(), annotationsMapping.getPathMatcher());
SimpleUrlHandlerMapping mapping = (SimpleUrlHandlerMapping) webMvcConfig.viewControllerHandlerMapping(
webMvcConfig.mvcConversionService(),
webMvcConfig.mvcResourceUrlProvider());
assertThat(mapping).isNotNull();
configAssertion.accept(mapping.getUrlPathHelper(), mapping.getPathMatcher());
mapping = (SimpleUrlHandlerMapping) webMvcConfig.resourceHandlerMapping(
webMvcConfig.mvcContentNegotiationManager(),
webMvcConfig.mvcConversionService(),
webMvcConfig.mvcResourceUrlProvider());
assertThat(mapping).isNotNull();
configAssertion.accept(mapping.getUrlPathHelper(), mapping.getPathMatcher());
configAssertion.accept(
webMvcConfig.mvcResourceUrlProvider().getUrlPathHelper(),
webMvcConfig.mvcResourceUrlProvider().getPathMatcher());
configAssertion.accept(webMvcConfig.mvcUrlPathHelper(), webMvcConfig.mvcPathMatcher());
}
@Test
public void configurePathPatternParser() {
PathPatternParser patternParser = new PathPatternParser();
PathMatcher pathMatcher = mock(PathMatcher.class);
UrlPathHelper pathHelper = mock(UrlPathHelper.class);
WebMvcConfigurer configurer = new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setPatternParser(patternParser)
.setUrlPathHelper(pathHelper)
.setPathMatcher(pathMatcher);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/");
}
};
MockServletContext servletContext = new MockServletContext();
webMvcConfig.setConfigurers(Collections.singletonList(configurer));
webMvcConfig.setServletContext(servletContext);
webMvcConfig.setApplicationContext(new GenericWebApplicationContext(servletContext));
BiConsumer<UrlPathHelper, PathMatcher> configAssertion = (helper, matcher) -> {
assertThat(helper).isNotSameAs(pathHelper);
assertThat(matcher).isNotSameAs(pathMatcher);
};
RequestMappingHandlerMapping annotationsMapping = webMvcConfig.requestMappingHandlerMapping(
webMvcConfig.mvcContentNegotiationManager(),
webMvcConfig.mvcConversionService(),
webMvcConfig.mvcResourceUrlProvider());
assertThat(annotationsMapping).isNotNull();
assertThat(annotationsMapping.getPatternParser()).isSameAs(patternParser);
configAssertion.accept(annotationsMapping.getUrlPathHelper(), annotationsMapping.getPathMatcher());
SimpleUrlHandlerMapping mapping = (SimpleUrlHandlerMapping) webMvcConfig.viewControllerHandlerMapping(
webMvcConfig.mvcConversionService(),
webMvcConfig.mvcResourceUrlProvider());
assertThat(mapping).isNotNull();
assertThat(mapping.getPatternParser()).isSameAs(patternParser);
configAssertion.accept(mapping.getUrlPathHelper(), mapping.getPathMatcher());
mapping = (SimpleUrlHandlerMapping) webMvcConfig.resourceHandlerMapping(
webMvcConfig.mvcContentNegotiationManager(),
webMvcConfig.mvcConversionService(),
webMvcConfig.mvcResourceUrlProvider());
assertThat(mapping).isNotNull();
assertThat(mapping.getPatternParser()).isSameAs(patternParser);
configAssertion.accept(mapping.getUrlPathHelper(), mapping.getPathMatcher());
assertThat(webMvcConfig.mvcResourceUrlProvider().getUrlPathHelper()).isSameAs(pathHelper);
assertThat(webMvcConfig.mvcResourceUrlProvider().getPathMatcher()).isSameAs(pathMatcher);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -150,10 +150,9 @@ public class WebMvcConfigurationSupportExtensionTests {
.findFirst()
.orElseThrow(() -> new AssertionError("UserController bean not found"))
.getKey();
assertThat(info.getPatternsCondition().getPatterns()).isEqualTo(Collections.singleton("/api/user/{id}"));
assertThat(info.getPatternValues()).isEqualTo(Collections.singleton("/api/user/{id}"));
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) this.config.viewControllerHandlerMapping(
this.config.mvcPathMatcher(), this.config.mvcUrlPathHelper(),
this.config.mvcConversionService(), this.config.mvcResourceUrlProvider());
handlerMapping.setApplicationContext(this.context);
assertThat(handlerMapping).isNotNull();
@@ -171,7 +170,6 @@ public class WebMvcConfigurationSupportExtensionTests {
assertThat(chain.getHandler()).isNotNull();
handlerMapping = (AbstractHandlerMapping) this.config.resourceHandlerMapping(
this.config.mvcUrlPathHelper(), this.config.mvcPathMatcher(),
this.config.mvcContentNegotiationManager(), this.config.mvcConversionService(),
this.config.mvcResourceUrlProvider());
handlerMapping.setApplicationContext(this.context);
@@ -290,7 +288,6 @@ public class WebMvcConfigurationSupportExtensionTests {
request.setRequestURI("/resources/foo.gif");
SimpleUrlHandlerMapping handlerMapping = (SimpleUrlHandlerMapping) this.config.resourceHandlerMapping(
this.config.mvcUrlPathHelper(), this.config.mvcPathMatcher(),
this.config.mvcContentNegotiationManager(), this.config.mvcConversionService(),
this.config.mvcResourceUrlProvider());
handlerMapping.setApplicationContext(this.context);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -156,7 +156,7 @@ public class WebMvcConfigurationSupportTests {
}
@Test
public void requestMappingHandlerAdapter() throws Exception {
public void requestMappingHandlerAdapter() {
ApplicationContext context = initContext(WebConfig.class);
RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class);
List<HttpMessageConverter<?>> converters = adapter.getMessageConverters();
@@ -196,7 +196,7 @@ public class WebMvcConfigurationSupportTests {
}
@Test
public void uriComponentsContributor() throws Exception {
public void uriComponentsContributor() {
ApplicationContext context = initContext(WebConfig.class);
CompositeUriComponentsContributor uriComponentsContributor = context.getBean(
MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME,
@@ -207,7 +207,7 @@ public class WebMvcConfigurationSupportTests {
@Test
@SuppressWarnings("unchecked")
public void handlerExceptionResolver() throws Exception {
public void handlerExceptionResolver() {
ApplicationContext context = initContext(WebConfig.class);
HandlerExceptionResolverComposite compositeResolver =
context.getBean("handlerExceptionResolver", HandlerExceptionResolverComposite.class);
@@ -300,7 +300,7 @@ public class WebMvcConfigurationSupportTests {
}
@Test
public void defaultPathMatchConfiguration() throws Exception {
public void defaultPathMatchConfiguration() {
ApplicationContext context = initContext(WebConfig.class);
UrlPathHelper urlPathHelper = context.getBean(UrlPathHelper.class);
PathMatcher pathMatcher = context.getBean(PathMatcher.class);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat;
@@ -35,14 +36,15 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Arjen Poutsma
*/
public class DefaultServerRequestBuilderTests {
class DefaultServerRequestBuilderTests {
private final List<HttpMessageConverter<?>> messageConverters =
Collections.singletonList(new StringHttpMessageConverter());
private final List<HttpMessageConverter<?>> messageConverters = Collections.singletonList(
new StringHttpMessageConverter());
@Test
public void from() throws ServletException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest("POST", "https://example.com");
void from() throws ServletException, IOException {
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("POST", "https://example.com", true);
request.addHeader("foo", "bar");
ServerRequest other = ServerRequest.create(request, messageConverters);
@@ -65,7 +67,7 @@ public class DefaultServerRequestBuilderTests {
assertThat(result.cookies().size()).isEqualTo(2);
assertThat(result.cookies().getFirst("foo").getValue()).isEqualTo("bar");
assertThat(result.cookies().getFirst("baz").getValue()).isEqualTo("qux");
assertThat(result.attributes().size()).isEqualTo(2);
assertThat(result.attributes().size()).isEqualTo(other.attributes().size() + 2);
assertThat(result.attributes().get("foo")).isEqualTo("bar");
assertThat(result.attributes().get("baz")).isEqualTo("qux");

View File

@@ -51,6 +51,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import org.springframework.web.testfixture.server.MockServerWebExchange;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpSession;
@@ -66,23 +67,22 @@ import static org.springframework.web.testfixture.http.server.reactive.MockServe
* @author Arjen Poutsma
* @since 5.1
*/
public class DefaultServerRequestTests {
class DefaultServerRequestTests {
private final List<HttpMessageConverter<?>> messageConverters = Collections.singletonList(
new StringHttpMessageConverter());
@Test
public void method() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("HEAD", "/");
DefaultServerRequest request =
new DefaultServerRequest(servletRequest, this.messageConverters);
void method() {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("HEAD", "/", true);
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
assertThat(request.method()).isEqualTo(HttpMethod.HEAD);
}
@Test
public void uri() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
void uri() {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.setServerName("example.com");
servletRequest.setScheme("https");
servletRequest.setServerPort(443);
@@ -94,8 +94,8 @@ public class DefaultServerRequestTests {
}
@Test
public void uriBuilder() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/path");
void uriBuilder() {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/path", true);
servletRequest.setQueryString("a=1");
DefaultServerRequest request =
@@ -110,8 +110,8 @@ public class DefaultServerRequestTests {
}
@Test
public void attribute() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
void attribute() {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.setAttribute("foo", "bar");
DefaultServerRequest request =
@@ -121,8 +121,8 @@ public class DefaultServerRequestTests {
}
@Test
public void params() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
void params() {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.setParameter("foo", "bar");
DefaultServerRequest request =
@@ -132,11 +132,11 @@ public class DefaultServerRequestTests {
}
@Test
public void multipartData() throws Exception {
void multipartData() throws Exception {
MockPart formPart = new MockPart("form", "foo".getBytes(UTF_8));
MockPart filePart = new MockPart("file", "foo.txt", "foo".getBytes(UTF_8));
MockHttpServletRequest servletRequest = new MockHttpServletRequest("POST", "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("POST", "/", true);
servletRequest.addPart(formPart);
servletRequest.addPart(filePart);
@@ -151,8 +151,8 @@ public class DefaultServerRequestTests {
}
@Test
public void emptyQueryParam() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
void emptyQueryParam() {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.setParameter("foo", "");
DefaultServerRequest request =
@@ -162,8 +162,8 @@ public class DefaultServerRequestTests {
}
@Test
public void absentQueryParam() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
void absentQueryParam() {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.setParameter("foo", "");
DefaultServerRequest request =
@@ -173,8 +173,8 @@ public class DefaultServerRequestTests {
}
@Test
public void pathVariable() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
void pathVariable() {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
Map<String, String> pathVariables = Collections.singletonMap("foo", "bar");
servletRequest
.setAttribute(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathVariables);
@@ -186,8 +186,8 @@ public class DefaultServerRequestTests {
}
@Test
public void pathVariableNotFound() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
void pathVariableNotFound() {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
Map<String, String> pathVariables = Collections.singletonMap("foo", "bar");
servletRequest
.setAttribute(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathVariables);
@@ -200,8 +200,8 @@ public class DefaultServerRequestTests {
}
@Test
public void pathVariables() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
void pathVariables() {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
Map<String, String> pathVariables = Collections.singletonMap("foo", "bar");
servletRequest
.setAttribute(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathVariables);
@@ -213,7 +213,7 @@ public class DefaultServerRequestTests {
}
@Test
public void header() {
void header() {
HttpHeaders httpHeaders = new HttpHeaders();
List<MediaType> accept =
Collections.singletonList(MediaType.APPLICATION_JSON);
@@ -229,7 +229,7 @@ public class DefaultServerRequestTests {
List<HttpRange> range = Collections.singletonList(HttpRange.createByteRange(0, 42));
httpHeaders.setRange(range);
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
httpHeaders.forEach(servletRequest::addHeader);
servletRequest.setContentType(MediaType.TEXT_PLAIN_VALUE);
@@ -247,10 +247,10 @@ public class DefaultServerRequestTests {
}
@Test
public void cookies() {
void cookies() {
Cookie cookie = new Cookie("foo", "bar");
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.setCookies(cookie);
DefaultServerRequest request = new DefaultServerRequest(servletRequest,
@@ -264,8 +264,8 @@ public class DefaultServerRequestTests {
}
@Test
public void bodyClass() throws Exception {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
void bodyClass() throws Exception {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.setContentType(MediaType.TEXT_PLAIN_VALUE);
servletRequest.setContent("foo".getBytes(UTF_8));
@@ -277,8 +277,8 @@ public class DefaultServerRequestTests {
}
@Test
public void bodyParameterizedTypeReference() throws Exception {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
void bodyParameterizedTypeReference() throws Exception {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
servletRequest.setContent("[\"foo\",\"bar\"]".getBytes(UTF_8));
@@ -292,8 +292,8 @@ public class DefaultServerRequestTests {
}
@Test
public void bodyUnacceptable() throws Exception {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
void bodyUnacceptable() throws Exception {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.setContentType(MediaType.TEXT_PLAIN_VALUE);
servletRequest.setContent("foo".getBytes(UTF_8));
@@ -305,8 +305,8 @@ public class DefaultServerRequestTests {
}
@Test
public void session() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
void session() {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
MockHttpSession session = new MockHttpSession();
servletRequest.setSession(session);
@@ -318,8 +318,8 @@ public class DefaultServerRequestTests {
}
@Test
public void principal() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
void principal() {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
Principal principal = new Principal() {
@Override
public String getName() {
@@ -336,7 +336,7 @@ public class DefaultServerRequestTests {
@ParameterizedHttpMethodTest
void checkNotModifiedTimestamp(String method) throws Exception {
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, now.toEpochMilli());
@@ -352,7 +352,7 @@ public class DefaultServerRequestTests {
@ParameterizedHttpMethodTest
void checkModifiedTimestamp(String method) {
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
Instant oneMinuteAgo = now.minus(1, ChronoUnit.MINUTES);
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, oneMinuteAgo.toEpochMilli());
@@ -366,7 +366,7 @@ public class DefaultServerRequestTests {
@ParameterizedHttpMethodTest
void checkNotModifiedETag(String method) {
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
String eTag = "\"Foo\"";
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
@@ -382,7 +382,7 @@ public class DefaultServerRequestTests {
@ParameterizedHttpMethodTest
void checkNotModifiedETagWithSeparatorChars(String method) {
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
String eTag = "\"Foo, Bar\"";
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
@@ -398,7 +398,7 @@ public class DefaultServerRequestTests {
@ParameterizedHttpMethodTest
void checkModifiedETag(String method) {
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
String currentETag = "\"Foo\"";
String oldEtag = "Bar";
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, oldEtag);
@@ -412,7 +412,7 @@ public class DefaultServerRequestTests {
@ParameterizedHttpMethodTest
void checkNotModifiedUnpaddedETag(String method) {
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
String eTag = "Foo";
String paddedEtag = String.format("\"%s\"", eTag);
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, paddedEtag);
@@ -429,7 +429,7 @@ public class DefaultServerRequestTests {
@ParameterizedHttpMethodTest
void checkModifiedUnpaddedETag(String method) {
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
String currentETag = "Foo";
String oldEtag = "Bar";
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, oldEtag);
@@ -443,7 +443,7 @@ public class DefaultServerRequestTests {
@ParameterizedHttpMethodTest
void checkNotModifiedWildcardIsIgnored(String method) {
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
String eTag = "\"Foo\"";
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, "*");
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
@@ -455,7 +455,7 @@ public class DefaultServerRequestTests {
@ParameterizedHttpMethodTest
void checkNotModifiedETagAndTimestamp(String method) {
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
String eTag = "\"Foo\"";
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
@@ -475,7 +475,7 @@ public class DefaultServerRequestTests {
@ParameterizedHttpMethodTest
void checkNotModifiedETagAndModifiedTimestamp(String method) {
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
String eTag = "\"Foo\"";
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
Instant oneMinuteAgo = now.minus(1, ChronoUnit.MINUTES);
@@ -498,8 +498,8 @@ public class DefaultServerRequestTests {
}
@ParameterizedHttpMethodTest
void checkModifiedETagAndNotModifiedTimestamp(String method) throws Exception {
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
void checkModifiedETagAndNotModifiedTimestamp(String method) {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
String currentETag = "\"Foo\"";
String oldEtag = "\"Bar\"";
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -25,26 +25,20 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Arjen Poutsma
*/
public class PathResourceLookupFunctionTests {
class PathResourceLookupFunctionTests {
@Test
public void normal() throws Exception {
ClassPathResource location =
new ClassPathResource("org/springframework/web/servlet/function/");
PathResourceLookupFunction function =
new PathResourceLookupFunction("/resources/**", location);
MockHttpServletRequest servletRequest =
new MockHttpServletRequest("GET", "/resources/response.txt");
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.emptyList());
void normal() throws Exception {
ClassPathResource location = new ClassPathResource("org/springframework/web/servlet/function/");
PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location);
ServerRequest request = initRequest("GET", "/resources/response.txt");
Optional<Resource> result = function.apply(request);
assertThat(result.isPresent()).isTrue();
@@ -54,44 +48,30 @@ public class PathResourceLookupFunctionTests {
}
@Test
public void subPath() throws Exception {
ClassPathResource location =
new ClassPathResource("org/springframework/web/servlet/function/");
PathResourceLookupFunction function =
new PathResourceLookupFunction("/resources/**", location);
MockHttpServletRequest servletRequest =
new MockHttpServletRequest("GET", "/resources/child/response.txt");
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.emptyList());
void subPath() throws Exception {
ClassPathResource location = new ClassPathResource("org/springframework/web/servlet/function/");
PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location);
ServerRequest request = initRequest("GET", "/resources/child/response.txt");
Optional<Resource> result = function.apply(request);
assertThat(result.isPresent()).isTrue();
File expected =
new ClassPathResource("org/springframework/web/servlet/function/child/response.txt")
.getFile();
File expected = new ClassPathResource("org/springframework/web/servlet/function/child/response.txt").getFile();
assertThat(result.get().getFile()).isEqualTo(expected);
}
@Test
public void notFound() {
ClassPathResource location =
new ClassPathResource("org/springframework/web/reactive/function/server/");
PathResourceLookupFunction function =
new PathResourceLookupFunction("/resources/**", location);
MockHttpServletRequest servletRequest =
new MockHttpServletRequest("GET", "/resources/foo.txt");
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.emptyList());
void notFound() {
ClassPathResource location = new ClassPathResource("org/springframework/web/reactive/function/server/");
PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location);
ServerRequest request = initRequest("GET", "/resources/foo.txt");
Optional<Resource> result = function.apply(request);
assertThat(result.isPresent()).isFalse();
}
@Test
public void composeResourceLookupFunction() throws Exception {
void composeResourceLookupFunction() throws Exception {
ClassPathResource defaultResource = new ClassPathResource("response.txt", getClass());
Function<ServerRequest, Optional<Resource>> lookupFunction =
@@ -108,8 +88,7 @@ public class PathResourceLookupFunctionTests {
}
});
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/resources/foo");
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.emptyList());
ServerRequest request = initRequest("GET", "/resources/foo");
Optional<Resource> result = customLookupFunction.apply(request);
assertThat(result.isPresent()).isTrue();
@@ -117,4 +96,10 @@ public class PathResourceLookupFunctionTests {
assertThat(result.get().getFile()).isEqualTo(defaultResource.getFile());
}
private ServerRequest initRequest(String httpMethod, String requestUri) {
return new DefaultServerRequest(
PathPatternsTestUtils.initRequest(httpMethod, requestUri, true),
Collections.emptyList());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -21,6 +21,7 @@ import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat;
@@ -28,18 +29,18 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Arjen Poutsma
*/
public class RequestPredicateTests {
class RequestPredicateTests {
private ServerRequest request;
@BeforeEach
public void createRequest() {
this.request = new DefaultServerRequest(new MockHttpServletRequest(),
Collections.emptyList());
void createRequest() {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
this.request = new DefaultServerRequest(servletRequest, Collections.emptyList());
}
@Test
public void and() {
void and() {
RequestPredicate predicate1 = request -> true;
RequestPredicate predicate2 = request -> true;
RequestPredicate predicate3 = request -> false;
@@ -50,7 +51,7 @@ public class RequestPredicateTests {
}
@Test
public void negate() {
void negate() {
RequestPredicate predicate = request -> false;
RequestPredicate negated = predicate.negate();
@@ -63,7 +64,7 @@ public class RequestPredicateTests {
}
@Test
public void or() {
void or() {
RequestPredicate predicate1 = request -> true;
RequestPredicate predicate2 = request -> false;
RequestPredicate predicate3 = request -> false;

View File

@@ -17,6 +17,7 @@
package org.springframework.web.servlet.function;
import java.util.Collections;
import java.util.function.Consumer;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
@@ -24,245 +25,189 @@ import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.util.pattern.PathPatternParser;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.http.MediaType.TEXT_XML_VALUE;
/**
* @author Arjen Poutsma
*/
public class RequestPredicatesTests {
class RequestPredicatesTests {
@Test
public void all() {
void all() {
RequestPredicate predicate = RequestPredicates.all();
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest request = initRequest("GET", "/");
assertThat(predicate.test(request)).isTrue();
}
@Test
public void method() {
void method() {
HttpMethod httpMethod = HttpMethod.GET;
RequestPredicate predicate = RequestPredicates.method(httpMethod);
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "https://example.com");
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
assertThat(predicate.test(request)).isTrue();
servletRequest.setMethod("POST");
assertThat(predicate.test(request)).isFalse();
assertThat(predicate.test(initRequest("GET", "https://example.com"))).isTrue();
assertThat(predicate.test(initRequest("POST", "https://example.com"))).isFalse();
}
@Test
public void methodCorsPreFlight() {
void methodCorsPreFlight() {
RequestPredicate predicate = RequestPredicates.method(HttpMethod.PUT);
MockHttpServletRequest servletRequest = new MockHttpServletRequest("OPTIONS", "https://example.com");
servletRequest.addHeader("Origin", "https://example.com");
servletRequest.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "PUT");
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest request = initRequest("OPTIONS", "https://example.com", servletRequest -> {
servletRequest.addHeader("Origin", "https://example.com");
servletRequest.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "PUT");
});
assertThat(predicate.test(request)).isTrue();
servletRequest.removeHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
servletRequest.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST");
request = new DefaultServerRequest(servletRequest, emptyList());
request = initRequest("OPTIONS", "https://example.com", servletRequest -> {
servletRequest.removeHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
servletRequest.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST");
});
assertThat(predicate.test(request)).isFalse();
}
@Test
public void methods() {
void methods() {
RequestPredicate predicate = RequestPredicates.methods(HttpMethod.GET, HttpMethod.HEAD);
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "https://example.com");
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
assertThat(predicate.test(request)).isTrue();
servletRequest.setMethod("HEAD");
assertThat(predicate.test(request)).isTrue();
servletRequest.setMethod("POST");
assertThat(predicate.test(request)).isFalse();
assertThat(predicate.test(initRequest("GET", "https://example.com"))).isTrue();
assertThat(predicate.test(initRequest("HEAD", "https://example.com"))).isTrue();
assertThat(predicate.test(initRequest("POST", "https://example.com"))).isFalse();
}
@Test
public void allMethods() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/path");
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
void allMethods() {
RequestPredicate predicate = RequestPredicates.GET("/p*");
assertThat(predicate.test(request)).isTrue();
assertThat(predicate.test(initRequest("GET", "/path"))).isTrue();
predicate = RequestPredicates.HEAD("/p*");
servletRequest.setMethod("HEAD");
assertThat(predicate.test(request)).isTrue();
assertThat(predicate.test(initRequest("HEAD", "/path"))).isTrue();
predicate = RequestPredicates.POST("/p*");
servletRequest.setMethod("POST");
assertThat(predicate.test(request)).isTrue();
assertThat(predicate.test(initRequest("POST", "/path"))).isTrue();
predicate = RequestPredicates.PUT("/p*");
servletRequest.setMethod("PUT");
assertThat(predicate.test(request)).isTrue();
assertThat(predicate.test(initRequest("PUT", "/path"))).isTrue();
predicate = RequestPredicates.PATCH("/p*");
servletRequest.setMethod("PATCH");
assertThat(predicate.test(request)).isTrue();
assertThat(predicate.test(initRequest("PATCH", "/path"))).isTrue();
predicate = RequestPredicates.DELETE("/p*");
servletRequest.setMethod("DELETE");
assertThat(predicate.test(request)).isTrue();
assertThat(predicate.test(initRequest("DELETE", "/path"))).isTrue();
predicate = RequestPredicates.OPTIONS("/p*");
servletRequest.setMethod("OPTIONS");
assertThat(predicate.test(request)).isTrue();
assertThat(predicate.test(initRequest("OPTIONS", "/path"))).isTrue();
}
@Test
public void path() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/path");
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
void path() {
RequestPredicate predicate = RequestPredicates.path("/p*");
assertThat(predicate.test(request)).isTrue();
servletRequest = new MockHttpServletRequest("GET", "/foo");
request = new DefaultServerRequest(servletRequest, emptyList());
assertThat(predicate.test(request)).isFalse();
assertThat(predicate.test(initRequest("GET", "/path"))).isTrue();
assertThat(predicate.test(initRequest("GET", "/foo"))).isFalse();
}
@Test
public void servletPath() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/foo/bar");
servletRequest.setServletPath("/foo");
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
void contextPath() {
RequestPredicate predicate = RequestPredicates.path("/bar");
ServerRequest request = new DefaultServerRequest(
PathPatternsTestUtils.initRequest("GET", "/foo", "/bar", true),
Collections.emptyList());
assertThat(predicate.test(request)).isTrue();
servletRequest = new MockHttpServletRequest("GET", "/foo");
request = new DefaultServerRequest(servletRequest, emptyList());
request = initRequest("GET", "/foo");
assertThat(predicate.test(request)).isFalse();
}
@Test
public void pathNoLeadingSlash() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/path");
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
void pathNoLeadingSlash() {
RequestPredicate predicate = RequestPredicates.path("p*");
assertThat(predicate.test(request)).isTrue();
assertThat(predicate.test(initRequest("GET", "/path"))).isTrue();
}
@Test
public void pathEncoded() {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/foo%20bar");
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
void pathEncoded() {
RequestPredicate predicate = RequestPredicates.path("/foo bar");
assertThat(predicate.test(request)).isTrue();
servletRequest = new MockHttpServletRequest();
request = new DefaultServerRequest(servletRequest, emptyList());
assertThat(predicate.test(request)).isFalse();
assertThat(predicate.test(initRequest("GET", "/foo%20bar"))).isTrue();
assertThat(predicate.test(initRequest("GET", ""))).isFalse();
}
@Test
public void pathPredicates() {
void pathPredicates() {
PathPatternParser parser = new PathPatternParser();
parser.setCaseSensitive(false);
Function<String, RequestPredicate> pathPredicates = RequestPredicates.pathPredicates(parser);
RequestPredicate predicate = pathPredicates.apply("/P*");
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/path");
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
assertThat(predicate.test(request)).isTrue();
assertThat(predicate.test(initRequest("GET", "/path"))).isTrue();
}
@Test
public void headers() {
void headers() {
String name = "MyHeader";
String value = "MyValue";
RequestPredicate predicate =
RequestPredicates.headers(
headers -> headers.header(name).equals(Collections.singletonList(value)));
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/path");
servletRequest.addHeader(name, value);
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest request = initRequest("GET", "/path", req -> req.addHeader(name, value));
assertThat(predicate.test(request)).isTrue();
servletRequest = new MockHttpServletRequest();
request = new DefaultServerRequest(servletRequest, emptyList());
assertThat(predicate.test(request)).isFalse();
assertThat(predicate.test(initRequest("GET", ""))).isFalse();
}
@Test
public void headersCors() {
void headersCors() {
RequestPredicate predicate = RequestPredicates.headers(headers -> false);
MockHttpServletRequest servletRequest = new MockHttpServletRequest("OPTIONS", "https://example.com");
servletRequest.addHeader("Origin", "https://example.com");
servletRequest.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "PUT");
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest request = initRequest("OPTIONS", "https://example.com", servletRequest -> {
servletRequest.addHeader("Origin", "https://example.com");
servletRequest.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "PUT");
});
assertThat(predicate.test(request)).isTrue();
}
@Test
public void contentType() {
void contentType() {
MediaType json = MediaType.APPLICATION_JSON;
RequestPredicate predicate = RequestPredicates.contentType(json);
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/path");
servletRequest.setContentType(json.toString());
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest request = initRequest("GET", "/path", req -> req.setContentType(json.toString()));
assertThat(predicate.test(request)).isTrue();
servletRequest = new MockHttpServletRequest();
request = new DefaultServerRequest(servletRequest, emptyList());
assertThat(predicate.test(request)).isFalse();
assertThat(predicate.test(initRequest("GET", ""))).isFalse();
}
@Test
public void accept() {
void accept() {
MediaType json = MediaType.APPLICATION_JSON;
RequestPredicate predicate = RequestPredicates.accept(json);
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/path");
servletRequest.addHeader("Accept", json.toString());
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest request = initRequest("GET", "/path", req -> req.addHeader("Accept", json.toString()));
assertThat(predicate.test(request)).isTrue();
servletRequest = new MockHttpServletRequest();
servletRequest.addHeader("Accept", TEXT_XML_VALUE);
request = new DefaultServerRequest(servletRequest, emptyList());
request = initRequest("GET", "", req -> req.addHeader("Accept", TEXT_XML_VALUE));
assertThat(predicate.test(request)).isFalse();
}
@Test
public void pathExtension() {
void pathExtension() {
RequestPredicate predicate = RequestPredicates.pathExtension("txt");
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/file.txt");
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
assertThat(predicate.test(request)).isTrue();
servletRequest = new MockHttpServletRequest("GET", "/FILE.TXT");
request = new DefaultServerRequest(servletRequest, emptyList());
assertThat(predicate.test(request)).isTrue();
assertThat(predicate.test(initRequest("GET", "/file.txt"))).isTrue();
assertThat(predicate.test(initRequest("GET", "/FILE.TXT"))).isTrue();
predicate = RequestPredicates.pathExtension("bar");
assertThat(predicate.test(request)).isFalse();
assertThat(predicate.test(initRequest("GET", "/FILE.TXT"))).isFalse();
servletRequest = new MockHttpServletRequest("GET", "/file.foo");
request = new DefaultServerRequest(servletRequest, emptyList());
assertThat(predicate.test(request)).isFalse();
assertThat(predicate.test(initRequest("GET", "/file.foo"))).isFalse();
}
@Test
public void param() {
void param() {
RequestPredicate predicate = RequestPredicates.param("foo", "bar");
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/path");
servletRequest.addParameter("foo", "bar");
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest request = initRequest("GET", "/path", req -> req.addParameter("foo", "bar"));
assertThat(predicate.test(request)).isTrue();
predicate = RequestPredicates.param("foo", s -> s.equals("bar"));
@@ -275,4 +220,17 @@ public class RequestPredicatesTests {
assertThat(predicate.test(request)).isFalse();
}
private ServerRequest initRequest(String httpMethod, String requestUri) {
return initRequest(httpMethod, requestUri, null);
}
private ServerRequest initRequest(
String httpMethod, String requestUri, @Nullable Consumer<MockHttpServletRequest> initializer) {
return new DefaultServerRequest(
PathPatternsTestUtils.initRequest(httpMethod, null, requestUri, true, initializer),
Collections.emptyList());
}
}

View File

@@ -22,7 +22,6 @@ import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import javax.servlet.ServletException;
@@ -35,10 +34,10 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.ResourceRegionHttpMessageConverter;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
@@ -61,19 +60,13 @@ public class ResourceHandlerFunctionTests {
public void createContext() {
this.messageConverter = new ResourceHttpMessageConverter();
ResourceRegionHttpMessageConverter regionConverter = new ResourceRegionHttpMessageConverter();
this.context = new ServerResponse.Context() {
@Override
public List<HttpMessageConverter<?>> messageConverters() {
return Arrays.asList(messageConverter, regionConverter);
}
};
this.context = () -> Arrays.asList(messageConverter, regionConverter);
}
@Test
public void get() throws IOException, ServletException {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.singletonList(messageConverter));
ServerResponse response = this.handlerFunction.handle(request);
@@ -97,7 +90,7 @@ public class ResourceHandlerFunctionTests {
@Test
public void getRange() throws IOException, ServletException {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.addHeader("Range", "bytes=0-5");
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.singletonList(messageConverter));
@@ -126,7 +119,7 @@ public class ResourceHandlerFunctionTests {
@Test
public void getInvalidRange() throws IOException, ServletException {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.addHeader("Range", "bytes=0-10, 0-10, 0-10, 0-10, 0-10, 0-10");
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.singletonList(messageConverter));
@@ -152,7 +145,7 @@ public class ResourceHandlerFunctionTests {
@Test
public void head() throws IOException, ServletException {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("HEAD", "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("HEAD", "/", true);
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.singletonList(messageConverter));
ServerResponse response = this.handlerFunction.handle(request);
@@ -174,10 +167,9 @@ public class ResourceHandlerFunctionTests {
assertThat(servletResponse.getContentLength()).isEqualTo(this.resource.contentLength());
}
@Test
public void options() throws ServletException, IOException {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("OPTIONS", "/");
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("OPTIONS", "/", true);
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.singletonList(messageConverter));
ServerResponse response = this.handlerFunction.handle(request);
@@ -194,5 +186,4 @@ public class ResourceHandlerFunctionTests {
assertThat(actualBytes.length).isEqualTo(0);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -18,6 +18,7 @@ package org.springframework.web.servlet.function;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
@@ -25,6 +26,8 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import static java.util.Collections.emptyList;
@@ -36,10 +39,10 @@ import static org.springframework.web.servlet.function.RequestPredicates.HEAD;
*
* @author Arjen Poutsma
*/
public class RouterFunctionBuilderTests {
class RouterFunctionBuilderTests {
@Test
public void route() {
void route() {
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/foo", request -> ServerResponse.ok().build())
.POST("/", RequestPredicates.contentType(MediaType.TEXT_PLAIN),
@@ -47,8 +50,7 @@ public class RouterFunctionBuilderTests {
.route(HEAD("/foo"), request -> ServerResponse.accepted().build())
.build();
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/foo");
ServerRequest getFooRequest = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest getFooRequest = initRequest("GET", "/foo");
Optional<Integer> responseStatus = route.route(getFooRequest)
.map(handlerFunction -> handle(handlerFunction, getFooRequest))
@@ -56,8 +58,7 @@ public class RouterFunctionBuilderTests {
.map(HttpStatus::value);
assertThat(responseStatus.get().intValue()).isEqualTo(200);
servletRequest = new MockHttpServletRequest("HEAD", "/foo");
ServerRequest headFooRequest = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest headFooRequest = initRequest("HEAD", "/foo");
responseStatus = route.route(headFooRequest)
.map(handlerFunction -> handle(handlerFunction, getFooRequest))
@@ -65,9 +66,7 @@ public class RouterFunctionBuilderTests {
.map(HttpStatus::value);
assertThat(responseStatus.get().intValue()).isEqualTo(202);
servletRequest = new MockHttpServletRequest("POST", "/");
servletRequest.setContentType("text/plain");
ServerRequest barRequest = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest barRequest = initRequest("POST", "/", req -> req.setContentType("text/plain"));
responseStatus = route.route(barRequest)
.map(handlerFunction -> handle(handlerFunction, barRequest))
@@ -75,8 +74,7 @@ public class RouterFunctionBuilderTests {
.map(HttpStatus::value);
assertThat(responseStatus.get().intValue()).isEqualTo(204);
servletRequest = new MockHttpServletRequest("POST", "/");
ServerRequest invalidRequest = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest invalidRequest = initRequest("POST", "/");
responseStatus = route.route(invalidRequest)
.map(handlerFunction -> handle(handlerFunction, invalidRequest))
@@ -84,7 +82,6 @@ public class RouterFunctionBuilderTests {
.map(HttpStatus::value);
assertThat(responseStatus.isPresent()).isFalse();
}
private static ServerResponse handle(HandlerFunction<ServerResponse> handlerFunction,
@@ -98,7 +95,7 @@ public class RouterFunctionBuilderTests {
}
@Test
public void resources() {
void resources() {
Resource resource = new ClassPathResource("/org/springframework/web/servlet/function/");
assertThat(resource.exists()).isTrue();
@@ -106,9 +103,7 @@ public class RouterFunctionBuilderTests {
.resources("/resources/**", resource)
.build();
MockHttpServletRequest servletRequest =
new MockHttpServletRequest("GET", "/resources/response.txt");
ServerRequest resourceRequest = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest resourceRequest = initRequest("GET", "/resources/response.txt");
Optional<Integer> responseStatus = route.route(resourceRequest)
.map(handlerFunction -> handle(handlerFunction, resourceRequest))
@@ -116,8 +111,7 @@ public class RouterFunctionBuilderTests {
.map(HttpStatus::value);
assertThat(responseStatus.get().intValue()).isEqualTo(200);
servletRequest = new MockHttpServletRequest("POST", "/resources/foo.txt");
ServerRequest invalidRequest = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest invalidRequest = initRequest("POST", "/resources/foo.txt");
responseStatus = route.route(invalidRequest)
.map(handlerFunction -> handle(handlerFunction, invalidRequest))
@@ -127,7 +121,7 @@ public class RouterFunctionBuilderTests {
}
@Test
public void nest() {
void nest() {
RouterFunction<ServerResponse> route = RouterFunctions.route()
.path("/foo", builder ->
builder.path("/bar",
@@ -136,8 +130,7 @@ public class RouterFunctionBuilderTests {
.build()))
.build();
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/foo/bar/baz");
ServerRequest fooRequest = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest fooRequest = initRequest("GET", "/foo/bar/baz");
Optional<Integer> responseStatus = route.route(fooRequest)
.map(handlerFunction -> handle(handlerFunction, fooRequest))
@@ -147,7 +140,7 @@ public class RouterFunctionBuilderTests {
}
@Test
public void filters() {
void filters() {
AtomicInteger filterCount = new AtomicInteger();
RouterFunction<ServerResponse> route = RouterFunctions.route()
@@ -178,8 +171,7 @@ public class RouterFunctionBuilderTests {
.build())
.build();
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/foo");
ServerRequest fooRequest = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest fooRequest = initRequest("GET", "/foo");
route.route(fooRequest)
.map(handlerFunction -> handle(handlerFunction, fooRequest));
@@ -187,8 +179,7 @@ public class RouterFunctionBuilderTests {
filterCount.set(0);
servletRequest = new MockHttpServletRequest("GET", "/bar");
ServerRequest barRequest = new DefaultServerRequest(servletRequest, emptyList());
ServerRequest barRequest = initRequest("GET", "/bar");
Optional<Integer> responseStatus = route.route(barRequest)
.map(handlerFunction -> handle(handlerFunction, barRequest))
@@ -197,4 +188,16 @@ public class RouterFunctionBuilderTests {
assertThat(responseStatus.get().intValue()).isEqualTo(500);
}
private ServerRequest initRequest(String httpMethod, String requestUri) {
return initRequest(httpMethod, requestUri, null);
}
private ServerRequest initRequest(
String httpMethod, String requestUri, @Nullable Consumer<MockHttpServletRequest> consumer) {
return new DefaultServerRequest(
PathPatternsTestUtils.initRequest(httpMethod, null, requestUri, true, consumer), emptyList());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -16,22 +16,26 @@
package org.springframework.web.servlet.function;
import java.util.Collections;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Arjen Poutsma
*/
public class RouterFunctionTests {
class RouterFunctionTests {
private final ServerRequest request = new DefaultServerRequest(
PathPatternsTestUtils.initRequest("GET", "", true), Collections.emptyList());
@Test
public void and() {
void and() {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
RouterFunction<ServerResponse> routerFunction1 = request -> Optional.empty();
RouterFunction<ServerResponse> routerFunction2 = request -> Optional.of(handlerFunction);
@@ -39,9 +43,6 @@ public class RouterFunctionTests {
RouterFunction<ServerResponse> result = routerFunction1.and(routerFunction2);
assertThat(result).isNotNull();
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
Optional<HandlerFunction<ServerResponse>> resultHandlerFunction = result.route(request);
assertThat(resultHandlerFunction.isPresent()).isTrue();
assertThat(resultHandlerFunction.get()).isEqualTo(handlerFunction);
@@ -49,7 +50,7 @@ public class RouterFunctionTests {
@Test
public void andOther() {
void andOther() {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().body("42");
RouterFunction<?> routerFunction1 = request -> Optional.empty();
RouterFunction<ServerResponse> routerFunction2 = request -> Optional.of(handlerFunction);
@@ -57,9 +58,6 @@ public class RouterFunctionTests {
RouterFunction<?> result = routerFunction1.andOther(routerFunction2);
assertThat(result).isNotNull();
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
Optional<? extends HandlerFunction<?>> resultHandlerFunction = result.route(request);
assertThat(resultHandlerFunction.isPresent()).isTrue();
assertThat(resultHandlerFunction.get()).isEqualTo(handlerFunction);
@@ -67,23 +65,20 @@ public class RouterFunctionTests {
@Test
public void andRoute() {
void andRoute() {
RouterFunction<ServerResponse> routerFunction1 = request -> Optional.empty();
RequestPredicate requestPredicate = request -> true;
RouterFunction<ServerResponse> result = routerFunction1.andRoute(requestPredicate, this::handlerMethod);
assertThat(result).isNotNull();
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
Optional<? extends HandlerFunction<?>> resultHandlerFunction = result.route(request);
assertThat(resultHandlerFunction.isPresent()).isTrue();
}
@Test
public void filter() {
void filter() {
String string = "42";
HandlerFunction<EntityResponse<String>> handlerFunction =
request -> EntityResponse.fromObject(string).build();
@@ -100,9 +95,6 @@ public class RouterFunctionTests {
RouterFunction<EntityResponse<Integer>> result = routerFunction.filter(filterFunction);
assertThat(result).isNotNull();
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
ServerRequest request = new DefaultServerRequest(servletRequest, emptyList());
Optional<EntityResponse<Integer>> resultHandlerFunction = result.route(request)
.map(hf -> {
try {

View File

@@ -21,7 +21,7 @@ import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@@ -32,12 +32,14 @@ import static org.mockito.Mockito.mock;
*/
public class RouterFunctionsTests {
private final ServerRequest request = new DefaultServerRequest(
PathPatternsTestUtils.initRequest("GET", "", true), Collections.emptyList());
@Test
public void routeMatch() {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.emptyList());
RequestPredicate requestPredicate = mock(RequestPredicate.class);
given(requestPredicate.test(request)).willReturn(true);
@@ -54,8 +56,6 @@ public class RouterFunctionsTests {
public void routeNoMatch() {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.emptyList());
RequestPredicate requestPredicate = mock(RequestPredicate.class);
given(requestPredicate.test(request)).willReturn(false);
@@ -71,8 +71,6 @@ public class RouterFunctionsTests {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
RouterFunction<ServerResponse> routerFunction = request -> Optional.of(handlerFunction);
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.emptyList());
RequestPredicate requestPredicate = mock(RequestPredicate.class);
given(requestPredicate.nest(request)).willReturn(Optional.of(request));
@@ -89,8 +87,6 @@ public class RouterFunctionsTests {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
RouterFunction<ServerResponse> routerFunction = request -> Optional.of(handlerFunction);
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.emptyList());
RequestPredicate requestPredicate = mock(RequestPredicate.class);
given(requestPredicate.nest(request)).willReturn(Optional.empty());

View File

@@ -16,22 +16,18 @@
package org.springframework.web.servlet.handler;
import java.io.IOException;
import java.util.Collections;
import java.util.stream.Stream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.util.ObjectUtils;
import org.springframework.http.server.RequestPath;
import org.springframework.lang.Nullable;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
@@ -39,6 +35,8 @@ import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -50,205 +48,214 @@ import static org.mockito.Mockito.mock;
*/
class CorsAbstractHandlerMappingTests {
private MockHttpServletRequest request;
private AbstractHandlerMapping handlerMapping;
@BeforeEach
void setup() {
StaticWebApplicationContext context = new StaticWebApplicationContext();
this.handlerMapping = new TestHandlerMapping();
this.handlerMapping.setInterceptors(mock(HandlerInterceptor.class));
this.handlerMapping.setApplicationContext(context);
this.request = new MockHttpServletRequest();
this.request.setRemoteHost("domain1.com");
@SuppressWarnings("unused")
private static Stream<TestHandlerMapping> pathPatternsArguments() {
return Stream.of(new TestHandlerMapping(new PathPatternParser()), new TestHandlerMapping());
}
@Test
void actualRequestWithoutCorsConfigurationProvider() throws Exception {
this.request.setMethod(RequestMethod.GET.name());
this.request.setRequestURI("/foo");
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
@PathPatternsParameterizedTest
void actualRequestWithoutCorsConfig(TestHandlerMapping mapping) throws Exception {
HandlerExecutionChain chain = mapping.getHandler(getCorsRequest("/foo"));
assertThat(chain).isNotNull();
assertThat(chain.getHandler()).isInstanceOf(SimpleHandler.class);
assertThat(mapping.hasSavedCorsConfig()).isFalse();
}
@Test
void preflightRequestWithoutCorsConfigurationProvider() throws Exception {
this.request.setMethod(RequestMethod.OPTIONS.name());
this.request.setRequestURI("/foo");
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
@PathPatternsParameterizedTest
void preflightRequestWithoutCorsConfig(TestHandlerMapping mapping) throws Exception {
HandlerExecutionChain chain = mapping.getHandler(getPreFlightRequest("/foo"));
assertThat(chain).isNotNull();
assertThat(chain.getHandler()).isNotNull();
assertThat(chain.getHandler().getClass().getSimpleName()).isEqualTo("PreFlightHandler");
assertThat(mapping.hasSavedCorsConfig()).isFalse();
}
@Test
void actualRequestWithCorsConfigurationProvider() throws Exception {
this.request.setMethod(RequestMethod.GET.name());
this.request.setRequestURI("/cors");
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
@PathPatternsParameterizedTest
void actualRequestWithCorsConfigProvider(TestHandlerMapping mapping) throws Exception {
HandlerExecutionChain chain = mapping.getHandler(getCorsRequest("/cors"));
assertThat(chain).isNotNull();
assertThat(chain.getHandler()).isInstanceOf(CorsAwareHandler.class);
assertThat(getRequiredCorsConfiguration(chain, false).getAllowedOrigins()).containsExactly("*");
assertThat(mapping.getRequiredCorsConfig().getAllowedOrigins()).containsExactly("*");
}
@Test // see gh-23843
void actualRequestWithCorsConfigurationProviderForHandlerChain() throws Exception {
this.request.setMethod(RequestMethod.GET.name());
this.request.setRequestURI("/chain");
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
@PathPatternsParameterizedTest // see gh-23843
void actualRequestWithCorsConfigProviderForHandlerChain(TestHandlerMapping mapping) throws Exception {
HandlerExecutionChain chain = mapping.getHandler(getCorsRequest("/chain"));
assertThat(chain).isNotNull();
assertThat(chain.getHandler()).isInstanceOf(CorsAwareHandler.class);
assertThat(getRequiredCorsConfiguration(chain, false).getAllowedOrigins()).containsExactly("*");
assertThat(mapping.getRequiredCorsConfig().getAllowedOrigins()).containsExactly("*");
}
@Test
void preflightRequestWithCorsConfigurationProvider() throws Exception {
this.request.setMethod(RequestMethod.OPTIONS.name());
this.request.setRequestURI("/cors");
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
@PathPatternsParameterizedTest
void preflightRequestWithCorsConfigProvider(TestHandlerMapping mapping) throws Exception {
HandlerExecutionChain chain = mapping.getHandler(getPreFlightRequest("/cors"));
assertThat(chain).isNotNull();
assertThat(chain.getHandler()).isNotNull();
assertThat(chain.getHandler().getClass().getSimpleName()).isEqualTo("PreFlightHandler");
assertThat(getRequiredCorsConfiguration(chain, true).getAllowedOrigins()).containsExactly("*");
assertThat(mapping.getRequiredCorsConfig().getAllowedOrigins()).containsExactly("*");
}
@Test
void actualRequestWithMappedCorsConfiguration() throws Exception {
@PathPatternsParameterizedTest
void actualRequestWithMappedCorsConfig(TestHandlerMapping mapping) throws Exception {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/foo", config));
this.request.setMethod(RequestMethod.GET.name());
this.request.setRequestURI("/foo");
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
mapping.setCorsConfigurations(Collections.singletonMap("/foo", config));
HandlerExecutionChain chain = mapping.getHandler(getCorsRequest("/foo"));
assertThat(chain).isNotNull();
assertThat(chain.getHandler()).isInstanceOf(SimpleHandler.class);
assertThat(getRequiredCorsConfiguration(chain, false).getAllowedOrigins()).containsExactly("*");
assertThat(mapping.getRequiredCorsConfig().getAllowedOrigins()).containsExactly("*");
}
@Test
void preflightRequestWithMappedCorsConfiguration() throws Exception {
@PathPatternsParameterizedTest
void preflightRequestWithMappedCorsConfig(TestHandlerMapping mapping) throws Exception {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/foo", config));
this.request.setMethod(RequestMethod.OPTIONS.name());
this.request.setRequestURI("/foo");
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
mapping.setCorsConfigurations(Collections.singletonMap("/foo", config));
HandlerExecutionChain chain = mapping.getHandler(getPreFlightRequest("/foo"));
assertThat(chain).isNotNull();
assertThat(chain.getHandler()).isNotNull();
assertThat(chain.getHandler().getClass().getSimpleName()).isEqualTo("PreFlightHandler");
assertThat(getRequiredCorsConfiguration(chain, true).getAllowedOrigins()).containsExactly("*");
assertThat(mapping.getRequiredCorsConfig().getAllowedOrigins()).containsExactly("*");
}
@Test
void actualRequestWithCorsConfigurationSource() throws Exception {
this.handlerMapping.setCorsConfigurationSource(new CustomCorsConfigurationSource());
this.request.setMethod(RequestMethod.GET.name());
this.request.setRequestURI("/foo");
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
@PathPatternsParameterizedTest
void actualRequestWithCorsConfigSource(TestHandlerMapping mapping) throws Exception {
mapping.setCorsConfigurationSource(new CustomCorsConfigurationSource());
HandlerExecutionChain chain = mapping.getHandler(getCorsRequest("/foo"));
assertThat(chain).isNotNull();
assertThat(chain.getHandler()).isInstanceOf(SimpleHandler.class);
CorsConfiguration config = getRequiredCorsConfiguration(chain, false);
CorsConfiguration config = mapping.getRequiredCorsConfig();
assertThat(config).isNotNull();
assertThat(config.getAllowedOrigins()).containsExactly("*");
assertThat(config.getAllowCredentials()).isTrue();
}
@Test
void preflightRequestWithCorsConfigurationSource() throws Exception {
this.handlerMapping.setCorsConfigurationSource(new CustomCorsConfigurationSource());
this.request.setMethod(RequestMethod.OPTIONS.name());
this.request.setRequestURI("/foo");
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
@PathPatternsParameterizedTest
void preflightRequestWithCorsConfigSource(TestHandlerMapping mapping) throws Exception {
mapping.setCorsConfigurationSource(new CustomCorsConfigurationSource());
HandlerExecutionChain chain = mapping.getHandler(getPreFlightRequest("/foo"));
assertThat(chain).isNotNull();
assertThat(chain.getHandler()).isNotNull();
assertThat(chain.getHandler().getClass().getSimpleName()).isEqualTo("PreFlightHandler");
CorsConfiguration config = getRequiredCorsConfiguration(chain, true);
CorsConfiguration config = mapping.getRequiredCorsConfig();
assertThat(config).isNotNull();
assertThat(config.getAllowedOrigins()).containsExactly("*");
assertThat(config.getAllowCredentials()).isTrue();
}
@SuppressWarnings("ConstantConditions")
private CorsConfiguration getRequiredCorsConfiguration(HandlerExecutionChain chain, boolean isPreFlightRequest) {
CorsConfiguration corsConfig = null;
if (isPreFlightRequest) {
Object handler = chain.getHandler();
assertThat(handler.getClass().getSimpleName()).isEqualTo("PreFlightHandler");
DirectFieldAccessor accessor = new DirectFieldAccessor(handler);
corsConfig = (CorsConfiguration) accessor.getPropertyValue("config");
private MockHttpServletRequest getCorsRequest(String requestURI) {
return createCorsRequest(HttpMethod.GET, requestURI);
}
private MockHttpServletRequest getPreFlightRequest(String requestURI) {
return createCorsRequest(HttpMethod.OPTIONS, requestURI);
}
private MockHttpServletRequest createCorsRequest(HttpMethod method, String requestURI) {
MockHttpServletRequest request = new MockHttpServletRequest(method.name(), requestURI);
request.setRemoteHost("domain1.com");
request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
return request;
}
private static class TestHandlerMapping extends AbstractHandlerMapping {
@Nullable
private CorsConfiguration savedCorsConfig;
TestHandlerMapping() {
this(null);
}
else {
HandlerInterceptor[] interceptors = chain.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
DirectFieldAccessor accessor = new DirectFieldAccessor(interceptors[0]);
corsConfig = (CorsConfiguration) accessor.getPropertyValue("config");
TestHandlerMapping(@Nullable PathPatternParser parser) {
setInterceptors(mock(HandlerInterceptor.class));
setApplicationContext(new StaticWebApplicationContext());
if (parser != null) {
setPatternParser(parser);
}
}
assertThat(corsConfig).isNotNull();
return corsConfig;
}
public class TestHandlerMapping extends AbstractHandlerMapping {
boolean hasSavedCorsConfig() {
return this.savedCorsConfig != null;
}
CorsConfiguration getRequiredCorsConfig() {
assertThat(this.savedCorsConfig).isNotNull();
return this.savedCorsConfig;
}
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
if (request.getRequestURI().equals("/cors")) {
protected Object getHandlerInternal(HttpServletRequest request) {
String lookupPath = initLookupPath(request);
if (lookupPath.equals("/cors")) {
return new CorsAwareHandler();
}
else if (request.getRequestURI().equals("/chain")) {
else if (lookupPath.equals("/chain")) {
return new HandlerExecutionChain(new CorsAwareHandler());
}
return new SimpleHandler();
}
@Override
protected String initLookupPath(HttpServletRequest request) {
// At runtime this is done by the DispatcherServlet
if (getPatternParser() != null) {
RequestPath requestPath = ServletRequestPathUtils.parseAndCache(request);
return requestPath.pathWithinApplication().value();
}
return super.initLookupPath(request);
}
@Override
protected HandlerExecutionChain getCorsHandlerExecutionChain(
HttpServletRequest request, HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
this.savedCorsConfig = config;
return super.getCorsHandlerExecutionChain(request, chain, config);
}
}
public class SimpleHandler extends WebContentGenerator implements HttpRequestHandler {
private static class SimpleHandler extends WebContentGenerator implements HttpRequestHandler {
public SimpleHandler() {
SimpleHandler() {
super(METHOD_GET);
}
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
response.setStatus(HttpStatus.OK.value());
}
}
public class CorsAwareHandler extends SimpleHandler implements CorsConfigurationSource {
private static class CorsAwareHandler extends SimpleHandler implements CorsConfigurationSource {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
@@ -259,7 +266,7 @@ class CorsAbstractHandlerMappingTests {
}
public class CustomCorsConfigurationSource implements CorsConfigurationSource {
private static class CustomCorsConfigurationSource implements CorsConfigurationSource {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -23,8 +23,9 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
@@ -33,12 +34,16 @@ import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@@ -53,85 +58,116 @@ import static org.springframework.web.servlet.HandlerMapping.BEST_MATCHING_PATTE
public class HandlerMappingIntrospectorTests {
@Test
public void detectHandlerMappings() throws Exception {
StaticWebApplicationContext cxt = new StaticWebApplicationContext();
cxt.registerSingleton("hmA", SimpleUrlHandlerMapping.class);
cxt.registerSingleton("hmB", SimpleUrlHandlerMapping.class);
cxt.registerSingleton("hmC", SimpleUrlHandlerMapping.class);
cxt.refresh();
void detectHandlerMappings() {
StaticWebApplicationContext context = new StaticWebApplicationContext();
context.registerSingleton("A", SimpleUrlHandlerMapping.class);
context.registerSingleton("B", SimpleUrlHandlerMapping.class);
context.registerSingleton("C", SimpleUrlHandlerMapping.class);
context.refresh();
List<?> expected = Arrays.asList(cxt.getBean("hmA"), cxt.getBean("hmB"), cxt.getBean("hmC"));
List<HandlerMapping> actual = getIntrospector(cxt).getHandlerMappings();
List<?> expected = Arrays.asList(context.getBean("A"), context.getBean("B"), context.getBean("C"));
List<HandlerMapping> actual = initIntrospector(context).getHandlerMappings();
assertThat(actual).isEqualTo(expected);
}
@Test
public void detectHandlerMappingsOrdered() throws Exception {
StaticWebApplicationContext cxt = new StaticWebApplicationContext();
MutablePropertyValues pvs = new MutablePropertyValues(Collections.singletonMap("order", "3"));
cxt.registerSingleton("hmA", SimpleUrlHandlerMapping.class, pvs);
pvs = new MutablePropertyValues(Collections.singletonMap("order", "2"));
cxt.registerSingleton("hmB", SimpleUrlHandlerMapping.class, pvs);
pvs = new MutablePropertyValues(Collections.singletonMap("order", "1"));
cxt.registerSingleton("hmC", SimpleUrlHandlerMapping.class, pvs);
cxt.refresh();
void detectHandlerMappingsOrdered() {
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBean("B", SimpleUrlHandlerMapping.class, () -> {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(2);
return mapping;
});
context.registerBean("C", SimpleUrlHandlerMapping.class, () -> {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(3);
return mapping;
});
context.registerBean("A", SimpleUrlHandlerMapping.class, () -> {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(1);
return mapping;
});
context.refresh();
List<?> expected = Arrays.asList(cxt.getBean("hmC"), cxt.getBean("hmB"), cxt.getBean("hmA"));
List<HandlerMapping> actual = getIntrospector(cxt).getHandlerMappings();
List<?> expected = Arrays.asList(context.getBean("A"), context.getBean("B"), context.getBean("C"));
List<HandlerMapping> actual = initIntrospector(context).getHandlerMappings();
assertThat(actual).isEqualTo(expected);
}
public void defaultHandlerMappings() throws Exception {
StaticWebApplicationContext cxt = new StaticWebApplicationContext();
cxt.refresh();
void defaultHandlerMappings() {
StaticWebApplicationContext context = new StaticWebApplicationContext();
context.refresh();
List<HandlerMapping> actual = initIntrospector(context).getHandlerMappings();
List<HandlerMapping> actual = getIntrospector(cxt).getHandlerMappings();
assertThat(actual.size()).isEqualTo(2);
assertThat(actual.get(0).getClass()).isEqualTo(BeanNameUrlHandlerMapping.class);
assertThat(actual.get(1).getClass()).isEqualTo(RequestMappingHandlerMapping.class);
}
@Test
public void getMatchable() throws Exception {
MutablePropertyValues pvs = new MutablePropertyValues(
Collections.singletonMap("urlMap", Collections.singletonMap("/path", new Object())));
@ParameterizedTest
@ValueSource(booleans = {true, false})
void getMatchable(boolean usePathPatterns) throws Exception {
StaticWebApplicationContext cxt = new StaticWebApplicationContext();
cxt.registerSingleton("hm", SimpleUrlHandlerMapping.class, pvs);
cxt.refresh();
PathPatternParser parser = new PathPatternParser();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/path");
MatchableHandlerMapping hm = getIntrospector(cxt).getMatchableHandlerMapping(request);
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBean("mapping", SimpleUrlHandlerMapping.class, () -> {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
if (usePathPatterns) {
mapping.setPatternParser(parser);
}
mapping.setUrlMap(Collections.singletonMap("/path/*", new Object()));
return mapping;
});
context.refresh();
assertThat(hm).isEqualTo(cxt.getBean("hm"));
assertThat(request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE)).as("Attributes changes not ignored").isNull();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/path/123");
// Initialize the RequestPath. At runtime, ServletRequestPathFilter is expected to do that.
if (usePathPatterns) {
ServletRequestPathUtils.parseAndCache(request);
}
MatchableHandlerMapping mapping = initIntrospector(context).getMatchableHandlerMapping(request);
assertThat(mapping).isNotNull();
assertThat(mapping).isEqualTo(context.getBean("mapping"));
assertThat(request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE)).as("Attribute changes not ignored").isNull();
String pattern = "/p*/*";
PathPattern pathPattern = parser.parse(pattern);
assertThat(usePathPatterns ? mapping.match(request, pathPattern) : mapping.match(request, pattern)).isNotNull();
pattern = "/b*/*";
pathPattern = parser.parse(pattern);
assertThat(usePathPatterns ? mapping.match(request, pathPattern) : mapping.match(request, pattern)).isNull();
}
@Test
public void getMatchableWhereHandlerMappingDoesNotImplementMatchableInterface() throws Exception {
void getMatchableWhereHandlerMappingDoesNotImplementMatchableInterface() {
StaticWebApplicationContext cxt = new StaticWebApplicationContext();
cxt.registerSingleton("hm1", TestHandlerMapping.class);
cxt.registerSingleton("mapping", TestHandlerMapping.class);
cxt.refresh();
MockHttpServletRequest request = new MockHttpServletRequest();
assertThatIllegalStateException().isThrownBy(() ->
getIntrospector(cxt).getMatchableHandlerMapping(request));
assertThatIllegalStateException().isThrownBy(() -> initIntrospector(cxt).getMatchableHandlerMapping(request));
}
@Test
public void getCorsConfigurationPreFlight() throws Exception {
AnnotationConfigWebApplicationContext cxt = new AnnotationConfigWebApplicationContext();
cxt.register(TestConfig.class);
cxt.refresh();
void getCorsConfigurationPreFlight() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(TestConfig.class);
context.refresh();
// PRE-FLIGHT
MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/path");
request.addHeader("Origin", "http://localhost:9000");
request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST");
CorsConfiguration corsConfig = getIntrospector(cxt).getCorsConfiguration(request);
CorsConfiguration corsConfig = initIntrospector(context).getCorsConfiguration(request);
assertThat(corsConfig).isNotNull();
assertThat(corsConfig.getAllowedOrigins()).isEqualTo(Collections.singletonList("http://localhost:9000"));
@@ -139,23 +175,23 @@ public class HandlerMappingIntrospectorTests {
}
@Test
public void getCorsConfigurationActual() throws Exception {
AnnotationConfigWebApplicationContext cxt = new AnnotationConfigWebApplicationContext();
cxt.register(TestConfig.class);
cxt.refresh();
void getCorsConfigurationActual() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(TestConfig.class);
context.refresh();
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/path");
request.addHeader("Origin", "http://localhost:9000");
CorsConfiguration corsConfig = getIntrospector(cxt).getCorsConfiguration(request);
CorsConfiguration corsConfig = initIntrospector(context).getCorsConfiguration(request);
assertThat(corsConfig).isNotNull();
assertThat(corsConfig.getAllowedOrigins()).isEqualTo(Collections.singletonList("http://localhost:9000"));
assertThat(corsConfig.getAllowedMethods()).isEqualTo(Collections.singletonList("POST"));
}
private HandlerMappingIntrospector getIntrospector(WebApplicationContext cxt) {
private HandlerMappingIntrospector initIntrospector(WebApplicationContext context) {
HandlerMappingIntrospector introspector = new HandlerMappingIntrospector();
introspector.setApplicationContext(cxt);
introspector.setApplicationContext(context);
introspector.afterPropertiesSet();
return introspector;
}
@@ -164,14 +200,13 @@ public class HandlerMappingIntrospectorTests {
private static class TestHandlerMapping implements HandlerMapping {
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
public HandlerExecutionChain getHandler(HttpServletRequest request) {
return new HandlerExecutionChain(new Object());
}
}
@Configuration
@SuppressWarnings({"WeakerAccess", "unused"})
static class TestConfig {
@Bean
@@ -191,7 +226,7 @@ public class HandlerMappingIntrospectorTests {
private static class TestController {
@PostMapping("/path")
public void handle() {
void handle() {
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -16,20 +16,18 @@
package org.springframework.web.servlet.handler;
import java.io.IOException;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.provider.Arguments;
import org.springframework.http.HttpStatus;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat;
@@ -38,49 +36,51 @@ import static org.mockito.Mockito.mock;
/**
* Unit tests for {@link org.springframework.web.servlet.HandlerMapping}.
* @author Brian Clozel
* @author Rossen Stoyanchev
*/
public class HandlerMappingTests {
class HandlerMappingTests {
private AbstractHandlerMapping handlerMapping = new TestHandlerMapping();
private StaticWebApplicationContext context = new StaticWebApplicationContext();
private MockHttpServletRequest request = new MockHttpServletRequest();
@SuppressWarnings("unused")
private static Stream<Arguments> pathPatternsArguments() {
List<Function<String, MockHttpServletRequest>> factories =
PathPatternsTestUtils.requestArguments().collect(Collectors.toList());
return Stream.of(
Arguments.arguments(new TestHandlerMapping(), factories.get(0)),
Arguments.arguments(new TestHandlerMapping(), factories.get(1))
);
}
@Test
public void orderedInterceptors() throws Exception {
HandlerInterceptor i1 = mock(HandlerInterceptor.class);
MappedInterceptor mappedInterceptor1 = new MappedInterceptor(new String[]{"/**"}, i1);
@PathPatternsParameterizedTest
void orderedInterceptors(
TestHandlerMapping mapping, Function<String, MockHttpServletRequest> requestFactory)
throws Exception {
MappedInterceptor i1 = new MappedInterceptor(new String[] {"/**"}, mock(HandlerInterceptor.class));
HandlerInterceptor i2 = mock(HandlerInterceptor.class);
HandlerInterceptor i3 = mock(HandlerInterceptor.class);
MappedInterceptor mappedInterceptor3 = new MappedInterceptor(new String[]{"/**"}, i3);
MappedInterceptor i3 = new MappedInterceptor(new String[] {"/**"}, mock(HandlerInterceptor.class));
HandlerInterceptor i4 = mock(HandlerInterceptor.class);
this.handlerMapping.setInterceptors(mappedInterceptor1, i2, mappedInterceptor3, i4);
this.handlerMapping.setApplicationContext(this.context);
HandlerExecutionChain chain = this.handlerMapping.getHandlerExecutionChain(new SimpleHandler(), this.request);
assertThat(chain.getInterceptors()).contains(
mappedInterceptor1.getInterceptor(), i2, mappedInterceptor3.getInterceptor(), i4);
mapping.setInterceptors(i1, i2, i3, i4);
mapping.setApplicationContext(new StaticWebApplicationContext());
HandlerExecutionChain chain = mapping.getHandler(requestFactory.apply("/"));
assertThat(chain).isNotNull();
assertThat(chain.getInterceptors()).contains(i1.getInterceptor(), i2, i3.getInterceptor(), i4);
}
class TestHandlerMapping extends AbstractHandlerMapping {
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
return new SimpleHandler();
}
}
private static class TestHandlerMapping extends AbstractHandlerMapping {
class SimpleHandler extends WebContentGenerator implements HttpRequestHandler {
public SimpleHandler() {
super(METHOD_GET);
TestHandlerMapping() {
}
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setStatus(HttpStatus.OK.value());
protected Object getHandlerInternal(HttpServletRequest request) {
initLookupPath(request);
return new Object();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -17,18 +17,19 @@ package org.springframework.web.servlet.handler;
import java.util.Comparator;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -37,96 +38,95 @@ import static org.mockito.Mockito.mock;
/**
* Test fixture for {@link MappedInterceptor} tests.
*
* @author Rossen Stoyanchev
*/
public class MappedInterceptorTests {
class MappedInterceptorTests {
private LocaleChangeInterceptor interceptor;
private static final LocaleChangeInterceptor delegate = new LocaleChangeInterceptor();
private final AntPathMatcher pathMatcher = new AntPathMatcher();
@BeforeEach
public void setup() {
this.interceptor = new LocaleChangeInterceptor();
@SuppressWarnings("unused")
private static Stream<Function<String, MockHttpServletRequest>> pathPatternsArguments() {
return PathPatternsTestUtils.requestArguments();
}
@PathPatternsParameterizedTest
void noPatterns(Function<String, MockHttpServletRequest> requestFactory) {
MappedInterceptor interceptor = new MappedInterceptor(null, null, delegate);
assertThat(interceptor.matches(requestFactory.apply("/foo"))).isTrue();
}
@PathPatternsParameterizedTest
void includePattern(Function<String, MockHttpServletRequest> requestFactory) {
MappedInterceptor interceptor = new MappedInterceptor(new String[] { "/foo/*" }, null, delegate);
assertThat(interceptor.matches(requestFactory.apply("/foo/bar"))).isTrue();
assertThat(interceptor.matches(requestFactory.apply("/bar/foo"))).isFalse();
}
@PathPatternsParameterizedTest
void includePatternWithMatrixVariables(Function<String, MockHttpServletRequest> requestFactory) {
MappedInterceptor interceptor = new MappedInterceptor(new String[] { "/foo*/*" }, null, delegate);
assertThat(interceptor.matches(requestFactory.apply("/foo;q=1/bar;s=2"))).isTrue();
}
@PathPatternsParameterizedTest
void excludePattern(Function<String, MockHttpServletRequest> requestFactory) {
MappedInterceptor interceptor = new MappedInterceptor(null, new String[] { "/admin/**" }, delegate);
assertThat(interceptor.matches(requestFactory.apply("/foo"))).isTrue();
assertThat(interceptor.matches(requestFactory.apply("/admin/foo"))).isFalse();
}
@PathPatternsParameterizedTest
void includeAndExcludePatterns(Function<String, MockHttpServletRequest> requestFactory) {
MappedInterceptor interceptor =
new MappedInterceptor(new String[] { "/**" }, new String[] { "/admin/**" }, delegate);
assertThat(interceptor.matches(requestFactory.apply("/foo"))).isTrue();
assertThat(interceptor.matches(requestFactory.apply("/admin/foo"))).isFalse();
}
@PathPatternsParameterizedTest
void customPathMatcher(Function<String, MockHttpServletRequest> requestFactory) {
MappedInterceptor interceptor = new MappedInterceptor(new String[] { "/foo/[0-9]*" }, null, delegate);
interceptor.setPathMatcher(new TestPathMatcher());
assertThat(interceptor.matches(requestFactory.apply("/foo/123"))).isTrue();
assertThat(interceptor.matches(requestFactory.apply("/foo/bar"))).isFalse();
}
@Test
public void noPatterns() {
MappedInterceptor mappedInterceptor = new MappedInterceptor(null, null, this.interceptor);
assertThat(mappedInterceptor.matches("/foo", pathMatcher)).isTrue();
void preHandle() throws Exception {
HandlerInterceptor delegate = mock(HandlerInterceptor.class);
new MappedInterceptor(null, delegate).preHandle(
mock(HttpServletRequest.class), mock(HttpServletResponse.class), null);
then(delegate).should().preHandle(any(HttpServletRequest.class), any(HttpServletResponse.class), any());
}
@Test
public void includePattern() {
MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] { "/foo/*" }, this.interceptor);
void postHandle() throws Exception {
HandlerInterceptor delegate = mock(HandlerInterceptor.class);
assertThat(mappedInterceptor.matches("/foo/bar", pathMatcher)).isTrue();
assertThat(mappedInterceptor.matches("/bar/foo", pathMatcher)).isFalse();
new MappedInterceptor(null, delegate).postHandle(
mock(HttpServletRequest.class), mock(HttpServletResponse.class), null, mock(ModelAndView.class));
then(delegate).should().postHandle(any(), any(), any(), any());
}
@Test
public void includePatternWithMatrixVariables() {
MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] { "/foo*/*" }, this.interceptor);
assertThat(mappedInterceptor.matches("/foo;q=1/bar;s=2", pathMatcher)).isTrue();
void afterCompletion() throws Exception {
HandlerInterceptor delegate = mock(HandlerInterceptor.class);
new MappedInterceptor(null, delegate).afterCompletion(
mock(HttpServletRequest.class), mock(HttpServletResponse.class), null, mock(Exception.class));
then(delegate).should().afterCompletion(any(), any(), any(), any());
}
@Test
public void excludePattern() {
MappedInterceptor mappedInterceptor = new MappedInterceptor(null, new String[] { "/admin/**" }, this.interceptor);
assertThat(mappedInterceptor.matches("/foo", pathMatcher)).isTrue();
assertThat(mappedInterceptor.matches("/admin/foo", pathMatcher)).isFalse();
}
@Test
public void includeAndExcludePatterns() {
MappedInterceptor mappedInterceptor = new MappedInterceptor(
new String[] { "/**" }, new String[] { "/admin/**" }, this.interceptor);
assertThat(mappedInterceptor.matches("/foo", pathMatcher)).isTrue();
assertThat(mappedInterceptor.matches("/admin/foo", pathMatcher)).isFalse();
}
@Test
public void customPathMatcher() {
MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] { "/foo/[0-9]*" }, this.interceptor);
mappedInterceptor.setPathMatcher(new TestPathMatcher());
assertThat(mappedInterceptor.matches("/foo/123", pathMatcher)).isTrue();
assertThat(mappedInterceptor.matches("/foo/bar", pathMatcher)).isFalse();
}
@Test
public void preHandle() throws Exception {
HandlerInterceptor interceptor = mock(HandlerInterceptor.class);
MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] { "/**" }, interceptor);
mappedInterceptor.preHandle(mock(HttpServletRequest.class), mock(HttpServletResponse.class), null);
then(interceptor).should().preHandle(any(HttpServletRequest.class), any(HttpServletResponse.class), any());
}
@Test
public void postHandle() throws Exception {
HandlerInterceptor interceptor = mock(HandlerInterceptor.class);
MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] { "/**" }, interceptor);
mappedInterceptor.postHandle(mock(HttpServletRequest.class), mock(HttpServletResponse.class),
null, mock(ModelAndView.class));
then(interceptor).should().postHandle(any(), any(), any(), any());
}
@Test
public void afterCompletion() throws Exception {
HandlerInterceptor interceptor = mock(HandlerInterceptor.class);
MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] { "/**" }, interceptor);
mappedInterceptor.afterCompletion(mock(HttpServletRequest.class), mock(HttpServletResponse.class),
null, mock(Exception.class));
then(interceptor).should().afterCompletion(any(), any(), any(), any());
}
public static class TestPathMatcher implements PathMatcher {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -16,16 +16,19 @@
package org.springframework.web.servlet.handler;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.stream.Stream;
import org.junit.jupiter.params.provider.Arguments;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockServletContext;
import org.springframework.web.util.ServletRequestPathUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -35,41 +38,49 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class PathMatchingUrlHandlerMappingTests {
public static final String CONF = "/org/springframework/web/servlet/handler/map3.xml";
@SuppressWarnings("unused")
static Stream<?> pathPatternsArguments() {
String location = "/org/springframework/web/servlet/handler/map3.xml";
WebApplicationContext wac = initConfig(location);
private HandlerMapping hm;
SimpleUrlHandlerMapping mapping1 = wac.getBean("urlMapping1", SimpleUrlHandlerMapping.class);
assertThat(mapping1.getPathPatternHandlerMap()).isNotEmpty();
private ConfigurableWebApplicationContext wac;
SimpleUrlHandlerMapping mapping2 = wac.getBean("urlMapping2", SimpleUrlHandlerMapping.class);
assertThat(mapping2.getPathPatternHandlerMap()).isEmpty();
@BeforeEach
public void setUp() throws Exception {
MockServletContext sc = new MockServletContext("");
wac = new XmlWebApplicationContext();
wac.setServletContext(sc);
wac.setConfigLocations(new String[] {CONF});
wac.refresh();
hm = (HandlerMapping) wac.getBean("urlMapping");
return Stream.of(Arguments.of(mapping1, wac), Arguments.of(mapping2, wac));
}
@Test
public void requestsWithHandlers() throws Exception {
private static WebApplicationContext initConfig(String... configLocations) {
MockServletContext sc = new MockServletContext("");
ConfigurableWebApplicationContext context = new XmlWebApplicationContext();
context.setServletContext(sc);
context.setConfigLocations(configLocations);
context.refresh();
return context;
}
@PathPatternsParameterizedTest
void requestsWithHandlers(HandlerMapping mapping, WebApplicationContext wac) throws Exception {
Object bean = wac.getBean("mainController");
MockHttpServletRequest req = new MockHttpServletRequest("GET", "/welcome.html");
HandlerExecutionChain hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
HandlerExecutionChain hec = getHandler(mapping, wac, req);
assertThat(hec.getHandler() == bean).isTrue();
req = new MockHttpServletRequest("GET", "/show.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
hec = getHandler(mapping, wac, req);
assertThat(hec.getHandler() == bean).isTrue();
req = new MockHttpServletRequest("GET", "/bookseats.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
hec = getHandler(mapping, wac, req);
assertThat(hec.getHandler() == bean).isTrue();
}
@Test
public void actualPathMatching() throws Exception {
@PathPatternsParameterizedTest
void actualPathMatching(SimpleUrlHandlerMapping mapping, WebApplicationContext wac) throws Exception {
// there a couple of mappings defined with which we can test the
// path matching, let's do that...
@@ -77,178 +88,199 @@ public class PathMatchingUrlHandlerMappingTests {
Object defaultBean = wac.getBean("starController");
// testing some normal behavior
MockHttpServletRequest req = new MockHttpServletRequest("GET", "/pathmatchingTest.html");
HandlerExecutionChain hec = getHandler(req);
assertThat(hec != null).as("Handler is null").isTrue();
assertThat(hec.getHandler() == bean).as("Handler is correct bean").isTrue();
assertThat(req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/pathmatchingTest.html");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/pathmatchingTest.html");
HandlerExecutionChain chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
assertThat(request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE))
.isEqualTo("/pathmatchingTest.html");
// no match, no forward slash included
req = new MockHttpServletRequest("GET", "welcome.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
assertThat(req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("welcome.html");
request = new MockHttpServletRequest("GET", "welcome.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
assertThat(request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE))
.isEqualTo("welcome.html");
// testing some ????? behavior
req = new MockHttpServletRequest("GET", "/pathmatchingAA.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
assertThat(req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("pathmatchingAA.html");
request = new MockHttpServletRequest("GET", "/pathmatchingAA.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
assertThat(request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE))
.isEqualTo("pathmatchingAA.html");
// testing some ????? behavior
req = new MockHttpServletRequest("GET", "/pathmatchingA.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
assertThat(req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/pathmatchingA.html");
request = new MockHttpServletRequest("GET", "/pathmatchingA.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
assertThat(request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE))
.isEqualTo("/pathmatchingA.html");
// testing some ????? behavior
req = new MockHttpServletRequest("GET", "/administrator/pathmatching.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/administrator/pathmatching.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
// testing simple /**/behavior
req = new MockHttpServletRequest("GET", "/administrator/test/pathmatching.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/administrator/test/pathmatching.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
// this should not match because of the administratorT
req = new MockHttpServletRequest("GET", "/administratort/pathmatching.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/administratort/pathmatching.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
// this should match because of *.jsp
req = new MockHttpServletRequest("GET", "/bla.jsp");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/bla.jsp");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
// should match because exact pattern is there
req = new MockHttpServletRequest("GET", "/administrator/another/bla.xml");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/administrator/another/bla.xml");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
// should not match, because there's not .gif extension in there
req = new MockHttpServletRequest("GET", "/administrator/another/bla.gif");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/administrator/another/bla.gif");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
// should match because there testlast* in there
req = new MockHttpServletRequest("GET", "/administrator/test/testlastbit");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/administrator/test/testlastbit");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
// but this not, because it's testlast and not testla
req = new MockHttpServletRequest("GET", "/administrator/test/testla");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/administrator/test/testla");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
req = new MockHttpServletRequest("GET", "/administrator/testing/longer/bla");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/administrator/testing/longer/bla");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/administrator/testing/longer/test.jsp");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/administrator/testing/longer/test.jsp");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/administrator/testing/longer2/notmatching/notmatching");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/administrator/testing/longer2/notmatching/notmatching");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
req = new MockHttpServletRequest("GET", "/shortpattern/testing/toolong");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/shortpattern/testing/toolong");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
req = new MockHttpServletRequest("GET", "/XXpathXXmatching.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/XXpathXXmatching.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/pathXXmatching.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/pathXXmatching.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/XpathXXmatching.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/XpathXXmatching.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
req = new MockHttpServletRequest("GET", "/XXpathmatching.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/XXpathmatching.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
req = new MockHttpServletRequest("GET", "/show12.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/show12.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/show123.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/show123.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/show1.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/show1.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/reallyGood-test-is-this.jpeg");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/reallyGood-test-is-this.jpeg");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/reallyGood-tst-is-this.jpeg");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/reallyGood-tst-is-this.jpeg");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
req = new MockHttpServletRequest("GET", "/testing/test.jpeg");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/testing/test.jpeg");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/testing/test.jpg");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/testing/test.jpg");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
req = new MockHttpServletRequest("GET", "/anotherTest");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/anotherTest");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/stillAnotherTest");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/stillAnotherTest");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
// there outofpattern*yeah in the pattern, so this should fail
req = new MockHttpServletRequest("GET", "/outofpattern*ye");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/outofpattern*ye");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
req = new MockHttpServletRequest("GET", "/test't est/path'm atching.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/test't%20est/path'm%20atching.html");
chain = getHandler(mapping, wac, request);
assertThat(chain.getHandler()).isSameAs(defaultBean);
req = new MockHttpServletRequest("GET", "/test%26t%20est/path%26m%20atching.html");
hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
request = new MockHttpServletRequest("GET", "/test%26t%20est/path%26m%20atching.html");
chain = getHandler(mapping, wac, request);
if (!mapping.getPathPatternHandlerMap().isEmpty()) {
assertThat(chain.getHandler())
.as("PathPattern always matches to encoded paths.")
.isSameAs(bean);
}
else {
assertThat(chain.getHandler())
.as("PathMatcher should not match encoded pattern with urlDecode=true")
.isSameAs(defaultBean);
}
}
@Test
public void defaultMapping() throws Exception {
@PathPatternsParameterizedTest
void defaultMapping(HandlerMapping mapping, WebApplicationContext wac) throws Exception {
Object bean = wac.getBean("starController");
MockHttpServletRequest req = new MockHttpServletRequest("GET", "/goggog.html");
HandlerExecutionChain hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
HandlerExecutionChain hec = getHandler(mapping, wac, req);
assertThat(hec.getHandler()).isSameAs(bean);
}
@Test
public void mappingExposedInRequest() throws Exception {
@PathPatternsParameterizedTest
void mappingExposedInRequest(HandlerMapping mapping, WebApplicationContext wac) throws Exception {
Object bean = wac.getBean("mainController");
MockHttpServletRequest req = new MockHttpServletRequest("GET", "/show.html");
HandlerExecutionChain hec = getHandler(req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
assertThat(req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).as("Mapping not exposed").isEqualTo("show.html");
HandlerExecutionChain hec = getHandler(mapping, wac, req);
assertThat(hec.getHandler()).isSameAs(bean);
assertThat(req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE))
.as("Mapping not exposed").isEqualTo("show.html");
}
private HandlerExecutionChain getHandler(MockHttpServletRequest req) throws Exception {
HandlerExecutionChain hec = hm.getHandler(req);
HandlerInterceptor[] interceptors = hec.getInterceptors();
private HandlerExecutionChain getHandler(
HandlerMapping mapping, WebApplicationContext wac, MockHttpServletRequest request)
throws Exception {
// At runtime this is done by the DispatcherServlet
if (((AbstractHandlerMapping) mapping).getPatternParser() != null) {
ServletRequestPathUtils.parseAndCache(request);
}
HandlerExecutionChain executionChain = mapping.getHandler(request);
HandlerInterceptor[] interceptors = executionChain.getInterceptors();
if (interceptors != null) {
for (HandlerInterceptor interceptor : interceptors) {
interceptor.preHandle(req, null, hec.getHandler());
interceptor.preHandle(request, null, executionChain.getHandler());
}
}
return hec;
return executionChain;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.handler;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation for tests parameterized to use either
* {@link org.springframework.web.util.pattern.PathPatternParser} or
* {@link org.springframework.util.PathMatcher} for URL pattern matching.
*
* @author Rossen Stoyanchev
* @since 5.3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@org.junit.jupiter.params.ParameterizedTest
@org.junit.jupiter.params.provider.MethodSource("pathPatternsArguments")
public @interface PathPatternsParameterizedTest {
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.handler;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import org.springframework.lang.Nullable;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UrlPathHelper;
/**
* Utility methods to help with parameterized tests for URL pattern matching
* via pre-parsed {@code PathPattern}s or String pattern matching with
* {@code PathMatcher}.
*
* @author Rossen Stoyanchev
* @since 5.3
*/
public abstract class PathPatternsTestUtils {
public static Stream<Function<String, MockHttpServletRequest>> requestArguments() {
return requestArguments(null);
}
public static Stream<Function<String, MockHttpServletRequest>> requestArguments(@Nullable String contextPath) {
return Stream.of(
path -> {
MockHttpServletRequest request = createRequest("GET", contextPath, path);
ServletRequestPathUtils.parseAndCache(request);
return request;
},
path -> {
MockHttpServletRequest request = createRequest("GET", contextPath, path);
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
return request;
}
);
}
/**
* Create a MockHttpServletRequest and initialize the request attributes for
* the lookupPath via {@link ServletRequestPathUtils} or {@link UrlPathHelper}
* depending on the setting of the parsedPatterns argument.
* <p>At runtime this would be done by the DispatcherServlet (for the RequestPath)
* and by the AbstractHandlerMapping (for UrlPathHelper).
*/
public static MockHttpServletRequest initRequest(String method, String requestUri, boolean parsedPatterns) {
return initRequest(method, null, requestUri, parsedPatterns);
}
/**
* See {@link #initRequest(String, String, boolean)}. This variant adds a contextPath.
*/
public static MockHttpServletRequest initRequest(
String method, @Nullable String contextPath, String path, boolean parsedPatterns) {
return initRequest(method, contextPath, path, parsedPatterns, null);
}
/**
* See {@link #initRequest(String, String, boolean)}. This variant adds a contextPath
* and a post-construct initializer to apply further changes before the
* lookupPath is resolved.
*/
public static MockHttpServletRequest initRequest(
String method, @Nullable String contextPath, String path,
boolean parsedPatterns, @Nullable Consumer<MockHttpServletRequest> postConstructInitializer) {
MockHttpServletRequest request = createRequest(method, contextPath, path);
if (postConstructInitializer != null) {
postConstructInitializer.accept(request);
}
// At runtime this is done by the DispatcherServlet and AbstractHandlerMapping
if (parsedPatterns) {
ServletRequestPathUtils.parseAndCache(request);
}
else {
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
}
return request;
}
private static MockHttpServletRequest createRequest(String method, @Nullable String contextPath, String path) {
if (contextPath != null) {
String requestUri = contextPath + (path.startsWith("/") ? "" : "/") + path;
MockHttpServletRequest request = new MockHttpServletRequest(method, requestUri);
request.setContextPath(contextPath);
return request;
}
else {
return new MockHttpServletRequest(method, path);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -19,6 +19,8 @@ package org.springframework.web.servlet.handler;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@@ -33,6 +35,8 @@ import org.springframework.web.util.WebUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.web.servlet.HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE;
import static org.springframework.web.servlet.HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE;
/**
* @author Rod Johnson
@@ -48,44 +52,38 @@ public class SimpleUrlHandlerMappingTests {
root.setServletContext(sc);
root.setConfigLocations("/org/springframework/web/servlet/handler/map1.xml");
root.refresh();
XmlWebApplicationContext wac = new XmlWebApplicationContext();
wac.setParent(root);
wac.setServletContext(sc);
wac.setNamespace("map2err");
wac.setConfigLocations("/org/springframework/web/servlet/handler/map2err.xml");
assertThatExceptionOfType(FatalBeanException.class).isThrownBy(
wac::refresh)
.withCauseInstanceOf(NoSuchBeanDefinitionException.class)
.satisfies(ex -> assertThat(((NoSuchBeanDefinitionException) ex.getCause()).getBeanName()).isEqualTo("mainControlle"));
}
@Test
public void urlMappingWithUrlMap() throws Exception {
checkMappings("urlMapping");
}
@Test
public void urlMappingWithProps() throws Exception {
checkMappings("urlMappingWithProps");
assertThatExceptionOfType(FatalBeanException.class)
.isThrownBy(wac::refresh)
.withCauseInstanceOf(NoSuchBeanDefinitionException.class)
.satisfies(ex -> {
NoSuchBeanDefinitionException cause = (NoSuchBeanDefinitionException) ex.getCause();
assertThat(cause.getBeanName()).isEqualTo("mainControlle");
});
}
@Test
public void testNewlineInRequest() throws Exception {
Object controller = new Object();
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(
Collections.singletonMap("/*/baz", controller));
handlerMapping.setUrlDecode(false);
handlerMapping.setApplicationContext(new StaticApplicationContext());
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(Collections.singletonMap("/*/baz", controller));
mapping.setUrlDecode(false);
mapping.setApplicationContext(new StaticApplicationContext());
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo%0a%0dbar/baz");
HandlerExecutionChain hec = handlerMapping.getHandler(request);
HandlerExecutionChain hec = mapping.getHandler(request);
assertThat(hec).isNotNull();
assertThat(hec.getHandler()).isSameAs(controller);
}
@SuppressWarnings("resource")
private void checkMappings(String beanName) throws Exception {
@ParameterizedTest
@ValueSource(strings = {"urlMapping", "urlMappingWithProps", "urlMappingWithPathPatterns"})
void checkMappings(String beanName) throws Exception {
MockServletContext sc = new MockServletContext("");
XmlWebApplicationContext wac = new XmlWebApplicationContext();
wac.setServletContext(sc);
@@ -96,76 +94,76 @@ public class SimpleUrlHandlerMappingTests {
Object defaultBean = wac.getBean("starController");
HandlerMapping hm = (HandlerMapping) wac.getBean(beanName);
MockHttpServletRequest req = new MockHttpServletRequest("GET", "/welcome.html");
HandlerExecutionChain hec = getHandler(hm, req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
assertThat(req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/welcome.html");
assertThat(req.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(bean);
boolean usePathPatterns = (((AbstractHandlerMapping) hm).getPatternParser() != null);
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/welcome.html", usePathPatterns);
HandlerExecutionChain chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(bean);
assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/welcome.html");
assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(bean);
req = new MockHttpServletRequest("GET", "/welcome.x");
hec = getHandler(hm, req);
assertThat(hec != null && hec.getHandler() == otherBean).as("Handler is correct bean").isTrue();
assertThat(req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("welcome.x");
assertThat(req.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(otherBean);
request = PathPatternsTestUtils.initRequest("GET", "/welcome.x", usePathPatterns);
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(otherBean);
assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("welcome.x");
assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(otherBean);
req = new MockHttpServletRequest("GET", "/welcome/");
hec = getHandler(hm, req);
assertThat(hec != null && hec.getHandler() == otherBean).as("Handler is correct bean").isTrue();
assertThat(req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("welcome");
request = PathPatternsTestUtils.initRequest("GET", "/welcome/", usePathPatterns);
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(otherBean);
assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("welcome");
req = new MockHttpServletRequest("GET", "/");
req.setServletPath("/welcome.html");
hec = getHandler(hm, req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = PathPatternsTestUtils.initRequest("GET", "/", usePathPatterns);
request.setServletPath("/welcome.html");
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/welcome.html");
req.setContextPath("/app");
hec = getHandler(hm, req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = PathPatternsTestUtils.initRequest("GET", "/app", "/welcome.html", usePathPatterns);
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/show.html");
hec = getHandler(hm, req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = PathPatternsTestUtils.initRequest("GET", "/show.html", usePathPatterns);
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/bookseats.html");
hec = getHandler(hm, req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = PathPatternsTestUtils.initRequest("GET", "/bookseats.html", usePathPatterns);
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/original-welcome.html");
req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/welcome.html");
hec = getHandler(hm, req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = PathPatternsTestUtils.initRequest("GET", null, "/original-welcome.html", usePathPatterns,
req -> req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/welcome.html"));
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/original-show.html");
req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/show.html");
hec = getHandler(hm, req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = PathPatternsTestUtils.initRequest("GET", null, "/original-show.html", usePathPatterns,
req -> req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/show.html"));
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/original-bookseats.html");
req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/bookseats.html");
hec = getHandler(hm, req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
request = PathPatternsTestUtils.initRequest("GET", null, "/original-bookseats.html", usePathPatterns,
req -> req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/bookseats.html"));
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(bean);
req = new MockHttpServletRequest("GET", "/");
hec = getHandler(hm, req);
assertThat(hec != null && hec.getHandler() == bean).as("Handler is correct bean").isTrue();
assertThat(req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/");
request = PathPatternsTestUtils.initRequest("GET", "/", usePathPatterns);
chain = getHandler(hm, request);
assertThat(chain.getHandler()).isSameAs(bean);
assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/");
req = new MockHttpServletRequest("GET", "/somePath");
hec = getHandler(hm, req);
assertThat(hec != null && hec.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
assertThat(req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/somePath");
request = PathPatternsTestUtils.initRequest("GET", "/somePath", usePathPatterns);
chain = getHandler(hm, request);
assertThat(chain.getHandler() == defaultBean).as("Handler is correct bean").isTrue();
assertThat(request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).isEqualTo("/somePath");
}
private HandlerExecutionChain getHandler(HandlerMapping hm, MockHttpServletRequest req) throws Exception {
HandlerExecutionChain hec = hm.getHandler(req);
HandlerInterceptor[] interceptors = hec.getInterceptors();
private HandlerExecutionChain getHandler(HandlerMapping mapping, MockHttpServletRequest request) throws Exception {
HandlerExecutionChain chain = mapping.getHandler(request);
HandlerInterceptor[] interceptors = chain.getInterceptors();
if (interceptors != null) {
for (HandlerInterceptor interceptor : interceptors) {
interceptor.preHandle(req, null, hec.getHandler());
interceptor.preHandle(request, null, chain.getHandler());
}
}
return hec;
return chain;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -16,16 +16,21 @@
package org.springframework.web.servlet.mvc;
import java.util.function.Function;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.ui.ModelMap;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.PathPatternsParameterizedTest;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import org.springframework.web.util.ServletRequestPathUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -34,162 +39,164 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Rick Evans
* @since 14.09.2005
*/
public class UrlFilenameViewControllerTests {
class UrlFilenameViewControllerTests {
private PathMatcher pathMatcher = new AntPathMatcher();
@SuppressWarnings("unused")
private static Stream<Function<String, MockHttpServletRequest>> pathPatternsArguments() {
return PathPatternsTestUtils.requestArguments();
}
@Test
public void withPlainFilename() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/index");
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = ctrl.handleRequest(request, response);
@PathPatternsParameterizedTest
void withPlainFilename(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
UrlFilenameViewController controller = new UrlFilenameViewController();
MockHttpServletRequest request = requestFactory.apply("/index");
ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
assertThat(mv.getViewName()).isEqualTo("index");
assertThat(mv.getModel().isEmpty()).isTrue();
}
@Test
public void withFilenamePlusExtension() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/index.html");
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = ctrl.handleRequest(request, response);
@PathPatternsParameterizedTest
void withFilenamePlusExtension(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
UrlFilenameViewController controller = new UrlFilenameViewController();
MockHttpServletRequest request = requestFactory.apply("/index.html");
ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
assertThat(mv.getViewName()).isEqualTo("index");
assertThat(mv.getModel().isEmpty()).isTrue();
}
@Test
public void withFilenameAndMatrixVariables() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/index;a=A;b=B");
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = ctrl.handleRequest(request, response);
@PathPatternsParameterizedTest
void withFilenameAndMatrixVariables(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
UrlFilenameViewController controller = new UrlFilenameViewController();
MockHttpServletRequest request = requestFactory.apply("/index;a=A;b=B");
ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
assertThat(mv.getViewName()).isEqualTo("index");
assertThat(mv.getModel().isEmpty()).isTrue();
}
@Test
public void withPrefixAndSuffix() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
ctrl.setPrefix("mypre_");
ctrl.setSuffix("_mysuf");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/index.html");
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = ctrl.handleRequest(request, response);
@PathPatternsParameterizedTest
void withPrefixAndSuffix(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
UrlFilenameViewController controller = new UrlFilenameViewController();
controller.setPrefix("mypre_");
controller.setSuffix("_mysuf");
MockHttpServletRequest request = requestFactory.apply("/index.html");
ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
assertThat(mv.getViewName()).isEqualTo("mypre_index_mysuf");
assertThat(mv.getModel().isEmpty()).isTrue();
}
@Test
public void withPrefix() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
ctrl.setPrefix("mypre_");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/index.html");
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = ctrl.handleRequest(request, response);
@PathPatternsParameterizedTest
void withPrefix(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
UrlFilenameViewController controller = new UrlFilenameViewController();
controller.setPrefix("mypre_");
MockHttpServletRequest request = requestFactory.apply("/index.html");
ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
assertThat(mv.getViewName()).isEqualTo("mypre_index");
assertThat(mv.getModel().isEmpty()).isTrue();
}
@Test
public void withSuffix() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
ctrl.setSuffix("_mysuf");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/index.html");
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = ctrl.handleRequest(request, response);
@PathPatternsParameterizedTest
void withSuffix(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
UrlFilenameViewController controller = new UrlFilenameViewController();
controller.setSuffix("_mysuf");
MockHttpServletRequest request = requestFactory.apply("/index.html");
ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
assertThat(mv.getViewName()).isEqualTo("index_mysuf");
assertThat(mv.getModel().isEmpty()).isTrue();
}
@Test
public void multiLevel() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/docs/cvs/commit.html");
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = ctrl.handleRequest(request, response);
@PathPatternsParameterizedTest
void multiLevel(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
UrlFilenameViewController controller = new UrlFilenameViewController();
MockHttpServletRequest request = requestFactory.apply("/docs/cvs/commit.html");
ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
assertThat(mv.getViewName()).isEqualTo("docs/cvs/commit");
assertThat(mv.getModel().isEmpty()).isTrue();
}
@Test
public void multiLevelWithMapping() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/docs/cvs/commit.html");
@PathPatternsParameterizedTest
void multiLevelWithMapping(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
UrlFilenameViewController controller = new UrlFilenameViewController();
MockHttpServletRequest request = requestFactory.apply("/docs/cvs/commit.html");
exposePathInMapping(request, "/docs/**");
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = ctrl.handleRequest(request, response);
ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
assertThat(mv.getViewName()).isEqualTo("cvs/commit");
assertThat(mv.getModel().isEmpty()).isTrue();
}
@Test
public void multiLevelMappingWithFallback() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/docs/cvs/commit.html");
@PathPatternsParameterizedTest
void multiLevelMappingWithFallback(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
UrlFilenameViewController controller = new UrlFilenameViewController();
MockHttpServletRequest request = requestFactory.apply("/docs/cvs/commit.html");
exposePathInMapping(request, "/docs/cvs/commit.html");
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = ctrl.handleRequest(request, response);
ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
assertThat(mv.getViewName()).isEqualTo("docs/cvs/commit");
assertThat(mv.getModel().isEmpty()).isTrue();
}
@Test
public void withContextMapping() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
@PathPatternsParameterizedTest
void withContextMapping(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
UrlFilenameViewController controller = new UrlFilenameViewController();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myapp/docs/cvs/commit.html");
request.setContextPath("/myapp");
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = ctrl.handleRequest(request, response);
ServletRequestPathUtils.parseAndCache(request);
ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
assertThat(mv.getViewName()).isEqualTo("docs/cvs/commit");
assertThat(mv.getModel().isEmpty()).isTrue();
}
@Test
public void settingPrefixToNullCausesEmptyStringToBeUsed() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
ctrl.setPrefix(null);
assertThat(ctrl.getPrefix()).as("For setPrefix(..) with null, the empty string must be used instead.").isNotNull();
assertThat(ctrl.getPrefix()).as("For setPrefix(..) with null, the empty string must be used instead.").isEqualTo("");
void settingPrefixToNullCausesEmptyStringToBeUsed() {
UrlFilenameViewController controller = new UrlFilenameViewController();
controller.setPrefix(null);
assertThat(controller.getPrefix())
.as("For setPrefix(..) with null, the empty string must be used instead.")
.isNotNull();
assertThat(controller.getPrefix())
.as("For setPrefix(..) with null, the empty string must be used instead.")
.isEqualTo("");
}
@Test
public void settingSuffixToNullCausesEmptyStringToBeUsed() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
ctrl.setSuffix(null);
assertThat(ctrl.getSuffix()).as("For setPrefix(..) with null, the empty string must be used instead.").isNotNull();
assertThat(ctrl.getSuffix()).as("For setPrefix(..) with null, the empty string must be used instead.").isEqualTo("");
void settingSuffixToNullCausesEmptyStringToBeUsed() {
UrlFilenameViewController controller = new UrlFilenameViewController();
controller.setSuffix(null);
assertThat(controller.getSuffix())
.as("For setPrefix(..) with null, the empty string must be used instead.")
.isNotNull();
assertThat(controller.getSuffix())
.as("For setPrefix(..) with null, the empty string must be used instead.")
.isEqualTo("");
}
/**
* This is the expected behavior, and it now has a test to prove it.
* https://opensource.atlassian.com/projects/spring/browse/SPR-2789
*/
@Test
public void nestedPathisUsedAsViewName_InBreakingChangeFromSpring12Line() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/products/view.html");
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = ctrl.handleRequest(request, response);
@PathPatternsParameterizedTest
void nestedPathisUsedAsViewName_InBreakingChangeFromSpring12Line(
Function<String, MockHttpServletRequest> requestFactory) throws Exception {
UrlFilenameViewController controller = new UrlFilenameViewController();
MockHttpServletRequest request = requestFactory.apply("/products/view.html");
ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
assertThat(mv.getViewName()).isEqualTo("products/view");
assertThat(mv.getModel().isEmpty()).isTrue();
}
@Test
public void withFlashAttributes() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/index");
@PathPatternsParameterizedTest
void withFlashAttributes(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
UrlFilenameViewController controller = new UrlFilenameViewController();
MockHttpServletRequest request = requestFactory.apply("/index");
request.setAttribute(DispatcherServlet.INPUT_FLASH_MAP_ATTRIBUTE, new ModelMap("name", "value"));
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = ctrl.handleRequest(request, response);
ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
assertThat(mv.getViewName()).isEqualTo("index");
assertThat(mv.getModel().size()).isEqualTo(1);
assertThat(mv.getModel().get("name")).isEqualTo("value");
}
private void exposePathInMapping(MockHttpServletRequest request, String mapping) {
String pathInMapping = this.pathMatcher.extractPathWithinPattern(mapping, request.getRequestURI());
String pathInMapping = new AntPathMatcher().extractPathWithinPattern(mapping, request.getRequestURI());
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathInMapping);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -17,80 +17,83 @@
package org.springframework.web.servlet.mvc;
import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.web.servlet.handler.PathPatternsParameterizedTest;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Unit tests for {@link WebContentInterceptor}.
* @author Rick Evans
* @author Brian Clozel
* @author Rossen Stoyanchev
*/
public class WebContentInterceptorTests {
class WebContentInterceptorTests {
private MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
private final MockHttpServletResponse response = new MockHttpServletResponse();
private MockHttpServletResponse response = new MockHttpServletResponse();
private final WebContentInterceptor interceptor = new WebContentInterceptor();
private final Object handler = new Object();
@Test
public void cacheResourcesConfiguration() throws Exception {
WebContentInterceptor interceptor = new WebContentInterceptor();
@SuppressWarnings("unused")
private static Stream<Function<String, MockHttpServletRequest>> pathPatternsArguments() {
return PathPatternsTestUtils.requestArguments();
}
@PathPatternsParameterizedTest
void cacheResourcesConfiguration(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
interceptor.setCacheSeconds(10);
interceptor.preHandle(request, response, null);
interceptor.preHandle(requestFactory.apply("/"), response, handler);
Iterable<String> cacheControlHeaders = response.getHeaders("Cache-Control");
assertThat(cacheControlHeaders).contains("max-age=10");
}
@Test
public void mappedCacheConfigurationOverridesGlobal() throws Exception {
@PathPatternsParameterizedTest
void mappedCacheConfigurationOverridesGlobal(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
Properties mappings = new Properties();
mappings.setProperty("*/*handle.vm", "-1"); // was **/*handle.vm
mappings.setProperty("/*/*handle.vm", "-1");
WebContentInterceptor interceptor = new WebContentInterceptor();
interceptor.setCacheSeconds(10);
interceptor.setCacheMappings(mappings);
// request.setRequestURI("http://localhost:7070/example/adminhandle.vm");
request.setRequestURI("example/adminhandle.vm");
interceptor.preHandle(request, response, null);
MockHttpServletRequest request = requestFactory.apply("/example/adminhandle.vm");
interceptor.preHandle(request, response, handler);
Iterable<String> cacheControlHeaders = response.getHeaders("Cache-Control");
assertThat(cacheControlHeaders).isEmpty();
// request.setRequestURI("http://localhost:7070/example/bingo.html");
request.setRequestURI("example/bingo.html");
interceptor.preHandle(request, response, null);
request = requestFactory.apply("/example/bingo.html");
interceptor.preHandle(request, response, handler);
cacheControlHeaders = response.getHeaders("Cache-Control");
assertThat(cacheControlHeaders).contains("max-age=10");
}
@Test
public void preventCacheConfiguration() throws Exception {
WebContentInterceptor interceptor = new WebContentInterceptor();
@PathPatternsParameterizedTest
void preventCacheConfiguration(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
interceptor.setCacheSeconds(0);
interceptor.preHandle(request, response, null);
interceptor.preHandle(requestFactory.apply("/"), response, handler);
Iterable<String> cacheControlHeaders = response.getHeaders("Cache-Control");
assertThat(cacheControlHeaders).contains("no-store");
}
@Test
public void emptyCacheConfiguration() throws Exception {
WebContentInterceptor interceptor = new WebContentInterceptor();
@PathPatternsParameterizedTest
void emptyCacheConfiguration(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
interceptor.setCacheSeconds(-1);
interceptor.preHandle(request, response, null);
interceptor.preHandle(requestFactory.apply("/"), response, handler);
Iterable<String> expiresHeaders = response.getHeaders("Expires");
assertThat(expiresHeaders).isEmpty();
@@ -98,50 +101,45 @@ public class WebContentInterceptorTests {
assertThat(cacheControlHeaders).isEmpty();
}
// SPR-13252, SPR-14053
@Test
public void cachingConfigAndPragmaHeader() throws Exception {
WebContentInterceptor interceptor = new WebContentInterceptor();
interceptor.setCacheSeconds(10);
@PathPatternsParameterizedTest // SPR-13252, SPR-14053
void cachingConfigAndPragmaHeader(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
interceptor.preHandle(request, response, null);
interceptor.setCacheSeconds(10);
interceptor.preHandle(requestFactory.apply("/"), response, handler);
assertThat(response.getHeader("Pragma")).isEqualTo("");
assertThat(response.getHeader("Expires")).isEqualTo("");
}
// SPR-13252, SPR-14053
@SuppressWarnings("deprecation")
@Test
public void http10CachingConfigAndPragmaHeader() throws Exception {
WebContentInterceptor interceptor = new WebContentInterceptor();
@PathPatternsParameterizedTest // SPR-13252, SPR-14053
void http10CachingConfigAndPragmaHeader(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
interceptor.setCacheSeconds(10);
interceptor.setAlwaysMustRevalidate(true);
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
interceptor.preHandle(request, response, null);
interceptor.preHandle(requestFactory.apply("/"), response, handler);
assertThat(response.getHeader("Pragma")).isEqualTo("");
assertThat(response.getHeader("Expires")).isEqualTo("");
}
@SuppressWarnings("deprecation")
@Test
public void http10CachingConfigAndSpecificMapping() throws Exception {
WebContentInterceptor interceptor = new WebContentInterceptor();
@PathPatternsParameterizedTest
void http10CachingConfigAndSpecificMapping(Function<String, MockHttpServletRequest> requestFactory) throws Exception {
interceptor.setCacheSeconds(0);
interceptor.setUseExpiresHeader(true);
interceptor.setAlwaysMustRevalidate(true);
Properties mappings = new Properties();
mappings.setProperty("*/*.cache.html", "10"); // was **/*.cache.html
mappings.setProperty("/*/*.cache.html", "10");
interceptor.setCacheMappings(mappings);
// request.setRequestURI("https://example.org/foo/page.html");
request.setRequestURI("foo/page.html");
interceptor.preHandle(request, response, null);
MockHttpServletRequest request = requestFactory.apply("/foo/page.html");
MockHttpServletResponse response = new MockHttpServletResponse();
interceptor.preHandle(request, response, handler);
Iterable<String> expiresHeaders = response.getHeaders("Expires");
assertThat(expiresHeaders).hasSize(1);
@@ -150,10 +148,9 @@ public class WebContentInterceptorTests {
Iterable<String> pragmaHeaders = response.getHeaders("Pragma");
assertThat(pragmaHeaders).containsExactly("no-cache");
// request.setRequestURI("https://example.org/page.cache.html");
request = new MockHttpServletRequest("GET", "foo/page.cache.html");
request = requestFactory.apply("/foo/page.cache.html");
response = new MockHttpServletResponse();
interceptor.preHandle(request, response, null);
interceptor.preHandle(request, response, handler);
expiresHeaders = response.getHeaders("Expires");
assertThat(expiresHeaders).hasSize(1);
@@ -162,10 +159,9 @@ public class WebContentInterceptorTests {
}
@Test
public void throwsExceptionWithNullPathMatcher() throws Exception {
WebContentInterceptor interceptor = new WebContentInterceptor();
assertThatIllegalArgumentException().isThrownBy(() ->
interceptor.setPathMatcher(null));
void throwsExceptionWithNullPathMatcher() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new WebContentInterceptor().setPathMatcher(null));
}
}

View File

@@ -0,0 +1,206 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.mvc.condition;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link PathPatternsRequestCondition}.
* @author Rossen Stoyanchev
*/
public class PathPatternsRequestConditionTests {
private static final PathPatternParser parser = new PathPatternParser();
@Test
void prependSlash() {
assertThat(createCondition("foo").getPatternValues().iterator().next())
.isEqualTo("/foo");
}
@Test
void prependNonEmptyPatternsOnly() {
assertThat(createCondition("").getPatternValues().iterator().next())
.as("Do not prepend empty patterns (SPR-8255)")
.isEqualTo("");
}
@Test
void combineEmptySets() {
PathPatternsRequestCondition c1 = createCondition();
PathPatternsRequestCondition c2 = createCondition();
PathPatternsRequestCondition c3 = c1.combine(c2);
assertThat(c3).isSameAs(c1);
assertThat(c1.getPatternValues()).isSameAs(c2.getPatternValues()).containsExactly("");
}
@Test
void combineOnePatternWithEmptySet() {
PathPatternsRequestCondition c1 = createCondition("/type1", "/type2");
PathPatternsRequestCondition c2 = createCondition();
assertThat(c1.combine(c2)).isEqualTo(createCondition("/type1", "/type2"));
c1 = createCondition();
c2 = createCondition("/method1", "/method2");
assertThat(c1.combine(c2)).isEqualTo(createCondition("/method1", "/method2"));
}
@Test
void combineMultiplePatterns() {
PathPatternsRequestCondition c1 = createCondition("/t1", "/t2");
PathPatternsRequestCondition c2 = createCondition("/m1", "/m2");
assertThat(c1.combine(c2)).isEqualTo(createCondition("/t1/m1", "/t1/m2", "/t2/m1", "/t2/m2"));
}
@Test
void matchDirectPath() {
MockHttpServletRequest request = createRequest("/foo");
PathPatternsRequestCondition condition = createCondition("/foo");
PathPatternsRequestCondition match = condition.getMatchingCondition(request);
assertThat(match).isNotNull();
}
@Test
void matchPattern() {
MockHttpServletRequest request = createRequest("/foo/bar");
PathPatternsRequestCondition condition = createCondition("/foo/*");
PathPatternsRequestCondition match = condition.getMatchingCondition(request);
assertThat(match).isNotNull();
}
@Test
void matchPatternWithContextPath() {
MockHttpServletRequest request = createRequest("/app", "/app/foo/bar");
PathPatternsRequestCondition condition = createCondition("/foo/*");
PathPatternsRequestCondition match = condition.getMatchingCondition(request);
assertThat(match).isNotNull();
}
@Test
void matchSortPatterns() {
MockHttpServletRequest request = createRequest("/foo/bar");
PathPatternsRequestCondition condition = createCondition("/**", "/foo/bar", "/foo/*");
PathPatternsRequestCondition match = condition.getMatchingCondition(request);
PathPatternsRequestCondition expected = createCondition("/foo/bar", "/foo/*", "/**");
assertThat(match).isEqualTo(expected);
}
@Test
void matchTrailingSlash() {
MockHttpServletRequest request = createRequest("/foo/");
PathPatternsRequestCondition condition = createCondition("/foo");
PathPatternsRequestCondition match = condition.getMatchingCondition(request);
assertThat(match).isNotNull();
assertThat(match.getPatternValues().iterator().next()).as("Should match by default").isEqualTo("/foo");
PathPatternParser strictParser = new PathPatternParser();
strictParser.setMatchOptionalTrailingSeparator(false);
condition = new PathPatternsRequestCondition(strictParser, "/foo");
match = condition.getMatchingCondition(request);
assertThat(match).isNull();
}
@Test
void matchPatternContainsExtension() {
MockHttpServletRequest request = createRequest("/foo.html");
PathPatternsRequestCondition match = createCondition("/foo.jpg").getMatchingCondition(request);
assertThat(match).isNull();
}
@Test // gh-22543
void matchWithEmptyPatterns() {
PathPatternsRequestCondition condition = createCondition();
assertThat(condition.getMatchingCondition(createRequest(""))).isNotNull();
assertThat(condition.getMatchingCondition(createRequest("/anything"))).isNull();
condition = condition.combine(createCondition());
assertThat(condition.getMatchingCondition(createRequest(""))).isNotNull();
assertThat(condition.getMatchingCondition(createRequest("/anything"))).isNull();
}
@Test
void compareEqualPatterns() {
PathPatternsRequestCondition c1 = createCondition("/foo*");
PathPatternsRequestCondition c2 = createCondition("/foo*");
assertThat(c1.compareTo(c2, createRequest("/foo"))).isEqualTo(0);
}
@Test
void comparePatternSpecificity() {
PathPatternsRequestCondition c1 = createCondition("/fo*");
PathPatternsRequestCondition c2 = createCondition("/foo");
assertThat(c1.compareTo(c2, createRequest("/foo"))).isEqualTo(1);
}
@Test
void compareNumberOfMatchingPatterns() {
HttpServletRequest request = createRequest("/foo");
PathPatternsRequestCondition c1 = createCondition("/foo", "/bar");
PathPatternsRequestCondition c2 = createCondition("/foo", "/f*");
PathPatternsRequestCondition match1 = c1.getMatchingCondition(request);
PathPatternsRequestCondition match2 = c2.getMatchingCondition(request);
assertThat(match1.compareTo(match2, request)).isEqualTo(1);
}
private MockHttpServletRequest createRequest(String requestURI) {
return createRequest("", requestURI);
}
private MockHttpServletRequest createRequest(String contextPath, String requestURI) {
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestURI);
request.setContextPath(contextPath);
ServletRequestPathUtils.parseAndCache(request);
return request;
}
private PathPatternsRequestCondition createCondition(String... patterns) {
return new PathPatternsRequestCondition(parser, patterns);
}
}

View File

@@ -16,7 +16,6 @@
package org.springframework.web.servlet.mvc.condition;
import java.util.Arrays;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
@@ -24,28 +23,32 @@ import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.util.UrlPathHelper;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link PatternsRequestCondition}.
*
* @author Rossen Stoyanchev
*/
public class PatternsRequestConditionTests {
class PatternsRequestConditionTests {
@Test
public void prependSlash() {
PatternsRequestCondition c = new PatternsRequestCondition("foo");
assertThat(c.getPatterns().iterator().next()).isEqualTo("/foo");
void prependSlash() {
assertThat(new PatternsRequestCondition("foo").getPatterns().iterator().next())
.isEqualTo("/foo");
}
@Test
public void prependNonEmptyPatternsOnly() {
PatternsRequestCondition c = new PatternsRequestCondition("");
assertThat(c.getPatterns().iterator().next()).as("Do not prepend empty patterns (SPR-8255)").isEqualTo("");
void prependNonEmptyPatternsOnly() {
assertThat(new PatternsRequestCondition("").getPatterns().iterator().next())
.as("Do not prepend empty patterns (SPR-8255)")
.isEqualTo("");
}
@Test
public void combineEmptySets() {
void combineEmptySets() {
PatternsRequestCondition c1 = new PatternsRequestCondition();
PatternsRequestCondition c2 = new PatternsRequestCondition();
PatternsRequestCondition c3 = c1.combine(c2);
@@ -55,7 +58,7 @@ public class PatternsRequestConditionTests {
}
@Test
public void combineOnePatternWithEmptySet() {
void combineOnePatternWithEmptySet() {
PatternsRequestCondition c1 = new PatternsRequestCondition("/type1", "/type2");
PatternsRequestCondition c2 = new PatternsRequestCondition();
@@ -68,7 +71,7 @@ public class PatternsRequestConditionTests {
}
@Test
public void combineMultiplePatterns() {
void combineMultiplePatterns() {
PatternsRequestCondition c1 = new PatternsRequestCondition("/t1", "/t2");
PatternsRequestCondition c2 = new PatternsRequestCondition("/m1", "/m2");
@@ -76,42 +79,49 @@ public class PatternsRequestConditionTests {
}
@Test
public void matchDirectPath() {
void matchDirectPath() {
PatternsRequestCondition condition = new PatternsRequestCondition("/foo");
PatternsRequestCondition match = condition.getMatchingCondition(new MockHttpServletRequest("GET", "/foo"));
PatternsRequestCondition match = condition.getMatchingCondition(initRequest("/foo"));
assertThat(match).isNotNull();
}
@Test
public void matchPattern() {
void matchPattern() {
MockHttpServletRequest request = initRequest("/foo/bar");
PatternsRequestCondition condition = new PatternsRequestCondition("/foo/*");
PatternsRequestCondition match = condition.getMatchingCondition(new MockHttpServletRequest("GET", "/foo/bar"));
PatternsRequestCondition match = condition.getMatchingCondition(request);
assertThat(match).isNotNull();
}
@Test
public void matchSortPatterns() {
void matchSortPatterns() {
MockHttpServletRequest request = initRequest("/foo/bar");
PatternsRequestCondition condition = new PatternsRequestCondition("/**", "/foo/bar", "/foo/*");
PatternsRequestCondition match = condition.getMatchingCondition(new MockHttpServletRequest("GET", "/foo/bar"));
PatternsRequestCondition match = condition.getMatchingCondition(request);
PatternsRequestCondition expected = new PatternsRequestCondition("/foo/bar", "/foo/*", "/**");
assertThat(match).isEqualTo(expected);
}
@Test
public void matchSuffixPattern() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.html");
void matchSuffixPattern() {
MockHttpServletRequest request = initRequest("/foo.html");
PatternsRequestCondition condition = new PatternsRequestCondition("/{foo}");
boolean useSuffixPatternMatch = true;
PatternsRequestCondition condition =
new PatternsRequestCondition(new String[] {"/{foo}"}, null, null, useSuffixPatternMatch, true);
PatternsRequestCondition match = condition.getMatchingCondition(request);
assertThat(match).isNotNull();
assertThat(match.getPatterns().iterator().next()).isEqualTo("/{foo}.*");
boolean useSuffixPatternMatch = false;
condition = new PatternsRequestCondition(new String[] {"/{foo}"}, null, null, useSuffixPatternMatch, false);
useSuffixPatternMatch = false;
condition = new PatternsRequestCondition(
new String[] {"/{foo}"}, null, null, useSuffixPatternMatch, false);
match = condition.getMatchingCondition(request);
assertThat(match).isNotNull();
@@ -119,17 +129,17 @@ public class PatternsRequestConditionTests {
}
@Test // SPR-8410
public void matchSuffixPatternUsingFileExtensions() {
void matchSuffixPatternUsingFileExtensions() {
PatternsRequestCondition condition = new PatternsRequestCondition(
new String[] {"/jobs/{jobName}"}, null, null, true, false, Collections.singletonList("json"));
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/jobs/my.job");
MockHttpServletRequest request = initRequest("/jobs/my.job");
PatternsRequestCondition match = condition.getMatchingCondition(request);
assertThat(match).isNotNull();
assertThat(match.getPatterns().iterator().next()).isEqualTo("/jobs/{jobName}");
request = new MockHttpServletRequest("GET", "/jobs/my.job.json");
request = initRequest("/jobs/my.job.json");
match = condition.getMatchingCondition(request);
assertThat(match).isNotNull();
@@ -137,24 +147,24 @@ public class PatternsRequestConditionTests {
}
@Test
public void matchSuffixPatternUsingFileExtensions2() {
void matchSuffixPatternUsingFileExtensions2() {
PatternsRequestCondition condition1 = new PatternsRequestCondition(
new String[] {"/prefix"}, null, null, true, false, Arrays.asList("json"));
new String[] {"/prefix"}, null, null, true, false, Collections.singletonList("json"));
PatternsRequestCondition condition2 = new PatternsRequestCondition(
new String[] {"/suffix"}, null, null, true, false, null);
PatternsRequestCondition combined = condition1.combine(condition2);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/prefix/suffix.json");
MockHttpServletRequest request = initRequest("/prefix/suffix.json");
PatternsRequestCondition match = combined.getMatchingCondition(request);
assertThat(match).isNotNull();
}
@Test
public void matchTrailingSlash() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/");
void matchTrailingSlash() {
MockHttpServletRequest request = initRequest("/foo/");
PatternsRequestCondition condition = new PatternsRequestCondition("/foo");
PatternsRequestCondition match = condition.getMatchingCondition(request);
@@ -162,7 +172,7 @@ public class PatternsRequestConditionTests {
assertThat(match).isNotNull();
assertThat(match.getPatterns().iterator().next()).as("Should match by default").isEqualTo("/foo/");
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, false, true);
condition = new PatternsRequestCondition(new String[] {"/foo"}, true, null);
match = condition.getMatchingCondition(request);
assertThat(match).isNotNull();
@@ -170,53 +180,53 @@ public class PatternsRequestConditionTests {
.as("Trailing slash should be insensitive to useSuffixPatternMatch settings (SPR-6164, SPR-5636)")
.isEqualTo("/foo/");
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, false);
condition = new PatternsRequestCondition(new String[] {"/foo"}, false, null);
match = condition.getMatchingCondition(request);
assertThat(match).isNull();
}
@Test
public void matchPatternContainsExtension() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.html");
void matchPatternContainsExtension() {
MockHttpServletRequest request = initRequest("/foo.html");
PatternsRequestCondition match = new PatternsRequestCondition("/foo.jpg").getMatchingCondition(request);
assertThat(match).isNull();
}
@Test // gh-22543
public void matchWithEmptyPatterns() {
void matchWithEmptyPatterns() {
PatternsRequestCondition condition = new PatternsRequestCondition();
assertThat(condition.getMatchingCondition(new MockHttpServletRequest("GET", ""))).isNotNull();
assertThat(condition.getMatchingCondition(new MockHttpServletRequest("GET", "/anything"))).isNull();
assertThat(condition.getMatchingCondition(initRequest(""))).isNotNull();
assertThat(condition.getMatchingCondition(initRequest("/anything"))).isNull();
condition = condition.combine(new PatternsRequestCondition());
assertThat(condition.getMatchingCondition(new MockHttpServletRequest("GET", ""))).isNotNull();
assertThat(condition.getMatchingCondition(new MockHttpServletRequest("GET", "/anything"))).isNull();
assertThat(condition.getMatchingCondition(initRequest(""))).isNotNull();
assertThat(condition.getMatchingCondition(initRequest("/anything"))).isNull();
}
@Test
public void compareEqualPatterns() {
void compareEqualPatterns() {
PatternsRequestCondition c1 = new PatternsRequestCondition("/foo*");
PatternsRequestCondition c2 = new PatternsRequestCondition("/foo*");
assertThat(c1.compareTo(c2, new MockHttpServletRequest("GET", "/foo"))).isEqualTo(0);
assertThat(c1.compareTo(c2, initRequest("/foo"))).isEqualTo(0);
}
@Test
public void comparePatternSpecificity() {
void comparePatternSpecificity() {
PatternsRequestCondition c1 = new PatternsRequestCondition("/fo*");
PatternsRequestCondition c2 = new PatternsRequestCondition("/foo");
assertThat(c1.compareTo(c2, new MockHttpServletRequest("GET", "/foo"))).isEqualTo(1);
assertThat(c1.compareTo(c2, initRequest("/foo"))).isEqualTo(1);
}
@Test
public void compareNumberOfMatchingPatterns() {
HttpServletRequest request = new MockHttpServletRequest("GET", "/foo.html");
void compareNumberOfMatchingPatterns() {
HttpServletRequest request = initRequest("/foo.html");
PatternsRequestCondition c1 = new PatternsRequestCondition("/foo", "*.jpeg");
PatternsRequestCondition c2 = new PatternsRequestCondition("/foo", "*.html");
PatternsRequestCondition c1 = new PatternsRequestCondition("/foo.html", "*.jpeg");
PatternsRequestCondition c2 = new PatternsRequestCondition("/foo.html", "*.html");
PatternsRequestCondition match1 = c1.getMatchingCondition(request);
PatternsRequestCondition match2 = c2.getMatchingCondition(request);
@@ -224,4 +234,11 @@ public class PatternsRequestConditionTests {
assertThat(match1.compareTo(match2, request)).isEqualTo(1);
}
private MockHttpServletRequest initRequest(String requestUri) {
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
return request;
}
}

View File

@@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
@@ -31,6 +32,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.RequestPath;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
@@ -49,14 +51,11 @@ import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.handler.PathPatternsParameterizedTest;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -67,9 +66,25 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Arjen Poutsma
* @author Rossen Stoyanchev
*/
public class RequestMappingInfoHandlerMappingTests {
class RequestMappingInfoHandlerMappingTests {
@SuppressWarnings("unused")
static Stream<?> pathPatternsArguments() {
TestController controller = new TestController();
TestRequestMappingInfoHandlerMapping mapping1 = new TestRequestMappingInfoHandlerMapping();
mapping1.setPatternParser(new PathPatternParser());
TestRequestMappingInfoHandlerMapping mapping2 = new TestRequestMappingInfoHandlerMapping();
mapping2.setRemoveSemicolonContent(false);
return Stream.of(mapping1, mapping2).peek(mapping -> {
mapping.setApplicationContext(new StaticWebApplicationContext());
mapping.registerHandler(controller);
mapping.afterPropertiesSet();
});
}
private TestRequestMappingInfoHandlerMapping handlerMapping;
private HandlerMethod fooMethod;
@@ -81,148 +96,145 @@ public class RequestMappingInfoHandlerMappingTests {
@BeforeEach
public void setup() throws Exception {
TestController testController = new TestController();
this.fooMethod = new HandlerMethod(testController, "foo");
this.fooParamMethod = new HandlerMethod(testController, "fooParam");
this.barMethod = new HandlerMethod(testController, "bar");
this.emptyMethod = new HandlerMethod(testController, "empty");
this.handlerMapping = new TestRequestMappingInfoHandlerMapping();
this.handlerMapping.registerHandler(testController);
this.handlerMapping.setRemoveSemicolonContent(false);
void setup() throws Exception {
TestController controller = new TestController();
this.fooMethod = new HandlerMethod(controller, "foo");
this.fooParamMethod = new HandlerMethod(controller, "fooParam");
this.barMethod = new HandlerMethod(controller, "bar");
this.emptyMethod = new HandlerMethod(controller, "empty");
}
@Test
public void getMappingPathPatterns() throws Exception {
@PathPatternsParameterizedTest
void getMappingPathPatterns(TestRequestMappingInfoHandlerMapping mapping) {
String[] patterns = {"/foo/*", "/foo", "/bar/*", "/bar"};
RequestMappingInfo info = RequestMappingInfo.paths(patterns).build();
Set<String> actual = this.handlerMapping.getMappingPathPatterns(info);
RequestMappingInfo info = mapping.createInfo(patterns);
Set<String> actual = mapping.getMappingPathPatterns(info);
assertThat(actual).isEqualTo(new HashSet<>(Arrays.asList(patterns)));
}
@Test
public void getHandlerDirectMatch() throws Exception {
@PathPatternsParameterizedTest
void getHandlerDirectMatch(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
HandlerMethod handlerMethod = getHandler(request);
HandlerMethod handlerMethod = getHandler(mapping, request);
assertThat(handlerMethod.getMethod()).isEqualTo(this.fooMethod.getMethod());
}
@Test
public void getHandlerGlobMatch() throws Exception {
@PathPatternsParameterizedTest
void getHandlerGlobMatch(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bar");
HandlerMethod handlerMethod = getHandler(request);
HandlerMethod handlerMethod = getHandler(mapping, request);
assertThat(handlerMethod.getMethod()).isEqualTo(this.barMethod.getMethod());
}
@Test
public void getHandlerEmptyPathMatch() throws Exception {
@PathPatternsParameterizedTest
void getHandlerEmptyPathMatch(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
HandlerMethod handlerMethod = getHandler(request);
HandlerMethod handlerMethod = getHandler(mapping, request);
assertThat(handlerMethod.getMethod()).isEqualTo(this.emptyMethod.getMethod());
request = new MockHttpServletRequest("GET", "/");
handlerMethod = getHandler(request);
handlerMethod = getHandler(mapping, request);
assertThat(handlerMethod.getMethod()).isEqualTo(this.emptyMethod.getMethod());
}
@Test
public void getHandlerBestMatch() throws Exception {
@PathPatternsParameterizedTest
void getHandlerBestMatch(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setParameter("p", "anything");
HandlerMethod handlerMethod = getHandler(request);
HandlerMethod handlerMethod = getHandler(mapping, request);
assertThat(handlerMethod.getMethod()).isEqualTo(this.fooParamMethod.getMethod());
}
@Test
public void getHandlerRequestMethodNotAllowed() throws Exception {
@PathPatternsParameterizedTest
void getHandlerRequestMethodNotAllowed(TestRequestMappingInfoHandlerMapping mapping) {
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/bar");
assertThatExceptionOfType(HttpRequestMethodNotSupportedException.class).isThrownBy(() ->
this.handlerMapping.getHandler(request))
.satisfies(ex -> assertThat(ex.getSupportedMethods()).containsExactly("GET", "HEAD"));
assertThatExceptionOfType(HttpRequestMethodNotSupportedException.class)
.isThrownBy(() -> mapping.getHandler(request))
.satisfies(ex -> assertThat(ex.getSupportedMethods()).containsExactly("GET", "HEAD"));
}
@Test // SPR-9603
public void getHandlerRequestMethodMatchFalsePositive() throws Exception {
@PathPatternsParameterizedTest // SPR-9603
void getHandlerRequestMethodMatchFalsePositive(TestRequestMappingInfoHandlerMapping mapping) {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/users");
request.addHeader("Accept", "application/xml");
this.handlerMapping.registerHandler(new UserController());
assertThatExceptionOfType(HttpMediaTypeNotAcceptableException.class).isThrownBy(() ->
this.handlerMapping.getHandler(request));
mapping.registerHandler(new UserController());
assertThatExceptionOfType(HttpMediaTypeNotAcceptableException.class)
.isThrownBy(() -> mapping.getHandler(request));
}
@Test // SPR-8462
public void getHandlerMediaTypeNotSupported() throws Exception {
testHttpMediaTypeNotSupportedException("/person/1");
testHttpMediaTypeNotSupportedException("/person/1/");
testHttpMediaTypeNotSupportedException("/person/1.json");
@PathPatternsParameterizedTest // SPR-8462
void getHandlerMediaTypeNotSupported(TestRequestMappingInfoHandlerMapping mapping) {
testHttpMediaTypeNotSupportedException(mapping, "/person/1");
testHttpMediaTypeNotSupportedException(mapping, "/person/1/");
testHttpMediaTypeNotSupportedException(mapping, "/person/1.json");
}
@Test
public void getHandlerHttpOptions() throws Exception {
testHttpOptions("/foo", "GET,HEAD,OPTIONS");
testHttpOptions("/person/1", "PUT,OPTIONS");
testHttpOptions("/persons", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS");
testHttpOptions("/something", "PUT,POST");
@PathPatternsParameterizedTest
void getHandlerHttpOptions(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
testHttpOptions(mapping, "/foo", "GET,HEAD,OPTIONS");
testHttpOptions(mapping, "/person/1", "PUT,OPTIONS");
testHttpOptions(mapping, "/persons", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS");
testHttpOptions(mapping, "/something", "PUT,POST");
}
@Test
public void getHandlerTestInvalidContentType() throws Exception {
@PathPatternsParameterizedTest
void getHandlerTestInvalidContentType(TestRequestMappingInfoHandlerMapping mapping) {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/person/1");
request.setContentType("bogus");
assertThatExceptionOfType(HttpMediaTypeNotSupportedException.class).isThrownBy(() ->
this.handlerMapping.getHandler(request))
.withMessage("Invalid mime type \"bogus\": does not contain '/'");
assertThatExceptionOfType(HttpMediaTypeNotSupportedException.class)
.isThrownBy(() -> mapping.getHandler(request))
.withMessage("Invalid mime type \"bogus\": does not contain '/'");
}
@Test // SPR-8462
public void getHandlerMediaTypeNotAccepted() throws Exception {
testHttpMediaTypeNotAcceptableException("/persons");
testHttpMediaTypeNotAcceptableException("/persons/");
testHttpMediaTypeNotAcceptableException("/persons.json");
@PathPatternsParameterizedTest // SPR-8462
void getHandlerMediaTypeNotAccepted(TestRequestMappingInfoHandlerMapping mapping) {
testHttpMediaTypeNotAcceptableException(mapping, "/persons");
testHttpMediaTypeNotAcceptableException(mapping, "/persons/");
if (mapping.getPatternParser() == null) {
testHttpMediaTypeNotAcceptableException(mapping, "/persons.json");
}
}
@Test // SPR-12854
public void getHandlerUnsatisfiedServletRequestParameterException() throws Exception {
@PathPatternsParameterizedTest // SPR-12854
void getHandlerUnsatisfiedServletRequestParameterException(TestRequestMappingInfoHandlerMapping mapping) {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/params");
assertThatExceptionOfType(UnsatisfiedServletRequestParameterException.class).isThrownBy(() ->
this.handlerMapping.getHandler(request))
.satisfies(ex -> assertThat(ex.getParamConditionGroups().stream().map(group -> group[0]))
.containsExactlyInAnyOrder("foo=bar", "bar=baz"));
assertThatExceptionOfType(UnsatisfiedServletRequestParameterException.class)
.isThrownBy(() -> mapping.getHandler(request))
.satisfies(ex -> assertThat(ex.getParamConditionGroups().stream().map(group -> group[0]))
.containsExactlyInAnyOrder("foo=bar", "bar=baz"));
}
@Test
public void getHandlerProducibleMediaTypesAttribute() throws Exception {
@PathPatternsParameterizedTest
void getHandlerProducibleMediaTypesAttribute(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/content");
request.addHeader("Accept", "application/xml");
this.handlerMapping.getHandler(request);
mapping.getHandler(request);
String name = HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
assertThat(request.getAttribute(name)).isEqualTo(Collections.singleton(MediaType.APPLICATION_XML));
request = new MockHttpServletRequest("GET", "/content");
request.addHeader("Accept", "application/json");
this.handlerMapping.getHandler(request);
mapping.getHandler(request);
assertThat(request.getAttribute(name)).as("Negated expression shouldn't be listed as producible type").isNull();
}
@Test
public void getHandlerMappedInterceptors() throws Exception {
void getHandlerMappedInterceptors() throws Exception {
String path = "/foo";
HandlerInterceptor interceptor = new HandlerInterceptor() {};
MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] {path}, interceptor);
TestRequestMappingInfoHandlerMapping mapping = new TestRequestMappingInfoHandlerMapping();
mapping.registerHandler(new TestController());
mapping.setInterceptors(new Object[] { mappedInterceptor });
mapping.setInterceptors(mappedInterceptor);
mapping.setApplicationContext(new StaticWebApplicationContext());
HandlerExecutionChain chain = mapping.getHandler(new MockHttpServletRequest("GET", path));
@@ -235,12 +247,12 @@ public class RequestMappingInfoHandlerMappingTests {
}
@SuppressWarnings("unchecked")
@Test
public void handleMatchUriTemplateVariables() {
@PathPatternsParameterizedTest
void handleMatchUriTemplateVariables(TestRequestMappingInfoHandlerMapping mapping) {
RequestMappingInfo key = RequestMappingInfo.paths("/{path1}/{path2}").build();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
this.handlerMapping.handleMatch(key, lookupPath, request);
mapping.handleMatch(key, lookupPath, request);
String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
Map<String, String> uriVariables = (Map<String, String>) request.getAttribute(name);
@@ -251,8 +263,8 @@ public class RequestMappingInfoHandlerMappingTests {
}
@SuppressWarnings("unchecked")
@Test // SPR-9098
public void handleMatchUriTemplateVariablesDecode() {
@PathPatternsParameterizedTest // SPR-9098
void handleMatchUriTemplateVariablesDecode(TestRequestMappingInfoHandlerMapping mapping) {
RequestMappingInfo key = RequestMappingInfo.paths("/{group}/{identifier}").build();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/group/a%2Fb");
@@ -260,8 +272,8 @@ public class RequestMappingInfoHandlerMappingTests {
pathHelper.setUrlDecode(false);
String lookupPath = pathHelper.getLookupPathForRequest(request);
this.handlerMapping.setUrlPathHelper(pathHelper);
this.handlerMapping.handleMatch(key, lookupPath, request);
mapping.setUrlPathHelper(pathHelper);
mapping.handleMatch(key, lookupPath, request);
String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
Map<String, String> uriVariables = (Map<String, String>) request.getAttribute(name);
@@ -271,32 +283,32 @@ public class RequestMappingInfoHandlerMappingTests {
assertThat(uriVariables.get("identifier")).isEqualTo("a/b");
}
@Test
public void handleMatchBestMatchingPatternAttribute() {
@PathPatternsParameterizedTest
void handleMatchBestMatchingPatternAttribute(TestRequestMappingInfoHandlerMapping mapping) {
RequestMappingInfo key = RequestMappingInfo.paths("/{path1}/2", "/**").build();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
this.handlerMapping.handleMatch(key, "/1/2", request);
mapping.handleMatch(key, "/1/2", request);
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)).isEqualTo("/{path1}/2");
}
@Test // gh-22543
public void handleMatchBestMatchingPatternAttributeNoPatternsDefined() {
@PathPatternsParameterizedTest // gh-22543
void handleMatchBestMatchingPatternAttributeNoPatternsDefined(TestRequestMappingInfoHandlerMapping mapping) {
String path = "";
MockHttpServletRequest request = new MockHttpServletRequest("GET", path);
this.handlerMapping.handleMatch(RequestMappingInfo.paths().build(), path, request);
mapping.handleMatch(RequestMappingInfo.paths().build(), path, request);
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)).isEqualTo(path);
}
@Test
public void handleMatchMatrixVariables() {
@PathPatternsParameterizedTest
void handleMatchMatrixVariables(TestRequestMappingInfoHandlerMapping mapping) {
MockHttpServletRequest request;
MultiValueMap<String, String> matrixVariables;
Map<String, String> uriVariables;
// URI var parsed into path variable + matrix params..
request = new MockHttpServletRequest();
handleMatch(request, "/{cars}", "/cars;colors=red,blue,green;year=2012");
request = new MockHttpServletRequest("GET", "/cars;colors=red,blue,green;year=2012");
handleMatch(mapping, request, "/{cars}", request.getRequestURI());
matrixVariables = getMatrixVariables(request, "cars");
uriVariables = getUriTemplateVariables(request);
@@ -307,8 +319,8 @@ public class RequestMappingInfoHandlerMappingTests {
assertThat(uriVariables.get("cars")).isEqualTo("cars");
// URI var with regex for path variable, and URI var for matrix params..
request = new MockHttpServletRequest();
handleMatch(request, "/{cars:[^;]+}{params}", "/cars;colors=red,blue,green;year=2012");
request = new MockHttpServletRequest("GET", "/cars;colors=red,blue,green;year=2012");
handleMatch(mapping, request, "/{cars:[^;]+}{params}", request.getRequestURI());
matrixVariables = getMatrixVariables(request, "params");
uriVariables = getUriTemplateVariables(request);
@@ -317,11 +329,13 @@ public class RequestMappingInfoHandlerMappingTests {
assertThat(matrixVariables.get("colors")).isEqualTo(Arrays.asList("red", "blue", "green"));
assertThat(matrixVariables.getFirst("year")).isEqualTo("2012");
assertThat(uriVariables.get("cars")).isEqualTo("cars");
assertThat(uriVariables.get("params")).isEqualTo(";colors=red,blue,green;year=2012");
if (mapping.getPatternParser() == null) {
assertThat(uriVariables.get("params")).isEqualTo(";colors=red,blue,green;year=2012");
}
// URI var with regex for path variable, and (empty) URI var for matrix params..
request = new MockHttpServletRequest();
handleMatch(request, "/{cars:[^;]+}{params}", "/cars");
request = new MockHttpServletRequest("GET", "/cars");
handleMatch(mapping, request, "/{cars:[^;]+}{params}", request.getRequestURI());
matrixVariables = getMatrixVariables(request, "params");
uriVariables = getUriTemplateVariables(request);
@@ -331,32 +345,38 @@ public class RequestMappingInfoHandlerMappingTests {
assertThat(uriVariables.get("params")).isEqualTo("");
// SPR-11897
request = new MockHttpServletRequest();
handleMatch(request, "/{foo}", "/a=42;b=c");
request = new MockHttpServletRequest("GET", "/a=42;b=c");
handleMatch(mapping, request, "/{foo}", request.getRequestURI());
matrixVariables = getMatrixVariables(request, "foo");
uriVariables = getUriTemplateVariables(request);
assertThat(matrixVariables).isNotNull();
assertThat(matrixVariables.size()).isEqualTo(2);
assertThat(matrixVariables.getFirst("a")).isEqualTo("42");
assertThat(matrixVariables.getFirst("b")).isEqualTo("c");
assertThat(uriVariables.get("foo")).isEqualTo("a=42");
if (mapping.getPatternParser() != null) {
assertThat(matrixVariables.size()).isEqualTo(1);
assertThat(matrixVariables.getFirst("b")).isEqualTo("c");
assertThat(uriVariables.get("foo")).isEqualTo("a=42");
}
else {
assertThat(matrixVariables.size()).isEqualTo(2);
assertThat(matrixVariables.getFirst("a")).isEqualTo("42");
assertThat(matrixVariables.getFirst("b")).isEqualTo("c");
assertThat(uriVariables.get("foo")).isEqualTo("a=42");
}
}
@Test // SPR-10140, SPR-16867
public void handleMatchMatrixVariablesDecoding() {
@PathPatternsParameterizedTest // SPR-10140, SPR-16867
void handleMatchMatrixVariablesDecoding(TestRequestMappingInfoHandlerMapping mapping) {
MockHttpServletRequest request;
if (mapping.getPatternParser() == null) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setUrlDecode(false);
urlPathHelper.setRemoveSemicolonContent(false);
mapping.setUrlPathHelper(urlPathHelper);
}
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setUrlDecode(false);
urlPathHelper.setRemoveSemicolonContent(false);
this.handlerMapping.setUrlPathHelper(urlPathHelper);
request = new MockHttpServletRequest();
handleMatch(request, "/{cars}", "/cars;mvar=a%2Fb");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/cars;mvar=a%2Fb");
handleMatch(mapping, request, "/{cars}", request.getRequestURI());
MultiValueMap<String, String> matrixVariables = getMatrixVariables(request, "cars");
Map<String, String> uriVariables = getUriTemplateVariables(request);
@@ -366,24 +386,27 @@ public class RequestMappingInfoHandlerMappingTests {
assertThat(uriVariables.get("cars")).isEqualTo("cars");
}
private HandlerMethod getHandler(
TestRequestMappingInfoHandlerMapping mapping, MockHttpServletRequest request) throws Exception {
private HandlerMethod getHandler(MockHttpServletRequest request) throws Exception {
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
assertThat(chain).isNotNull();
return (HandlerMethod) chain.getHandler();
}
private void testHttpMediaTypeNotSupportedException(String url) throws Exception {
private void testHttpMediaTypeNotSupportedException(TestRequestMappingInfoHandlerMapping mapping, String url) {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", url);
request.setContentType("application/json");
assertThatExceptionOfType(HttpMediaTypeNotSupportedException.class).isThrownBy(() ->
this.handlerMapping.getHandler(request))
.satisfies(ex -> assertThat(ex.getSupportedMediaTypes()).containsExactly(MediaType.APPLICATION_XML));
assertThatExceptionOfType(HttpMediaTypeNotSupportedException.class)
.isThrownBy(() -> mapping.getHandler(request))
.satisfies(ex -> assertThat(ex.getSupportedMediaTypes()).containsExactly(MediaType.APPLICATION_XML));
}
private void testHttpOptions(String requestURI, String allowHeader) throws Exception {
private void testHttpOptions(
TestRequestMappingInfoHandlerMapping mapping, String requestURI, String allowHeader) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", requestURI);
HandlerMethod handlerMethod = getHandler(request);
HandlerMethod handlerMethod = getHandler(mapping, request);
ServletWebRequest webRequest = new ServletWebRequest(request);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
@@ -394,17 +417,22 @@ public class RequestMappingInfoHandlerMappingTests {
assertThat(((HttpHeaders) result).getFirst("Allow")).isEqualTo(allowHeader);
}
private void testHttpMediaTypeNotAcceptableException(String url) throws Exception {
private void testHttpMediaTypeNotAcceptableException(TestRequestMappingInfoHandlerMapping mapping, String url) {
MockHttpServletRequest request = new MockHttpServletRequest("GET", url);
request.addHeader("Accept", "application/json");
assertThatExceptionOfType(HttpMediaTypeNotAcceptableException.class).isThrownBy(() ->
this.handlerMapping.getHandler(request))
mapping.getHandler(request))
.satisfies(ex -> assertThat(ex.getSupportedMediaTypes()).containsExactly(MediaType.APPLICATION_XML));
}
private void handleMatch(MockHttpServletRequest request, String pattern, String lookupPath) {
RequestMappingInfo info = RequestMappingInfo.paths(pattern).build();
this.handlerMapping.handleMatch(info, lookupPath, request);
private void handleMatch(TestRequestMappingInfoHandlerMapping mapping,
MockHttpServletRequest request, String pattern, String lookupPath) {
if (mapping.getPatternParser() != null) {
ServletRequestPathUtils.parseAndCache(request);
}
mapping.handleMatch(mapping.createInfo(pattern), lookupPath, request);
}
@SuppressWarnings("unchecked")
@@ -494,7 +522,8 @@ public class RequestMappingInfoHandlerMappingTests {
private static class TestRequestMappingInfoHandlerMapping extends RequestMappingInfoHandlerMapping {
public void registerHandler(Object handler) {
void registerHandler(Object handler) {
super.detectHandlerMethods(handler);
}
@@ -507,18 +536,46 @@ public class RequestMappingInfoHandlerMappingTests {
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMapping annot = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (annot != null) {
return new RequestMappingInfo(
new PatternsRequestCondition(annot.value(), getUrlPathHelper(), getPathMatcher(), true, true),
new RequestMethodsRequestCondition(annot.method()),
new ParamsRequestCondition(annot.params()),
new HeadersRequestCondition(annot.headers()),
new ConsumesRequestCondition(annot.consumes(), annot.headers()),
new ProducesRequestCondition(annot.produces(), annot.headers()), null);
return RequestMappingInfo.paths(annot.value())
.methods(annot.method())
.params(annot.params())
.headers(annot.headers())
.consumes(annot.consumes())
.produces(annot.produces())
.options(getBuilderConfig())
.build();
}
else {
return null;
}
}
private RequestMappingInfo.BuilderConfiguration getBuilderConfig() {
RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
if (getPatternParser() != null) {
config.setPatternParser(getPatternParser());
}
else {
config.setSuffixPatternMatch(true);
config.setRegisteredSuffixPatternMatch(true);
config.setPathMatcher(getPathMatcher());
}
return config;
}
RequestMappingInfo createInfo(String... patterns) {
return RequestMappingInfo.paths(patterns).options(getBuilderConfig()).build();
}
@Override
protected String initLookupPath(HttpServletRequest request) {
// At runtime this is done by the DispatcherServlet
if (getPatternParser() != null) {
RequestPath requestPath = ServletRequestPathUtils.parseAndCache(request);
return requestPath.pathWithinApplication().value();
}
return super.initLookupPath(request);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -19,6 +19,7 @@ package org.springframework.web.servlet.mvc.method;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
@@ -26,13 +27,16 @@ import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.handler.PathPatternsParameterizedTest;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPatternParser;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.HEAD;
import static org.springframework.web.servlet.mvc.method.RequestMappingInfo.paths;
/**
* Test fixture for {@link RequestMappingInfo} tests.
@@ -40,15 +44,22 @@ import static org.springframework.web.servlet.mvc.method.RequestMappingInfo.path
* @author Arjen Poutsma
* @author Rossen Stoyanchev
*/
public class RequestMappingInfoTests {
class RequestMappingInfoTests {
@Test
public void createEmpty() {
@SuppressWarnings("unused")
static Stream<RequestMappingInfo.Builder> pathPatternsArguments() {
RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
config.setPatternParser(new PathPatternParser());
return Stream.of(RequestMappingInfo.paths().options(config), RequestMappingInfo.paths());
}
RequestMappingInfo info = paths().build();
@PathPatternsParameterizedTest
void createEmpty(RequestMappingInfo.Builder infoBuilder) {
// gh-22543
assertThat(info.getPatternsCondition().getPatterns()).isEqualTo(Collections.singleton(""));
RequestMappingInfo info = infoBuilder.build();
assertThat(info.getPatternValues()).isEqualTo(Collections.singleton(""));
assertThat(info.getMethodsCondition().getMethods().size()).isEqualTo(0);
assertThat(info.getParamsCondition()).isNotNull();
assertThat(info.getHeadersCondition()).isNotNull();
@@ -56,7 +67,8 @@ public class RequestMappingInfoTests {
assertThat(info.getProducesCondition().isEmpty()).isEqualTo(true);
assertThat(info.getCustomCondition()).isNull();
RequestMappingInfo anotherInfo = paths().build();
RequestMappingInfo anotherInfo = infoBuilder.build();
assertThat(info.getActivePatternsCondition()).isSameAs(anotherInfo.getActivePatternsCondition());
assertThat(info.getPatternsCondition()).isSameAs(anotherInfo.getPatternsCondition());
assertThat(info.getMethodsCondition()).isSameAs(anotherInfo.getMethodsCondition());
assertThat(info.getParamsCondition()).isSameAs(anotherInfo.getParamsCondition());
@@ -66,7 +78,7 @@ public class RequestMappingInfoTests {
assertThat(info.getCustomCondition()).isSameAs(anotherInfo.getCustomCondition());
RequestMappingInfo result = info.combine(anotherInfo);
assertThat(info.getPatternsCondition()).isSameAs(result.getPatternsCondition());
assertThat(info.getActivePatternsCondition()).isSameAs(result.getActivePatternsCondition());
assertThat(info.getMethodsCondition()).isSameAs(result.getMethodsCondition());
assertThat(info.getParamsCondition()).isSameAs(result.getParamsCondition());
assertThat(info.getHeadersCondition()).isSameAs(result.getHeadersCondition());
@@ -75,148 +87,157 @@ public class RequestMappingInfoTests {
assertThat(info.getCustomCondition()).isSameAs(result.getCustomCondition());
}
@Test
public void matchPatternsCondition() {
HttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
@PathPatternsParameterizedTest
void matchPatternsCondition(RequestMappingInfo.Builder builder) {
RequestMappingInfo info = paths("/foo*", "/bar").build();
RequestMappingInfo expected = paths("/foo*").build();
boolean useParsedPatterns = builder.build().getPathPatternsCondition() != null;
HttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/foo", useParsedPatterns);
RequestMappingInfo info = builder.paths("/foo*", "/bar").build();
RequestMappingInfo expected = builder.paths("/foo*").build();
assertThat(info.getMatchingCondition(request)).isEqualTo(expected);
info = paths("/**", "/foo*", "/foo").build();
expected = paths("/foo", "/foo*", "/**").build();
info = builder.paths("/**", "/foo*", "/foo").build();
expected = builder.paths("/foo", "/foo*", "/**").build();
assertThat(info.getMatchingCondition(request)).isEqualTo(expected);
}
@Test
public void matchParamsCondition() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
void matchParamsCondition() {
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/foo", false);
request.setParameter("foo", "bar");
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
RequestMappingInfo info = paths("/foo").params("foo=bar").build();
RequestMappingInfo info = RequestMappingInfo.paths("/foo").params("foo=bar").build();
RequestMappingInfo match = info.getMatchingCondition(request);
assertThat(match).isNotNull();
info = paths("/foo").params("foo!=bar").build();
info = RequestMappingInfo.paths("/foo").params("foo!=bar").build();
match = info.getMatchingCondition(request);
assertThat(match).isNull();
}
@Test
public void matchHeadersCondition() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
void matchHeadersCondition() {
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/foo", false);
request.addHeader("foo", "bar");
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
RequestMappingInfo info = paths("/foo").headers("foo=bar").build();
RequestMappingInfo info = RequestMappingInfo.paths("/foo").headers("foo=bar").build();
RequestMappingInfo match = info.getMatchingCondition(request);
assertThat(match).isNotNull();
info = paths("/foo").headers("foo!=bar").build();
info = RequestMappingInfo.paths("/foo").headers("foo!=bar").build();
match = info.getMatchingCondition(request);
assertThat(match).isNull();
}
@Test
public void matchConsumesCondition() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
void matchConsumesCondition() {
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/foo", false);
request.setContentType("text/plain");
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
RequestMappingInfo info = paths("/foo").consumes("text/plain").build();
RequestMappingInfo info = RequestMappingInfo.paths("/foo").consumes("text/plain").build();
RequestMappingInfo match = info.getMatchingCondition(request);
assertThat(match).isNotNull();
info = paths("/foo").consumes("application/xml").build();
info = RequestMappingInfo.paths("/foo").consumes("application/xml").build();
match = info.getMatchingCondition(request);
assertThat(match).isNull();
}
@Test
public void matchProducesCondition() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
void matchProducesCondition() {
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/foo", false);
request.addHeader("Accept", "text/plain");
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
RequestMappingInfo info = paths("/foo").produces("text/plain").build();
RequestMappingInfo info = RequestMappingInfo.paths("/foo").produces("text/plain").build();
RequestMappingInfo match = info.getMatchingCondition(request);
assertThat(match).isNotNull();
info = paths("/foo").produces("application/xml").build();
info = RequestMappingInfo.paths("/foo").produces("application/xml").build();
match = info.getMatchingCondition(request);
assertThat(match).isNull();
}
@Test
public void matchCustomCondition() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
void matchCustomCondition() {
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/foo", false);
request.setParameter("foo", "bar");
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
RequestMappingInfo info = paths("/foo").params("foo=bar").build();
RequestMappingInfo info = RequestMappingInfo.paths("/foo").params("foo=bar").build();
RequestMappingInfo match = info.getMatchingCondition(request);
assertThat(match).isNotNull();
info = paths("/foo").params("foo!=bar").params("foo!=bar").build();
info = RequestMappingInfo.paths("/foo").params("foo!=bar").params("foo!=bar").build();
match = info.getMatchingCondition(request);
assertThat(match).isNull();
}
@Test
public void compareToWithImpicitVsExplicitHttpMethodDeclaration() {
RequestMappingInfo noMethods = paths().build();
RequestMappingInfo oneMethod = paths().methods(GET).build();
RequestMappingInfo oneMethodOneParam = paths().methods(GET).params("foo").build();
void compareToWithImpicitVsExplicitHttpMethodDeclaration() {
RequestMappingInfo noMethods = RequestMappingInfo.paths().build();
RequestMappingInfo oneMethod = RequestMappingInfo.paths().methods(GET).build();
RequestMappingInfo oneMethodOneParam = RequestMappingInfo.paths().methods(GET).params("foo").build();
Comparator<RequestMappingInfo> comparator =
(info, otherInfo) -> info.compareTo(otherInfo, new MockHttpServletRequest());
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/", false);
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
Comparator<RequestMappingInfo> comparator = (info, otherInfo) -> info.compareTo(otherInfo, request);
List<RequestMappingInfo> list = asList(noMethods, oneMethod, oneMethodOneParam);
Collections.shuffle(list);
Collections.sort(list, comparator);
list.sort(comparator);
assertThat(list.get(0)).isEqualTo(oneMethodOneParam);
assertThat(list.get(1)).isEqualTo(oneMethod);
assertThat(list.get(2)).isEqualTo(noMethods);
}
@Test // SPR-14383
public void compareToWithHttpHeadMapping() {
MockHttpServletRequest request = new MockHttpServletRequest();
@Test
// SPR-14383
void compareToWithHttpHeadMapping() {
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/", false);
request.setMethod("HEAD");
request.addHeader("Accept", "application/json");
RequestMappingInfo noMethods = paths().build();
RequestMappingInfo getMethod = paths().methods(GET).produces("application/json").build();
RequestMappingInfo headMethod = paths().methods(HEAD).build();
RequestMappingInfo noMethods = RequestMappingInfo.paths().build();
RequestMappingInfo getMethod = RequestMappingInfo.paths().methods(GET).produces("application/json").build();
RequestMappingInfo headMethod = RequestMappingInfo.paths().methods(HEAD).build();
Comparator<RequestMappingInfo> comparator = (info, otherInfo) -> info.compareTo(otherInfo, request);
List<RequestMappingInfo> list = asList(noMethods, getMethod, headMethod);
Collections.shuffle(list);
Collections.sort(list, comparator);
list.sort(comparator);
assertThat(list.get(0)).isEqualTo(headMethod);
assertThat(list.get(1)).isEqualTo(getMethod);
assertThat(list.get(2)).isEqualTo(noMethods);
}
@Test
public void equals() {
RequestMappingInfo info1 = paths("/foo").methods(GET)
@PathPatternsParameterizedTest
void equalsMethod(RequestMappingInfo.Builder infoBuilder) {
RequestMappingInfo info1 = infoBuilder.paths("/foo").methods(GET)
.params("foo=bar", "customFoo=customBar").headers("foo=bar")
.consumes("text/plain").produces("text/plain")
.build();
RequestMappingInfo info2 = paths("/foo").methods(GET)
RequestMappingInfo info2 = infoBuilder.paths("/foo").methods(GET)
.params("foo=bar", "customFoo=customBar").headers("foo=bar")
.consumes("text/plain").produces("text/plain")
.build();
@@ -224,7 +245,7 @@ public class RequestMappingInfoTests {
assertThat(info2).isEqualTo(info1);
assertThat(info2.hashCode()).isEqualTo(info1.hashCode());
info2 = paths("/foo", "/NOOOOOO").methods(GET)
info2 = infoBuilder.paths("/foo", "/NOOOOOO").methods(GET)
.params("foo=bar", "customFoo=customBar").headers("foo=bar")
.consumes("text/plain").produces("text/plain")
.build();
@@ -232,7 +253,7 @@ public class RequestMappingInfoTests {
assertThat(info1.equals(info2)).isFalse();
assertThat(info2.hashCode()).isNotEqualTo((long) info1.hashCode());
info2 = paths("/foo").methods(GET, RequestMethod.POST)
info2 = infoBuilder.paths("/foo").methods(GET, RequestMethod.POST)
.params("foo=bar", "customFoo=customBar").headers("foo=bar")
.consumes("text/plain").produces("text/plain")
.build();
@@ -240,7 +261,7 @@ public class RequestMappingInfoTests {
assertThat(info1.equals(info2)).isFalse();
assertThat(info2.hashCode()).isNotEqualTo((long) info1.hashCode());
info2 = paths("/foo").methods(GET)
info2 = infoBuilder.paths("/foo").methods(GET)
.params("/NOOOOOO", "customFoo=customBar").headers("foo=bar")
.consumes("text/plain").produces("text/plain")
.build();
@@ -248,7 +269,7 @@ public class RequestMappingInfoTests {
assertThat(info1.equals(info2)).isFalse();
assertThat(info2.hashCode()).isNotEqualTo((long) info1.hashCode());
info2 = paths("/foo").methods(GET)
info2 = infoBuilder.paths("/foo").methods(GET)
.params("foo=bar", "customFoo=customBar").headers("/NOOOOOO")
.consumes("text/plain").produces("text/plain")
.build();
@@ -256,7 +277,7 @@ public class RequestMappingInfoTests {
assertThat(info1.equals(info2)).isFalse();
assertThat(info2.hashCode()).isNotEqualTo((long) info1.hashCode());
info2 = paths("/foo").methods(GET)
info2 = infoBuilder.paths("/foo").methods(GET)
.params("foo=bar", "customFoo=customBar").headers("foo=bar")
.consumes("text/NOOOOOO").produces("text/plain")
.build();
@@ -264,7 +285,7 @@ public class RequestMappingInfoTests {
assertThat(info1.equals(info2)).isFalse();
assertThat(info2.hashCode()).isNotEqualTo((long) info1.hashCode());
info2 = paths("/foo").methods(GET)
info2 = infoBuilder.paths("/foo").methods(GET)
.params("foo=bar", "customFoo=customBar").headers("foo=bar")
.consumes("text/plain").produces("text/NOOOOOO")
.build();
@@ -272,7 +293,7 @@ public class RequestMappingInfoTests {
assertThat(info1.equals(info2)).isFalse();
assertThat(info2.hashCode()).isNotEqualTo((long) info1.hashCode());
info2 = paths("/foo").methods(GET)
info2 = infoBuilder.paths("/foo").methods(GET)
.params("foo=bar", "customFoo=NOOOOOO").headers("foo=bar")
.consumes("text/plain").produces("text/plain")
.build();
@@ -282,16 +303,16 @@ public class RequestMappingInfoTests {
}
@Test
public void preFlightRequest() {
MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/foo");
void preFlightRequest() {
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("OPTIONS", "/foo", false);
request.addHeader(HttpHeaders.ORIGIN, "https://domain.com");
request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST");
RequestMappingInfo info = paths("/foo").methods(RequestMethod.POST).build();
RequestMappingInfo info = RequestMappingInfo.paths("/foo").methods(RequestMethod.POST).build();
RequestMappingInfo match = info.getMatchingCondition(request);
assertThat(match).isNotNull();
info = paths("/foo").methods(RequestMethod.OPTIONS).build();
info = RequestMappingInfo.paths("/foo").methods(RequestMethod.OPTIONS).build();
match = info.getMatchingCondition(request);
assertThat(match).as("Pre-flight should match the ACCESS_CONTROL_REQUEST_METHOD").isNull();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -20,6 +20,7 @@ import javax.servlet.ServletException;
import org.junit.jupiter.api.AfterEach;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.lang.Nullable;
@@ -29,6 +30,7 @@ import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import org.springframework.web.testfixture.servlet.MockServletConfig;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.assertj.core.api.Assertions.assertThat;
@@ -44,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public abstract class AbstractServletHandlerMethodTests {
@Nullable
private DispatcherServlet servlet;
@@ -57,49 +60,54 @@ public abstract class AbstractServletHandlerMethodTests {
this.servlet = null;
}
/**
* Initialize a DispatcherServlet instance registering zero or more controller classes.
*/
protected WebApplicationContext initServletWithControllers(final Class<?>... controllerClasses)
throws ServletException {
protected WebApplicationContext initDispatcherServlet(
Class<?> controllerClass, boolean usePathPatterns) throws ServletException {
return initServlet(null, controllerClasses);
return initDispatcherServlet(controllerClass, usePathPatterns, null);
}
/**
* Initialize a DispatcherServlet instance registering zero or more controller classes
* and also providing additional bean definitions through a callback.
*/
@SuppressWarnings("serial")
protected WebApplicationContext initServlet(
final ApplicationContextInitializer<GenericWebApplicationContext> initializer,
final Class<?>... controllerClasses) throws ServletException {
WebApplicationContext initDispatcherServlet(
@Nullable Class<?> controllerClass, boolean usePathPatterns,
@Nullable ApplicationContextInitializer<GenericWebApplicationContext> initializer)
throws ServletException {
final GenericWebApplicationContext wac = new GenericWebApplicationContext();
servlet = new DispatcherServlet() {
@Override
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
for (Class<?> clazz : controllerClasses) {
wac.registerBeanDefinition(clazz.getSimpleName(), new RootBeanDefinition(clazz));
}
RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
mappingDef.getPropertyValues().add("removeSemicolonContent", "false");
wac.registerBeanDefinition("handlerMapping", mappingDef);
wac.registerBeanDefinition("handlerAdapter",
new RootBeanDefinition(RequestMappingHandlerAdapter.class));
wac.registerBeanDefinition("requestMappingResolver",
new RootBeanDefinition(ExceptionHandlerExceptionResolver.class));
wac.registerBeanDefinition("responseStatusResolver",
new RootBeanDefinition(ResponseStatusExceptionResolver.class));
wac.registerBeanDefinition("defaultResolver",
new RootBeanDefinition(DefaultHandlerExceptionResolver.class));
if (controllerClass != null) {
wac.registerBeanDefinition(
controllerClass.getSimpleName(), new RootBeanDefinition(controllerClass));
}
if (initializer != null) {
initializer.initialize(wac);
}
if (!wac.containsBeanDefinition("handlerMapping")) {
BeanDefinition def = register("handlerMapping", RequestMappingHandlerMapping.class, wac);
def.getPropertyValues().add("removeSemicolonContent", "false");
}
BeanDefinition mappingDef = wac.getBeanDefinition("handlerMapping");
if (usePathPatterns && !mappingDef.hasAttribute("patternParser")) {
BeanDefinition parserDef = register("parser", PathPatternParser.class, wac);
mappingDef.getPropertyValues().add("patternParser", parserDef);
}
register("handlerAdapter", RequestMappingHandlerAdapter.class, wac);
register("requestMappingResolver", ExceptionHandlerExceptionResolver.class, wac);
register("responseStatusResolver", ResponseStatusExceptionResolver.class, wac);
register("defaultResolver", DefaultHandlerExceptionResolver.class, wac);
wac.refresh();
return wac;
}
@@ -110,4 +118,13 @@ public abstract class AbstractServletHandlerMethodTests {
return wac;
}
private BeanDefinition register(String beanName, Class<?> beanType, GenericWebApplicationContext wac) {
if (wac.containsBeanDefinition(beanName)) {
return wac.getBeanDefinition(beanName);
}
RootBeanDefinition beanDef = new RootBeanDefinition(beanType);
wac.registerBeanDefinition(beanName, beanDef);
return beanDef;
}
}

View File

@@ -21,11 +21,13 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.Properties;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
@@ -33,6 +35,8 @@ import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.RequestPath;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
@@ -44,14 +48,11 @@ import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.handler.PathPatternsParameterizedTest;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@@ -62,21 +63,12 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
* @author Sebastien Deleuze
* @author Sam Brannen
* @author Nicolas Labrot
* @author Rossen Stoyanchev
*/
public class CrossOriginTests {
class CrossOriginTests {
private final TestRequestMappingInfoHandlerMapping handlerMapping = new TestRequestMappingInfoHandlerMapping();
private final MockHttpServletRequest request = new MockHttpServletRequest();
private final String optionsHandler = "org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping$HttpOptionsHandler#handle()";
private final String corsPreflightHandler = "org.springframework.web.servlet.handler.AbstractHandlerMapping$PreFlightHandler";
@BeforeEach
@SuppressWarnings("resource")
public void setup() {
@SuppressWarnings("unused")
static Stream<TestRequestMappingInfoHandlerMapping> pathPatternsArguments() {
StaticWebApplicationContext wac = new StaticWebApplicationContext();
Properties props = new Properties();
props.setProperty("myOrigin", "https://example.com");
@@ -84,234 +76,252 @@ public class CrossOriginTests {
wac.registerSingleton("ppc", PropertySourcesPlaceholderConfigurer.class);
wac.refresh();
this.handlerMapping.setRemoveSemicolonContent(false);
wac.getAutowireCapableBeanFactory().initializeBean(this.handlerMapping, "hm");
TestRequestMappingInfoHandlerMapping mapping1 = new TestRequestMappingInfoHandlerMapping();
mapping1.setPatternParser(new PathPatternParser());
wac.getAutowireCapableBeanFactory().initializeBean(mapping1, "mapping1");
TestRequestMappingInfoHandlerMapping mapping2 = new TestRequestMappingInfoHandlerMapping();
wac.getAutowireCapableBeanFactory().initializeBean(mapping2, "mapping2");
return Stream.of(mapping1, mapping2);
}
private final MockHttpServletRequest request = new MockHttpServletRequest();
@BeforeEach
void setup() {
this.request.setMethod("GET");
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain.com/");
}
@Test
public void noAnnotationWithoutOrigin() throws Exception {
this.handlerMapping.registerHandler(new MethodLevelController());
@PathPatternsParameterizedTest
void noAnnotationWithoutOrigin(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new MethodLevelController());
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/no");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
assertThat(getCorsConfiguration(chain, false)).isNull();
}
@Test
public void noAnnotationWithAccessControlRequestMethod() throws Exception {
this.handlerMapping.registerHandler(new MethodLevelController());
@PathPatternsParameterizedTest
void noAnnotationWithAccessControlRequestMethod(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new MethodLevelController());
MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/no");
request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
assertThat(chain.getHandler().toString()).isEqualTo(optionsHandler);
HandlerExecutionChain chain = mapping.getHandler(request);
assertThat(chain).isNotNull();
assertThat(chain.getHandler().toString())
.endsWith("RequestMappingInfoHandlerMapping$HttpOptionsHandler#handle()");
}
@Test
public void noAnnotationWithPreflightRequest() throws Exception {
this.handlerMapping.registerHandler(new MethodLevelController());
@PathPatternsParameterizedTest
void noAnnotationWithPreflightRequest(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new MethodLevelController());
MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/no");
request.addHeader(HttpHeaders.ORIGIN, "https://domain.com/");
request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
assertThat(chain.getHandler().getClass().getName()).isEqualTo(corsPreflightHandler);
HandlerExecutionChain chain = mapping.getHandler(request);
assertThat(chain).isNotNull();
assertThat(chain.getHandler().getClass().getName()).endsWith("AbstractHandlerMapping$PreFlightHandler");
}
@Test // SPR-12931
public void noAnnotationWithOrigin() throws Exception {
this.handlerMapping.registerHandler(new MethodLevelController());
@PathPatternsParameterizedTest // SPR-12931
void noAnnotationWithOrigin(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new MethodLevelController());
this.request.setRequestURI("/no");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
assertThat(getCorsConfiguration(chain, false)).isNull();
}
@Test // SPR-12931
public void noAnnotationPostWithOrigin() throws Exception {
this.handlerMapping.registerHandler(new MethodLevelController());
@PathPatternsParameterizedTest // SPR-12931
void noAnnotationPostWithOrigin(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new MethodLevelController());
this.request.setMethod("POST");
this.request.setRequestURI("/no");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
assertThat(getCorsConfiguration(chain, false)).isNull();
}
@Test
public void defaultAnnotation() throws Exception {
this.handlerMapping.registerHandler(new MethodLevelController());
@PathPatternsParameterizedTest
void defaultAnnotation(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new MethodLevelController());
this.request.setRequestURI("/default");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertThat(config).isNotNull();
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"GET"});
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[] {"*"});
assertThat(config.getAllowedMethods()).containsExactly("GET");
assertThat(config.getAllowedOrigins()).containsExactly("*");
assertThat(config.getAllowCredentials()).isNull();
assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[] {"*"});
assertThat(config.getAllowedHeaders()).containsExactly("*");
assertThat(CollectionUtils.isEmpty(config.getExposedHeaders())).isTrue();
assertThat(config.getMaxAge()).isEqualTo(new Long(1800));
}
@Test
public void customized() throws Exception {
this.handlerMapping.registerHandler(new MethodLevelController());
@PathPatternsParameterizedTest
void customized(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new MethodLevelController());
this.request.setRequestURI("/customized");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertThat(config).isNotNull();
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"DELETE"});
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[] {"https://site1.com", "https://site2.com"});
assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[] {"header1", "header2"});
assertThat(config.getExposedHeaders().toArray()).isEqualTo(new String[] {"header3", "header4"});
assertThat(config.getAllowedMethods()).containsExactly("DELETE");
assertThat(config.getAllowedOrigins()).containsExactly("https://site1.com", "https://site2.com");
assertThat(config.getAllowedHeaders()).containsExactly("header1", "header2");
assertThat(config.getExposedHeaders()).containsExactly("header3", "header4");
assertThat(config.getMaxAge()).isEqualTo(new Long(123));
assertThat((boolean) config.getAllowCredentials()).isFalse();
assertThat(config.getAllowCredentials()).isFalse();
}
@Test
public void customOriginDefinedViaValueAttribute() throws Exception {
this.handlerMapping.registerHandler(new MethodLevelController());
@PathPatternsParameterizedTest
void customOriginDefinedViaValueAttribute(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new MethodLevelController());
this.request.setRequestURI("/customOrigin");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertThat(config).isNotNull();
assertThat(config.getAllowedOrigins()).isEqualTo(Arrays.asList("https://example.com"));
assertThat(config.getAllowedOrigins()).isEqualTo(Collections.singletonList("https://example.com"));
assertThat(config.getAllowCredentials()).isNull();
}
@Test
public void customOriginDefinedViaPlaceholder() throws Exception {
this.handlerMapping.registerHandler(new MethodLevelController());
@PathPatternsParameterizedTest
void customOriginDefinedViaPlaceholder(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new MethodLevelController());
this.request.setRequestURI("/someOrigin");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertThat(config).isNotNull();
assertThat(config.getAllowedOrigins()).isEqualTo(Arrays.asList("https://example.com"));
assertThat(config.getAllowedOrigins()).isEqualTo(Collections.singletonList("https://example.com"));
assertThat(config.getAllowCredentials()).isNull();
}
@Test
public void bogusAllowCredentialsValue() throws Exception {
@PathPatternsParameterizedTest
void bogusAllowCredentialsValue(TestRequestMappingInfoHandlerMapping mapping) {
assertThatIllegalStateException().isThrownBy(() ->
this.handlerMapping.registerHandler(new MethodLevelControllerWithBogusAllowCredentialsValue()))
mapping.registerHandler(new MethodLevelControllerWithBogusAllowCredentialsValue()))
.withMessageContaining("@CrossOrigin's allowCredentials")
.withMessageContaining("current value is [bogus]");
}
@Test
public void classLevel() throws Exception {
this.handlerMapping.registerHandler(new ClassLevelController());
@PathPatternsParameterizedTest
void classLevel(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new ClassLevelController());
this.request.setRequestURI("/foo");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertThat(config).isNotNull();
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"GET"});
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[] {"*"});
assertThat((boolean) config.getAllowCredentials()).isFalse();
assertThat(config.getAllowedMethods()).containsExactly("GET");
assertThat(config.getAllowedOrigins()).containsExactly("*");
assertThat(config.getAllowCredentials()).isFalse();
this.request.setRequestURI("/bar");
chain = this.handlerMapping.getHandler(request);
chain = mapping.getHandler(request);
config = getCorsConfiguration(chain, false);
assertThat(config).isNotNull();
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"GET"});
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[] {"*"});
assertThat((boolean) config.getAllowCredentials()).isFalse();
assertThat(config.getAllowedMethods()).containsExactly("GET");
assertThat(config.getAllowedOrigins()).containsExactly("*");
assertThat(config.getAllowCredentials()).isFalse();
this.request.setRequestURI("/baz");
chain = this.handlerMapping.getHandler(request);
chain = mapping.getHandler(request);
config = getCorsConfiguration(chain, false);
assertThat(config).isNotNull();
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"GET"});
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[] {"*"});
assertThat((boolean) config.getAllowCredentials()).isTrue();
assertThat(config.getAllowedMethods()).containsExactly("GET");
assertThat(config.getAllowedOrigins()).containsExactly("*");
assertThat(config.getAllowCredentials()).isTrue();
}
@Test // SPR-13468
public void classLevelComposedAnnotation() throws Exception {
this.handlerMapping.registerHandler(new ClassLevelMappingWithComposedAnnotation());
@PathPatternsParameterizedTest // SPR-13468
void classLevelComposedAnnotation(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new ClassLevelMappingWithComposedAnnotation());
this.request.setRequestURI("/foo");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertThat(config).isNotNull();
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"GET"});
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[] {"http://www.foo.example/"});
assertThat((boolean) config.getAllowCredentials()).isTrue();
assertThat(config.getAllowedMethods()).containsExactly("GET");
assertThat(config.getAllowedOrigins()).containsExactly("http://www.foo.example/");
assertThat(config.getAllowCredentials()).isTrue();
}
@Test // SPR-13468
public void methodLevelComposedAnnotation() throws Exception {
this.handlerMapping.registerHandler(new MethodLevelMappingWithComposedAnnotation());
@PathPatternsParameterizedTest // SPR-13468
void methodLevelComposedAnnotation(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new MethodLevelMappingWithComposedAnnotation());
this.request.setRequestURI("/foo");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertThat(config).isNotNull();
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"GET"});
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[] {"http://www.foo.example/"});
assertThat((boolean) config.getAllowCredentials()).isTrue();
assertThat(config.getAllowedMethods()).containsExactly("GET");
assertThat(config.getAllowedOrigins()).containsExactly("http://www.foo.example/");
assertThat(config.getAllowCredentials()).isTrue();
}
@Test
public void preFlightRequest() throws Exception {
this.handlerMapping.registerHandler(new MethodLevelController());
@PathPatternsParameterizedTest
void preFlightRequest(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new MethodLevelController());
this.request.setMethod("OPTIONS");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
this.request.setRequestURI("/default");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, true);
assertThat(config).isNotNull();
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"GET"});
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[] {"*"});
assertThat(config.getAllowedMethods()).containsExactly("GET");
assertThat(config.getAllowedOrigins()).containsExactly("*");
assertThat(config.getAllowCredentials()).isNull();
assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[] {"*"});
assertThat(config.getAllowedHeaders()).containsExactly("*");
assertThat(CollectionUtils.isEmpty(config.getExposedHeaders())).isTrue();
assertThat(config.getMaxAge()).isEqualTo(new Long(1800));
}
@Test
public void ambiguousHeaderPreFlightRequest() throws Exception {
this.handlerMapping.registerHandler(new MethodLevelController());
@PathPatternsParameterizedTest
void ambiguousHeaderPreFlightRequest(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new MethodLevelController());
this.request.setMethod("OPTIONS");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1");
this.request.setRequestURI("/ambiguous-header");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, true);
assertThat(config).isNotNull();
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"*"});
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[] {"*"});
assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[] {"*"});
assertThat((boolean) config.getAllowCredentials()).isTrue();
assertThat(config.getAllowedMethods()).containsExactly("*");
assertThat(config.getAllowedOrigins()).containsExactly("*");
assertThat(config.getAllowedHeaders()).containsExactly("*");
assertThat(config.getAllowCredentials()).isTrue();
assertThat(CollectionUtils.isEmpty(config.getExposedHeaders())).isTrue();
assertThat(config.getMaxAge()).isNull();
}
@Test
public void ambiguousProducesPreFlightRequest() throws Exception {
this.handlerMapping.registerHandler(new MethodLevelController());
@PathPatternsParameterizedTest
void ambiguousProducesPreFlightRequest(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
mapping.registerHandler(new MethodLevelController());
this.request.setMethod("OPTIONS");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
this.request.setRequestURI("/ambiguous-produces");
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
HandlerExecutionChain chain = mapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, true);
assertThat(config).isNotNull();
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"*"});
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[] {"*"});
assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[] {"*"});
assertThat((boolean) config.getAllowCredentials()).isTrue();
assertThat(config.getAllowedMethods()).containsExactly("*");
assertThat(config.getAllowedOrigins()).containsExactly("*");
assertThat(config.getAllowedHeaders()).containsExactly("*");
assertThat(config.getAllowCredentials()).isTrue();
assertThat(CollectionUtils.isEmpty(config.getExposedHeaders())).isTrue();
assertThat(config.getMaxAge()).isNull();
}
@Test
public void preFlightRequestWithoutRequestMethodHeader() throws Exception {
@PathPatternsParameterizedTest
void preFlightRequestWithoutRequestMethodHeader(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/default");
request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
assertThat(this.handlerMapping.getHandler(request)).isNull();
assertThat(mapping.getHandler(request)).isNull();
}
private CorsConfiguration getCorsConfiguration(HandlerExecutionChain chain, boolean isPreFlightRequest) {
@Nullable
private CorsConfiguration getCorsConfiguration(@Nullable HandlerExecutionChain chain, boolean isPreFlightRequest) {
assertThat(chain).isNotNull();
if (isPreFlightRequest) {
Object handler = chain.getHandler();
assertThat(handler.getClass().getSimpleName().equals("PreFlightHandler")).isTrue();
@@ -334,6 +344,7 @@ public class CrossOriginTests {
@Controller
@SuppressWarnings("unused")
private static class MethodLevelController {
@GetMapping("/no")
@@ -399,6 +410,7 @@ public class CrossOriginTests {
@Controller
@SuppressWarnings("unused")
private static class MethodLevelControllerWithBogusAllowCredentialsValue {
@CrossOrigin(allowCredentials = "bogus")
@@ -462,7 +474,7 @@ public class CrossOriginTests {
private static class TestRequestMappingInfoHandlerMapping extends RequestMappingHandlerMapping {
public void registerHandler(Object handler) {
void registerHandler(Object handler) {
super.detectHandlerMethods(handler);
}
@@ -475,18 +487,33 @@ public class CrossOriginTests {
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMapping annotation = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
if (annotation != null) {
return new RequestMappingInfo(
new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(), true, true),
new RequestMethodsRequestCondition(annotation.method()),
new ParamsRequestCondition(annotation.params()),
new HeadersRequestCondition(annotation.headers()),
new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
new ProducesRequestCondition(annotation.produces(), annotation.headers()), null);
RequestMappingInfo.BuilderConfiguration options = new RequestMappingInfo.BuilderConfiguration();
if (getPatternParser() != null) {
options.setPatternParser(getPatternParser());
}
return RequestMappingInfo.paths(annotation.value())
.methods(annotation.method())
.params(annotation.params())
.headers(annotation.headers())
.consumes(annotation.consumes())
.produces(annotation.produces())
.options(options)
.build();
}
else {
return null;
}
}
@Override
protected String initLookupPath(HttpServletRequest request) {
// At runtime this is done by the DispatcherServlet
if (getPatternParser() != null) {
RequestPath requestPath = ServletRequestPathUtils.parseAndCache(request);
return requestPath.pathWithinApplication().value();
}
return super.initLookupPath(request);
}
}
}

View File

@@ -26,8 +26,10 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.provider.Arguments;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.MediaType;
@@ -46,9 +48,12 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.method.HandlerTypePredicate;
import org.springframework.web.servlet.handler.PathPatternsParameterizedTest;
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -61,41 +66,49 @@ import static org.mockito.Mockito.mock;
*/
public class RequestMappingHandlerMappingTests {
private final StaticWebApplicationContext wac = new StaticWebApplicationContext();
@SuppressWarnings("unused")
static Stream<Arguments> pathPatternsArguments() {
RequestMappingHandlerMapping mapping1 = new RequestMappingHandlerMapping();
StaticWebApplicationContext wac1 = new StaticWebApplicationContext();
mapping1.setPatternParser(new PathPatternParser());
mapping1.setApplicationContext(wac1);
private final RequestMappingHandlerMapping handlerMapping = new RequestMappingHandlerMapping();
{
this.handlerMapping.setApplicationContext(wac);
RequestMappingHandlerMapping mapping2 = new RequestMappingHandlerMapping();
StaticWebApplicationContext wac2 = new StaticWebApplicationContext();
mapping2.setApplicationContext(wac2);
return Stream.of(Arguments.of(mapping1, wac1), Arguments.of(mapping2, wac2));
}
@Test
public void useRegisteredSuffixPatternMatch() {
assertThat(this.handlerMapping.useSuffixPatternMatch()).isFalse();
assertThat(this.handlerMapping.useRegisteredSuffixPatternMatch()).isFalse();
void useRegisteredSuffixPatternMatch() {
RequestMappingHandlerMapping handlerMapping = new RequestMappingHandlerMapping();
handlerMapping.setApplicationContext(new StaticWebApplicationContext());
Map<String, MediaType> fileExtensions = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(fileExtensions);
ContentNegotiationManager manager = new ContentNegotiationManager(strategy);
this.handlerMapping.setContentNegotiationManager(manager);
this.handlerMapping.setUseRegisteredSuffixPatternMatch(true);
this.handlerMapping.afterPropertiesSet();
handlerMapping.setContentNegotiationManager(manager);
handlerMapping.setUseRegisteredSuffixPatternMatch(true);
handlerMapping.afterPropertiesSet();
assertThat(this.handlerMapping.useSuffixPatternMatch()).isTrue();
assertThat(this.handlerMapping.useRegisteredSuffixPatternMatch()).isTrue();
assertThat(this.handlerMapping.getFileExtensions()).isEqualTo(Collections.singletonList("json"));
assertThat(handlerMapping.useSuffixPatternMatch()).isTrue();
assertThat(handlerMapping.useRegisteredSuffixPatternMatch()).isTrue();
assertThat(handlerMapping.getFileExtensions()).isEqualTo(Collections.singletonList("json"));
}
@Test
public void useRegisteredSuffixPatternMatchInitialization() {
void useRegisteredSuffixPatternMatchInitialization() {
Map<String, MediaType> fileExtensions = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(fileExtensions);
ContentNegotiationManager manager = new ContentNegotiationManager(strategy);
final Set<String> extensions = new HashSet<>();
RequestMappingHandlerMapping hm = new RequestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping() {
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
extensions.addAll(getFileExtensions());
@@ -103,76 +116,99 @@ public class RequestMappingHandlerMappingTests {
}
};
StaticWebApplicationContext wac = new StaticWebApplicationContext();
wac.registerSingleton("testController", ComposedAnnotationController.class);
wac.refresh();
hm.setContentNegotiationManager(manager);
hm.setUseRegisteredSuffixPatternMatch(true);
hm.setApplicationContext(wac);
hm.afterPropertiesSet();
mapping.setContentNegotiationManager(manager);
mapping.setUseRegisteredSuffixPatternMatch(true);
mapping.setApplicationContext(wac);
mapping.afterPropertiesSet();
assertThat(extensions).isEqualTo(Collections.singleton("json"));
}
@Test
public void useSuffixPatternMatch() {
assertThat(this.handlerMapping.useSuffixPatternMatch()).isFalse();
void suffixPatternMatchSettings() {
RequestMappingHandlerMapping handlerMapping = new RequestMappingHandlerMapping();
this.handlerMapping.setUseRegisteredSuffixPatternMatch(false);
assertThat(this.handlerMapping.useSuffixPatternMatch())
.as("'false' registeredSuffixPatternMatch shouldn't impact suffixPatternMatch").isFalse();
assertThat(handlerMapping.useSuffixPatternMatch()).isFalse();
assertThat(handlerMapping.useRegisteredSuffixPatternMatch()).isFalse();
this.handlerMapping.setUseRegisteredSuffixPatternMatch(true);
assertThat(this.handlerMapping.useSuffixPatternMatch())
.as("'true' registeredSuffixPatternMatch should enable suffixPatternMatch").isTrue();
handlerMapping.setUseRegisteredSuffixPatternMatch(false);
assertThat(handlerMapping.useSuffixPatternMatch())
.as("'false' registeredSuffixPatternMatch shouldn't impact suffixPatternMatch")
.isFalse();
handlerMapping.setUseRegisteredSuffixPatternMatch(true);
assertThat(handlerMapping.useSuffixPatternMatch())
.as("'true' registeredSuffixPatternMatch should enable suffixPatternMatch")
.isTrue();
}
@Test
public void resolveEmbeddedValuesInPatterns() {
this.handlerMapping.setEmbeddedValueResolver(
@PathPatternsParameterizedTest
void resolveEmbeddedValuesInPatterns(RequestMappingHandlerMapping mapping) {
mapping.setEmbeddedValueResolver(
value -> "/${pattern}/bar".equals(value) ? "/foo/bar" : value
);
String[] patterns = new String[] { "/foo", "/${pattern}/bar" };
String[] result = this.handlerMapping.resolveEmbeddedValuesInPatterns(patterns);
String[] result = mapping.resolveEmbeddedValuesInPatterns(patterns);
assertThat(result).isEqualTo(new String[] { "/foo", "/foo/bar" });
}
@Test
public void pathPrefix() throws NoSuchMethodException {
this.handlerMapping.setEmbeddedValueResolver(value -> "/${prefix}".equals(value) ? "/api" : value);
this.handlerMapping.setPathPrefixes(Collections.singletonMap(
@PathPatternsParameterizedTest
void pathPrefix(RequestMappingHandlerMapping mapping) throws Exception {
mapping.setEmbeddedValueResolver(value -> "/${prefix}".equals(value) ? "/api" : value);
mapping.setPathPrefixes(Collections.singletonMap(
"/${prefix}", HandlerTypePredicate.forAnnotation(RestController.class)));
mapping.afterPropertiesSet();
Method method = UserController.class.getMethod("getUser");
RequestMappingInfo info = this.handlerMapping.getMappingForMethod(method, UserController.class);
RequestMappingInfo info = mapping.getMappingForMethod(method, UserController.class);
assertThat(info).isNotNull();
assertThat(info.getPatternsCondition().getPatterns()).isEqualTo(Collections.singleton("/api/user/{id}"));
assertThat(info.getPatternValues()).isEqualTo(Collections.singleton("/api/user/{id}"));
}
@Test // gh-23907
public void pathPrefixPreservesPathMatchingSettings() throws NoSuchMethodException {
this.handlerMapping.setUseSuffixPatternMatch(false);
this.handlerMapping.setPathPrefixes(Collections.singletonMap("/api", HandlerTypePredicate.forAnyHandlerType()));
this.handlerMapping.afterPropertiesSet();
@PathPatternsParameterizedTest // gh-23907
void pathPrefixPreservesPathMatchingSettings(RequestMappingHandlerMapping mapping) throws Exception {
mapping.setPathPrefixes(Collections.singletonMap("/api", HandlerTypePredicate.forAnyHandlerType()));
mapping.afterPropertiesSet();
Method method = ComposedAnnotationController.class.getMethod("get");
RequestMappingInfo info = this.handlerMapping.getMappingForMethod(method, ComposedAnnotationController.class);
RequestMappingInfo info = mapping.getMappingForMethod(method, ComposedAnnotationController.class);
assertThat(info).isNotNull();
assertThat(info.getActivePatternsCondition()).isNotNull();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/api/get");
assertThat(info.getPatternsCondition().getMatchingCondition(request)).isNotNull();
initRequestPath(mapping, request);
assertThat(info.getActivePatternsCondition().getMatchingCondition(request)).isNotNull();
request = new MockHttpServletRequest("GET", "/api/get.pdf");
assertThat(info.getPatternsCondition().getMatchingCondition(request)).isNull();
initRequestPath(mapping, request);
assertThat(info.getActivePatternsCondition().getMatchingCondition(request)).isNull();
}
@Test
public void resolveRequestMappingViaComposedAnnotation() throws Exception {
RequestMappingInfo info = assertComposedAnnotationMapping("postJson", "/postJson", RequestMethod.POST);
private void initRequestPath(RequestMappingHandlerMapping mapping, MockHttpServletRequest request) {
PathPatternParser parser = mapping.getPatternParser();
if (parser != null) {
ServletRequestPathUtils.parseAndCache(request);
}
else {
mapping.getUrlPathHelper().resolveAndCacheLookupPath(request);
}
}
@PathPatternsParameterizedTest
void resolveRequestMappingViaComposedAnnotation(RequestMappingHandlerMapping mapping) {
RequestMappingInfo info = assertComposedAnnotationMapping(
mapping, "postJson", "/postJson", RequestMethod.POST);
assertThat(info.getConsumesCondition().getConsumableMediaTypes().iterator().next().toString())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
@@ -181,68 +217,72 @@ public class RequestMappingHandlerMappingTests {
}
@Test // SPR-14988
public void getMappingOverridesConsumesFromTypeLevelAnnotation() throws Exception {
void getMappingOverridesConsumesFromTypeLevelAnnotation() throws Exception {
RequestMappingInfo requestMappingInfo = assertComposedAnnotationMapping(RequestMethod.POST);
ConsumesRequestCondition condition = requestMappingInfo.getConsumesCondition();
assertThat(condition.getConsumableMediaTypes()).isEqualTo(Collections.singleton(MediaType.APPLICATION_XML));
}
@Test // gh-22010
public void consumesWithOptionalRequestBody() {
this.wac.registerSingleton("testController", ComposedAnnotationController.class);
this.wac.refresh();
this.handlerMapping.afterPropertiesSet();
RequestMappingInfo info = this.handlerMapping.getHandlerMethods().keySet().stream()
.filter(i -> i.getPatternsCondition().getPatterns().equals(Collections.singleton("/post")))
@PathPatternsParameterizedTest // gh-22010
void consumesWithOptionalRequestBody(RequestMappingHandlerMapping mapping, StaticWebApplicationContext wac) {
wac.registerSingleton("testController", ComposedAnnotationController.class);
wac.refresh();
mapping.afterPropertiesSet();
RequestMappingInfo result = mapping.getHandlerMethods().keySet().stream()
.filter(info -> info.getPatternValues().equals(Collections.singleton("/post")))
.findFirst()
.orElseThrow(() -> new AssertionError("No /post"));
assertThat(info.getConsumesCondition().isBodyRequired()).isFalse();
assertThat(result.getConsumesCondition().isBodyRequired()).isFalse();
}
@Test
public void getMapping() throws Exception {
void getMapping() throws Exception {
assertComposedAnnotationMapping(RequestMethod.GET);
}
@Test
public void postMapping() throws Exception {
void postMapping() throws Exception {
assertComposedAnnotationMapping(RequestMethod.POST);
}
@Test
public void putMapping() throws Exception {
void putMapping() throws Exception {
assertComposedAnnotationMapping(RequestMethod.PUT);
}
@Test
public void deleteMapping() throws Exception {
void deleteMapping() throws Exception {
assertComposedAnnotationMapping(RequestMethod.DELETE);
}
@Test
public void patchMapping() throws Exception {
void patchMapping() throws Exception {
assertComposedAnnotationMapping(RequestMethod.PATCH);
}
private RequestMappingInfo assertComposedAnnotationMapping(RequestMethod requestMethod) throws Exception {
RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
mapping.setApplicationContext(new StaticWebApplicationContext());
String methodName = requestMethod.name().toLowerCase();
String path = "/" + methodName;
return assertComposedAnnotationMapping(methodName, path, requestMethod);
return assertComposedAnnotationMapping(mapping, methodName, path, requestMethod);
}
private RequestMappingInfo assertComposedAnnotationMapping(String methodName, String path,
RequestMethod requestMethod) throws Exception {
private RequestMappingInfo assertComposedAnnotationMapping(
RequestMappingHandlerMapping mapping, String methodName, String path, RequestMethod requestMethod) {
Class<?> clazz = ComposedAnnotationController.class;
Method method = ClassUtils.getMethod(clazz, methodName, (Class<?>[]) null);
RequestMappingInfo info = this.handlerMapping.getMappingForMethod(method, clazz);
RequestMappingInfo info = mapping.getMappingForMethod(method, clazz);
assertThat(info).isNotNull();
Set<String> paths = info.getPatternsCondition().getPatterns();
Set<String> paths = info.getPatternValues();
assertThat(paths.size()).isEqualTo(1);
assertThat(paths.iterator().next()).isEqualTo(path);

View File

@@ -19,19 +19,17 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
@@ -45,6 +43,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.handler.PathPatternsParameterizedTest;
import org.springframework.web.servlet.view.AbstractView;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
@@ -57,9 +56,15 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class UriTemplateServletAnnotationControllerHandlerMethodTests extends AbstractServletHandlerMethodTests {
@Test
public void simple() throws Exception {
initServletWithControllers(SimpleUriTemplateController.class);
@SuppressWarnings("unused")
static Stream<Boolean> pathPatternsArguments() {
return Stream.of(true, false);
}
@PathPatternsParameterizedTest
void simple(boolean usePathPatterns) throws Exception {
initDispatcherServlet(SimpleUriTemplateController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -67,9 +72,9 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("test-42-7");
}
@Test
public void multiple() throws Exception {
initServletWithControllers(MultipleUriTemplateController.class);
@PathPatternsParameterizedTest
void multiple(boolean usePathPatterns) throws Exception {
initDispatcherServlet(MultipleUriTemplateController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42;q=24/bookings/21-other;q=12");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -78,29 +83,29 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("test-42-q24-21-other-q12");
}
@Test
public void pathVarsInModel() throws Exception {
@PathPatternsParameterizedTest
void pathVarsInModel(boolean usePathPatterns) throws Exception {
final Map<String, Object> pathVars = new HashMap<>();
pathVars.put("hotel", "42");
pathVars.put("booking", 21);
pathVars.put("other", "other");
WebApplicationContext wac = initServlet(context -> {
WebApplicationContext wac = initDispatcherServlet(ViewRenderingController.class, usePathPatterns, context -> {
RootBeanDefinition beanDef = new RootBeanDefinition(ModelValidatingViewResolver.class);
beanDef.getConstructorArgumentValues().addGenericArgumentValue(pathVars);
context.registerBeanDefinition("viewResolver", beanDef);
}, ViewRenderingController.class);
});
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42;q=1,2/bookings/21-other;q=3;r=R");
HttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42;q=1,2/bookings/21-other;q=3;r=R");
getServlet().service(request, new MockHttpServletResponse());
ModelValidatingViewResolver resolver = wac.getBean(ModelValidatingViewResolver.class);
assertThat(resolver.validatedAttrCount).isEqualTo(3);
}
@Test
public void binding() throws Exception {
initServletWithControllers(BindingUriTemplateController.class);
@PathPatternsParameterizedTest
void binding(boolean usePathPatterns) throws Exception {
initDispatcherServlet(BindingUriTemplateController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42/dates/2008-11-18");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -112,16 +117,16 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
getServlet().service(request, response);
assertThat(response.getStatus()).isEqualTo(400);
initServletWithControllers(NonBindingUriTemplateController.class);
initDispatcherServlet(NonBindingUriTemplateController.class, usePathPatterns);
request = new MockHttpServletRequest("GET", "/hotels/42/dates/2008-foo-bar");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertThat(response.getStatus()).isEqualTo(500);
}
@Test
public void ambiguous() throws Exception {
initServletWithControllers(AmbiguousUriTemplateController.class);
@PathPatternsParameterizedTest
void ambiguous(boolean usePathPatterns) throws Exception {
initDispatcherServlet(AmbiguousUriTemplateController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/new");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -129,9 +134,9 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("specific");
}
@Test
public void relative() throws Exception {
initServletWithControllers(RelativePathUriTemplateController.class);
@PathPatternsParameterizedTest
void relative(boolean usePathPatterns) throws Exception {
initDispatcherServlet(RelativePathUriTemplateController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42/bookings/21");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -139,24 +144,27 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("test-42-21");
}
@Test
public void extension() throws Exception {
initServlet(wac -> {
RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
mappingDef.getPropertyValues().add("useSuffixPatternMatch", true);
mappingDef.getPropertyValues().add("removeSemicolonContent", "false");
wac.registerBeanDefinition("handlerMapping", mappingDef);
}, SimpleUriTemplateController.class);
@PathPatternsParameterizedTest
void extension(boolean usePathPatterns) throws Exception {
initDispatcherServlet(SimpleUriTemplateController.class, usePathPatterns, wac -> {
if (!usePathPatterns) {
RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
mappingDef.getPropertyValues().add("useSuffixPatternMatch", true);
mappingDef.getPropertyValues().add("removeSemicolonContent", "false");
wac.registerBeanDefinition("handlerMapping", mappingDef);
}
});
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42;jsessionid=c0o7fszeb1;q=24.xml");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertThat(response.getContentAsString()).isEqualTo("test-42-24");
assertThat(response.getContentAsString())
.isEqualTo(!usePathPatterns ? "test-42-24" : "test-42-24.xml");
}
@Test
public void typeConversionError() throws Exception {
initServletWithControllers(SimpleUriTemplateController.class);
@PathPatternsParameterizedTest
void typeConversionError(boolean usePathPatterns) throws Exception {
initDispatcherServlet(SimpleUriTemplateController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.xml");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -164,9 +172,9 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getStatus()).as("Invalid response status code").isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
}
@Test
public void explicitSubPath() throws Exception {
initServletWithControllers(ExplicitSubPathController.class);
@PathPatternsParameterizedTest
void explicitSubPath(boolean usePathPatterns) throws Exception {
initDispatcherServlet(ExplicitSubPathController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -174,9 +182,9 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("test-42");
}
@Test
public void implicitSubPath() throws Exception {
initServletWithControllers(ImplicitSubPathController.class);
@PathPatternsParameterizedTest
void implicitSubPath(boolean usePathPatterns) throws Exception {
initDispatcherServlet(ImplicitSubPathController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -184,9 +192,9 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("test-42");
}
@Test
public void crud() throws Exception {
initServletWithControllers(CrudController.class);
@PathPatternsParameterizedTest
void crud(boolean usePathPatterns) throws Exception {
initDispatcherServlet(CrudController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -224,9 +232,9 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("remove-42");
}
@Test
public void methodNotSupported() throws Exception {
initServletWithControllers(MethodNotAllowedController.class);
@PathPatternsParameterizedTest
void methodNotSupported(boolean usePathPatterns) throws Exception {
initDispatcherServlet(MethodNotAllowedController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/1");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -249,9 +257,9 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getStatus()).isEqualTo(405);
}
@Test
public void multiPaths() throws Exception {
initServletWithControllers(MultiPathController.class);
@PathPatternsParameterizedTest
void multiPaths(boolean usePathPatterns) throws Exception {
initDispatcherServlet(MultiPathController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/category/page/5");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -259,20 +267,21 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("handle4-page-5");
}
@Test
public void customRegex() throws Exception {
initServletWithControllers(CustomRegexController.class);
@PathPatternsParameterizedTest
void customRegex(boolean usePathPatterns) throws Exception {
initDispatcherServlet(CustomRegexController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42;q=1;q=2");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getContentAsString()).isEqualTo("test-42-;q=1;q=2-[1, 2]");
assertThat(response.getContentAsString())
.isEqualTo(!usePathPatterns ? "test-42-;q=1;q=2-[1, 2]" : "test-42--[1, 2]");
}
@Test // gh-11306
public void menuTree() throws Exception {
initServletWithControllers(MenuTreeController.class);
@PathPatternsParameterizedTest // gh-11306
void menuTree(boolean usePathPatterns) throws Exception {
initDispatcherServlet(MenuTreeController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/book/menu/type/M5");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -280,9 +289,9 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("M5");
}
@Test // gh-11542
public void variableNames() throws Exception {
initServletWithControllers(VariableNamesController.class);
@PathPatternsParameterizedTest // gh-11542
void variableNames(boolean usePathPatterns) throws Exception {
initDispatcherServlet(VariableNamesController.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test/foo");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -295,23 +304,25 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("bar-bar");
}
@Test // gh-13187
public void variableNamesWithUrlExtension() throws Exception {
initServlet(wac -> {
RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
mappingDef.getPropertyValues().add("useSuffixPatternMatch", true);
wac.registerBeanDefinition("handlerMapping", mappingDef);
}, VariableNamesController.class);
@PathPatternsParameterizedTest // gh-13187
void variableNamesWithUrlExtension(boolean usePathPatterns) throws Exception {
initDispatcherServlet(VariableNamesController.class, usePathPatterns, wac -> {
if (!usePathPatterns) {
RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
mappingDef.getPropertyValues().add("useSuffixPatternMatch", true);
wac.registerBeanDefinition("handlerMapping", mappingDef);
}
});
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test/foo.json");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertThat(response.getContentAsString()).isEqualTo("foo-foo");
assertThat(response.getContentAsString()).isEqualTo(!usePathPatterns ? "foo-foo" : "foo-foo.json");
}
@Test // gh-11643
public void doIt() throws Exception {
initServletWithControllers(Spr6978Controller.class);
@PathPatternsParameterizedTest // gh-11643
void doIt(boolean usePathPatterns) throws Exception {
initDispatcherServlet(Spr6978Controller.class, usePathPatterns);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/100");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -344,7 +355,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class SimpleUriTemplateController {
@RequestMapping("/{root}")
public void handle(@PathVariable("root") int root, @MatrixVariable(required=false, defaultValue="7") int q,
void handle(@PathVariable("root") int root, @MatrixVariable(required=false, defaultValue="7") String q,
Writer writer) throws IOException {
assertThat(root).as("Invalid path variable value").isEqualTo(42);
@@ -357,7 +368,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class MultipleUriTemplateController {
@RequestMapping("/hotels/{hotel}/bookings/{booking}-{other}")
public void handle(@PathVariable("hotel") String hotel,
void handle(@PathVariable("hotel") String hotel,
@PathVariable int booking,
@PathVariable String other,
@MatrixVariable(name = "q", pathVar = "hotel") int qHotel,
@@ -374,12 +385,12 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class ViewRenderingController {
@RequestMapping("/hotels/{hotel}/bookings/{booking}-{other}")
public void handle(@PathVariable("hotel") String hotel, @PathVariable int booking,
void handle(@PathVariable("hotel") String hotel, @PathVariable int booking,
@PathVariable String other, @MatrixVariable MultiValueMap<String, String> params) {
assertThat(hotel).as("Invalid path variable value").isEqualTo("42");
assertThat(booking).as("Invalid path variable value").isEqualTo(21);
assertThat(params.get("q")).isEqualTo(Arrays.asList("1", "2", "3"));
assertThat(params.get("q")).containsExactlyInAnyOrder("1", "2", "3");
assertThat(params.getFirst("r")).isEqualTo("R");
}
@@ -389,7 +400,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class BindingUriTemplateController {
@InitBinder
public void initBinder(WebDataBinder binder, @PathVariable("hotel") String hotel) {
void initBinder(WebDataBinder binder, @PathVariable("hotel") String hotel) {
assertThat(hotel).as("Invalid path variable value").isEqualTo("42");
binder.initBeanPropertyAccess();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
@@ -398,7 +409,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
}
@RequestMapping("/hotels/{hotel}/dates/{date}")
public void handle(@PathVariable("hotel") String hotel, @PathVariable Date date, Writer writer)
void handle(@PathVariable("hotel") String hotel, @PathVariable Date date, Writer writer)
throws IOException {
assertThat(hotel).as("Invalid path variable value").isEqualTo("42");
assertThat(date).as("Invalid path variable value").isEqualTo(new GregorianCalendar(2008, 10, 18).getTime());
@@ -410,7 +421,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class NonBindingUriTemplateController {
@RequestMapping("/hotels/{hotel}/dates/{date}")
public void handle(@PathVariable("hotel") String hotel, @PathVariable Date date, Writer writer)
void handle(@PathVariable("hotel") String hotel, @PathVariable Date date, Writer writer)
throws IOException {
}
@@ -421,7 +432,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class RelativePathUriTemplateController {
@RequestMapping("bookings/{booking}")
public void handle(@PathVariable("hotel") String hotel, @PathVariable int booking, Writer writer)
void handle(@PathVariable("hotel") String hotel, @PathVariable int booking, Writer writer)
throws IOException {
assertThat(hotel).as("Invalid path variable value").isEqualTo("42");
assertThat(booking).as("Invalid path variable value").isEqualTo(21);
@@ -435,18 +446,18 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class AmbiguousUriTemplateController {
@RequestMapping("/{hotel}")
public void handleVars(@PathVariable("hotel") String hotel, Writer writer) throws IOException {
void handleVars(@PathVariable("hotel") String hotel, Writer writer) throws IOException {
assertThat(hotel).as("Invalid path variable value").isEqualTo("42");
writer.write("variables");
}
@RequestMapping("/new")
public void handleSpecific(Writer writer) throws IOException {
void handleSpecific(Writer writer) throws IOException {
writer.write("specific");
}
@RequestMapping("/*")
public void handleWildCard(Writer writer) throws IOException {
void handleWildCard(Writer writer) throws IOException {
writer.write("wildcard");
}
@@ -457,7 +468,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class ExplicitSubPathController {
@RequestMapping("{hotel}")
public void handleHotel(@PathVariable String hotel, Writer writer) throws IOException {
void handleHotel(@PathVariable String hotel, Writer writer) throws IOException {
writer.write("test-" + hotel);
}
@@ -468,7 +479,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class ImplicitSubPathController {
@RequestMapping("{hotel}")
public void handleHotel(@PathVariable String hotel, Writer writer) throws IOException {
void handleHotel(@PathVariable String hotel, Writer writer) throws IOException {
writer.write("test-" + hotel);
}
}
@@ -477,7 +488,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class CustomRegexController {
@RequestMapping("/{root:\\d+}{params}")
public void handle(@PathVariable("root") int root, @PathVariable("params") String paramString,
void handle(@PathVariable("root") int root, @PathVariable("params") String paramString,
@MatrixVariable List<Integer> q, Writer writer) throws IOException {
assertThat(root).as("Invalid path variable value").isEqualTo(42);
@@ -489,7 +500,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class DoubleController {
@RequestMapping("/lat/{latitude}/long/{longitude}")
public void testLatLong(@PathVariable Double latitude, @PathVariable Double longitude, Writer writer)
void testLatLong(@PathVariable Double latitude, @PathVariable Double longitude, Writer writer)
throws IOException {
writer.write("latitude-" + latitude + "-longitude-" + longitude);
}
@@ -501,27 +512,27 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class CrudController {
@RequestMapping(method = RequestMethod.GET)
public void list(Writer writer) throws IOException {
void list(Writer writer) throws IOException {
writer.write("list");
}
@RequestMapping(method = RequestMethod.POST)
public void create(Writer writer) throws IOException {
void create(Writer writer) throws IOException {
writer.write("create");
}
@RequestMapping(value = "/{hotel}", method = RequestMethod.GET)
public void show(@PathVariable String hotel, Writer writer) throws IOException {
void show(@PathVariable String hotel, Writer writer) throws IOException {
writer.write("show-" + hotel);
}
@RequestMapping(value = "{hotel}", method = RequestMethod.PUT)
public void createOrUpdate(@PathVariable String hotel, Writer writer) throws IOException {
void createOrUpdate(@PathVariable String hotel, Writer writer) throws IOException {
writer.write("createOrUpdate-" + hotel);
}
@RequestMapping(value = "{hotel}", method = RequestMethod.DELETE)
public void remove(@PathVariable String hotel, Writer writer) throws IOException {
void remove(@PathVariable String hotel, Writer writer) throws IOException {
writer.write("remove-" + hotel);
}
@@ -532,19 +543,19 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class MethodNotAllowedController {
@RequestMapping(method = RequestMethod.GET)
public void list(Writer writer) {
void list(Writer writer) {
}
@RequestMapping(method = RequestMethod.GET, value = "{hotelId}")
public void show(@PathVariable long hotelId, Writer writer) {
void show(@PathVariable long hotelId, Writer writer) {
}
@RequestMapping(method = RequestMethod.PUT, value = "{hotelId}")
public void createOrUpdate(@PathVariable long hotelId, Writer writer) {
void createOrUpdate(@PathVariable long hotelId, Writer writer) {
}
@RequestMapping(method = RequestMethod.DELETE, value = "/{hotelId}")
public void remove(@PathVariable long hotelId, Writer writer) {
void remove(@PathVariable long hotelId, Writer writer) {
}
}
@@ -553,25 +564,25 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class MultiPathController {
@RequestMapping(value = {"/{category}/page/{page}", "/*/{category}/page/{page}"})
public void category(@PathVariable String category, @PathVariable int page, Writer writer) throws IOException {
void category(@PathVariable String category, @PathVariable int page, Writer writer) throws IOException {
writer.write("handle1-");
writer.write("category-" + category);
writer.write("page-" + page);
}
@RequestMapping(value = {"/{category}", "/*/{category}"})
public void category(@PathVariable String category, Writer writer) throws IOException {
void category(@PathVariable String category, Writer writer) throws IOException {
writer.write("handle2-");
writer.write("category-" + category);
}
@RequestMapping(value = {""})
public void category(Writer writer) throws IOException {
void category(Writer writer) throws IOException {
writer.write("handle3");
}
@RequestMapping(value = {"/page/{page}"})
public void category(@PathVariable int page, Writer writer) throws IOException {
void category(@PathVariable int page, Writer writer) throws IOException {
writer.write("handle4-");
writer.write("page-" + page);
}
@@ -583,7 +594,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class MenuTreeController {
@RequestMapping("type/{var}")
public void getFirstLevelFunctionNodes(@PathVariable("var") String var, Writer writer) throws IOException {
void getFirstLevelFunctionNodes(@PathVariable("var") String var, Writer writer) throws IOException {
writer.write(var);
}
}
@@ -593,12 +604,12 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class VariableNamesController {
@RequestMapping(value = "/{foo}", method=RequestMethod.GET)
public void foo(@PathVariable String foo, Writer writer) throws IOException {
void foo(@PathVariable String foo, Writer writer) throws IOException {
writer.write("foo-" + foo);
}
@RequestMapping(value = "/{bar}", method=RequestMethod.DELETE)
public void bar(@PathVariable String bar, Writer writer) throws IOException {
void bar(@PathVariable String bar, Writer writer) throws IOException {
writer.write("bar-" + bar);
}
}
@@ -607,18 +618,18 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class Spr6978Controller {
@RequestMapping(value = "/{type}/{id}", method = RequestMethod.GET)
public void loadEntity(@PathVariable final String type, @PathVariable final long id, Writer writer)
void loadEntity(@PathVariable final String type, @PathVariable final long id, Writer writer)
throws IOException {
writer.write("loadEntity:" + type + ":" + id);
}
@RequestMapping(value = "/module/{id}", method = RequestMethod.GET)
public void loadModule(@PathVariable final long id, Writer writer) throws IOException {
void loadModule(@PathVariable final long id, Writer writer) throws IOException {
writer.write("loadModule:" + id);
}
@RequestMapping(value = "/{type}/{id}", method = RequestMethod.POST)
public void publish(@PathVariable final String type, @PathVariable final long id, Writer writer)
void publish(@PathVariable final String type, @PathVariable final long id, Writer writer)
throws IOException {
writer.write("publish:" + type + ":" + id);
}
@@ -655,12 +666,12 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
}
// @Disabled("ControllerClassNameHandlerMapping")
// public void controllerClassName() throws Exception {
// void controllerClassName() throws Exception {
// @Disabled("useDefaultSuffixPattern property not supported")
// public void doubles() throws Exception {
// void doubles() throws Exception {
// @Disabled("useDefaultSuffixPattern property not supported")
// public void noDefaultSuffixPattern() throws Exception {
// void noDefaultSuffixPattern() throws Exception {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -16,9 +16,11 @@
package org.springframework.web.servlet.view;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.function.Function;
import java.util.stream.Stream;
import org.springframework.web.servlet.handler.PathPatternsParameterizedTest;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat;
@@ -31,110 +33,99 @@ public class DefaultRequestToViewNameTranslatorTests {
private static final String VIEW_NAME = "apple";
private static final String EXTENSION = ".html";
private static final String CONTEXT_PATH = "/sundays";
private DefaultRequestToViewNameTranslator translator;
private MockHttpServletRequest request;
private final DefaultRequestToViewNameTranslator translator = new DefaultRequestToViewNameTranslator();
@BeforeEach
public void setUp() {
this.translator = new DefaultRequestToViewNameTranslator();
this.request = new MockHttpServletRequest();
this.request.setContextPath(CONTEXT_PATH);
@SuppressWarnings("unused")
private static Stream<Function<String, MockHttpServletRequest>> pathPatternsArguments() {
return PathPatternsTestUtils.requestArguments("/sundays");
}
@Test
public void testGetViewNameLeavesLeadingSlashIfSoConfigured() {
request.setRequestURI(CONTEXT_PATH + "/" + VIEW_NAME + "/");
@PathPatternsParameterizedTest
void testGetViewNameLeavesLeadingSlashIfSoConfigured(Function<String, MockHttpServletRequest> requestFactory) {
MockHttpServletRequest request = requestFactory.apply(VIEW_NAME + "/");
this.translator.setStripLeadingSlash(false);
assertViewName("/" + VIEW_NAME);
assertViewName(request, "/" + VIEW_NAME);
}
@Test
public void testGetViewNameLeavesTrailingSlashIfSoConfigured() {
request.setRequestURI(CONTEXT_PATH + "/" + VIEW_NAME + "/");
@PathPatternsParameterizedTest
void testGetViewNameLeavesTrailingSlashIfSoConfigured(Function<String, MockHttpServletRequest> requestFactory) {
MockHttpServletRequest request = requestFactory.apply(VIEW_NAME + "/");
this.translator.setStripTrailingSlash(false);
assertViewName(VIEW_NAME + "/");
assertViewName(request, VIEW_NAME + "/");
}
@Test
public void testGetViewNameLeavesExtensionIfSoConfigured() {
request.setRequestURI(CONTEXT_PATH + "/" + VIEW_NAME + EXTENSION);
@PathPatternsParameterizedTest
void testGetViewNameLeavesExtensionIfSoConfigured(Function<String, MockHttpServletRequest> requestFactory) {
MockHttpServletRequest request = requestFactory.apply(VIEW_NAME + EXTENSION);
this.translator.setStripExtension(false);
assertViewName(VIEW_NAME + EXTENSION);
assertViewName(request, VIEW_NAME + EXTENSION);
}
@Test
public void testGetViewNameWithDefaultConfiguration() {
request.setRequestURI(CONTEXT_PATH + VIEW_NAME + EXTENSION);
assertViewName(VIEW_NAME);
@PathPatternsParameterizedTest
void testGetViewNameWithDefaultConfiguration(Function<String, MockHttpServletRequest> requestFactory) {
MockHttpServletRequest request = requestFactory.apply(VIEW_NAME + EXTENSION);
assertViewName(request, VIEW_NAME);
}
@Test
public void testGetViewNameWithCustomSeparator() {
request.setRequestURI(CONTEXT_PATH + VIEW_NAME + "/fiona" + EXTENSION);
@PathPatternsParameterizedTest
void testGetViewNameWithCustomSeparator(Function<String, MockHttpServletRequest> requestFactory) {
MockHttpServletRequest request = requestFactory.apply(VIEW_NAME + "/fiona" + EXTENSION);
this.translator.setSeparator("_");
assertViewName(VIEW_NAME + "_fiona");
assertViewName(request, VIEW_NAME + "_fiona");
}
@Test
public void testGetViewNameWithNoExtension() {
request.setRequestURI(CONTEXT_PATH + VIEW_NAME);
assertViewName(VIEW_NAME);
@PathPatternsParameterizedTest
void testGetViewNameWithNoExtension(Function<String, MockHttpServletRequest> requestFactory) {
MockHttpServletRequest request = requestFactory.apply(VIEW_NAME);
assertViewName(request, VIEW_NAME);
}
@Test
public void testGetViewNameWithSemicolonContent() {
request.setRequestURI(CONTEXT_PATH + VIEW_NAME + ";a=A;b=B");
assertViewName(VIEW_NAME);
@PathPatternsParameterizedTest
void testGetViewNameWithSemicolonContent(Function<String, MockHttpServletRequest> requestFactory) {
MockHttpServletRequest request = requestFactory.apply(VIEW_NAME + ";a=A;b=B");
assertViewName(request, VIEW_NAME);
}
@Test
public void testGetViewNameWithPrefix() {
@PathPatternsParameterizedTest
void testGetViewNameWithPrefix(Function<String, MockHttpServletRequest> requestFactory) {
final String prefix = "fiona_";
request.setRequestURI(CONTEXT_PATH + VIEW_NAME);
MockHttpServletRequest request = requestFactory.apply(VIEW_NAME);
this.translator.setPrefix(prefix);
assertViewName(prefix + VIEW_NAME);
assertViewName(request, prefix + VIEW_NAME);
}
@Test
public void testGetViewNameWithNullPrefix() {
request.setRequestURI(CONTEXT_PATH + VIEW_NAME);
@PathPatternsParameterizedTest
void testGetViewNameWithNullPrefix(Function<String, MockHttpServletRequest> requestFactory) {
MockHttpServletRequest request = requestFactory.apply(VIEW_NAME);
this.translator.setPrefix(null);
assertViewName(VIEW_NAME);
assertViewName(request, VIEW_NAME);
}
@Test
public void testGetViewNameWithSuffix() {
@PathPatternsParameterizedTest
void testGetViewNameWithSuffix(Function<String, MockHttpServletRequest> requestFactory) {
final String suffix = ".fiona";
request.setRequestURI(CONTEXT_PATH + VIEW_NAME);
MockHttpServletRequest request = requestFactory.apply(VIEW_NAME);
this.translator.setSuffix(suffix);
assertViewName(VIEW_NAME + suffix);
assertViewName(request, VIEW_NAME + suffix);
}
@Test
public void testGetViewNameWithNullSuffix() {
request.setRequestURI(CONTEXT_PATH + VIEW_NAME);
@PathPatternsParameterizedTest
void testGetViewNameWithNullSuffix(Function<String, MockHttpServletRequest> requestFactory) {
MockHttpServletRequest request = requestFactory.apply(VIEW_NAME);
this.translator.setSuffix(null);
assertViewName(VIEW_NAME);
}
@Test
public void testTrySetUrlPathHelperToNull() {
try {
this.translator.setUrlPathHelper(null);
}
catch (IllegalArgumentException expected) {
}
assertViewName(request, VIEW_NAME);
}
private void assertViewName(String expectedViewName) {
String actualViewName = this.translator.getViewName(this.request);
private void assertViewName(MockHttpServletRequest request, String expectedViewName) {
String actualViewName = this.translator.getViewName(request);
assertThat(actualViewName).isNotNull();
assertThat(actualViewName).as("Did not get the expected viewName from the DefaultRequestToViewNameTranslator.getViewName(..)").isEqualTo(expectedViewName);
assertThat(actualViewName)
.as("Did not get the expected viewName from the DefaultRequestToViewNameTranslator.getViewName(..)")
.isEqualTo(expectedViewName);
}
}

View File

@@ -24,7 +24,7 @@ import org.springframework.http.HttpHeaders.*
import org.springframework.http.HttpMethod.*
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType.*
import org.springframework.web.testfixture.servlet.MockHttpServletRequest
import org.springframework.web.servlet.handler.PathPatternsTestUtils
/**
* Tests for WebMvc.fn [RouterFunctionDsl].
@@ -35,7 +35,7 @@ class RouterFunctionDslTests {
@Test
fun header() {
val servletRequest = MockHttpServletRequest()
val servletRequest = PathPatternsTestUtils.initRequest("GET", "", true)
servletRequest.addHeader("bar", "bar")
val request = DefaultServerRequest(servletRequest, emptyList())
assertThat(sampleRouter().route(request).isPresent).isTrue()
@@ -43,7 +43,7 @@ class RouterFunctionDslTests {
@Test
fun accept() {
val servletRequest = MockHttpServletRequest("GET", "/content")
val servletRequest = PathPatternsTestUtils.initRequest("GET", "/content", true)
servletRequest.addHeader(ACCEPT, APPLICATION_ATOM_XML_VALUE)
val request = DefaultServerRequest(servletRequest, emptyList())
assertThat(sampleRouter().route(request).isPresent).isTrue()
@@ -51,7 +51,7 @@ class RouterFunctionDslTests {
@Test
fun acceptAndPOST() {
val servletRequest = MockHttpServletRequest("POST", "/api/foo/")
val servletRequest = PathPatternsTestUtils.initRequest("POST", "/api/foo/", true)
servletRequest.addHeader(ACCEPT, APPLICATION_JSON_VALUE)
val request = DefaultServerRequest(servletRequest, emptyList())
assertThat(sampleRouter().route(request).isPresent).isTrue()
@@ -59,7 +59,7 @@ class RouterFunctionDslTests {
@Test
fun acceptAndPOSTWithRequestPredicate() {
val servletRequest = MockHttpServletRequest("POST", "/api/bar/")
val servletRequest = PathPatternsTestUtils.initRequest("POST", "/api/bar/", true)
servletRequest.addHeader(ACCEPT, APPLICATION_JSON_VALUE)
servletRequest.addHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
val request = DefaultServerRequest(servletRequest, emptyList())
@@ -68,7 +68,7 @@ class RouterFunctionDslTests {
@Test
fun contentType() {
val servletRequest = MockHttpServletRequest("GET", "/content")
val servletRequest = PathPatternsTestUtils.initRequest("GET", "/content", true)
servletRequest.addHeader(CONTENT_TYPE, APPLICATION_OCTET_STREAM_VALUE)
val request = DefaultServerRequest(servletRequest, emptyList())
assertThat(sampleRouter().route(request).isPresent).isTrue()
@@ -76,28 +76,29 @@ class RouterFunctionDslTests {
@Test
fun resourceByPath() {
val servletRequest = MockHttpServletRequest("GET", "/org/springframework/web/servlet/function/response.txt")
val servletRequest = PathPatternsTestUtils.initRequest(
"GET", "/org/springframework/web/servlet/function/response.txt", true)
val request = DefaultServerRequest(servletRequest, emptyList())
assertThat(sampleRouter().route(request).isPresent).isTrue()
}
@Test
fun method() {
val servletRequest = MockHttpServletRequest("PATCH", "/")
val servletRequest = PathPatternsTestUtils.initRequest("PATCH", "/", true)
val request = DefaultServerRequest(servletRequest, emptyList())
assertThat(sampleRouter().route(request).isPresent).isTrue()
}
@Test
fun path() {
val servletRequest = MockHttpServletRequest("GET", "/baz")
val servletRequest = PathPatternsTestUtils.initRequest("GET", "/baz", true)
val request = DefaultServerRequest(servletRequest, emptyList())
assertThat(sampleRouter().route(request).isPresent).isTrue()
}
@Test
fun resource() {
val servletRequest = MockHttpServletRequest("GET", "/response.txt")
val servletRequest = PathPatternsTestUtils.initRequest("GET", "/response.txt", true)
val request = DefaultServerRequest(servletRequest, emptyList())
assertThat(sampleRouter().route(request).isPresent).isTrue()
}
@@ -105,7 +106,7 @@ class RouterFunctionDslTests {
@Test
fun noRoute() {
val servletRequest = MockHttpServletRequest("GET", "/bar")
val servletRequest = PathPatternsTestUtils.initRequest("GET", "/bar", true)
servletRequest.addHeader(ACCEPT, APPLICATION_PDF_VALUE)
servletRequest.addHeader(CONTENT_TYPE, APPLICATION_PDF_VALUE)
val request = DefaultServerRequest(servletRequest, emptyList())
@@ -114,7 +115,7 @@ class RouterFunctionDslTests {
@Test
fun rendering() {
val servletRequest = MockHttpServletRequest("GET", "/rendering")
val servletRequest = PathPatternsTestUtils.initRequest("GET", "/rendering", true)
val request = DefaultServerRequest(servletRequest, emptyList())
assertThat(sampleRouter().route(request).get().handle(request) is RenderingResponse).isTrue()
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -17,20 +17,28 @@
package org.springframework.web.servlet.mvc.method.annotation
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.handler.PathPatternsParameterizedTest
import org.springframework.web.testfixture.servlet.MockHttpServletRequest
import org.springframework.web.testfixture.servlet.MockHttpServletResponse
import java.util.stream.Stream
/**
* @author Sebastien Deleuze
*/
class ServletAnnotationControllerHandlerMethodKotlinTests : AbstractServletHandlerMethodTests() {
@Test
fun dataClassBinding() {
initServletWithControllers(DataClassController::class.java)
companion object {
@JvmStatic
fun pathPatternsArguments(): Stream<Boolean> {
return Stream.of(true, false)
}
}
@PathPatternsParameterizedTest
fun dataClassBinding(usePathPatterns: Boolean) {
initDispatcherServlet(DataClassController::class.java, usePathPatterns)
val request = MockHttpServletRequest("GET", "/bind")
request.addParameter("param1", "value1")
@@ -40,9 +48,9 @@ class ServletAnnotationControllerHandlerMethodKotlinTests : AbstractServletHandl
assertThat(response.contentAsString).isEqualTo("value1-2")
}
@Test
fun dataClassBindingWithOptionalParameterAndAllParameters() {
initServletWithControllers(DataClassController::class.java)
@PathPatternsParameterizedTest
fun dataClassBindingWithOptionalParameterAndAllParameters(usePathPatterns: Boolean) {
initDispatcherServlet(DataClassController::class.java, usePathPatterns)
val request = MockHttpServletRequest("GET", "/bind-optional-parameter")
request.addParameter("param1", "value1")
@@ -52,9 +60,9 @@ class ServletAnnotationControllerHandlerMethodKotlinTests : AbstractServletHandl
assertThat(response.contentAsString).isEqualTo("value1-2")
}
@Test
fun dataClassBindingWithOptionalParameterAndOnlyMissingParameters() {
initServletWithControllers(DataClassController::class.java)
@PathPatternsParameterizedTest
fun dataClassBindingWithOptionalParameterAndOnlyMissingParameters(usePathPatterns: Boolean) {
initDispatcherServlet(DataClassController::class.java, usePathPatterns)
val request = MockHttpServletRequest("GET", "/bind-optional-parameter")
request.addParameter("param1", "value1")

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="defaultHandler"><ref bean="starController"/></property>
@@ -24,10 +24,19 @@
<property name="defaultHandler"><ref bean="starController"/></property>
<property name="rootHandler"><ref bean="mainController"/></property>
<property name="useTrailingSlashMatch" value="true"/>
<property name="mappings"><ref bean="propsForUrlMapping2"/></property>
<property name="mappings"><ref bean="mappings"/></property>
</bean>
<bean id="propsForUrlMapping2" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<bean id="urlMappingWithPathPatterns" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="defaultHandler"><ref bean="starController"/></property>
<property name="rootHandler"><ref bean="mainController"/></property>
<property name="patternParser" >
<bean class="org.springframework.web.util.pattern.PathPatternParser"/>
</property>
<property name="mappings"><ref bean="mappings"/></property>
</bean>
<bean id="mappings" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location"><value>/org/springframework/web/servlet/handler/map2.properties</value></property>
</bean>

View File

@@ -0,0 +1,26 @@
welcome.html=mainController
/path??matching.html=mainController
/pathmatchingTest.html=mainController
??path??matching.html=mainController
/administrator/pathmatching.html=mainController
/administrator/testlast*=mainController
/administrator/testing/longer/{*foobar}=mainController
/administrator/*/testlast*=mainController
/administrator/*/pathmatching.html=mainController
/pathmatching??.html=mainController
/*.jsp=mainController
/administrator/another/bla.xml=mainController
/*test*.jpeg=mainController
/*/test.jpeg=mainController
/outofpattern*yeah=mainController
/anotherTest*=mainController
/stillAnotherTestYeah=mainController
/shortpattern/testing=mainController
/show123.html=mainController
/sho*=mainController
/bookseats.html=mainController
/reservation.html=mainController
/payment.html=mainController
/confirmation.html=mainController
/test%26t%20est/path%26m%20atching.html=mainController
*=starController

View File

@@ -1,40 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlDecode"><value>true</value></property>
<property name="mappings">
<value>
welcome.html=mainController
/path??matching.html=mainController
/pathmatchingTest.html=mainController
??path??matching.html=mainController
/administrator/pathmatching.html=mainController
/administrator/testlast*=mainController
/administrator/testing/longer/{*foobar}=mainController
/administrator/*/testlast*=mainController
/administrator/*/pathmatching.html=mainController
/pathmatching??.html=mainController
/*.jsp=mainController
/administrator/another/bla.xml=mainController
/*test*.jpeg=mainController
/*/test.jpeg=mainController
/outofpattern*yeah=mainController
/anotherTest*=mainController
/stillAnotherTestYeah=mainController
/shortpattern/testing=mainController
/show123.html=mainController
/sho*=mainController
/bookseats.html=mainController
/reservation.html=mainController
/payment.html=mainController
/confirmation.html=mainController
/test%26t%20est/path%26m%20atching.html=mainController
*=starController
</value>
<bean id="urlMapping1" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="patternParser" >
<bean class="org.springframework.web.util.pattern.PathPatternParser"/>
</property>
<property name="mappings"><ref bean="mappings"/></property>
</bean>
<bean id="urlMapping2" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlDecode"><value>true</value></property>
<property name="mappings"><ref bean="mappings"/></property>
</bean>
<bean id="mappings" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location"><value>/org/springframework/web/servlet/handler/map3.properties</value></property>
</bean>
<bean id="mainController" class="java.lang.Object"/>