Browse Source

Remove HttpRequestPathHelper

The use of the undecoded URL path by default and the removal of suffix
pattern matching effectively means HttpRequestPathHelper is no longer
needed.

Issue: SPR-15640, SPR-15639
pull/1457/head
Rossen Stoyanchev 9 years ago
parent
commit
b65bfdb979
  1. 153
      spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java
  2. 8
      spring-web/src/main/java/org/springframework/web/server/support/package-info.java
  3. 82
      spring-web/src/test/java/org/springframework/web/server/support/HttpRequestPathHelperTests.java
  4. 17
      spring-webflux/src/main/java/org/springframework/web/reactive/config/PathMatchConfigurer.java
  5. 9
      spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java
  6. 30
      spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java
  7. 19
      spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java
  8. 40
      spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java
  9. 7
      spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java
  10. 1
      spring-webflux/src/test/resources/org/springframework/web/reactive/handler/map.xml

153
spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java

@ -1,153 +0,0 @@ @@ -1,153 +0,0 @@
/*
* Copyright 2002-2016 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 java.io.UnsupportedEncodingException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.StringTokenizer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriUtils;
/**
* A helper class to obtain the lookup path for path matching purposes.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public class HttpRequestPathHelper {
private boolean urlDecode = false;
// TODO: sanitize path, default/request encoding?, remove path params?
/**
* Set if the request path should be URL-decoded.
* <p>Default is "true".
* @see UriUtils#decode(String, String)
*/
public void setUrlDecode(boolean urlDecode) {
this.urlDecode = urlDecode;
}
/**
* Whether the request path should be URL decoded.
*/
public boolean shouldUrlDecode() {
return this.urlDecode;
}
private String decode(ServerWebExchange exchange, String path) {
// TODO: look up request encoding?
try {
return UriUtils.decode(path, "UTF-8");
}
catch (UnsupportedEncodingException ex) {
// Should not happen
throw new IllegalStateException("Could not decode request string [" + path + "]");
}
}
/**
* Decode the given URI path variables unless {@link #setUrlDecode(boolean)}
* is set to {@code true} in which case it is assumed the URL path from
* which the variables were extracted is already decoded through a call to
* {@link #getLookupPathForRequest(ServerWebExchange)}.
* @param exchange current exchange
* @param vars URI variables extracted from the URL path
* @return the same Map or a new Map instance
*/
public Map<String, String> decodePathVariables(ServerWebExchange exchange, Map<String, String> vars) {
if (this.urlDecode) {
return vars;
}
Map<String, String> decodedVars = new LinkedHashMap<>(vars.size());
for (Map.Entry<String, String> entry : vars.entrySet()) {
decodedVars.put(entry.getKey(), decode(exchange, entry.getValue()));
}
return decodedVars;
}
/**
* Parse the given string with matrix variables. An example string would look
* like this {@code "q1=a;q1=b;q2=a,b,c"}. The resulting map would contain
* keys {@code "q1"} and {@code "q2"} with values {@code ["a","b"]} and
* {@code ["a","b","c"]} respectively.
* <p>The returned values are decoded unless {@link #setUrlDecode(boolean)}
* is set to {@code true} in which case it is assumed the URL path from
* which the variables were extracted is already decoded through a call to
* {@link #getLookupPathForRequest(ServerWebExchange)}.
* @param semicolonContent path parameter content to parse
* @return a map with matrix variable names and values (never {@code null})
*/
public MultiValueMap<String, String> parseMatrixVariables(ServerWebExchange exchange,
String semicolonContent) {
MultiValueMap<String, String> result = new LinkedMultiValueMap<>();
if (!StringUtils.hasText(semicolonContent)) {
return result;
}
StringTokenizer pairs = new StringTokenizer(semicolonContent, ";");
while (pairs.hasMoreTokens()) {
String pair = pairs.nextToken();
int index = pair.indexOf('=');
if (index != -1) {
String name = pair.substring(0, index);
String rawValue = pair.substring(index + 1);
for (String value : StringUtils.commaDelimitedListToStringArray(rawValue)) {
result.add(name, value);
}
}
else {
result.add(pair, "");
}
}
return decodeMatrixVariables(exchange, result);
}
/**
* Decode the given matrix variables unless {@link #setUrlDecode(boolean)}
* is set to {@code true} in which case it is assumed the URL path from
* which the variables were extracted is already decoded through a call to
* {@link #getLookupPathForRequest(ServerWebExchange)}.
* @param exchange current exchange
* @param vars URI variables extracted from the URL path
* @return the same Map or a new Map instance
*/
private MultiValueMap<String, String> decodeMatrixVariables(ServerWebExchange exchange,
MultiValueMap<String, String> vars) {
if (this.urlDecode) {
return vars;
}
MultiValueMap<String, String> decodedVars = new LinkedMultiValueMap<>(vars.size());
for (String key : vars.keySet()) {
for (String value : vars.get(key)) {
decodedVars.add(key, decode(exchange, value));
}
}
return decodedVars;
}
}

8
spring-web/src/main/java/org/springframework/web/server/support/package-info.java

