From 9c935215126116d437e571daf6aae478e56b370f Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 4 Jul 2017 15:44:15 +0200 Subject: [PATCH] Leverage PathPatternParser in CORS configuration source Previously `UrlBasedCorsConfigurationSource` was relying on `PathMatcher` implementations for matching incoming request lookup paths with the configured path patterns for CORS configuration. This commit replaces the use of `PathMatcher` with a `PathPatternParser` that parses the string patterns into `PathPattenr` instances and allows for faster matching against lookup paths. Issue: SPR-15688 --- .../UrlBasedCorsConfigurationSource.java | 47 +++++++------------ .../UrlBasedCorsConfigurationSourceTests.java | 9 ++-- .../handler/AbstractHandlerMapping.java | 16 +++---- 3 files changed, 26 insertions(+), 46 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSource.java b/spring-web/src/main/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSource.java index 395f229832..68db63fa70 100644 --- a/spring-web/src/main/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSource.java +++ b/spring-web/src/main/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSource.java @@ -16,16 +16,14 @@ package org.springframework.web.cors.reactive; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.util.PathMatcher; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.pattern.ParsingPathMatcher; +import org.springframework.web.util.pattern.PathPattern; +import org.springframework.web.util.pattern.PathPatternParser; /** * Provide a per reactive request {@link CorsConfiguration} instance based on a @@ -35,23 +33,18 @@ import org.springframework.web.util.pattern.ParsingPathMatcher; * as well as Ant-style path patterns (such as {@code "/admin/**"}). * * @author Sebastien Deleuze + * @author Brian Clozel * @since 5.0 */ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource { - private final Map corsConfigurations = new LinkedHashMap<>(); + private final Map corsConfigurations; - private PathMatcher pathMatcher = new ParsingPathMatcher(); - - - /** - * Set the PathMatcher implementation to use for matching URL paths - * against registered URL patterns. Default is ParsingPathMatcher. - * @see ParsingPathMatcher - */ - public void setPathMatcher(PathMatcher pathMatcher) { - Assert.notNull(pathMatcher, "PathMatcher must not be null"); - this.pathMatcher = pathMatcher; + private final PathPatternParser patternParser; + + public UrlBasedCorsConfigurationSource(PathPatternParser patternParser) { + this.corsConfigurations = new LinkedHashMap<>(); + this.patternParser = patternParser; } /** @@ -60,33 +53,25 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource public void setCorsConfigurations(@Nullable Map corsConfigurations) { this.corsConfigurations.clear(); if (corsConfigurations != null) { - this.corsConfigurations.putAll(corsConfigurations); + corsConfigurations.forEach((path, config) -> registerCorsConfiguration(path, config)); } } - /** - * Get the CORS configuration. - */ - public Map getCorsConfigurations() { - return Collections.unmodifiableMap(this.corsConfigurations); - } - /** * Register a {@link CorsConfiguration} for the specified path pattern. */ public void registerCorsConfiguration(String path, CorsConfiguration config) { - this.corsConfigurations.put(path, config); + this.corsConfigurations.put(this.patternParser.parse(path), config); } @Override public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) { String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value(); - for (Map.Entry entry : this.corsConfigurations.entrySet()) { - if (this.pathMatcher.match(entry.getKey(), lookupPath)) { - return entry.getValue(); - } - } - return null; + return this.corsConfigurations.entrySet().stream() + .filter(entry -> entry.getKey().matches(lookupPath)) + .map(entry -> entry.getValue()) + .findFirst() + .orElse(null); } } diff --git a/spring-web/src/test/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSourceTests.java b/spring-web/src/test/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSourceTests.java index 75775e790e..b4b0fefcc7 100644 --- a/spring-web/src/test/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSourceTests.java +++ b/spring-web/src/test/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSourceTests.java @@ -21,6 +21,7 @@ import org.junit.Test; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.util.pattern.PathPatternParser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -33,7 +34,8 @@ import static org.junit.Assert.assertNull; */ public class UrlBasedCorsConfigurationSourceTests { - private final UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(); + private final UrlBasedCorsConfigurationSource configSource + = new UrlBasedCorsConfigurationSource(new PathPatternParser()); @Test @@ -54,9 +56,4 @@ public class UrlBasedCorsConfigurationSourceTests { assertEquals(config, this.configSource.getCorsConfiguration(exchange)); } - @Test(expected = UnsupportedOperationException.class) - public void unmodifiableConfigurationsMap() { - this.configSource.getCorsConfigurations().put("/**", new CorsConfiguration()); - } - } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java index 84a097f837..152fb50cb0 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java @@ -51,13 +51,18 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im private int order = Integer.MAX_VALUE; // default: same as non-Ordered - private PathPatternParser patternParser = new PathPatternParser(); + private final PathPatternParser patternParser; - private final UrlBasedCorsConfigurationSource globalCorsConfigSource = new UrlBasedCorsConfigurationSource(); + private final UrlBasedCorsConfigurationSource globalCorsConfigSource; private CorsProcessor corsProcessor = new DefaultCorsProcessor(); + public AbstractHandlerMapping() { + this.patternParser = new PathPatternParser(); + this.globalCorsConfigSource = new UrlBasedCorsConfigurationSource(this.patternParser); + } + /** * Specify the order value for this HandlerMapping bean. *

Default value is {@code Integer.MAX_VALUE}, meaning that it's non-ordered. @@ -106,13 +111,6 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im this.globalCorsConfigSource.setCorsConfigurations(corsConfigurations); } - /** - * Return the "global" CORS configuration. - */ - public Map getCorsConfigurations() { - return this.globalCorsConfigSource.getCorsConfigurations(); - } - /** * Configure a custom {@link CorsProcessor} to use to apply the matched * {@link CorsConfiguration} for a request.