/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.reactive.handler; import java.util.Map; import reactor.core.publisher.Mono; import org.springframework.context.support.ApplicationObjectSupport; import org.springframework.core.Ordered; import org.springframework.util.Assert; import org.springframework.util.PathMatcher; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsConfigurationSource; import org.springframework.web.cors.reactive.CorsProcessor; import org.springframework.web.cors.reactive.CorsUtils; import org.springframework.web.cors.reactive.DefaultCorsProcessor; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebHandler; import org.springframework.web.server.support.HttpRequestPathHelper; import org.springframework.web.util.pattern.ParsingPathMatcher; /** * Abstract base class for {@link org.springframework.web.reactive.HandlerMapping} * implementations. * * @author Rossen Stoyanchev * @author Juergen Hoeller * @since 5.0 */ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport implements HandlerMapping, Ordered { private static final WebHandler REQUEST_HANDLED_HANDLER = exchange -> Mono.empty(); private int order = Integer.MAX_VALUE; // default: same as non-Ordered private HttpRequestPathHelper pathHelper = new HttpRequestPathHelper(); private PathMatcher pathMatcher = new ParsingPathMatcher(); private final UrlBasedCorsConfigurationSource globalCorsConfigSource = new UrlBasedCorsConfigurationSource(); private CorsProcessor corsProcessor = new DefaultCorsProcessor(); /** * Specify the order value for this HandlerMapping bean. * <p>Default value is {@code Integer.MAX_VALUE}, meaning that it's non-ordered. * @see org.springframework.core.Ordered#getOrder() */ public final void setOrder(int order) { this.order = order; } @Override public final int getOrder() { return this.order; } /** * Set if the path should be URL-decoded. This sets the same property on the * underlying path helper. * @see HttpRequestPathHelper#setUrlDecode(boolean) */ public void setUrlDecode(boolean urlDecode) { this.pathHelper.setUrlDecode(urlDecode); } /** * Set the {@link HttpRequestPathHelper} to use for resolution of lookup * paths. Use this to override the default implementation with a custom * subclass or to share common path helper settings across multiple * HandlerMappings. */ public void setPathHelper(HttpRequestPathHelper pathHelper) { this.pathHelper = pathHelper; } /** * Return the {@link HttpRequestPathHelper} implementation to use for * resolution of lookup paths. */ public HttpRequestPathHelper getPathHelper() { return this.pathHelper; } /** * Set the PathMatcher implementation to use for matching URL paths * against registered URL patterns. * <p>The default is a {@link ParsingPathMatcher}. */ public void setPathMatcher(PathMatcher pathMatcher) { Assert.notNull(pathMatcher, "PathMatcher must not be null"); this.pathMatcher = pathMatcher; this.globalCorsConfigSource.setPathMatcher(pathMatcher); } /** * Return the PathMatcher implementation to use for matching URL paths * against registered URL patterns. */ public PathMatcher getPathMatcher() { return this.pathMatcher; } /** * Set "global" CORS configuration based on URL patterns. By default the * first matching URL pattern is combined with handler-level CORS * configuration if any. */ public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) { this.globalCorsConfigSource.setCorsConfigurations(corsConfigurations); } /** * Return the "global" CORS configuration. */ public Map<String, CorsConfiguration> getCorsConfigurations() { return this.globalCorsConfigSource.getCorsConfigurations(); } /** * Configure a custom {@link CorsProcessor} to use to apply the matched * {@link CorsConfiguration} for a request. * <p>By default an instance of {@link DefaultCorsProcessor} is used. */ public void setCorsProcessor(CorsProcessor corsProcessor) { Assert.notNull(corsProcessor, "CorsProcessor must not be null"); this.corsProcessor = corsProcessor; } /** * Return the configured {@link CorsProcessor}. */ public CorsProcessor getCorsProcessor() { return this.corsProcessor; } @Override public Mono<Object> getHandler(ServerWebExchange exchange) { return getHandlerInternal(exchange).map(handler -> { if (CorsUtils.isCorsRequest(exchange.getRequest())) { CorsConfiguration configA = this.globalCorsConfigSource.getCorsConfiguration(exchange); CorsConfiguration configB = getCorsConfiguration(handler, exchange); CorsConfiguration config = (configA != null ? configA.combine(configB) : configB); if (!getCorsProcessor().processRequest(config, exchange) || CorsUtils.isPreFlightRequest(exchange.getRequest())) { return REQUEST_HANDLED_HANDLER; } } return handler; }); } /** * Look up a handler for the given request, returning an empty {@code Mono} * if no specific one is found. This method is called by {@link #getHandler}. * <p>On CORS pre-flight requests this method should return a match not for * the pre-flight request but for the expected actual request based on the URL * path, the HTTP methods from the "Access-Control-Request-Method" header, and * the headers from the "Access-Control-Request-Headers" header thus allowing * the CORS configuration to be obtained via {@link #getCorsConfigurations}, * @param exchange current exchange * @return {@code Mono} for the matching handler, if any */ protected abstract Mono<?> getHandlerInternal(ServerWebExchange exchange); /** * Retrieve the CORS configuration for the given handler. * @param handler the handler to check (never {@code null}) * @param exchange the current exchange * @return the CORS configuration for the handler, or {@code null} if none */ protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) { if (handler instanceof CorsConfigurationSource) { return ((CorsConfigurationSource) handler).getCorsConfiguration(exchange); } return null; } }