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}