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.reactive.handler; 018 019import java.util.Map; 020 021import reactor.core.publisher.Mono; 022 023import org.springframework.beans.factory.BeanNameAware; 024import org.springframework.context.support.ApplicationObjectSupport; 025import org.springframework.core.Ordered; 026import org.springframework.http.server.reactive.ServerHttpRequest; 027import org.springframework.lang.Nullable; 028import org.springframework.util.Assert; 029import org.springframework.web.cors.CorsConfiguration; 030import org.springframework.web.cors.reactive.CorsConfigurationSource; 031import org.springframework.web.cors.reactive.CorsProcessor; 032import org.springframework.web.cors.reactive.CorsUtils; 033import org.springframework.web.cors.reactive.DefaultCorsProcessor; 034import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; 035import org.springframework.web.reactive.HandlerMapping; 036import org.springframework.web.server.ServerWebExchange; 037import org.springframework.web.server.WebHandler; 038import org.springframework.web.util.pattern.PathPatternParser; 039 040/** 041 * Abstract base class for {@link org.springframework.web.reactive.HandlerMapping} 042 * implementations. 043 * 044 * @author Rossen Stoyanchev 045 * @author Juergen Hoeller 046 * @author Brian Clozel 047 * @since 5.0 048 */ 049public abstract class AbstractHandlerMapping extends ApplicationObjectSupport 050 implements HandlerMapping, Ordered, BeanNameAware { 051 052 private static final WebHandler REQUEST_HANDLED_HANDLER = exchange -> Mono.empty(); 053 054 055 private final PathPatternParser patternParser; 056 057 @Nullable 058 private CorsConfigurationSource corsConfigurationSource; 059 060 private CorsProcessor corsProcessor = new DefaultCorsProcessor(); 061 062 private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered 063 064 @Nullable 065 private String beanName; 066 067 068 public AbstractHandlerMapping() { 069 this.patternParser = new PathPatternParser(); 070 } 071 072 073 /** 074 * Shortcut method for setting the same property on the underlying pattern 075 * parser in use. For more details see: 076 * <ul> 077 * <li>{@link #getPathPatternParser()} -- the underlying pattern parser 078 * <li>{@link PathPatternParser#setCaseSensitive(boolean)} -- the case 079 * sensitive slash option, including its default value. 080 * </ul> 081 * <p><strong>Note:</strong> aside from 082 */ 083 public void setUseCaseSensitiveMatch(boolean caseSensitiveMatch) { 084 this.patternParser.setCaseSensitive(caseSensitiveMatch); 085 } 086 087 /** 088 * Shortcut method for setting the same property on the underlying pattern 089 * parser in use. For more details see: 090 * <ul> 091 * <li>{@link #getPathPatternParser()} -- the underlying pattern parser 092 * <li>{@link PathPatternParser#setMatchOptionalTrailingSeparator(boolean)} -- 093 * the trailing slash option, including its default value. 094 * </ul> 095 */ 096 public void setUseTrailingSlashMatch(boolean trailingSlashMatch) { 097 this.patternParser.setMatchOptionalTrailingSeparator(trailingSlashMatch); 098 } 099 100 /** 101 * Return the {@link PathPatternParser} instance that is used for 102 * {@link #setCorsConfigurations(Map) CORS configuration checks}. 103 * Sub-classes can also use this pattern parser for their own request 104 * mapping purposes. 105 */ 106 public PathPatternParser getPathPatternParser() { 107 return this.patternParser; 108 } 109 110 /** 111 * Set the "global" CORS configurations based on URL patterns. By default the 112 * first matching URL pattern is combined with handler-level CORS configuration if any. 113 * @see #setCorsConfigurationSource(CorsConfigurationSource) 114 */ 115 public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) { 116 Assert.notNull(corsConfigurations, "corsConfigurations must not be null"); 117 if (!corsConfigurations.isEmpty()) { 118 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(this.patternParser); 119 source.setCorsConfigurations(corsConfigurations); 120 this.corsConfigurationSource = source; 121 } 122 else { 123 this.corsConfigurationSource = null; 124 } 125 } 126 127 /** 128 * Set the "global" CORS configuration source. By default the first matching URL 129 * pattern is combined with the CORS configuration for the handler, if any. 130 * @since 5.1 131 * @see #setCorsConfigurations(Map) 132 */ 133 public void setCorsConfigurationSource(CorsConfigurationSource corsConfigurationSource) { 134 Assert.notNull(corsConfigurationSource, "corsConfigurationSource must not be null"); 135 this.corsConfigurationSource = corsConfigurationSource; 136 } 137 138 /** 139 * Configure a custom {@link CorsProcessor} to use to apply the matched 140 * {@link CorsConfiguration} for a request. 141 * <p>By default an instance of {@link DefaultCorsProcessor} is used. 142 */ 143 public void setCorsProcessor(CorsProcessor corsProcessor) { 144 Assert.notNull(corsProcessor, "CorsProcessor must not be null"); 145 this.corsProcessor = corsProcessor; 146 } 147 148 /** 149 * Return the configured {@link CorsProcessor}. 150 */ 151 public CorsProcessor getCorsProcessor() { 152 return this.corsProcessor; 153 } 154 155 /** 156 * Specify the order value for this HandlerMapping bean. 157 * <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered. 158 * @see org.springframework.core.Ordered#getOrder() 159 */ 160 public void setOrder(int order) { 161 this.order = order; 162 } 163 164 @Override 165 public int getOrder() { 166 return this.order; 167 } 168 169 @Override 170 public void setBeanName(String name) { 171 this.beanName = name; 172 } 173 174 protected String formatMappingName() { 175 return this.beanName != null ? "'" + this.beanName + "'" : "<unknown>"; 176 } 177 178 179 @Override 180 public Mono<Object> getHandler(ServerWebExchange exchange) { 181 return getHandlerInternal(exchange).map(handler -> { 182 if (logger.isDebugEnabled()) { 183 logger.debug(exchange.getLogPrefix() + "Mapped to " + handler); 184 } 185 ServerHttpRequest request = exchange.getRequest(); 186 if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { 187 CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(exchange) : null); 188 CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange); 189 config = (config != null ? config.combine(handlerConfig) : handlerConfig); 190 if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) { 191 return REQUEST_HANDLED_HANDLER; 192 } 193 } 194 return handler; 195 }); 196 } 197 198 /** 199 * Look up a handler for the given request, returning an empty {@code Mono} 200 * if no specific one is found. This method is called by {@link #getHandler}. 201 * <p>On CORS pre-flight requests this method should return a match not for 202 * the pre-flight request but for the expected actual request based on the URL 203 * path, the HTTP methods from the "Access-Control-Request-Method" header, and 204 * the headers from the "Access-Control-Request-Headers" header. 205 * @param exchange current exchange 206 * @return {@code Mono} for the matching handler, if any 207 */ 208 protected abstract Mono<?> getHandlerInternal(ServerWebExchange exchange); 209 210 /** 211 * Return {@code true} if there is a {@link CorsConfigurationSource} for this handler. 212 * @since 5.2 213 */ 214 protected boolean hasCorsConfigurationSource(Object handler) { 215 return (handler instanceof CorsConfigurationSource || this.corsConfigurationSource != null); 216 } 217 218 /** 219 * Retrieve the CORS configuration for the given handler. 220 * @param handler the handler to check (never {@code null}) 221 * @param exchange the current exchange 222 * @return the CORS configuration for the handler, or {@code null} if none 223 */ 224 @Nullable 225 protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) { 226 if (handler instanceof CorsConfigurationSource) { 227 return ((CorsConfigurationSource) handler).getCorsConfiguration(exchange); 228 } 229 return null; 230 } 231 232}