001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.handler; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.List; 023import java.util.Map; 024 025import javax.servlet.DispatcherType; 026import javax.servlet.http.HttpServletRequest; 027import javax.servlet.http.HttpServletResponse; 028 029import org.springframework.beans.BeansException; 030import org.springframework.beans.factory.BeanFactoryUtils; 031import org.springframework.beans.factory.BeanNameAware; 032import org.springframework.core.Ordered; 033import org.springframework.lang.Nullable; 034import org.springframework.util.AntPathMatcher; 035import org.springframework.util.Assert; 036import org.springframework.util.PathMatcher; 037import org.springframework.web.HttpRequestHandler; 038import org.springframework.web.context.request.WebRequestInterceptor; 039import org.springframework.web.context.request.async.WebAsyncManager; 040import org.springframework.web.context.request.async.WebAsyncUtils; 041import org.springframework.web.context.support.WebApplicationObjectSupport; 042import org.springframework.web.cors.CorsConfiguration; 043import org.springframework.web.cors.CorsConfigurationSource; 044import org.springframework.web.cors.CorsProcessor; 045import org.springframework.web.cors.CorsUtils; 046import org.springframework.web.cors.DefaultCorsProcessor; 047import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 048import org.springframework.web.servlet.HandlerExecutionChain; 049import org.springframework.web.servlet.HandlerInterceptor; 050import org.springframework.web.servlet.HandlerMapping; 051import org.springframework.web.util.UrlPathHelper; 052 053/** 054 * Abstract base class for {@link org.springframework.web.servlet.HandlerMapping} 055 * implementations. Supports ordering, a default handler, handler interceptors, 056 * including handler interceptors mapped by path patterns. 057 * 058 * <p>Note: This base class does <i>not</i> support exposure of the 059 * {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}. Support for this attribute 060 * is up to concrete subclasses, typically based on request URL mappings. 061 * 062 * @author Juergen Hoeller 063 * @author Rossen Stoyanchev 064 * @since 07.04.2003 065 * @see #getHandlerInternal 066 * @see #setDefaultHandler 067 * @see #setAlwaysUseFullPath 068 * @see #setUrlDecode 069 * @see org.springframework.util.AntPathMatcher 070 * @see #setInterceptors 071 * @see org.springframework.web.servlet.HandlerInterceptor 072 */ 073public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport 074 implements HandlerMapping, Ordered, BeanNameAware { 075 076 @Nullable 077 private Object defaultHandler; 078 079 private UrlPathHelper urlPathHelper = new UrlPathHelper(); 080 081 private PathMatcher pathMatcher = new AntPathMatcher(); 082 083 private final List<Object> interceptors = new ArrayList<>(); 084 085 private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>(); 086 087 @Nullable 088 private CorsConfigurationSource corsConfigurationSource; 089 090 private CorsProcessor corsProcessor = new DefaultCorsProcessor(); 091 092 private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered 093 094 @Nullable 095 private String beanName; 096 097 098 /** 099 * Set the default handler for this handler mapping. 100 * This handler will be returned if no specific mapping was found. 101 * <p>Default is {@code null}, indicating no default handler. 102 */ 103 public void setDefaultHandler(@Nullable Object defaultHandler) { 104 this.defaultHandler = defaultHandler; 105 } 106 107 /** 108 * Return the default handler for this handler mapping, 109 * or {@code null} if none. 110 */ 111 @Nullable 112 public Object getDefaultHandler() { 113 return this.defaultHandler; 114 } 115 116 /** 117 * Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}. 118 * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath(boolean) 119 */ 120 public void setAlwaysUseFullPath(boolean alwaysUseFullPath) { 121 this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath); 122 if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) { 123 ((UrlBasedCorsConfigurationSource) this.corsConfigurationSource).setAlwaysUseFullPath(alwaysUseFullPath); 124 } 125 } 126 127 /** 128 * Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}. 129 * @see org.springframework.web.util.UrlPathHelper#setUrlDecode(boolean) 130 */ 131 public void setUrlDecode(boolean urlDecode) { 132 this.urlPathHelper.setUrlDecode(urlDecode); 133 if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) { 134 ((UrlBasedCorsConfigurationSource) this.corsConfigurationSource).setUrlDecode(urlDecode); 135 } 136 } 137 138 /** 139 * Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}. 140 * @see org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean) 141 */ 142 public void setRemoveSemicolonContent(boolean removeSemicolonContent) { 143 this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent); 144 if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) { 145 ((UrlBasedCorsConfigurationSource) this.corsConfigurationSource).setRemoveSemicolonContent(removeSemicolonContent); 146 } 147 } 148 149 /** 150 * Set the UrlPathHelper to use for resolution of lookup paths. 151 * <p>Use this to override the default UrlPathHelper with a custom subclass, 152 * or to share common UrlPathHelper settings across multiple HandlerMappings 153 * and MethodNameResolvers. 154 */ 155 public void setUrlPathHelper(UrlPathHelper urlPathHelper) { 156 Assert.notNull(urlPathHelper, "UrlPathHelper must not be null"); 157 this.urlPathHelper = urlPathHelper; 158 if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) { 159 ((UrlBasedCorsConfigurationSource) this.corsConfigurationSource).setUrlPathHelper(urlPathHelper); 160 } 161 } 162 163 /** 164 * Return the UrlPathHelper implementation to use for resolution of lookup paths. 165 */ 166 public UrlPathHelper getUrlPathHelper() { 167 return this.urlPathHelper; 168 } 169 170 /** 171 * Set the PathMatcher implementation to use for matching URL paths 172 * against registered URL patterns. Default is AntPathMatcher. 173 * @see org.springframework.util.AntPathMatcher 174 */ 175 public void setPathMatcher(PathMatcher pathMatcher) { 176 Assert.notNull(pathMatcher, "PathMatcher must not be null"); 177 this.pathMatcher = pathMatcher; 178 if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) { 179 ((UrlBasedCorsConfigurationSource) this.corsConfigurationSource).setPathMatcher(pathMatcher); 180 } 181 } 182 183 /** 184 * Return the PathMatcher implementation to use for matching URL paths 185 * against registered URL patterns. 186 */ 187 public PathMatcher getPathMatcher() { 188 return this.pathMatcher; 189 } 190 191 /** 192 * Set the interceptors to apply for all handlers mapped by this handler mapping. 193 * <p>Supported interceptor types are HandlerInterceptor, WebRequestInterceptor, and MappedInterceptor. 194 * Mapped interceptors apply only to request URLs that match its path patterns. 195 * Mapped interceptor beans are also detected by type during initialization. 196 * @param interceptors array of handler interceptors 197 * @see #adaptInterceptor 198 * @see org.springframework.web.servlet.HandlerInterceptor 199 * @see org.springframework.web.context.request.WebRequestInterceptor 200 */ 201 public void setInterceptors(Object... interceptors) { 202 this.interceptors.addAll(Arrays.asList(interceptors)); 203 } 204 205 /** 206 * Set the "global" CORS configurations based on URL patterns. By default the first 207 * matching URL pattern is combined with the CORS configuration for the handler, if any. 208 * @since 4.2 209 * @see #setCorsConfigurationSource(CorsConfigurationSource) 210 */ 211 public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) { 212 Assert.notNull(corsConfigurations, "corsConfigurations must not be null"); 213 if (!corsConfigurations.isEmpty()) { 214 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 215 source.setCorsConfigurations(corsConfigurations); 216 source.setPathMatcher(this.pathMatcher); 217 source.setUrlPathHelper(this.urlPathHelper); 218 source.setLookupPathAttributeName(LOOKUP_PATH); 219 this.corsConfigurationSource = source; 220 } 221 else { 222 this.corsConfigurationSource = null; 223 } 224 } 225 226 /** 227 * Set the "global" CORS configuration source. By default the first matching URL 228 * pattern is combined with the CORS configuration for the handler, if any. 229 * @since 5.1 230 * @see #setCorsConfigurations(Map) 231 */ 232 public void setCorsConfigurationSource(CorsConfigurationSource corsConfigurationSource) { 233 Assert.notNull(corsConfigurationSource, "corsConfigurationSource must not be null"); 234 this.corsConfigurationSource = corsConfigurationSource; 235 } 236 237 /** 238 * Configure a custom {@link CorsProcessor} to use to apply the matched 239 * {@link CorsConfiguration} for a request. 240 * <p>By default {@link DefaultCorsProcessor} is used. 241 * @since 4.2 242 */ 243 public void setCorsProcessor(CorsProcessor corsProcessor) { 244 Assert.notNull(corsProcessor, "CorsProcessor must not be null"); 245 this.corsProcessor = corsProcessor; 246 } 247 248 /** 249 * Return the configured {@link CorsProcessor}. 250 */ 251 public CorsProcessor getCorsProcessor() { 252 return this.corsProcessor; 253 } 254 255 /** 256 * Specify the order value for this HandlerMapping bean. 257 * <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered. 258 * @see org.springframework.core.Ordered#getOrder() 259 */ 260 public void setOrder(int order) { 261 this.order = order; 262 } 263 264 @Override 265 public int getOrder() { 266 return this.order; 267 } 268 269 @Override 270 public void setBeanName(String name) { 271 this.beanName = name; 272 } 273 274 protected String formatMappingName() { 275 return this.beanName != null ? "'" + this.beanName + "'" : "<unknown>"; 276 } 277 278 279 /** 280 * Initializes the interceptors. 281 * @see #extendInterceptors(java.util.List) 282 * @see #initInterceptors() 283 */ 284 @Override 285 protected void initApplicationContext() throws BeansException { 286 extendInterceptors(this.interceptors); 287 detectMappedInterceptors(this.adaptedInterceptors); 288 initInterceptors(); 289 } 290 291 /** 292 * Extension hook that subclasses can override to register additional interceptors, 293 * given the configured interceptors (see {@link #setInterceptors}). 294 * <p>Will be invoked before {@link #initInterceptors()} adapts the specified 295 * interceptors into {@link HandlerInterceptor} instances. 296 * <p>The default implementation is empty. 297 * @param interceptors the configured interceptor List (never {@code null}), allowing 298 * to add further interceptors before as well as after the existing interceptors 299 */ 300 protected void extendInterceptors(List<Object> interceptors) { 301 } 302 303 /** 304 * Detect beans of type {@link MappedInterceptor} and add them to the list 305 * of mapped interceptors. 306 * <p>This is called in addition to any {@link MappedInterceptor}s that may 307 * have been provided via {@link #setInterceptors}, by default adding all 308 * beans of type {@link MappedInterceptor} from the current context and its 309 * ancestors. Subclasses can override and refine this policy. 310 * @param mappedInterceptors an empty list to add to 311 */ 312 protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) { 313 mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors( 314 obtainApplicationContext(), MappedInterceptor.class, true, false).values()); 315 } 316 317 /** 318 * Initialize the specified interceptors adapting 319 * {@link WebRequestInterceptor}s to {@link HandlerInterceptor}. 320 * @see #setInterceptors 321 * @see #adaptInterceptor 322 */ 323 protected void initInterceptors() { 324 if (!this.interceptors.isEmpty()) { 325 for (int i = 0; i < this.interceptors.size(); i++) { 326 Object interceptor = this.interceptors.get(i); 327 if (interceptor == null) { 328 throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null"); 329 } 330 this.adaptedInterceptors.add(adaptInterceptor(interceptor)); 331 } 332 } 333 } 334 335 /** 336 * Adapt the given interceptor object to {@link HandlerInterceptor}. 337 * <p>By default, the supported interceptor types are 338 * {@link HandlerInterceptor} and {@link WebRequestInterceptor}. Each given 339 * {@link WebRequestInterceptor} is wrapped with 340 * {@link WebRequestHandlerInterceptorAdapter}. 341 * @param interceptor the interceptor 342 * @return the interceptor downcast or adapted to HandlerInterceptor 343 * @see org.springframework.web.servlet.HandlerInterceptor 344 * @see org.springframework.web.context.request.WebRequestInterceptor 345 * @see WebRequestHandlerInterceptorAdapter 346 */ 347 protected HandlerInterceptor adaptInterceptor(Object interceptor) { 348 if (interceptor instanceof HandlerInterceptor) { 349 return (HandlerInterceptor) interceptor; 350 } 351 else if (interceptor instanceof WebRequestInterceptor) { 352 return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor); 353 } 354 else { 355 throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName()); 356 } 357 } 358 359 /** 360 * Return the adapted interceptors as {@link HandlerInterceptor} array. 361 * @return the array of {@link HandlerInterceptor HandlerInterceptor}s, 362 * or {@code null} if none 363 */ 364 @Nullable 365 protected final HandlerInterceptor[] getAdaptedInterceptors() { 366 return (!this.adaptedInterceptors.isEmpty() ? 367 this.adaptedInterceptors.toArray(new HandlerInterceptor[0]) : null); 368 } 369 370 /** 371 * Return all configured {@link MappedInterceptor}s as an array. 372 * @return the array of {@link MappedInterceptor}s, or {@code null} if none 373 */ 374 @Nullable 375 protected final MappedInterceptor[] getMappedInterceptors() { 376 List<MappedInterceptor> mappedInterceptors = new ArrayList<>(this.adaptedInterceptors.size()); 377 for (HandlerInterceptor interceptor : this.adaptedInterceptors) { 378 if (interceptor instanceof MappedInterceptor) { 379 mappedInterceptors.add((MappedInterceptor) interceptor); 380 } 381 } 382 return (!mappedInterceptors.isEmpty() ? mappedInterceptors.toArray(new MappedInterceptor[0]) : null); 383 } 384 385 386 /** 387 * Look up a handler for the given request, falling back to the default 388 * handler if no specific one is found. 389 * @param request current HTTP request 390 * @return the corresponding handler instance, or the default handler 391 * @see #getHandlerInternal 392 */ 393 @Override 394 @Nullable 395 public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 396 Object handler = getHandlerInternal(request); 397 if (handler == null) { 398 handler = getDefaultHandler(); 399 } 400 if (handler == null) { 401 return null; 402 } 403 // Bean name or resolved handler? 404 if (handler instanceof String) { 405 String handlerName = (String) handler; 406 handler = obtainApplicationContext().getBean(handlerName); 407 } 408 409 HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); 410 411 if (logger.isTraceEnabled()) { 412 logger.trace("Mapped to " + handler); 413 } 414 else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) { 415 logger.debug("Mapped to " + executionChain.getHandler()); 416 } 417 418 if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { 419 CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null); 420 CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); 421 config = (config != null ? config.combine(handlerConfig) : handlerConfig); 422 executionChain = getCorsHandlerExecutionChain(request, executionChain, config); 423 } 424 425 return executionChain; 426 } 427 428 /** 429 * Look up a handler for the given request, returning {@code null} if no 430 * specific one is found. This method is called by {@link #getHandler}; 431 * a {@code null} return value will lead to the default handler, if one is set. 432 * <p>On CORS pre-flight requests this method should return a match not for 433 * the pre-flight request but for the expected actual request based on the URL 434 * path, the HTTP methods from the "Access-Control-Request-Method" header, and 435 * the headers from the "Access-Control-Request-Headers" header thus allowing 436 * the CORS configuration to be obtained via {@link #getCorsConfiguration(Object, HttpServletRequest)}, 437 * <p>Note: This method may also return a pre-built {@link HandlerExecutionChain}, 438 * combining a handler object with dynamically determined interceptors. 439 * Statically specified interceptors will get merged into such an existing chain. 440 * @param request current HTTP request 441 * @return the corresponding handler instance, or {@code null} if none found 442 * @throws Exception if there is an internal error 443 */ 444 @Nullable 445 protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception; 446 447 /** 448 * Build a {@link HandlerExecutionChain} for the given handler, including 449 * applicable interceptors. 450 * <p>The default implementation builds a standard {@link HandlerExecutionChain} 451 * with the given handler, the common interceptors of the handler mapping, and any 452 * {@link MappedInterceptor MappedInterceptors} matching to the current request URL. Interceptors 453 * are added in the order they were registered. Subclasses may override this 454 * in order to extend/rearrange the list of interceptors. 455 * <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a 456 * pre-built {@link HandlerExecutionChain}. This method should handle those 457 * two cases explicitly, either building a new {@link HandlerExecutionChain} 458 * or extending the existing chain. 459 * <p>For simply adding an interceptor in a custom subclass, consider calling 460 * {@code super.getHandlerExecutionChain(handler, request)} and invoking 461 * {@link HandlerExecutionChain#addInterceptor} on the returned chain object. 462 * @param handler the resolved handler instance (never {@code null}) 463 * @param request current HTTP request 464 * @return the HandlerExecutionChain (never {@code null}) 465 * @see #getAdaptedInterceptors() 466 */ 467 protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { 468 HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? 469 (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); 470 471 String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH); 472 for (HandlerInterceptor interceptor : this.adaptedInterceptors) { 473 if (interceptor instanceof MappedInterceptor) { 474 MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; 475 if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { 476 chain.addInterceptor(mappedInterceptor.getInterceptor()); 477 } 478 } 479 else { 480 chain.addInterceptor(interceptor); 481 } 482 } 483 return chain; 484 } 485 486 /** 487 * Return {@code true} if there is a {@link CorsConfigurationSource} for this handler. 488 * @since 5.2 489 */ 490 protected boolean hasCorsConfigurationSource(Object handler) { 491 if (handler instanceof HandlerExecutionChain) { 492 handler = ((HandlerExecutionChain) handler).getHandler(); 493 } 494 return (handler instanceof CorsConfigurationSource || this.corsConfigurationSource != null); 495 } 496 497 /** 498 * Retrieve the CORS configuration for the given handler. 499 * @param handler the handler to check (never {@code null}). 500 * @param request the current request. 501 * @return the CORS configuration for the handler, or {@code null} if none 502 * @since 4.2 503 */ 504 @Nullable 505 protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) { 506 Object resolvedHandler = handler; 507 if (handler instanceof HandlerExecutionChain) { 508 resolvedHandler = ((HandlerExecutionChain) handler).getHandler(); 509 } 510 if (resolvedHandler instanceof CorsConfigurationSource) { 511 return ((CorsConfigurationSource) resolvedHandler).getCorsConfiguration(request); 512 } 513 return null; 514 } 515 516 /** 517 * Update the HandlerExecutionChain for CORS-related handling. 518 * <p>For pre-flight requests, the default implementation replaces the selected 519 * handler with a simple HttpRequestHandler that invokes the configured 520 * {@link #setCorsProcessor}. 521 * <p>For actual requests, the default implementation inserts a 522 * HandlerInterceptor that makes CORS-related checks and adds CORS headers. 523 * @param request the current request 524 * @param chain the handler chain 525 * @param config the applicable CORS configuration (possibly {@code null}) 526 * @since 4.2 527 */ 528 protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request, 529 HandlerExecutionChain chain, @Nullable CorsConfiguration config) { 530 531 if (CorsUtils.isPreFlightRequest(request)) { 532 HandlerInterceptor[] interceptors = chain.getInterceptors(); 533 return new HandlerExecutionChain(new PreFlightHandler(config), interceptors); 534 } 535 else { 536 chain.addInterceptor(0, new CorsInterceptor(config)); 537 return chain; 538 } 539 } 540 541 542 private class PreFlightHandler implements HttpRequestHandler, CorsConfigurationSource { 543 544 @Nullable 545 private final CorsConfiguration config; 546 547 public PreFlightHandler(@Nullable CorsConfiguration config) { 548 this.config = config; 549 } 550 551 @Override 552 public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { 553 corsProcessor.processRequest(this.config, request, response); 554 } 555 556 @Override 557 @Nullable 558 public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { 559 return this.config; 560 } 561 } 562 563 564 private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource { 565 566 @Nullable 567 private final CorsConfiguration config; 568 569 public CorsInterceptor(@Nullable CorsConfiguration config) { 570 this.config = config; 571 } 572 573 @Override 574 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 575 throws Exception { 576 577 // Consistent with CorsFilter, ignore ASYNC dispatches 578 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 579 if (asyncManager.hasConcurrentResult()) { 580 return true; 581 } 582 583 return corsProcessor.processRequest(this.config, request, response); 584 } 585 586 @Override 587 @Nullable 588 public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { 589 return this.config; 590 } 591 } 592 593}