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 cfb5e7ac3f..b74e2e13ec 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 @@ -167,6 +167,11 @@ public class DefaultUriBuilderFactory implements UriBuilderFactory { return new DefaultUriBuilder(uriTemplate); } + @Override + public UriBuilder builder() { + return new DefaultUriBuilder(""); + } + /** * {@link DefaultUriBuilderFactory} specific implementation of UriBuilder. @@ -182,9 +187,11 @@ public class DefaultUriBuilderFactory implements UriBuilderFactory { private UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) { - UriComponentsBuilder result = baseUri.cloneBuilder(); - UriComponents child = UriComponentsBuilder.fromUriString(uriTemplate).build(); - result.uriComponents(child); + UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(uriTemplate); + UriComponents uriComponents = uriComponentsBuilder.build(); + + UriComponentsBuilder result = (uriComponents.getHost() == null ? + baseUri.cloneBuilder().uriComponents(uriComponents) : uriComponentsBuilder); if (shouldParsePath()) { UriComponents uric = result.build(); diff --git a/spring-web/src/main/java/org/springframework/web/util/UriBuilderFactory.java b/spring-web/src/main/java/org/springframework/web/util/UriBuilderFactory.java index 504a7d5092..8eec565720 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriBuilderFactory.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriBuilderFactory.java @@ -30,14 +30,18 @@ package org.springframework.web.util; public interface UriBuilderFactory extends UriTemplateHandler { /** - * Return a builder that is initialized with the given URI string which may - * be a URI template and represent full URI or just a path. - *

Depending on the factory implementation and configuration, the builder - * may merge the given URI string with a base URI and apply other operations. - * Refer to the specific factory implementation for details. - * @param uriTemplate the URI template to create the builder with + * Return a builder initialized with the given URI string. + *

Concrete implementations may apply further initializations such as + * combining with a pre-configured base URI. + * @param uriTemplate the URI template to initialize the builder with * @return the UriBuilder */ UriBuilder uriString(String uriTemplate); + /** + * Return a builder to prepare a new URI. + * @return the UriBuilder + */ + UriBuilder builder(); + } 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 cab2ad614b..a6aae94dde 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 @@ -41,40 +41,48 @@ public class DefaultUriBuilderFactoryTests { @Test public void baseUri() throws Exception { - DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://foo.com/bar?id=123"); - URI uri = factory.uriString("/baz").port(8080).build(); - assertEquals("http://foo.com:8080/bar/baz?id=123", uri.toString()); + DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://foo.com/v1?id=123"); + URI uri = factory.uriString("/bar").port(8080).build(); + assertEquals("http://foo.com:8080/v1/bar?id=123", uri.toString()); + } + + @Test + public void baseUriWithFullOverride() throws Exception { + DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://foo.com/v1?id=123"); + URI uri = factory.uriString("http://example.com/1/2").build(); + assertEquals("Use of host should case baseUri to be completely ignored", + "http://example.com/1/2", uri.toString()); } @Test public void baseUriWithPathOverride() throws Exception { - DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://foo.com/bar"); - URI uri = factory.uriString("").replacePath("/baz").build(); + DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://foo.com/v1"); + URI uri = factory.builder().replacePath("/baz").build(); assertEquals("http://foo.com/baz", uri.toString()); } @Test public void defaultUriVars() throws Exception { - DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://{host}/bar"); + DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://{host}/v1"); factory.setDefaultUriVariables(singletonMap("host", "foo.com")); URI uri = factory.uriString("/{id}").build(singletonMap("id", "123")); - assertEquals("http://foo.com/bar/123", uri.toString()); + assertEquals("http://foo.com/v1/123", uri.toString()); } @Test public void defaultUriVarsWithOverride() throws Exception { - DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://{host}/bar"); + DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://{host}/v1"); factory.setDefaultUriVariables(singletonMap("host", "spring.io")); - URI uri = factory.uriString("/baz").build(singletonMap("host", "docs.spring.io")); - assertEquals("http://docs.spring.io/bar/baz", uri.toString()); + URI uri = factory.uriString("/bar").build(singletonMap("host", "docs.spring.io")); + assertEquals("http://docs.spring.io/v1/bar", uri.toString()); } @Test public void defaultUriVarsWithEmptyVarArg() throws Exception { - DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://{host}/bar"); + DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://{host}/v1"); factory.setDefaultUriVariables(singletonMap("host", "foo.com")); - URI uri = factory.uriString("/baz").build(); - assertEquals("Expected delegation to build(Map) method", "http://foo.com/bar/baz", uri.toString()); + URI uri = factory.uriString("/bar").build(); + assertEquals("Expected delegation to build(Map) method", "http://foo.com/v1/bar", uri.toString()); } @Test diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java index 0a6d365659..24dca9f29d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java @@ -38,6 +38,7 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.util.DefaultUriBuilderFactory; +import org.springframework.web.util.UriBuilder; import org.springframework.web.util.UriBuilderFactory; @@ -150,8 +151,8 @@ class DefaultWebClient implements WebClient { } @Override - public HeaderSpec uri(Function uriFunction) { - return uri(uriFunction.apply(getUriBuilderFactory())); + public HeaderSpec uri(Function uriFunction) { + return uri(uriFunction.apply(getUriBuilderFactory().builder())); } @Override diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java index 134c905462..72effb76ce 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java @@ -31,6 +31,7 @@ import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ClientHttpRequest; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserter; +import org.springframework.web.util.UriBuilder; import org.springframework.web.util.UriBuilderFactory; /** @@ -113,6 +114,7 @@ public interface WebClient { /** * Create a new {@code WebClient} with no default, shared preferences across * requests such as base URI, default headers, and others. + * @see #create(String) */ static WebClient create() { return new DefaultWebClientBuilder().build(); @@ -123,33 +125,48 @@ public interface WebClient { * example to avoid repeating the same host, port, base path, or even * query parameters with every request. * - *

Given the following initialization: + *

For example given this initialization: *

 	 * WebClient client = WebClient.create("http://abc.com/v1");
 	 * 
* - *

A base URI is applied when using a URI template: + *

The base URI is applied to exchanges with a URI template: *

-	 *
 	 * // GET http://abc.com/v1/accounts/43
-	 *
 	 * Mono<Account> result = client.get()
 	 *         .uri("/accounts/{id}", 43)
 	 *         .exchange()
-	 *         .then(response -> response.bodyToMono(String.class));
+	 *         .then(response -> response.bodyToMono(Account.class));
 	 * 
* - *

It is also applied when using a {@link UriBuilderFactory}: + *

The base URI is also applied to exchanges with a {@code UriBuilder}: *

-
 	 * // GET http://abc.com/v1/accounts?q=12
-	 *
-	 * Mono<Account> result = client.get()
-	 *         .uri(factory -> factory.uriString("/accounts").queryParam("q", "12").build())
+	 * Flux<Account> result = client.get()
+	 *         .uri(builder -> builder.path("/accounts").queryParam("q", "12").build())
 	 *         .exchange()
-	 *         .then(response -> response.bodyToMono(String.class));
+	 *         .then(response -> response.bodyToFlux(Account.class));
 	 * 
* + *

The base URI can be overridden with an absolute URI: + *

+	 * // GET http://xyz.com/path
+	 * Mono<Account> result = client.get()
+	 *         .uri("http://xyz.com/path")
+	 *         .exchange()
+	 *         .then(response -> response.bodyToMono(Account.class));
+	 * 
+ * + *

The base URI can be partially overridden with a {@code UriBuilder}: + *

+	 * // GET http://abc.com/v2/accounts?q=12
+	 * Flux<Account> result = client.get()
+	 *         .uri(builder -> builder.replacePath("/v2/accounts").queryParam("q", "12").build())
+	 *         .exchange()
+	 *         .then(response -> response.bodyToFlux(Account.class));
+	 * 
+ * + * * @param baseUrl the base URI for all requests */ static WebClient create(String baseUrl) { @@ -284,7 +301,7 @@ public interface WebClient { * Build the URI for the request using the {@link UriBuilderFactory} * configured for this client. */ - HeaderSpec uri(Function uriFunction); + HeaderSpec uri(Function uriFunction); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java index 4e39d93b4d..408d1bae98 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java @@ -64,6 +64,26 @@ public class DefaultWebClientTests { assertEquals(Collections.emptyMap(), request.cookies()); } + @Test + public void uriBuilder() throws Exception { + WebClient client = builder().build(); + client.get().uri(builder -> builder.path("/path").queryParam("q", "12").build()).exchange(); + + ClientRequest request = verifyExchange(); + assertEquals("/base/path?q=12", request.url().toString()); + verifyNoMoreInteractions(this.exchangeFunction); + } + + @Test + public void uriBuilderWithPathOverride() throws Exception { + WebClient client = builder().build(); + client.get().uri(builder -> builder.replacePath("/path").build()).exchange(); + + ClientRequest request = verifyExchange(); + assertEquals("/path", request.url().toString()); + verifyNoMoreInteractions(this.exchangeFunction); + } + @Test public void requestHeaderAndCookie() throws Exception { WebClient client = builder().build();