Merge changes for 22644

This commit is contained in:
Rossen Stoyanchev 2019-04-03 14:56:39 -04:00
commit 1c5aa6a8d3
25 changed files with 267 additions and 105 deletions

View File

@ -161,7 +161,7 @@ public abstract class MimeTypeUtils {
private static final ConcurrentLruCache<String, MimeType> cachedMimeTypes =
new ConcurrentLruCache<>(32, MimeTypeUtils::parseMimeTypeInternal);
new ConcurrentLruCache<>(64, MimeTypeUtils::parseMimeTypeInternal);
@Nullable
private static volatile Random random;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -91,6 +91,7 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont
if (request == null) {
return null;
}
// Ignore LOOKUP_PATH attribute, use our own "fixed" UrlPathHelper with decoding off
String path = this.urlPathHelper.getLookupPathForRequest(request);
String extension = UriUtils.extractFileExtension(path);
return (StringUtils.hasText(extension) ? extension.toLowerCase(Locale.ENGLISH) : null);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2019 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.
@ -45,6 +45,9 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
private UrlPathHelper urlPathHelper = new UrlPathHelper();
@Nullable
private String lookupPathAttributeName;
/**
* Set the PathMatcher implementation to use for matching URL paths
@ -72,6 +75,17 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
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)
@ -117,7 +131,7 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
@Override
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
String lookupPath = this.urlPathHelper.getLookupPathForRequest(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)) {
return entry.getValue();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -175,6 +175,26 @@ public class UrlPathHelper {
}
}
/**
* 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
* @return the lookup path
* @since 5.2
* @see org.springframework.web.servlet.HandlerMapping#LOOKUP_PATH
*/
public String getLookupPathForRequest(HttpServletRequest request, @Nullable String lookupPathAttributeName) {
if (lookupPathAttributeName != null) {
String result = (String) request.getAttribute(lookupPathAttributeName);
if (result != null) {
return result;
}
}
return getLookupPathForRequest(request);
}
/**
* Return the path within the servlet mapping for the given request,
* i.e. the part of the request's URL beyond the part that called the servlet,

View File

@ -25,6 +25,8 @@ import java.util.Set;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
@ -68,15 +70,16 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
* @param headers as described in {@link RequestMapping#headers()}
*/
public ConsumesRequestCondition(String[] consumes, String[] headers) {
this(parseExpressions(consumes, headers));
this.expressions = new ArrayList<>(parseExpressions(consumes, headers));
Collections.sort(this.expressions);
}
/**
* Private constructor accepting parsed media type expressions.
* Private constructor for internal when creating matching conditions.
* Note the expressions List is neither sorted nor deep copied.
*/
private ConsumesRequestCondition(Collection<ConsumeMediaTypeExpression> expressions) {
this.expressions = new ArrayList<>(expressions);
Collections.sort(this.expressions);
private ConsumesRequestCondition(List<ConsumeMediaTypeExpression> expressions) {
this.expressions = expressions;
}
@ -166,9 +169,20 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
if (isEmpty()) {
return this;
}
Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<>(this.expressions);
result.removeIf(expression -> !expression.match(exchange));
return (!result.isEmpty() ? new ConsumesRequestCondition(result) : null);
List<ConsumeMediaTypeExpression> result = getMatchingExpressions(exchange);
return !CollectionUtils.isEmpty(result) ? new ConsumesRequestCondition(result) : null;
}
@Nullable
private List<ConsumeMediaTypeExpression> getMatchingExpressions(ServerWebExchange exchange) {
List<ConsumeMediaTypeExpression> result = null;
for (ConsumeMediaTypeExpression expression : this.expressions) {
if (expression.match(exchange)) {
result = result != null ? result : new ArrayList<>();
result.add(expression);
}
}
return result;
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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,7 +17,6 @@
package org.springframework.web.reactive.result.condition;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
@ -56,12 +55,12 @@ public final class HeadersRequestCondition extends AbstractRequestCondition<Head
this(parseExpressions(headers));
}
private HeadersRequestCondition(Collection<HeaderExpression> conditions) {
this.expressions = Collections.unmodifiableSet(new LinkedHashSet<>(conditions));
private HeadersRequestCondition(Set<HeaderExpression> conditions) {
this.expressions = conditions;
}
private static Collection<HeaderExpression> parseExpressions(String... headers) {
private static Set<HeaderExpression> parseExpressions(String... headers) {
Set<HeaderExpression> expressions = new LinkedHashSet<>();
if (headers != null) {
for (String header : headers) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.

View File

@ -17,7 +17,6 @@
package org.springframework.web.reactive.result.condition;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
@ -25,11 +24,11 @@ import java.util.Set;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.server.NotAcceptableStatusException;
@ -48,8 +47,13 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException;
*/
public final class ProducesRequestCondition extends AbstractRequestCondition<ProducesRequestCondition> {
private static final RequestedContentTypeResolver DEFAULT_CONTENT_TYPE_RESOLVER =
new RequestedContentTypeResolverBuilder().build();
private static final ProducesRequestCondition EMPTY_CONDITION = new ProducesRequestCondition();
private static final String MEDIA_TYPES_ATTRIBUTE = ProducesRequestCondition.class.getName() + ".MEDIA_TYPES";
private final List<ProduceMediaTypeExpression> mediaTypeAllList =
Collections.singletonList(new ProduceMediaTypeExpression(MediaType.ALL_VALUE));
@ -90,18 +94,16 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
public ProducesRequestCondition(String[] produces, String[] headers, RequestedContentTypeResolver resolver) {
this.expressions = new ArrayList<>(parseExpressions(produces, headers));
Collections.sort(this.expressions);
this.contentTypeResolver = (resolver != null ? resolver : new HeaderContentTypeResolver());
this.contentTypeResolver = resolver != null ? resolver : DEFAULT_CONTENT_TYPE_RESOLVER;
}
/**
* Private constructor with already parsed media type expressions.
* Private constructor for internal use to create matching conditions.
* Note the expressions List is neither sorted, nor deep copied.
*/
private ProducesRequestCondition(Collection<ProduceMediaTypeExpression> expressions,
RequestedContentTypeResolver resolver) {
this.expressions = new ArrayList<>(expressions);
Collections.sort(this.expressions);
this.contentTypeResolver = (resolver != null ? resolver : new RequestedContentTypeResolverBuilder().build());
private ProducesRequestCondition(List<ProduceMediaTypeExpression> expressions, ProducesRequestCondition other) {
this.expressions = expressions;
this.contentTypeResolver = other.contentTypeResolver;
}
@ -189,10 +191,9 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
if (isEmpty() || CorsUtils.isPreFlightRequest(exchange.getRequest())) {
return EMPTY_CONDITION;
}
Set<ProduceMediaTypeExpression> result = new LinkedHashSet<>(this.expressions);
result.removeIf(expression -> !expression.match(exchange));
if (!result.isEmpty()) {
return new ProducesRequestCondition(result, this.contentTypeResolver);
List<ProduceMediaTypeExpression> result = getMatchingExpressions(exchange);
if (!CollectionUtils.isEmpty(result)) {
return new ProducesRequestCondition(result, this);
}
else {
try {
@ -207,6 +208,18 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
return null;
}
@Nullable
private List<ProduceMediaTypeExpression> getMatchingExpressions(ServerWebExchange exchange) {
List<ProduceMediaTypeExpression> result = null;
for (ProduceMediaTypeExpression expression : this.expressions) {
if (expression.match(exchange)) {
result = result != null ? result : new ArrayList<>();
result.add(expression);
}
}
return result;
}
/**
* Compares this and another "produces" condition as follows:
* <ol>
@ -251,7 +264,12 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
}
private List<MediaType> getAcceptedMediaTypes(ServerWebExchange exchange) throws NotAcceptableStatusException {
return this.contentTypeResolver.resolveMediaTypes(exchange);
List<MediaType> result = exchange.getAttribute(MEDIA_TYPES_ATTRIBUTE);
if (result == null) {
result = this.contentTypeResolver.resolveMediaTypes(exchange);
exchange.getAttributes().put(MEDIA_TYPES_ATTRIBUTE, result);
}
return result;
}
private int indexOfEqualMediaType(MediaType mediaType) {

View File

@ -19,8 +19,10 @@ package org.springframework.web.reactive.result.condition;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.http.HttpMethod;
@ -39,8 +41,15 @@ import org.springframework.web.server.ServerWebExchange;
*/
public final class RequestMethodsRequestCondition extends AbstractRequestCondition<RequestMethodsRequestCondition> {
private static final RequestMethodsRequestCondition GET_CONDITION =
new RequestMethodsRequestCondition(RequestMethod.GET);
/** Per HTTP method cache to return ready instances from getMatchingCondition. */
private static final Map<String, RequestMethodsRequestCondition> requestMethodConditionCache;
static {
requestMethodConditionCache = new HashMap<>(RequestMethod.values().length);
for (RequestMethod method : RequestMethod.values()) {
requestMethodConditionCache.put(method.name(), new RequestMethodsRequestCondition(method));
}
}
private final Set<RequestMethod> methods;
@ -110,11 +119,11 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
}
if (getMethods().isEmpty()) {
if (RequestMethod.OPTIONS.name().equals(exchange.getRequest().getMethodValue())) {
return null; // No implicit match for OPTIONS (we handle it)
return null; // We handle OPTIONS transparently, so don't match if no explicit declarations
}
return this;
}
return matchRequestMethod(exchange.getRequest().getMethod());
return matchRequestMethod(exchange.getRequest().getMethodValue());
}
/**
@ -122,24 +131,25 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
* Hence empty conditions is a match, otherwise try to match to the HTTP
* method in the "Access-Control-Request-Method" header.
*/
@Nullable
private RequestMethodsRequestCondition matchPreFlight(ServerHttpRequest request) {
if (getMethods().isEmpty()) {
return this;
}
HttpMethod expectedMethod = request.getHeaders().getAccessControlRequestMethod();
return matchRequestMethod(expectedMethod);
return expectedMethod != null ? matchRequestMethod(expectedMethod.name()) : null;
}
@Nullable
private RequestMethodsRequestCondition matchRequestMethod(@Nullable HttpMethod httpMethod) {
private RequestMethodsRequestCondition matchRequestMethod(@Nullable String httpMethod) {
if (httpMethod != null) {
for (RequestMethod method : getMethods()) {
if (httpMethod.matches(method.name())) {
return new RequestMethodsRequestCondition(method);
return requestMethodConditionCache.get(method.name());
}
}
if (httpMethod == HttpMethod.HEAD && getMethods().contains(RequestMethod.GET)) {
return GET_CONDITION;
if (HttpMethod.HEAD.matches(httpMethod) && getMethods().contains(RequestMethod.GET)) {
return requestMethodConditionCache.get(HttpMethod.GET.name());
}
}
return null;

View File

@ -62,6 +62,15 @@ public interface HandlerMapping {
*/
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
/**
* Name of the {@link HttpServletRequest} attribute that contains the path
* used to look up the matching handler, which depending on the configured
* {@link org.springframework.web.util.UrlPathHelper} could be the full path
* or without the context path, decoded or not, etc.
* @since 5.2
*/
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
/**
* Name of the {@link HttpServletRequest} attribute that contains the path
* within the handler mapping, in case of a pattern match, or the full

View File

@ -212,6 +212,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
source.setCorsConfigurations(corsConfigurations);
source.setPathMatcher(this.pathMatcher);
source.setUrlPathHelper(this.urlPathHelper);
source.setLookupPathAttributeName(LOOKUP_PATH);
this.corsConfigurationSource = source;
}
else {
@ -463,7 +464,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;

View File

@ -355,6 +355,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);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);

View File

@ -120,6 +120,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
@Nullable
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
@ -291,7 +292,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
@Override
@Nullable
public RequestMatchResult match(HttpServletRequest request, String pattern) {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request, LOOKUP_PATH);
if (getPathMatcher().match(pattern, lookupPath)) {
return new RequestMatchResult(pattern, lookupPath, getPathMatcher());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -110,7 +110,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);
urlPath = getUrlPathHelper().getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
}
return urlPath;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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,6 +30,7 @@ import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
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.UrlPathHelper;
@ -169,7 +170,7 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
checkRequest(request);
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
CacheControl cacheControl = lookupCacheControl(lookupPath);
Integer cacheSeconds = lookupCacheSeconds(lookupPath);

View File

@ -80,7 +80,7 @@ abstract class AbstractNameValueExpression<T> implements NameValueExpression<T>
else {
isMatch = matchName(request);
}
return (this.isNegated ? !isMatch : isMatch);
return this.isNegated != isMatch;
}

View File

@ -27,6 +27,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.cors.CorsUtils;
@ -70,15 +71,16 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
* @param headers as described in {@link RequestMapping#headers()}
*/
public ConsumesRequestCondition(String[] consumes, @Nullable String[] headers) {
this(parseExpressions(consumes, headers));
this.expressions = new ArrayList<>(parseExpressions(consumes, headers));
Collections.sort(this.expressions);
}
/**
* Private constructor accepting parsed media type expressions.
* Private constructor for internal when creating matching conditions.
* Note the expressions List is neither sorted nor deep copied.
*/
private ConsumesRequestCondition(Collection<ConsumeMediaTypeExpression> expressions) {
this.expressions = new ArrayList<>(expressions);
Collections.sort(this.expressions);
private ConsumesRequestCondition(List<ConsumeMediaTypeExpression> expressions) {
this.expressions = expressions;
}
@ -169,6 +171,8 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
return this;
}
// Common media types are cached at the level of MimeTypeUtils
MediaType contentType;
try {
contentType = (StringUtils.hasLength(request.getContentType()) ?
@ -179,9 +183,20 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
return null;
}
Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<>(this.expressions);
result.removeIf(expression -> !expression.match(contentType));
return (!result.isEmpty() ? new ConsumesRequestCondition(result) : null);
List<ConsumeMediaTypeExpression> result = getMatchingExpressions(contentType);
return !CollectionUtils.isEmpty(result) ? new ConsumesRequestCondition(result) : null;
}
@Nullable
private List<ConsumeMediaTypeExpression> getMatchingExpressions(MediaType contentType) {
List<ConsumeMediaTypeExpression> result = null;
for (ConsumeMediaTypeExpression expression : this.expressions) {
if (expression.match(contentType)) {
result = result != null ? result : new ArrayList<>();
result.add(expression);
}
}
return result;
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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,7 +17,6 @@
package org.springframework.web.servlet.mvc.condition;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
@ -58,12 +57,12 @@ public final class HeadersRequestCondition extends AbstractRequestCondition<Head
this(parseExpressions(headers));
}
private HeadersRequestCondition(Collection<HeaderExpression> conditions) {
this.expressions = Collections.unmodifiableSet(new LinkedHashSet<>(conditions));
private HeadersRequestCondition(Set<HeaderExpression> conditions) {
this.expressions = conditions;
}
private static Collection<HeaderExpression> parseExpressions(String... headers) {
private static Set<HeaderExpression> parseExpressions(String... headers) {
Set<HeaderExpression> expressions = new LinkedHashSet<>();
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.mvc.condition;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
@ -142,8 +143,15 @@ public final class ParamsRequestCondition extends AbstractRequestCondition<Param
*/
static class ParamExpression extends AbstractNameValueExpression<String> {
private final Set<String> namesToMatch = new HashSet<>(WebUtils.SUBMIT_IMAGE_SUFFIXES.length + 1);
ParamExpression(String expression) {
super(expression);
this.namesToMatch.add(getName());
for (String suffix : WebUtils.SUBMIT_IMAGE_SUFFIXES) {
this.namesToMatch.add(getName() + suffix);
}
}
@Override
@ -158,8 +166,12 @@ public final class ParamsRequestCondition extends AbstractRequestCondition<Param
@Override
protected boolean matchName(HttpServletRequest request) {
return (WebUtils.hasSubmitParameter(request, this.name) ||
request.getParameterMap().containsKey(this.name));
for (String current : this.namesToMatch) {
if (request.getParameterMap().get(current) != null) {
return true;
}
}
return request.getParameterMap().containsKey(this.name);
}
@Override

View File

@ -31,6 +31,7 @@ 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.util.UrlPathHelper;
/**
@ -105,8 +106,8 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
boolean useTrailingSlashMatch, @Nullable List<String> fileExtensions) {
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
this.pathHelper = (urlPathHelper != null ? urlPathHelper : new UrlPathHelper());
this.pathMatcher = (pathMatcher != null ? pathMatcher : new AntPathMatcher());
this.pathHelper = urlPathHelper != null ? urlPathHelper : new UrlPathHelper();
this.pathMatcher = pathMatcher != null ? pathMatcher : new AntPathMatcher();
this.useSuffixPatternMatch = useSuffixPatternMatch;
this.useTrailingSlashMatch = useTrailingSlashMatch;
@ -120,6 +121,18 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
}
}
/**
* Private constructor for use when combining and matching.
*/
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;
this.fileExtensions.addAll(other.fileExtensions);
}
private static Set<String> prependLeadingSlash(Collection<String> patterns) {
Set<String> result = new LinkedHashSet<>(patterns.size());
@ -175,8 +188,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
else {
result.add("");
}
return new PatternsRequestCondition(result, this.pathHelper, this.pathMatcher,
this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions);
return new PatternsRequestCondition(result, this);
}
/**
@ -201,30 +213,31 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
if (this.patterns.isEmpty()) {
return this;
}
String lookupPath = this.pathHelper.getLookupPathForRequest(request);
String lookupPath = this.pathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
List<String> matches = getMatchingPatterns(lookupPath);
return (!matches.isEmpty() ?
new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher,
this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions) : null);
return !matches.isEmpty() ? new PatternsRequestCondition(new LinkedHashSet<>(matches), this) : null;
}
/**
* Find the patterns matching the given lookup path. Invoking this method should
* yield results equivalent to those of calling
* {@link #getMatchingCondition(javax.servlet.http.HttpServletRequest)}.
* yield results equivalent to those of calling {@link #getMatchingCondition}.
* This method is provided as an alternative to be used if no request is available
* (e.g. introspection, tooling, etc).
* @param lookupPath the lookup path to match to existing patterns
* @return a collection of matching patterns sorted with the closest match at the top
*/
public List<String> getMatchingPatterns(String lookupPath) {
List<String> matches = new ArrayList<>();
List<String> matches = null;
for (String pattern : this.patterns) {
String match = getMatchingPattern(pattern, lookupPath);
if (match != null) {
matches = matches != null ? matches : new ArrayList<>();
matches.add(match);
}
}
if (matches == null) {
return Collections.emptyList();
}
if (matches.size() > 1) {
matches.sort(this.pathMatcher.getPatternComparator(lookupPath));
}
@ -275,7 +288,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
*/
@Override
public int compareTo(PatternsRequestCondition other, HttpServletRequest request) {
String lookupPath = this.pathHelper.getLookupPathForRequest(request);
String lookupPath = this.pathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
Comparator<String> patternComparator = this.pathMatcher.getPatternComparator(lookupPath);
Iterator<String> iterator = this.patterns.iterator();
Iterator<String> iteratorOther = other.patterns.iterator();

View File

@ -17,7 +17,6 @@
package org.springframework.web.servlet.mvc.condition;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
@ -26,6 +25,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
@ -48,11 +48,16 @@ import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.Hea
*/
public final class ProducesRequestCondition extends AbstractRequestCondition<ProducesRequestCondition> {
private static final ContentNegotiationManager DEFAULT_CONTENT_NEGOTIATION_MANAGER =
new ContentNegotiationManager();
private static final ProducesRequestCondition EMPTY_CONDITION = new ProducesRequestCondition();
private static final List<ProduceMediaTypeExpression> MEDIA_TYPE_ALL_LIST =
Collections.singletonList(new ProduceMediaTypeExpression(MediaType.ALL_VALUE));
private static final String MEDIA_TYPES_ATTRIBUTE = ProducesRequestCondition.class.getName() + ".MEDIA_TYPES";
private final List<ProduceMediaTypeExpression> expressions;
@ -92,18 +97,16 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
this.expressions = new ArrayList<>(parseExpressions(produces, headers));
Collections.sort(this.expressions);
this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
this.contentNegotiationManager = manager != null ? manager : DEFAULT_CONTENT_NEGOTIATION_MANAGER;
}
/**
* Private constructor with already parsed media type expressions.
* Private constructor for internal use to create matching conditions.
* Note the expressions List is neither sorted nor deep copied.
*/
private ProducesRequestCondition(Collection<ProduceMediaTypeExpression> expressions,
@Nullable ContentNegotiationManager manager) {
this.expressions = new ArrayList<>(expressions);
Collections.sort(this.expressions);
this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
private ProducesRequestCondition(List<ProduceMediaTypeExpression> expressions, ProducesRequestCondition other) {
this.expressions = expressions;
this.contentNegotiationManager = other.contentNegotiationManager;
}
@ -198,10 +201,9 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
return null;
}
Set<ProduceMediaTypeExpression> result = new LinkedHashSet<>(this.expressions);
result.removeIf(expression -> !expression.match(acceptedMediaTypes));
if (!result.isEmpty()) {
return new ProducesRequestCondition(result, this.contentNegotiationManager);
List<ProduceMediaTypeExpression> result = getMatchingExpressions(acceptedMediaTypes);
if (!CollectionUtils.isEmpty(result)) {
return new ProducesRequestCondition(result, this);
}
else if (MediaType.ALL.isPresentIn(acceptedMediaTypes)) {
return EMPTY_CONDITION;
@ -211,6 +213,18 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
}
}
@Nullable
private List<ProduceMediaTypeExpression> getMatchingExpressions(List<MediaType> acceptedMediaTypes) {
List<ProduceMediaTypeExpression> result = null;
for (ProduceMediaTypeExpression expression : this.expressions) {
if (expression.match(acceptedMediaTypes)) {
result = result != null ? result : new ArrayList<>();
result.add(expression);
}
}
return result;
}
/**
* Compares this and another "produces" condition as follows:
* <ol>
@ -254,8 +268,16 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
}
}
private List<MediaType> getAcceptedMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
@SuppressWarnings("unchecked")
private List<MediaType> getAcceptedMediaTypes(HttpServletRequest request)
throws HttpMediaTypeNotAcceptableException {
List<MediaType> result = (List<MediaType>) request.getAttribute(MEDIA_TYPES_ATTRIBUTE);
if (result == null) {
result = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
request.setAttribute(MEDIA_TYPES_ATTRIBUTE, result);
}
return result;
}
private int indexOfEqualMediaType(MediaType mediaType) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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,7 +19,9 @@ package org.springframework.web.servlet.mvc.condition;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletRequest;
@ -40,8 +42,16 @@ import org.springframework.web.cors.CorsUtils;
*/
public final class RequestMethodsRequestCondition extends AbstractRequestCondition<RequestMethodsRequestCondition> {
private static final RequestMethodsRequestCondition GET_CONDITION =
new RequestMethodsRequestCondition(RequestMethod.GET);
/** Per HTTP method cache to return ready instances from getMatchingCondition. */
private static final Map<String, RequestMethodsRequestCondition> requestMethodConditionCache;
static {
requestMethodConditionCache = new HashMap<>(RequestMethod.values().length);
for (RequestMethod method : RequestMethod.values()) {
requestMethodConditionCache.put(method.name(), new RequestMethodsRequestCondition(method));
}
}
private final Set<RequestMethod> methods;
@ -108,7 +118,7 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
if (RequestMethod.OPTIONS.name().equals(request.getMethod()) &&
!DispatcherType.ERROR.equals(request.getDispatcherType())) {
return null; // No implicit match for OPTIONS (we handle it)
return null; // We handle OPTIONS transparently, so don't match if no explicit declarations
}
return this;
}
@ -136,11 +146,11 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
if (httpMethod != null) {
for (RequestMethod method : getMethods()) {
if (httpMethod.matches(method.name())) {
return new RequestMethodsRequestCondition(method);
return requestMethodConditionCache.get(method.name());
}
}
if (httpMethod == HttpMethod.HEAD && getMethods().contains(RequestMethod.GET)) {
return GET_CONDITION;
return requestMethodConditionCache.get(HttpMethod.GET.name());
}
}
return null;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -340,7 +340,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
return null;
}
Set<String> patterns = matchingInfo.getPatternsCondition().getPatterns();
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request, LOOKUP_PATH);
return new RequestMatchResult(patterns.iterator().next(), lookupPath, getPathMatcher());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -34,6 +34,7 @@ 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;
@ -180,7 +181,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
private int getLookupPathIndex(HttpServletRequest request) {
UrlPathHelper pathHelper = getUrlPathHelper();
String requestUri = pathHelper.getRequestUri(request);
String lookupPath = pathHelper.getLookupPathForRequest(request);
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
return requestUri.indexOf(lookupPath);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2019 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 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.UrlPathHelper;
@ -167,7 +168,7 @@ public class DefaultRequestToViewNameTranslator implements RequestToViewNameTran
*/
@Override
public String getViewName(HttpServletRequest request) {
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
return (this.prefix + transformPath(lookupPath) + this.suffix);
}