diff --git a/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java b/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java
index 8daaf768a01..cfb5e7ac3f8 100644
--- a/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java
+++ b/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java
@@ -48,6 +48,8 @@ public class DefaultUriBuilderFactory implements UriBuilderFactory {
private EncodingMode encodingMode = EncodingMode.URI_COMPONENT;
+ private boolean parsePath = true;
+
/**
* Default constructor without a base URI.
@@ -126,6 +128,28 @@ public class DefaultUriBuilderFactory implements UriBuilderFactory {
return this.encodingMode;
}
+ /**
+ * Whether to parse the path into path segments for the URI string passed
+ * into {@link #uriString(String)} or one of the expand methods.
+ *
Setting this property to {@code true} ensures that URI variables
+ * expanded into the path are subject to path segment encoding rules and
+ * "/" characters are percent-encoded. If set to {@code false} the path is
+ * kept as a full path and expanded URI variables will have "/" characters
+ * preserved.
+ *
By default this is set to {@code true}.
+ * @param parsePath whether to parse the path into path segments
+ */
+ public void setParsePath(boolean parsePath) {
+ this.parsePath = parsePath;
+ }
+
+ /**
+ * Whether the handler is configured to parse the path into path segments.
+ */
+ public boolean shouldParsePath() {
+ return this.parsePath;
+ }
+
// UriTemplateHandler
@@ -158,16 +182,22 @@ public class DefaultUriBuilderFactory implements UriBuilderFactory {
private UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) {
- // Merge base URI with child URI template
UriComponentsBuilder result = baseUri.cloneBuilder();
UriComponents child = UriComponentsBuilder.fromUriString(uriTemplate).build();
result.uriComponents(child);
- // Split path into path segments
- List pathList = result.build().getPathSegments();
- String[] pathArray = pathList.toArray(new String[pathList.size()]);
- result.replacePath(null);
- result.pathSegment(pathArray);
+ if (shouldParsePath()) {
+ UriComponents uric = result.build();
+ String path = uric.getPath();
+ List pathSegments = uric.getPathSegments();
+
+ result.replacePath(null);
+ result.pathSegment(pathSegments.toArray(new String[0]));
+
+ if (path != null && path.endsWith("/")) {
+ result.path("/");
+ }
+ }
return result;
}
diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java
index 1ada39213a4..f153174f576 100644
--- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java
+++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2016 the original author or authors.
+ * 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.
@@ -153,6 +153,21 @@ public class RestTemplateTests {
verify(response).close();
}
+ @Test // SPR-15201
+ public void uriTemplateWithTrailingSlash() throws Exception {
+ String url = "http://example.com/spring/";
+ given(requestFactory.createRequest(new URI(url), HttpMethod.GET)).willReturn(request);
+ given(request.execute()).willReturn(response);
+ given(errorHandler.hasError(response)).willReturn(false);
+ HttpStatus status = HttpStatus.OK;
+ given(response.getStatusCode()).willReturn(status);
+ given(response.getStatusText()).willReturn(status.getReasonPhrase());
+
+ template.execute(url, HttpMethod.GET, null, null);
+
+ verify(response).close();
+ }
+
@Test
public void errorHandling() throws Exception {
given(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET)).willReturn(request);
diff --git a/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java b/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java
index c7481d7de51..cab2ad614b3 100644
--- a/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java
+++ b/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java
@@ -127,10 +127,32 @@ public class DefaultUriBuilderFactoryTests {
}
@Test
- public void initialPathSplitIntoPathSegments() throws Exception {
+ public void parsePathWithDefaultSettings() throws Exception {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("/foo/{bar}");
URI uri = factory.uriString("/baz/{id}").build("a/b", "c/d");
assertEquals("/foo/a%2Fb/baz/c%2Fd", uri.toString());
}
+ @Test
+ public void parsePathIsTurnedOff() throws Exception {
+ DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("/foo/{bar}");
+ factory.setParsePath(false);
+ URI uri = factory.uriString("/baz/{id}").build("a/b", "c/d");
+ assertEquals("/foo/a/b/baz/c/d", uri.toString());
+ }
+
+ @Test // SPR-15201
+ public void pathWithTrailingSlash() throws Exception {
+ DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
+ URI uri = factory.expand("http://localhost:8080/spring/");
+ assertEquals("http://localhost:8080/spring/", uri.toString());
+ }
+
+ @Test
+ public void pathWithDuplicateSlashes() throws Exception {
+ DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
+ URI uri = factory.expand("/foo/////////bar");
+ assertEquals("/foo/bar", uri.toString());
+ }
+
}