Skip to content

Commit af1b3c7

Browse files
committed
Merge branch '6.0.x'
2 parents d1867f1 + 770cbd2 commit af1b3c7

File tree

1 file changed

+56
-38
lines changed

1 file changed

+56
-38
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030

3131
import jakarta.servlet.DispatcherType;
3232
import jakarta.servlet.Filter;
33-
import jakarta.servlet.ServletException;
3433
import jakarta.servlet.ServletRequest;
3534
import jakarta.servlet.http.HttpServletRequest;
3635
import jakarta.servlet.http.HttpServletRequestWrapper;
@@ -75,15 +74,14 @@
7574
* <p>Note that this is primarily an SPI to allow Spring Security
7675
* to align its pattern matching with the same pattern matching that would be
7776
* used in Spring MVC for a given request, in order to avoid security issues.
78-
* Use of this introspector should be avoided for other purposes because it
79-
* incurs the overhead of resolving the handler for a request.
8077
*
81-
* <p>Alternative security filter solutions that also rely on
82-
* {@link HandlerMappingIntrospector} should consider adding an additional
83-
* {@link jakarta.servlet.Filter} that invokes
84-
* {@link #setCache(HttpServletRequest)} and {@link #resetCache(ServletRequest, CachedResult)}
85-
* before and after delegating to the rest of the chain. Such a Filter should
86-
* process all dispatcher types and should be ordered ahead of security filters.
78+
* <p>Use of this component incurs the performance overhead of mapping the
79+
* request, and should not be repeated multiple times per request.
80+
* {@link #createCacheFilter()} exposes a Filter to cache the results.
81+
* Applications that rely on Spring Security don't need to deploy this Filter
82+
* since Spring Security doe that. However, other custom security layers, used
83+
* in place of Spring Security that use this component should deploy the cache
84+
* Filter with requirements described in the Javadoc for the method.
8785
*
8886
* @author Rossen Stoyanchev
8987
* @since 4.3.1
@@ -192,16 +190,9 @@ public List<HandlerMapping> getHandlerMappings() {
192190
*/
193191
public Filter createCacheFilter() {
194192
return (request, response, chain) -> {
195-
HandlerMappingIntrospector.CachedResult previous = setCache((HttpServletRequest) request);
196-
try {
197-
chain.doFilter(request, response);
198-
}
199-
catch (Exception ex) {
200-
throw new ServletException("HandlerMapping introspection failed", ex);
201-
}
202-
finally {
203-
resetCache(request, previous);
204-
}
193+
CachedResult previous = setCache((HttpServletRequest) request);
194+
chain.doFilter(request, response);
195+
resetCache(request, previous);
205196
};
206197
}
207198

@@ -212,26 +203,39 @@ public Filter createCacheFilter() {
212203
* {@link #getCorsConfiguration(HttpServletRequest)} to avoid repeated lookups.
213204
* @param request the current request
214205
* @return the previous {@link CachedResult}, if there is one from a parent dispatch
215-
* @throws ServletException thrown the lookup fails for any reason
216206
* @since 6.0.14
217207
*/
218208
@Nullable
219-
private CachedResult setCache(HttpServletRequest request) throws ServletException {
209+
private CachedResult setCache(HttpServletRequest request) {
220210
CachedResult previous = (CachedResult) request.getAttribute(CACHED_RESULT_ATTRIBUTE);
221211
if (previous == null || !previous.matches(request)) {
212+
HttpServletRequest wrapped = new AttributesPreservingRequest(request);
213+
CachedResult result;
222214
try {
223-
HttpServletRequest wrapped = new AttributesPreservingRequest(request);
224-
CachedResult result = doWithHandlerMapping(wrapped, false, (mapping, executionChain) -> {
215+
// Try to get both in one lookup (with ignoringException=false)
216+
result = doWithHandlerMapping(wrapped, false, (mapping, executionChain) -> {
225217
MatchableHandlerMapping matchableMapping = createMatchableHandlerMapping(mapping, wrapped);
226-
CorsConfiguration corsConfig = getCorsConfiguration(wrapped, executionChain);
227-
return new CachedResult(request, matchableMapping, corsConfig);
218+
CorsConfiguration corsConfig = getCorsConfiguration(executionChain, wrapped);
219+
return new CachedResult(request, matchableMapping, corsConfig, null, null);
228220
});
229-
request.setAttribute(CACHED_RESULT_ATTRIBUTE,
230-
(result != null ? result : new CachedResult(request, null, null)));
231221
}
232-
catch (Throwable ex) {
233-
throw new ServletException("HandlerMapping introspection failed", ex);
222+
catch (Exception ex) {
223+
try {
224+
// Try CorsConfiguration at least with ignoreException=true
225+
AttributesPreservingRequest requestToUse = new AttributesPreservingRequest(request);
226+
result = doWithHandlerMapping(requestToUse, true, (mapping, executionChain) -> {
227+
CorsConfiguration corsConfig = getCorsConfiguration(executionChain, wrapped);
228+
return new CachedResult(request, null, corsConfig, ex, null);
229+
});
230+
}
231+
catch (Exception ex2) {
232+
result = new CachedResult(request, null, null, ex, new IllegalStateException(ex2));
233+
}
234+
}
235+
if (result == null) {
236+
result = new CachedResult(request, null, null, null, null);
234237
}
238+
request.setAttribute(CACHED_RESULT_ATTRIBUTE, result);
235239
}
236240
return previous;
237241
}
@@ -256,7 +260,7 @@ private void resetCache(ServletRequest request, @Nullable CachedResult cachedRes
256260
*/
257261
@Nullable
258262
public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
259-
CachedResult result = CachedResult.forRequest(request);
263+
CachedResult result = CachedResult.getResultFor(request);
260264
if (result != null) {
261265
return result.getHandlerMapping();
262266
}
@@ -284,7 +288,7 @@ private MatchableHandlerMapping createMatchableHandlerMapping(HandlerMapping map
284288
@Override
285289
@Nullable
286290
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
287-
CachedResult result = CachedResult.forRequest(request);
291+
CachedResult result = CachedResult.getResultFor(request);
288292
if (result != null) {
289293
return result.getCorsConfig();
290294
}
@@ -293,16 +297,16 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
293297
boolean ignoreException = true;
294298
AttributesPreservingRequest requestToUse = new AttributesPreservingRequest(request);
295299
return doWithHandlerMapping(requestToUse, ignoreException,
296-
(handlerMapping, executionChain) -> getCorsConfiguration(requestToUse, executionChain));
300+
(handlerMapping, executionChain) -> getCorsConfiguration(executionChain, requestToUse));
297301
}
298302
catch (Exception ex) {
299-
// HandlerMapping exceptions have been ignored. Some more basic error perhaps like request parsing
303+
// HandlerMapping exceptions are ignored. More basic error like parsing the request path.
300304
throw new IllegalStateException(ex);
301305
}
302306
}
303307

304308
@Nullable
305-
private static CorsConfiguration getCorsConfiguration(HttpServletRequest request, HandlerExecutionChain chain) {
309+
private static CorsConfiguration getCorsConfiguration(HandlerExecutionChain chain, HttpServletRequest request) {
306310
for (HandlerInterceptor interceptor : chain.getInterceptorList()) {
307311
if (interceptor instanceof CorsConfigurationSource source) {
308312
return source.getCorsConfiguration(request);
@@ -371,13 +375,22 @@ private static final class CachedResult {
371375
@Nullable
372376
private final CorsConfiguration corsConfig;
373377

378+
@Nullable
379+
private final Exception failure;
380+
381+
@Nullable
382+
private final IllegalStateException corsConfigFailure;
383+
374384
private CachedResult(HttpServletRequest request,
375-
@Nullable MatchableHandlerMapping mapping, @Nullable CorsConfiguration config) {
385+
@Nullable MatchableHandlerMapping mapping, @Nullable CorsConfiguration config,
386+
@Nullable Exception failure, @Nullable IllegalStateException corsConfigFailure) {
376387

377388
this.dispatcherType = request.getDispatcherType();
378389
this.requestURI = request.getRequestURI();
379390
this.handlerMapping = mapping;
380391
this.corsConfig = config;
392+
this.failure = failure;
393+
this.corsConfigFailure = corsConfigFailure;
381394
}
382395

383396
public boolean matches(HttpServletRequest request) {
@@ -386,12 +399,18 @@ public boolean matches(HttpServletRequest request) {
386399
}
387400

388401
@Nullable
389-
public MatchableHandlerMapping getHandlerMapping() {
402+
public MatchableHandlerMapping getHandlerMapping() throws Exception {
403+
if (this.failure != null) {
404+
throw this.failure;
405+
}
390406
return this.handlerMapping;
391407
}
392408

393409
@Nullable
394410
public CorsConfiguration getCorsConfig() {
411+
if (this.corsConfigFailure != null) {
412+
throw this.corsConfigFailure;
413+
}
395414
return this.corsConfig;
396415
}
397416

@@ -405,11 +424,10 @@ public String toString() {
405424
* Return a {@link CachedResult} that matches the given request.
406425
*/
407426
@Nullable
408-
public static CachedResult forRequest(HttpServletRequest request) {
427+
public static CachedResult getResultFor(HttpServletRequest request) {
409428
CachedResult result = (CachedResult) request.getAttribute(CACHED_RESULT_ATTRIBUTE);
410429
return (result != null && result.matches(request) ? result : null);
411430
}
412-
413431
}
414432

415433

0 commit comments

Comments
 (0)