diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java index 39a9585bdb..4cf07b62e1 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java @@ -341,7 +341,13 @@ final class DefaultRestClient implements RestClient { @Override public RequestBodySpec uri(URI uri) { - this.uri = uri; + if (uri.isAbsolute()) { + this.uri = uri; + } + else { + URI baseUri = DefaultRestClient.this.uriBuilderFactory.expand(""); + this.uri = baseUri.resolve(uri); + } return this; } diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java index e2ae3b6867..a0009ec1aa 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java @@ -16,6 +16,7 @@ package org.springframework.web.client; +import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; @@ -245,6 +246,12 @@ final class DefaultRestClientBuilder implements RestClient.Builder { return this; } + @Override + public RestClient.Builder baseUrl(URI baseUrl) { + this.baseUrl = baseUrl.toString(); + return this; + } + @Override public RestClient.Builder defaultUriVariables(Map defaultUriVariables) { this.defaultUriVariables = defaultUriVariables; diff --git a/spring-web/src/main/java/org/springframework/web/client/RestClient.java b/spring-web/src/main/java/org/springframework/web/client/RestClient.java index ef995daff8..71254a216e 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestClient.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestClient.java @@ -154,6 +154,17 @@ public interface RestClient { return new DefaultRestClientBuilder().baseUrl(baseUrl).build(); } + /** + * Variant of {@link #create()} that accepts a default base {@code URI}. For more + * details see {@link Builder#baseUrl(URI) Builder.baseUrl(URI)}. + * @param baseUrl the base URI for all requests + * @since 6.2 + * @see #builder() + */ + static RestClient create(URI baseUrl) { + return new DefaultRestClientBuilder().baseUrl(baseUrl).build(); + } + /** * Create a new {@code RestClient} based on the configuration of the given * {@code RestTemplate}. @@ -230,6 +241,26 @@ public interface RestClient { */ Builder baseUrl(String baseUrl); + /** + * Configure a base {@code URI} for requests. Effectively a shortcut for: + *
+		 * URI baseUrl = URI.create("https://abc.go.com/v1");
+		 * DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl.toString());
+		 * RestClient client = RestClient.builder().uriBuilderFactory(factory).build();
+		 * 
+ *

The {@code DefaultUriBuilderFactory} is used to prepare the URL + * for every request with the given base URL, unless the URL request + * for a given URL is absolute in which case the base URL is ignored. + *

Note: this method is mutually exclusive with + * {@link #uriBuilderFactory(UriBuilderFactory)}. If both are used, the + * {@code baseUrl} value provided here will be ignored. + * @return this builder + * @since 6.2 + * @see DefaultUriBuilderFactory#DefaultUriBuilderFactory(String) + * @see #uriBuilderFactory(UriBuilderFactory) + */ + Builder baseUrl(URI baseUrl); + /** * Configure default URL variable values to use when expanding URI * templates with a {@link Map}. Effectively a shortcut for: @@ -414,7 +445,11 @@ public interface RestClient { interface UriSpec> { /** - * Specify the URI using an absolute, fully constructed {@link URI}. + * Specify the URI using a fully constructed {@link URI}. + *

If the given URI is absolute, it is used as given. If it is + * a relative URI, the {@link UriBuilderFactory} configured for + * the client (e.g. with a base URI) will be used to + * {@linkplain URI#resolve(URI) resolve} the given URI against. */ S uri(URI uri); diff --git a/spring-web/src/test/java/org/springframework/web/client/RestClientBuilderTests.java b/spring-web/src/test/java/org/springframework/web/client/RestClientBuilderTests.java index 3a4f43ff84..001ed22144 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestClientBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestClientBuilderTests.java @@ -17,6 +17,7 @@ package org.springframework.web.client; import java.lang.reflect.Field; +import java.net.URI; import java.util.List; import org.junit.jupiter.api.Test; @@ -90,6 +91,18 @@ public class RestClientBuilderTests { assertThat(fieldValue("uriBuilderFactory", defaultBuilder)).isNull(); } + @Test + void defaultUri() { + URI baseUrl = URI.create("https://example.org"); + RestClient.Builder builder = RestClient.builder(); + builder.baseUrl(baseUrl); + + assertThat(builder).isInstanceOf(DefaultRestClientBuilder.class); + DefaultRestClientBuilder defaultBuilder = (DefaultRestClientBuilder) builder; + + assertThat(fieldValue("baseUrl", defaultBuilder)).isEqualTo(baseUrl.toString()); + } + @Nullable private static Object fieldValue(String name, DefaultRestClientBuilder instance) { try { diff --git a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java index 0b0542785f..48d83e6c45 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java @@ -21,6 +21,8 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; @@ -923,6 +925,28 @@ class RestClientIntegrationTests { expectRequest(request -> assertThat(request.getHeader("Accept")).isEqualTo(MediaType.TEXT_PLAIN_VALUE)); } + @ParameterizedRestClientTest + void relativeUri(ClientHttpRequestFactory requestFactory) throws URISyntaxException { + startServer(requestFactory); + + prepareResponse(response -> response.setHeader("Content-Type", "text/plain") + .setBody("Hello Spring!")); + + URI uri = new URI(null, null, "/foo bar", null); + + String result = this.restClient + .get() + .uri(uri) + .accept(MediaType.TEXT_PLAIN) + .retrieve() + .body(String.class); + + assertThat(result).isEqualTo("Hello Spring!"); + + expectRequestCount(1); + expectRequest(request -> assertThat(request.getPath()).isEqualTo("/foo%20bar")); + } + private void prepareResponse(Consumer consumer) { MockResponse response = new MockResponse();