|
|
|
@ -27,6 +27,9 @@ import java.util.Properties;
@@ -27,6 +27,9 @@ import java.util.Properties;
|
|
|
|
|
import java.util.function.BiFunction; |
|
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
|
|
import jakarta.servlet.Filter; |
|
|
|
|
import jakarta.servlet.ServletException; |
|
|
|
|
import jakarta.servlet.ServletRequest; |
|
|
|
|
import jakarta.servlet.http.HttpServletRequest; |
|
|
|
|
import jakarta.servlet.http.HttpServletRequestWrapper; |
|
|
|
|
|
|
|
|
@ -77,6 +80,15 @@ import org.springframework.web.util.pattern.PathPatternParser;
@@ -77,6 +80,15 @@ import org.springframework.web.util.pattern.PathPatternParser;
|
|
|
|
|
public class HandlerMappingIntrospector |
|
|
|
|
implements CorsConfigurationSource, ApplicationContextAware, InitializingBean { |
|
|
|
|
|
|
|
|
|
static final String MAPPING_ATTRIBUTE = |
|
|
|
|
HandlerMappingIntrospector.class.getName() + ".HandlerMapping"; |
|
|
|
|
|
|
|
|
|
static final String CORS_CONFIG_ATTRIBUTE = |
|
|
|
|
HandlerMappingIntrospector.class.getName() + ".CorsConfig"; |
|
|
|
|
|
|
|
|
|
private static final CorsConfiguration NO_CORS_CONFIG = new CorsConfiguration(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Nullable |
|
|
|
|
private ApplicationContext applicationContext; |
|
|
|
|
|
|
|
|
@ -153,6 +165,58 @@ public class HandlerMappingIntrospector
@@ -153,6 +165,58 @@ public class HandlerMappingIntrospector
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Return Filter that performs lookups, caches the results in request attributes, |
|
|
|
|
* and clears the attributes after the filter chain returns. |
|
|
|
|
* @since 6.0.14 |
|
|
|
|
*/ |
|
|
|
|
public Filter createCacheFilter() { |
|
|
|
|
return (request, response, chain) -> { |
|
|
|
|
MatchableHandlerMapping previousMapping = getCachedMapping(request); |
|
|
|
|
CorsConfiguration previousCorsConfig = getCachedCorsConfiguration(request); |
|
|
|
|
try { |
|
|
|
|
HttpServletRequest wrappedRequest = new AttributesPreservingRequest((HttpServletRequest) request); |
|
|
|
|
doWithHandlerMapping(wrappedRequest, false, (mapping, executionChain) -> { |
|
|
|
|
MatchableHandlerMapping matchableMapping = createMatchableHandlerMapping(mapping, wrappedRequest); |
|
|
|
|
CorsConfiguration corsConfig = getCorsConfiguration(wrappedRequest, executionChain); |
|
|
|
|
setCache(request, matchableMapping, corsConfig); |
|
|
|
|
return null; |
|
|
|
|
}); |
|
|
|
|
chain.doFilter(request, response); |
|
|
|
|
} |
|
|
|
|
catch (Exception ex) { |
|
|
|
|
throw new ServletException("HandlerMapping introspection failed", ex); |
|
|
|
|
} |
|
|
|
|
finally { |
|
|
|
|
setCache(request, previousMapping, previousCorsConfig); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Nullable |
|
|
|
|
private static MatchableHandlerMapping getCachedMapping(ServletRequest request) { |
|
|
|
|
return (MatchableHandlerMapping) request.getAttribute(MAPPING_ATTRIBUTE); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Nullable |
|
|
|
|
private static CorsConfiguration getCachedCorsConfiguration(ServletRequest request) { |
|
|
|
|
return (CorsConfiguration) request.getAttribute(CORS_CONFIG_ATTRIBUTE); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static void setCache( |
|
|
|
|
ServletRequest request, @Nullable MatchableHandlerMapping mapping, |
|
|
|
|
@Nullable CorsConfiguration corsConfig) { |
|
|
|
|
|
|
|
|
|
if (mapping != null) { |
|
|
|
|
request.setAttribute(MAPPING_ATTRIBUTE, mapping); |
|
|
|
|
request.setAttribute(CORS_CONFIG_ATTRIBUTE, (corsConfig != null ? corsConfig : NO_CORS_CONFIG)); |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
request.removeAttribute(MAPPING_ATTRIBUTE); |
|
|
|
|
request.removeAttribute(CORS_CONFIG_ATTRIBUTE); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Find the {@link HandlerMapping} that would handle the given request and |
|
|
|
|
* return a {@link MatchableHandlerMapping} to use for path matching. |
|
|
|
@ -164,39 +228,60 @@ public class HandlerMappingIntrospector
@@ -164,39 +228,60 @@ public class HandlerMappingIntrospector
|
|
|
|
|
*/ |
|
|
|
|
@Nullable |
|
|
|
|
public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception { |
|
|
|
|
HttpServletRequest wrappedRequest = new AttributesPreservingRequest(request); |
|
|
|
|
|
|
|
|
|
return doWithHandlerMapping(wrappedRequest, false, (mapping, executionChain) -> { |
|
|
|
|
if (mapping instanceof MatchableHandlerMapping) { |
|
|
|
|
PathPatternMatchableHandlerMapping pathPatternMapping = this.pathPatternMappings.get(mapping); |
|
|
|
|
if (pathPatternMapping != null) { |
|
|
|
|
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(wrappedRequest); |
|
|
|
|
return new LookupPathMatchableHandlerMapping(pathPatternMapping, requestPath); |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
String lookupPath = (String) wrappedRequest.getAttribute(UrlPathHelper.PATH_ATTRIBUTE); |
|
|
|
|
return new LookupPathMatchableHandlerMapping((MatchableHandlerMapping) mapping, lookupPath); |
|
|
|
|
} |
|
|
|
|
MatchableHandlerMapping cachedMapping = getCachedMapping(request); |
|
|
|
|
if (cachedMapping != null) { |
|
|
|
|
return cachedMapping; |
|
|
|
|
} |
|
|
|
|
HttpServletRequest requestToUse = new AttributesPreservingRequest(request); |
|
|
|
|
return doWithHandlerMapping(requestToUse, false, |
|
|
|
|
(mapping, executionChain) -> createMatchableHandlerMapping(mapping, requestToUse)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private MatchableHandlerMapping createMatchableHandlerMapping(HandlerMapping mapping, HttpServletRequest request) { |
|
|
|
|
if (mapping instanceof MatchableHandlerMapping) { |
|
|
|
|
PathPatternMatchableHandlerMapping pathPatternMapping = this.pathPatternMappings.get(mapping); |
|
|
|
|
if (pathPatternMapping != null) { |
|
|
|
|
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request); |
|
|
|
|
return new LookupPathMatchableHandlerMapping(pathPatternMapping, requestPath); |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
String lookupPath = (String) request.getAttribute(UrlPathHelper.PATH_ATTRIBUTE); |
|
|
|
|
return new LookupPathMatchableHandlerMapping((MatchableHandlerMapping) mapping, lookupPath); |
|
|
|
|
} |
|
|
|
|
throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping"); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
@Nullable |
|
|
|
|
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { |
|
|
|
|
AttributesPreservingRequest wrappedRequest = new AttributesPreservingRequest(request); |
|
|
|
|
return doWithHandlerMappingIgnoringException(wrappedRequest, (handlerMapping, executionChain) -> { |
|
|
|
|
for (HandlerInterceptor interceptor : executionChain.getInterceptorList()) { |
|
|
|
|
if (interceptor instanceof CorsConfigurationSource ccs) { |
|
|
|
|
return ccs.getCorsConfiguration(wrappedRequest); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (executionChain.getHandler() instanceof CorsConfigurationSource ccs) { |
|
|
|
|
return ccs.getCorsConfiguration(wrappedRequest); |
|
|
|
|
CorsConfiguration cachedCorsConfiguration = getCachedCorsConfiguration(request); |
|
|
|
|
if (cachedCorsConfiguration != null) { |
|
|
|
|
return (cachedCorsConfiguration != NO_CORS_CONFIG ? cachedCorsConfiguration : null); |
|
|
|
|
} |
|
|
|
|
try { |
|
|
|
|
boolean ignoreException = true; |
|
|
|
|
AttributesPreservingRequest requestToUse = new AttributesPreservingRequest(request); |
|
|
|
|
return doWithHandlerMapping(requestToUse, ignoreException, |
|
|
|
|
(handlerMapping, executionChain) -> getCorsConfiguration(requestToUse, executionChain)); |
|
|
|
|
} |
|
|
|
|
catch (Exception ex) { |
|
|
|
|
// HandlerMapping exceptions have been ignored. Some more basic error perhaps like request parsing
|
|
|
|
|
throw new IllegalStateException(ex); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Nullable |
|
|
|
|
private static CorsConfiguration getCorsConfiguration(HttpServletRequest request, HandlerExecutionChain chain) { |
|
|
|
|
for (HandlerInterceptor interceptor : chain.getInterceptorList()) { |
|
|
|
|
if (interceptor instanceof CorsConfigurationSource source) { |
|
|
|
|
return source.getCorsConfiguration(request); |
|
|
|
|
} |
|
|
|
|
return null; |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
if (chain.getHandler() instanceof CorsConfigurationSource source) { |
|
|
|
|
return source.getCorsConfiguration(request); |
|
|
|
|
} |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Nullable |
|
|
|
@ -237,18 +322,6 @@ public class HandlerMappingIntrospector
@@ -237,18 +322,6 @@ public class HandlerMappingIntrospector
|
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Nullable |
|
|
|
|
private <T> T doWithHandlerMappingIgnoringException( |
|
|
|
|
HttpServletRequest request, BiFunction<HandlerMapping, HandlerExecutionChain, T> matchHandler) { |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
return doWithHandlerMapping(request, true, matchHandler); |
|
|
|
|
} |
|
|
|
|
catch (Exception ex) { |
|
|
|
|
throw new IllegalStateException("HandlerMapping exception not suppressed", ex); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Request wrapper that buffers request attributes in order protect the |
|
|
|
|