@ -1,8 +0,0 @@ @@ -1,8 +0,0 @@
/**
* Helper classes on top of {@code org.springframework.web.server},
* as a convenience for working with {@code ServerWebExchange}.
*/
@NonNullApi
package org.springframework.web.server.support;
import org.springframework.lang.NonNullApi;

82
spring-web/src/test/java/org/springframework/web/server/support/HttpRequestPathHelperTests.java

@ -1,82 +0,0 @@ @@ -1,82 +0,0 @@
/*
* 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 java.util.Arrays;
import org.junit.Test;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link HttpRequestPathHelper}.
* @author Rossen Stoyanchev
*/
public class HttpRequestPathHelperTests {
@Test
public void parseMatrixVariables() {
HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
ServerWebExchange exchange = MockServerHttpRequest.get("").toExchange();
MultiValueMap<String, String> variables;
variables = pathHelper.parseMatrixVariables(exchange, null);
assertEquals(0, variables.size());
variables = pathHelper.parseMatrixVariables(exchange, "year");
assertEquals(1, variables.size());
assertEquals("", variables.getFirst("year"));
variables = pathHelper.parseMatrixVariables(exchange, "year=2012");
assertEquals(1, variables.size());
assertEquals("2012", variables.getFirst("year"));
variables = pathHelper.parseMatrixVariables(exchange, "year=2012;colors=red,blue,green");
assertEquals(2, variables.size());
assertEquals(Arrays.asList("red", "blue", "green"), variables.get("colors"));
assertEquals("2012", variables.getFirst("year"));
variables = pathHelper.parseMatrixVariables(exchange, ";year=2012;colors=red,blue,green;");
assertEquals(2, variables.size());
assertEquals(Arrays.asList("red", "blue", "green"), variables.get("colors"));
assertEquals("2012", variables.getFirst("year"));
variables = pathHelper.parseMatrixVariables(exchange, "colors=red;colors=blue;colors=green");
assertEquals(1, variables.size());
assertEquals(Arrays.asList("red", "blue", "green"), variables.get("colors"));
}
@Test
public void parseMatrixVariablesAndDecode() {
HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
pathHelper.setUrlDecode(false);
ServerWebExchange exchange = MockServerHttpRequest.get("").toExchange();
MultiValueMap<String, String> variables;
variables = pathHelper.parseMatrixVariables(exchange, "mvar=a%2fb");
assertEquals(1, variables.size());
assertEquals("a/b", variables.getFirst("mvar"));
}
}

17
spring-webflux/src/main/java/org/springframework/web/reactive/config/PathMatchConfigurer.java

@ -18,7 +18,6 @@ package org.springframework.web.reactive.config; @@ -18,7 +18,6 @@ package org.springframework.web.reactive.config;
import org.springframework.lang.Nullable;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.support.HttpRequestPathHelper;
import org.springframework.web.util.pattern.ParsingPathMatcher;
/**
@ -35,8 +34,6 @@ public class PathMatchConfigurer { @@ -35,8 +34,6 @@ public class PathMatchConfigurer {
private Boolean registeredSuffixPatternMatch;
private HttpRequestPathHelper pathHelper;
private PathMatcher pathMatcher;
@ -73,15 +70,6 @@ public class PathMatchConfigurer { @@ -73,15 +70,6 @@ public class PathMatchConfigurer {
return this;
}
/**
* Set a {@code HttpRequestPathHelper} for the resolution of lookup paths.
* <p>Default is {@code HttpRequestPathHelper}.
*/
public PathMatchConfigurer setPathHelper(HttpRequestPathHelper pathHelper) {
this.pathHelper = pathHelper;
return this;
}
/**
* Set the PathMatcher for matching URL paths against registered URL patterns.
* <p>The default is a {@link org.springframework.web.util.pattern.ParsingPathMatcher}.
@ -106,11 +94,6 @@ public class PathMatchConfigurer { @@ -106,11 +94,6 @@ public class PathMatchConfigurer {
return this.registeredSuffixPatternMatch;
}
@Nullable
protected HttpRequestPathHelper getPathHelper() {
return this.pathHelper;
}
@Nullable
public PathMatcher getPathMatcher() {
if (this.pathMatcher instanceof ParsingPathMatcher && (this.trailingSlashMatch || this.suffixPatternMatch)) {

9
spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java

@ -133,16 +133,10 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { @@ -133,16 +133,10 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
HttpRequestPathHelper pathHelper = configurer.getPathHelper();
if (pathHelper != null) {
mapping.setPathHelper(pathHelper);
}
PathMatcher pathMatcher = configurer.getPathMatcher();
if (pathMatcher != null) {
mapping.setPathMatcher(pathMatcher);
}
return mapping;
}
@ -251,9 +245,6 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { @@ -251,9 +245,6 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
if (pathMatchConfigurer.getPathMatcher() != null) {
handlerMapping.setPathMatcher(pathMatchConfigurer.getPathMatcher());
}
if (pathMatchConfigurer.getPathHelper() != null) {
handlerMapping.setPathHelper(pathMatchConfigurer.getPathHelper());
}
}
else {
handlerMapping = new EmptyHandlerMapping();

30
spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java

@ -34,7 +34,6 @@ import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; @@ -34,7 +34,6 @@ 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;
/**
@ -53,8 +52,6 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im @@ -53,8 +52,6 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im
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();
@ -76,33 +73,6 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im @@ -76,33 +73,6 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im
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.

19
spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java

@ -37,7 +37,6 @@ import org.springframework.lang.Nullable; @@ -37,7 +37,6 @@ import org.springframework.lang.Nullable;
import org.springframework.util.PathMatcher;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.support.HttpRequestPathHelper;
import org.springframework.web.util.pattern.ParsingPathMatcher;
/**
@ -55,8 +54,6 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed @@ -55,8 +54,6 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
protected final Log logger = LogFactory.getLog(getClass());
private HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
private PathMatcher pathMatcher = new ParsingPathMatcher();
private final Map<String, ResourceWebHandler> handlerMap = new LinkedHashMap<>();
@ -64,22 +61,6 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed @@ -64,22 +61,6 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
private boolean autodetect = true;
/**
* Configure a {@code HttpRequestPathHelper} to use in
* {@link #getForRequestUrl(ServerWebExchange, String)}
* in order to derive the lookup path for a target request URL path.
*/
public void setPathHelper(HttpRequestPathHelper pathHelper) {
this.pathHelper = pathHelper;
}
/**
* Return the configured {@code HttpRequestPathHelper}.
*/
public HttpRequestPathHelper getPathHelper() {
return this.pathHelper;
}
/**
* Configure a {@code PathMatcher} to use when comparing target lookup path
* against resource mappings.

40
spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.web.reactive.result.method;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -27,6 +28,7 @@ import java.util.List; @@ -27,6 +28,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import org.springframework.http.HttpHeaders;
@ -34,7 +36,9 @@ import org.springframework.http.HttpMethod; @@ -34,7 +36,9 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.result.condition.NameValueExpression;
@ -123,7 +127,9 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe @@ -123,7 +127,9 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
// Now decode URI variables
if (!uriVariables.isEmpty()) {
uriVariables = getPathHelper().decodePathVariables(exchange, uriVariables);
uriVariables = uriVariables.entrySet().stream().collect(Collectors.toMap(
Entry::getKey, e -> StringUtils.uriDecode(e.getValue(), StandardCharsets.UTF_8)
));
}
exchange.getAttributes().put(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
@ -156,11 +162,41 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe @@ -156,11 +162,41 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
semicolonContent = uriVarValue.substring(semicolonIndex + 1);
uriVariables.put(uriVar.getKey(), uriVarValue.substring(0, semicolonIndex));
}
result.put(uriVar.getKey(), getPathHelper().parseMatrixVariables(exchange, semicolonContent));
result.put(uriVar.getKey(), parseMatrixVariables(exchange, semicolonContent));
}
return result;
}
private static MultiValueMap<String, String> parseMatrixVariables(ServerWebExchange exchange,
String semicolonContent) {
MultiValueMap<String, String> vars = new LinkedMultiValueMap<>();
if (!StringUtils.hasText(semicolonContent)) {
return vars;
}
StringTokenizer pairs = new StringTokenizer(semicolonContent, ";");
while (pairs.hasMoreTokens()) {
String pair = pairs.nextToken();
int index = pair.indexOf('=');
if (index != -1) {
String name = pair.substring(0, index);
String rawValue = pair.substring(index + 1);
for (String value : StringUtils.commaDelimitedListToStringArray(rawValue)) {
vars.add(name, value);
}
}
else {
vars.add(pair, "");
}
}
MultiValueMap<String, String> decoded = new LinkedMultiValueMap<>(vars.size());
vars.forEach((key, values) -> values.forEach(value -> {
String decodedValue = StringUtils.uriDecode(value, StandardCharsets.UTF_8);
decoded.add(key, decodedValue);
}));
return decoded;
}
/**
* Iterate all RequestMappingInfos once again, look if any match by URL at
* least and raise exceptions accordingly.

7
spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java

@ -63,7 +63,11 @@ import org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewRes @@ -63,7 +63,11 @@ import org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewRes
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebHandler;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.springframework.core.ResolvableType.forClass;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
@ -250,7 +254,6 @@ public class WebFluxConfigurationSupportTests { @@ -250,7 +254,6 @@ public class WebFluxConfigurationSupportTests {
assertEquals(Ordered.LOWEST_PRECEDENCE - 1, handlerMapping.getOrder());
assertNotNull(handlerMapping.getPathHelper());
assertNotNull(handlerMapping.getPathMatcher());
SimpleUrlHandlerMapping urlHandlerMapping = (SimpleUrlHandlerMapping) handlerMapping;

1
spring-webflux/src/test/resources/org/springframework/web/reactive/handler/map.xml

@ -4,7 +4,6 @@ @@ -4,7 +4,6 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="mapping" class="org.springframework.web.reactive.handler.SimpleUrlHandlerMapping">
<property name="urlDecode" value="true" />
<property name="mappings">
<value>
welcome.html=mainController

Loading…
Cancel
Save