27
27
import java .util .function .BiFunction ;
28
28
import java .util .stream .Collectors ;
29
29
30
+ import jakarta .servlet .Filter ;
31
+ import jakarta .servlet .ServletException ;
32
+ import jakarta .servlet .ServletRequest ;
30
33
import jakarta .servlet .http .HttpServletRequest ;
31
34
import jakarta .servlet .http .HttpServletRequestWrapper ;
32
35
77
80
public class HandlerMappingIntrospector
78
81
implements CorsConfigurationSource , ApplicationContextAware , InitializingBean {
79
82
83
+ static final String MAPPING_ATTRIBUTE =
84
+ HandlerMappingIntrospector .class .getName () + ".HandlerMapping" ;
85
+
86
+ static final String CORS_CONFIG_ATTRIBUTE =
87
+ HandlerMappingIntrospector .class .getName () + ".CorsConfig" ;
88
+
89
+ private static final CorsConfiguration NO_CORS_CONFIG = new CorsConfiguration ();
90
+
91
+
80
92
@ Nullable
81
93
private ApplicationContext applicationContext ;
82
94
@@ -153,6 +165,58 @@ public List<HandlerMapping> getHandlerMappings() {
153
165
}
154
166
155
167
168
+ /**
169
+ * Return Filter that performs lookups, caches the results in request attributes,
170
+ * and clears the attributes after the filter chain returns.
171
+ * @since 6.0.14
172
+ */
173
+ public Filter createCacheFilter () {
174
+ return (request , response , chain ) -> {
175
+ MatchableHandlerMapping previousMapping = getCachedMapping (request );
176
+ CorsConfiguration previousCorsConfig = getCachedCorsConfiguration (request );
177
+ try {
178
+ HttpServletRequest wrappedRequest = new AttributesPreservingRequest ((HttpServletRequest ) request );
179
+ doWithHandlerMapping (wrappedRequest , false , (mapping , executionChain ) -> {
180
+ MatchableHandlerMapping matchableMapping = createMatchableHandlerMapping (mapping , wrappedRequest );
181
+ CorsConfiguration corsConfig = getCorsConfiguration (wrappedRequest , executionChain );
182
+ setCache (request , matchableMapping , corsConfig );
183
+ return null ;
184
+ });
185
+ chain .doFilter (request , response );
186
+ }
187
+ catch (Exception ex ) {
188
+ throw new ServletException ("HandlerMapping introspection failed" , ex );
189
+ }
190
+ finally {
191
+ setCache (request , previousMapping , previousCorsConfig );
192
+ }
193
+ };
194
+ }
195
+
196
+ @ Nullable
197
+ private static MatchableHandlerMapping getCachedMapping (ServletRequest request ) {
198
+ return (MatchableHandlerMapping ) request .getAttribute (MAPPING_ATTRIBUTE );
199
+ }
200
+
201
+ @ Nullable
202
+ private static CorsConfiguration getCachedCorsConfiguration (ServletRequest request ) {
203
+ return (CorsConfiguration ) request .getAttribute (CORS_CONFIG_ATTRIBUTE );
204
+ }
205
+
206
+ private static void setCache (
207
+ ServletRequest request , @ Nullable MatchableHandlerMapping mapping ,
208
+ @ Nullable CorsConfiguration corsConfig ) {
209
+
210
+ if (mapping != null ) {
211
+ request .setAttribute (MAPPING_ATTRIBUTE , mapping );
212
+ request .setAttribute (CORS_CONFIG_ATTRIBUTE , (corsConfig != null ? corsConfig : NO_CORS_CONFIG ));
213
+ }
214
+ else {
215
+ request .removeAttribute (MAPPING_ATTRIBUTE );
216
+ request .removeAttribute (CORS_CONFIG_ATTRIBUTE );
217
+ }
218
+ }
219
+
156
220
/**
157
221
* Find the {@link HandlerMapping} that would handle the given request and
158
222
* return a {@link MatchableHandlerMapping} to use for path matching.
@@ -164,39 +228,60 @@ public List<HandlerMapping> getHandlerMappings() {
164
228
*/
165
229
@ Nullable
166
230
public MatchableHandlerMapping getMatchableHandlerMapping (HttpServletRequest request ) throws Exception {
167
- HttpServletRequest wrappedRequest = new AttributesPreservingRequest (request );
168
-
169
- return doWithHandlerMapping (wrappedRequest , false , (mapping , executionChain ) -> {
170
- if (mapping instanceof MatchableHandlerMapping ) {
171
- PathPatternMatchableHandlerMapping pathPatternMapping = this .pathPatternMappings .get (mapping );
172
- if (pathPatternMapping != null ) {
173
- RequestPath requestPath = ServletRequestPathUtils .getParsedRequestPath (wrappedRequest );
174
- return new LookupPathMatchableHandlerMapping (pathPatternMapping , requestPath );
175
- }
176
- else {
177
- String lookupPath = (String ) wrappedRequest .getAttribute (UrlPathHelper .PATH_ATTRIBUTE );
178
- return new LookupPathMatchableHandlerMapping ((MatchableHandlerMapping ) mapping , lookupPath );
179
- }
231
+ MatchableHandlerMapping cachedMapping = getCachedMapping (request );
232
+ if (cachedMapping != null ) {
233
+ return cachedMapping ;
234
+ }
235
+ HttpServletRequest requestToUse = new AttributesPreservingRequest (request );
236
+ return doWithHandlerMapping (requestToUse , false ,
237
+ (mapping , executionChain ) -> createMatchableHandlerMapping (mapping , requestToUse ));
238
+ }
239
+
240
+ private MatchableHandlerMapping createMatchableHandlerMapping (HandlerMapping mapping , HttpServletRequest request ) {
241
+ if (mapping instanceof MatchableHandlerMapping ) {
242
+ PathPatternMatchableHandlerMapping pathPatternMapping = this .pathPatternMappings .get (mapping );
243
+ if (pathPatternMapping != null ) {
244
+ RequestPath requestPath = ServletRequestPathUtils .getParsedRequestPath (request );
245
+ return new LookupPathMatchableHandlerMapping (pathPatternMapping , requestPath );
246
+ }
247
+ else {
248
+ String lookupPath = (String ) request .getAttribute (UrlPathHelper .PATH_ATTRIBUTE );
249
+ return new LookupPathMatchableHandlerMapping ((MatchableHandlerMapping ) mapping , lookupPath );
180
250
}
181
- throw new IllegalStateException ( "HandlerMapping is not a MatchableHandlerMapping" );
182
- } );
251
+ }
252
+ throw new IllegalStateException ( "HandlerMapping is not a MatchableHandlerMapping" );
183
253
}
184
254
185
255
@ Override
186
256
@ Nullable
187
257
public CorsConfiguration getCorsConfiguration (HttpServletRequest request ) {
188
- AttributesPreservingRequest wrappedRequest = new AttributesPreservingRequest (request );
189
- return doWithHandlerMappingIgnoringException (wrappedRequest , (handlerMapping , executionChain ) -> {
190
- for (HandlerInterceptor interceptor : executionChain .getInterceptorList ()) {
191
- if (interceptor instanceof CorsConfigurationSource ccs ) {
192
- return ccs .getCorsConfiguration (wrappedRequest );
193
- }
194
- }
195
- if (executionChain .getHandler () instanceof CorsConfigurationSource ccs ) {
196
- return ccs .getCorsConfiguration (wrappedRequest );
258
+ CorsConfiguration cachedCorsConfiguration = getCachedCorsConfiguration (request );
259
+ if (cachedCorsConfiguration != null ) {
260
+ return (cachedCorsConfiguration != NO_CORS_CONFIG ? cachedCorsConfiguration : null );
261
+ }
262
+ try {
263
+ boolean ignoreException = true ;
264
+ AttributesPreservingRequest requestToUse = new AttributesPreservingRequest (request );
265
+ return doWithHandlerMapping (requestToUse , ignoreException ,
266
+ (handlerMapping , executionChain ) -> getCorsConfiguration (requestToUse , executionChain ));
267
+ }
268
+ catch (Exception ex ) {
269
+ // HandlerMapping exceptions have been ignored. Some more basic error perhaps like request parsing
270
+ throw new IllegalStateException (ex );
271
+ }
272
+ }
273
+
274
+ @ Nullable
275
+ private static CorsConfiguration getCorsConfiguration (HttpServletRequest request , HandlerExecutionChain chain ) {
276
+ for (HandlerInterceptor interceptor : chain .getInterceptorList ()) {
277
+ if (interceptor instanceof CorsConfigurationSource source ) {
278
+ return source .getCorsConfiguration (request );
197
279
}
198
- return null ;
199
- });
280
+ }
281
+ if (chain .getHandler () instanceof CorsConfigurationSource source ) {
282
+ return source .getCorsConfiguration (request );
283
+ }
284
+ return null ;
200
285
}
201
286
202
287
@ Nullable
@@ -237,18 +322,6 @@ private <T> T doWithHandlerMapping(
237
322
return null ;
238
323
}
239
324
240
- @ Nullable
241
- private <T > T doWithHandlerMappingIgnoringException (
242
- HttpServletRequest request , BiFunction <HandlerMapping , HandlerExecutionChain , T > matchHandler ) {
243
-
244
- try {
245
- return doWithHandlerMapping (request , true , matchHandler );
246
- }
247
- catch (Exception ex ) {
248
- throw new IllegalStateException ("HandlerMapping exception not suppressed" , ex );
249
- }
250
- }
251
-
252
325
253
326
/**
254
327
* Request wrapper that buffers request attributes in order protect the
0 commit comments