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 f1eaa47e25a..35268a886d6 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
@@ -24,8 +24,8 @@ 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.server.support.HttpRequestPathHelper;
import org.springframework.web.util.pattern.ParsingPathMatcher;
+import org.springframework.web.server.support.LookupPath;
/**
* Provide a per reactive request {@link CorsConfiguration} instance based on a
@@ -43,8 +43,6 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
private PathMatcher pathMatcher = new ParsingPathMatcher();
- private HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
-
/**
* Set the PathMatcher implementation to use for matching URL paths
@@ -56,26 +54,6 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
this.pathMatcher = pathMatcher;
}
- /**
- * Set if context path and request URI should be URL-decoded. Both are returned
- * undecoded by the Servlet API, in contrast to the servlet path.
- *
Uses either the request encoding or the default encoding according
- * to the Servlet spec (ISO-8859-1).
- * @see HttpRequestPathHelper#setUrlDecode
- */
- public void setUrlDecode(boolean urlDecode) {
- this.pathHelper.setUrlDecode(urlDecode);
- }
-
- /**
- * Set the UrlPathHelper to use for resolution of lookup paths.
- *
Use this to override the default UrlPathHelper with a custom subclass.
- */
- public void setHttpRequestPathHelper(HttpRequestPathHelper pathHelper) {
- Assert.notNull(pathHelper, "HttpRequestPathHelper must not be null");
- this.pathHelper = pathHelper;
- }
-
/**
* Set CORS configuration based on URL patterns.
*/
@@ -102,7 +80,7 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
@Override
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
- String lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
+ String lookupPath = exchange.getAttribute(LookupPath.LOOKUP_PATH_ATTRIBUTE).get().getPath();
for (Map.Entry entry : this.corsConfigurations.entrySet()) {
if (this.pathMatcher.match(entry.getKey(), lookupPath)) {
return entry.getValue();
diff --git a/spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java b/spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java
index ac92db6a69a..3842184adec 100644
--- a/spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java
+++ b/spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java
@@ -57,9 +57,14 @@ public class HttpRequestPathHelper {
}
- public String getLookupPathForRequest(ServerWebExchange exchange) {
+ public LookupPath getLookupPathForRequest(ServerWebExchange exchange) {
String path = getPathWithinApplication(exchange.getRequest());
- return (shouldUrlDecode() ? decode(exchange, path) : path);
+ path = (shouldUrlDecode() ? decode(exchange, path) : path);
+ int begin = path.lastIndexOf('/') + 1;
+ int end = path.length();
+ int paramIndex = path.indexOf(';', begin);
+ int extIndex = path.lastIndexOf('.', paramIndex != -1 ? paramIndex : end);
+ return new LookupPath(path, extIndex, paramIndex);
}
private String getPathWithinApplication(ServerHttpRequest request) {
diff --git a/spring-web/src/main/java/org/springframework/web/server/support/LookupPath.java b/spring-web/src/main/java/org/springframework/web/server/support/LookupPath.java
new file mode 100644
index 00000000000..3fc00e0970c
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/server/support/LookupPath.java
@@ -0,0 +1,84 @@
+/*
+ * 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.server.support;
+
+import org.springframework.lang.Nullable;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Lookup path information of an incoming HTTP request.
+ *
+ * @author Brian Clozel
+ * @since 5.0
+ * @see HttpRequestPathHelper
+ */
+public final class LookupPath {
+
+ public static final String LOOKUP_PATH_ATTRIBUTE = LookupPath.class.getName();
+
+ private final String path;
+
+ private final int fileExtensionIndex;
+
+ private final int pathParametersIndex;
+
+ public LookupPath(String path, int fileExtensionIndex, int pathParametersIndex) {
+ this.path = path;
+ this.fileExtensionIndex = fileExtensionIndex;
+ this.pathParametersIndex = pathParametersIndex;
+ }
+
+ public String getPath() {
+ if (this.pathParametersIndex != -1) {
+ // TODO: variant without the path parameter information?
+ //return this.path.substring(0, this.pathParametersIndex);
+ return this.path;
+ }
+ else {
+ return this.path;
+ }
+ }
+
+ public String getPathWithoutExtension() {
+ if (this.fileExtensionIndex != -1) {
+ return this.path.substring(0, this.fileExtensionIndex);
+ }
+ else {
+ return this.path;
+ }
+ }
+
+ @Nullable
+ public String getFileExtension() {
+ if (this.fileExtensionIndex == -1) {
+ return null;
+ }
+ else if (this.pathParametersIndex == -1) {
+ return this.path.substring(this.fileExtensionIndex);
+ }
+ else {
+ return this.path.substring(this.fileExtensionIndex, this.pathParametersIndex);
+ }
+ }
+
+ @Nullable
+ public String getPathParameters() {
+ return this.pathParametersIndex == -1 ?
+ null : this.path.substring(this.pathParametersIndex + 1);
+ }
+
+}
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 75775e790e2..4fe628ed947 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,8 @@ 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.server.support.HttpRequestPathHelper;
+import org.springframework.web.server.support.LookupPath;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -39,6 +41,7 @@ public class UrlBasedCorsConfigurationSourceTests {
@Test
public void empty() {
ServerWebExchange exchange = MockServerHttpRequest.get("/bar/test.html").toExchange();
+ setLookupPathAttribute(exchange);
assertNull(this.configSource.getCorsConfiguration(exchange));
}
@@ -48,9 +51,11 @@ public class UrlBasedCorsConfigurationSourceTests {
this.configSource.registerCorsConfiguration("/bar/**", config);
ServerWebExchange exchange = MockServerHttpRequest.get("/foo/test.html").toExchange();
+ setLookupPathAttribute(exchange);
assertNull(this.configSource.getCorsConfiguration(exchange));
exchange = MockServerHttpRequest.get("/bar/test.html").toExchange();
+ setLookupPathAttribute(exchange);
assertEquals(config, this.configSource.getCorsConfiguration(exchange));
}
@@ -59,4 +64,10 @@ public class UrlBasedCorsConfigurationSourceTests {
this.configSource.getCorsConfigurations().put("/**", new CorsConfiguration());
}
+ public void setLookupPathAttribute(ServerWebExchange exchange) {
+ HttpRequestPathHelper helper = new HttpRequestPathHelper();
+ exchange.getAttributes().put(LookupPath.LOOKUP_PATH_ATTRIBUTE,
+ helper.getLookupPathForRequest(exchange));
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/web/server/support/LookupPathTests.java b/spring-web/src/test/java/org/springframework/web/server/support/LookupPathTests.java
new file mode 100644
index 00000000000..11a46dd12a0
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/web/server/support/LookupPathTests.java
@@ -0,0 +1,61 @@
+/*
+ * 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.server.support;
+
+import org.junit.Test;
+
+import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
+import org.springframework.web.server.ServerWebExchange;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link LookupPath}
+ * @author Brian Clozel
+ */
+public class LookupPathTests {
+
+ @Test
+ public void parsePath() {
+ LookupPath path = create("/foo");
+ assertEquals("/foo", path.getPath());
+ assertEquals("/foo", path.getPathWithoutExtension());
+ }
+
+ @Test
+ public void parsePathWithExtension() {
+ LookupPath path = create("/foo.txt");
+ assertEquals("/foo.txt", path.getPath());
+ assertEquals("/foo", path.getPathWithoutExtension());
+ assertEquals(".txt", path.getFileExtension());
+ }
+
+ @Test
+ public void parsePathWithParams() {
+ LookupPath path = create("/test/foo.txt;foo=bar?framework=spring");
+ assertEquals("/test/foo.txt;foo=bar", path.getPath());
+ assertEquals("/test/foo", path.getPathWithoutExtension());
+ assertEquals(".txt", path.getFileExtension());
+ assertEquals("foo=bar", path.getPathParameters());
+ }
+
+ private LookupPath create(String path) {
+ HttpRequestPathHelper helper = new HttpRequestPathHelper();
+ ServerWebExchange exchange = MockServerHttpRequest.get(path).build().toExchange();
+ return helper.getLookupPathForRequest(exchange);
+ }
+}
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 d2a20b0befa..92d8937abb3 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
@@ -17,6 +17,7 @@
package org.springframework.web.reactive.handler;
import java.util.Map;
+import java.util.Optional;
import reactor.core.publisher.Mono;
@@ -32,6 +33,7 @@ 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.support.LookupPath;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.support.HttpRequestPathHelper;
@@ -43,6 +45,7 @@ import org.springframework.web.util.pattern.ParsingPathMatcher;
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
+ * @author Brian Clozel
* @since 5.0
*/
public abstract class AbstractHandlerMapping extends ApplicationObjectSupport implements HandlerMapping, Ordered {
@@ -171,6 +174,19 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im
});
}
+ protected LookupPath getLookupPath(ServerWebExchange exchange) {
+ Optional attribute = exchange.getAttribute(LookupPath.LOOKUP_PATH_ATTRIBUTE);
+ return attribute.orElseGet(() -> {
+ LookupPath lookupPath = createLookupPath(exchange);
+ exchange.getAttributes().put(LookupPath.LOOKUP_PATH_ATTRIBUTE, lookupPath);
+ return lookupPath;
+ });
+ }
+
+ protected LookupPath createLookupPath(ServerWebExchange exchange) {
+ return getPathHelper().getLookupPathForRequest(exchange);
+ }
+
/**
* 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}.
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java
index 4458bb34c63..c353b4376a9 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java
@@ -28,6 +28,7 @@ import reactor.core.publisher.Mono;
import org.springframework.beans.BeansException;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
+import org.springframework.web.server.support.LookupPath;
import org.springframework.web.server.ServerWebExchange;
/**
@@ -46,6 +47,7 @@ import org.springframework.web.server.ServerWebExchange;
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
+ * @author Brian Clozel
* @since 5.0
*/
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
@@ -99,7 +101,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
@Override
public Mono