Better support for overriding base URI in WebClient
The base URI is ignored for requests that include a host. WebClient exposes UriBuilder (rather than UriBuilderFactory) for per-request URI building based on the base URI. That provides full control to add or replace components of the base URI.
This commit is contained in:
parent
82a34f4b24
commit
1466c82f53
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
* <p>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.
|
||||
* <p>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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<UriBuilderFactory, URI> uriFunction) {
|
||||
return uri(uriFunction.apply(getUriBuilderFactory()));
|
||||
public HeaderSpec uri(Function<UriBuilder, URI> uriFunction) {
|
||||
return uri(uriFunction.apply(getUriBuilderFactory().builder()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>Given the following initialization:
|
||||
* <p>For example given this initialization:
|
||||
* <pre class="code">
|
||||
* WebClient client = WebClient.create("http://abc.com/v1");
|
||||
* </pre>
|
||||
*
|
||||
* <p>A base URI is applied when using a URI template:
|
||||
* <p>The base URI is applied to exchanges with a URI template:
|
||||
* <pre class="code">
|
||||
*
|
||||
* // 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));
|
||||
* </pre>
|
||||
*
|
||||
* <p>It is also applied when using a {@link UriBuilderFactory}:
|
||||
* <p>The base URI is also applied to exchanges with a {@code UriBuilder}:
|
||||
* <pre class="code">
|
||||
|
||||
* // 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));
|
||||
* </pre>
|
||||
*
|
||||
* <p>The base URI can be overridden with an absolute URI:
|
||||
* <pre class="code">
|
||||
* // GET http://xyz.com/path
|
||||
* Mono<Account> result = client.get()
|
||||
* .uri("http://xyz.com/path")
|
||||
* .exchange()
|
||||
* .then(response -> response.bodyToMono(Account.class));
|
||||
* </pre>
|
||||
*
|
||||
* <p>The base URI can be partially overridden with a {@code UriBuilder}:
|
||||
* <pre class="code">
|
||||
* // 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));
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @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<UriBuilderFactory, URI> uriFunction);
|
||||
HeaderSpec uri(Function<UriBuilder, URI> uriFunction);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue