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