Support for parsed PathPatterns in Spring MVC
See gh-24945
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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}.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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"/>
|
||||
|
||||
Reference in New Issue
Block a user