Browse Source
Update `RestClient`, `WebClient`, Spring MVC and Spring WebFlux auto-configuration to support API versioning. Closes gh-46519pull/46602/head
32 changed files with 1640 additions and 56 deletions
@ -0,0 +1,98 @@
@@ -0,0 +1,98 @@
|
||||
/* |
||||
* Copyright 2012-present 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 |
||||
* |
||||
* https://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.boot.http.client.autoconfigure; |
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesSource; |
||||
import org.springframework.boot.context.properties.bind.Name; |
||||
|
||||
/** |
||||
* API Version properties for reactive and blocking HTTP Clients. |
||||
* |
||||
* @author Phillip Webb |
||||
* @since 4.0.0 |
||||
*/ |
||||
@ConfigurationPropertiesSource |
||||
public class ApiversionProperties { |
||||
|
||||
/** |
||||
* Default version that should be used for each request. |
||||
*/ |
||||
@Name("default") |
||||
private String defaultVersion; |
||||
|
||||
/** |
||||
* How version details should be inserted into requests. |
||||
*/ |
||||
private final Insert insert = new Insert(); |
||||
|
||||
public String getDefaultVersion() { |
||||
return this.defaultVersion; |
||||
} |
||||
|
||||
public void setDefaultVersion(String defaultVersion) { |
||||
this.defaultVersion = defaultVersion; |
||||
} |
||||
|
||||
public Insert getInsert() { |
||||
return this.insert; |
||||
} |
||||
|
||||
@ConfigurationPropertiesSource |
||||
public static class Insert { |
||||
|
||||
/** |
||||
* Insert the version into a header with the given name. |
||||
*/ |
||||
private String header; |
||||
|
||||
/** |
||||
* Insert the version into a query parameter with the given name. |
||||
*/ |
||||
private String queryParameter; |
||||
|
||||
/** |
||||
* Insert the version into a path segment at the given index. |
||||
*/ |
||||
private Integer pathSegment; |
||||
|
||||
public String getHeader() { |
||||
return this.header; |
||||
} |
||||
|
||||
public void setHeader(String header) { |
||||
this.header = header; |
||||
} |
||||
|
||||
public String getQueryParameter() { |
||||
return this.queryParameter; |
||||
} |
||||
|
||||
public void setQueryParameter(String queryParameter) { |
||||
this.queryParameter = queryParameter; |
||||
} |
||||
|
||||
public Integer getPathSegment() { |
||||
return this.pathSegment; |
||||
} |
||||
|
||||
public void setPathSegment(Integer pathSegment) { |
||||
this.pathSegment = pathSegment; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,127 @@
@@ -0,0 +1,127 @@
|
||||
/* |
||||
* Copyright 2012-present 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 |
||||
* |
||||
* https://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.boot.http.client.autoconfigure; |
||||
|
||||
import java.net.URI; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.stream.Stream; |
||||
|
||||
import org.springframework.boot.context.properties.PropertyMapper; |
||||
import org.springframework.boot.http.client.autoconfigure.ApiversionProperties.Insert; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.web.client.ApiVersionFormatter; |
||||
import org.springframework.web.client.ApiVersionInserter; |
||||
|
||||
/** |
||||
* {@link ApiVersionInserter} to apply {@link ApiversionProperties}. |
||||
* |
||||
* @author Phillip Webb |
||||
* @since 4.0.0 |
||||
*/ |
||||
public final class PropertiesApiVersionInserter implements ApiVersionInserter { |
||||
|
||||
private final List<ApiVersionInserter> inserters; |
||||
|
||||
private PropertiesApiVersionInserter(List<ApiVersionInserter> inserters) { |
||||
this.inserters = inserters; |
||||
} |
||||
|
||||
@Override |
||||
public URI insertVersion(Object version, URI uri) { |
||||
for (ApiVersionInserter delegate : this.inserters) { |
||||
uri = delegate.insertVersion(version, uri); |
||||
} |
||||
return uri; |
||||
} |
||||
|
||||
@Override |
||||
public void insertVersion(Object version, HttpHeaders headers) { |
||||
for (ApiVersionInserter delegate : this.inserters) { |
||||
delegate.insertVersion(version, headers); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Factory method that returns an {@link ApiVersionInserter} to apply the given |
||||
* properties and delegate. |
||||
* @param apiVersionInserter a delegate {@link ApiVersionInserter} that should also |
||||
* apply (may be {@code null}) |
||||
* @param apiVersionFormatter the version formatter to use or {@code null} |
||||
* @param properties the properties that should be applied |
||||
* @return an {@link ApiVersionInserter} or {@code null} if no API version should be |
||||
* inserted |
||||
*/ |
||||
public static ApiVersionInserter get(ApiVersionInserter apiVersionInserter, ApiVersionFormatter apiVersionFormatter, |
||||
ApiversionProperties... properties) { |
||||
return get(apiVersionInserter, apiVersionFormatter, Arrays.stream(properties)); |
||||
} |
||||
|
||||
/** |
||||
* Factory method that returns an {@link ApiVersionInserter} to apply the given |
||||
* properties and delegate. |
||||
* @param apiVersionInserter a delegate {@link ApiVersionInserter} that should also |
||||
* apply (may be {@code null}) |
||||
* @param apiVersionFormatter the version formatter to use or {@code null} |
||||
* @param propertiesStream the properties that should be applied |
||||
* @return an {@link ApiVersionInserter} or {@code null} if no API version should be |
||||
* inserted |
||||
*/ |
||||
public static ApiVersionInserter get(ApiVersionInserter apiVersionInserter, ApiVersionFormatter apiVersionFormatter, |
||||
Stream<ApiversionProperties> propertiesStream) { |
||||
List<ApiVersionInserter> inserters = new ArrayList<>(); |
||||
if (apiVersionInserter != null) { |
||||
inserters.add(apiVersionInserter); |
||||
} |
||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); |
||||
propertiesStream.forEach((properties) -> { |
||||
if (properties != null && properties.getInsert() != null) { |
||||
Insert insert = properties.getInsert(); |
||||
Counter counter = new Counter(); |
||||
ApiVersionInserter.Builder builder = ApiVersionInserter.builder(); |
||||
map.from(apiVersionFormatter).to(builder::withVersionFormatter); |
||||
map.from(insert::getHeader).whenHasText().as(counter::counted).to(builder::useHeader); |
||||
map.from(insert::getQueryParameter).whenHasText().as(counter::counted).to(builder::useQueryParam); |
||||
map.from(insert::getPathSegment).as(counter::counted).to(builder::usePathSegment); |
||||
if (!counter.isEmpty()) { |
||||
inserters.add(builder.build()); |
||||
} |
||||
} |
||||
}); |
||||
return (!inserters.isEmpty()) ? new PropertiesApiVersionInserter(inserters) : null; |
||||
} |
||||
|
||||
/** |
||||
* Internal counter used to track if properties were applied. |
||||
*/ |
||||
private static final class Counter { |
||||
|
||||
private boolean empty = true; |
||||
|
||||
<T> T counted(T value) { |
||||
this.empty = false; |
||||
return value; |
||||
} |
||||
|
||||
boolean isEmpty() { |
||||
return this.empty; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,91 @@
@@ -0,0 +1,91 @@
|
||||
/* |
||||
* Copyright 2012-present 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 |
||||
* |
||||
* https://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.boot.http.client.autoconfigure; |
||||
|
||||
import java.net.URI; |
||||
import java.util.Locale; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.web.client.ApiVersionFormatter; |
||||
import org.springframework.web.client.ApiVersionInserter; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Tests for {@link PropertiesApiVersionInserter}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
class PropertiesApiVersionInserterTests { |
||||
|
||||
@Test |
||||
void getWhenEmptyPropertiesArrayAndNoDeleteReturnsNull() { |
||||
assertThat(PropertiesApiVersionInserter.get(null, null)).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void getWhenNoPropertiesAndNoDelegateReturnsNull() { |
||||
assertThat(PropertiesApiVersionInserter.get(null, null, new ApiversionProperties(), new ApiversionProperties())) |
||||
.isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void getWhenNoPropertiesAndDelegateUsesDelegate() throws Exception { |
||||
ApiVersionInserter inserter = PropertiesApiVersionInserter.get(ApiVersionInserter.useQueryParam("v"), null); |
||||
URI uri = new URI("https://example.com"); |
||||
assertThat(inserter.insertVersion("123", uri)).hasToString("https://example.com?v=123"); |
||||
} |
||||
|
||||
@Test |
||||
void getReturnsInserterThatAppliesProperties() throws Exception { |
||||
ApiversionProperties properties1 = new ApiversionProperties(); |
||||
properties1.getInsert().setHeader("x-test"); |
||||
properties1.getInsert().setQueryParameter("v1"); |
||||
ApiversionProperties properties2 = new ApiversionProperties(); |
||||
properties2.getInsert().setQueryParameter("v2"); |
||||
properties2.getInsert().setPathSegment(1); |
||||
ApiVersionInserter inserter = PropertiesApiVersionInserter.get(null, null, properties1, properties2); |
||||
URI uri = new URI("https://example.com/foo/bar"); |
||||
assertThat(inserter.insertVersion("123", uri)).hasToString("https://example.com/foo/123/bar?v1=123&v2=123"); |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
inserter.insertVersion("123", headers); |
||||
assertThat(headers.get("x-test")).containsExactly("123"); |
||||
} |
||||
|
||||
@Test |
||||
void getWhenHasDelegateReturnsInserterThatAppliesPropertiesAndDelegate() throws Exception { |
||||
ApiVersionInserter delegate = ApiVersionInserter.useQueryParam("d"); |
||||
ApiversionProperties properties = new ApiversionProperties(); |
||||
properties.getInsert().setQueryParameter("v"); |
||||
ApiVersionInserter inserter = PropertiesApiVersionInserter.get(delegate, null, properties); |
||||
assertThat(inserter.insertVersion("123", new URI("https://example.com"))) |
||||
.hasToString("https://example.com?d=123&v=123"); |
||||
} |
||||
|
||||
@Test |
||||
void getWhenHasFormatterAppliesToProperties() throws Exception { |
||||
ApiversionProperties properties1 = new ApiversionProperties(); |
||||
properties1.getInsert().setQueryParameter("v"); |
||||
ApiVersionFormatter formatter = (version) -> String.valueOf(version).toUpperCase(Locale.ROOT); |
||||
ApiVersionInserter inserter = PropertiesApiVersionInserter.get(null, formatter, properties1); |
||||
URI uri = new URI("https://example.com"); |
||||
assertThat(inserter.insertVersion("latest", uri)).hasToString("https://example.com?v=LATEST"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
/* |
||||
* Copyright 2012-present 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 |
||||
* |
||||
* https://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.boot.restclient.autoconfigure; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.function.Consumer; |
||||
|
||||
import org.springframework.boot.context.properties.PropertyMapper; |
||||
import org.springframework.boot.http.client.autoconfigure.ApiversionProperties; |
||||
import org.springframework.boot.http.client.autoconfigure.PropertiesApiVersionInserter; |
||||
import org.springframework.boot.restclient.RestClientCustomizer; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.web.client.ApiVersionFormatter; |
||||
import org.springframework.web.client.ApiVersionInserter; |
||||
import org.springframework.web.client.RestClient; |
||||
|
||||
/** |
||||
* {@link RestClientCustomizer} to apply {@link AbstractRestClientProperties}. |
||||
* |
||||
* @author Phillip Webb |
||||
* @since 4.0.0 |
||||
*/ |
||||
public class PropertiesRestClientCustomizer implements RestClientCustomizer { |
||||
|
||||
private final AbstractRestClientProperties[] orderedProperties; |
||||
|
||||
private ApiVersionInserter apiVersionInserter; |
||||
|
||||
public PropertiesRestClientCustomizer(ApiVersionInserter apiVersionInserter, |
||||
ApiVersionFormatter apiVersionFormatter, AbstractRestClientProperties... orderedProperties) { |
||||
this.orderedProperties = orderedProperties; |
||||
this.apiVersionInserter = PropertiesApiVersionInserter.get(apiVersionInserter, apiVersionFormatter, |
||||
Arrays.stream(orderedProperties).map(this::getApiVersion)); |
||||
} |
||||
|
||||
private ApiversionProperties getApiVersion(AbstractRestClientProperties properties) { |
||||
return (properties != null) ? properties.getApiversion() : null; |
||||
} |
||||
|
||||
@Override |
||||
public void customize(RestClient.Builder builder) { |
||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); |
||||
map.from(this.apiVersionInserter).to(builder::apiVersionInserter); |
||||
for (int i = this.orderedProperties.length - 1; i >= 0; i--) { |
||||
AbstractRestClientProperties properties = this.orderedProperties[i]; |
||||
if (properties != null) { |
||||
map.from(properties::getBaseUrl).whenHasText().to(builder::baseUrl); |
||||
map.from(properties::getDefaultHeader).as(this::putAllHeaders).to(builder::defaultHeaders); |
||||
map.from(properties.getApiversion()) |
||||
.as(ApiversionProperties::getDefaultVersion) |
||||
.to(builder::defaultApiVersion); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private Consumer<HttpHeaders> putAllHeaders(Map<String, List<String>> defaultHeaders) { |
||||
return (httpHeaders) -> httpHeaders.putAll(defaultHeaders); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
/* |
||||
* Copyright 2012-present 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 |
||||
* |
||||
* https://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.boot.restclient.autoconfigure; |
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
import org.springframework.web.client.RestClient; |
||||
|
||||
/** |
||||
* Properties for {@link RestClient}. |
||||
* |
||||
* @author Phillip Webb |
||||
* @since 4.0.0 |
||||
*/ |
||||
@ConfigurationProperties("spring.http.client.restclient") |
||||
public class RestClientProperties extends AbstractRestClientProperties { |
||||
|
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
/* |
||||
* Copyright 2012-present 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 |
||||
* |
||||
* https://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.boot.restclient.autoconfigure; |
||||
|
||||
import java.net.URI; |
||||
import java.util.List; |
||||
import java.util.Locale; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.test.util.ReflectionTestUtils; |
||||
import org.springframework.web.client.ApiVersionFormatter; |
||||
import org.springframework.web.client.ApiVersionInserter; |
||||
import org.springframework.web.client.RestClient; |
||||
import org.springframework.web.util.UriBuilderFactory; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Tests for {@link PropertiesRestClientCustomizer}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
class PropertiesRestClientCustomizerTests { |
||||
|
||||
@Test |
||||
void customizeAppliesPropertiesInOrder() throws Exception { |
||||
ApiVersionInserter delegateApiVersionInserter = ApiVersionInserter.useQueryParam("v"); |
||||
ApiVersionFormatter apiVersionFormatter = (version) -> String.valueOf(version).toUpperCase(Locale.ROOT); |
||||
TestRestClientProperties properties1 = new TestRestClientProperties(); |
||||
properties1.setBaseUrl("https://example.com/b1"); |
||||
properties1.getDefaultHeader().put("x-h1", List.of("v1")); |
||||
properties1.getApiversion().setDefaultVersion("dv1"); |
||||
properties1.getApiversion().getInsert().setQueryParameter("p1"); |
||||
TestRestClientProperties properties2 = new TestRestClientProperties(); |
||||
properties2.setBaseUrl("https://example.com/b2"); |
||||
properties1.getDefaultHeader().put("x-h2", List.of("v2")); |
||||
properties2.getApiversion().setDefaultVersion("dv2"); |
||||
PropertiesRestClientCustomizer customizer = new PropertiesRestClientCustomizer(delegateApiVersionInserter, |
||||
apiVersionFormatter, properties1, properties2); |
||||
RestClient.Builder builder = RestClient.builder(); |
||||
customizer.customize(builder); |
||||
RestClient client = builder.build(); |
||||
assertThat(client).extracting("defaultApiVersion").isEqualTo("dv1"); |
||||
UriBuilderFactory uriBuilderFactory = (UriBuilderFactory) ReflectionTestUtils.getField(client, |
||||
"uriBuilderFactory"); |
||||
assertThat(uriBuilderFactory.builder().build()).hasToString("https://example.com/b1"); |
||||
HttpHeaders defaultHeaders = (HttpHeaders) ReflectionTestUtils.getField(client, "defaultHeaders"); |
||||
assertThat(defaultHeaders.get("x-h1")).containsExactly("v1"); |
||||
assertThat(defaultHeaders.get("x-h2")).containsExactly("v2"); |
||||
ApiVersionInserter apiVersionInserter = (ApiVersionInserter) ReflectionTestUtils.getField(client, |
||||
"apiVersionInserter"); |
||||
assertThat(apiVersionInserter.insertVersion("v123", new URI("https://example.com"))) |
||||
.hasToString("https://example.com?v=v123&p1=V123"); |
||||
} |
||||
|
||||
static class TestRestClientProperties extends AbstractRestClientProperties { |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
/* |
||||
* Copyright 2012-present 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 |
||||
* |
||||
* https://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.boot.webclient.autoconfigure; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.function.Consumer; |
||||
|
||||
import org.springframework.boot.context.properties.PropertyMapper; |
||||
import org.springframework.boot.http.client.autoconfigure.ApiversionProperties; |
||||
import org.springframework.boot.http.client.autoconfigure.PropertiesApiVersionInserter; |
||||
import org.springframework.boot.webclient.WebClientCustomizer; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.web.client.ApiVersionFormatter; |
||||
import org.springframework.web.client.ApiVersionInserter; |
||||
import org.springframework.web.reactive.function.client.WebClient; |
||||
|
||||
/** |
||||
* {@link WebClientCustomizer} to apply {@link AbstractWebClientProperties}. |
||||
* |
||||
* @author Phillip Webb |
||||
* @since 4.0.0 |
||||
*/ |
||||
public class PropertiesWebClientCustomizer implements WebClientCustomizer { |
||||
|
||||
private final AbstractWebClientProperties[] orderedProperties; |
||||
|
||||
private ApiVersionInserter apiVersionInserter; |
||||
|
||||
public PropertiesWebClientCustomizer(ApiVersionInserter apiVersionInserter, ApiVersionFormatter apiVersionFormatter, |
||||
AbstractWebClientProperties... orderedProperties) { |
||||
this.orderedProperties = orderedProperties; |
||||
this.apiVersionInserter = PropertiesApiVersionInserter.get(apiVersionInserter, apiVersionFormatter, |
||||
Arrays.stream(orderedProperties).map(this::getApiVersion)); |
||||
} |
||||
|
||||
private ApiversionProperties getApiVersion(AbstractWebClientProperties properties) { |
||||
return (properties != null) ? properties.getApiversion() : null; |
||||
} |
||||
|
||||
@Override |
||||
public void customize(WebClient.Builder builder) { |
||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); |
||||
map.from(this.apiVersionInserter).to(builder::apiVersionInserter); |
||||
for (int i = this.orderedProperties.length - 1; i >= 0; i--) { |
||||
AbstractWebClientProperties properties = this.orderedProperties[i]; |
||||
if (properties != null) { |
||||
map.from(properties::getBaseUrl).whenHasText().to(builder::baseUrl); |
||||
map.from(properties::getDefaultHeader).as(this::putAllHeaders).to(builder::defaultHeaders); |
||||
map.from(properties.getApiversion()) |
||||
.as(ApiversionProperties::getDefaultVersion) |
||||
.to(builder::defaultApiVersion); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private Consumer<HttpHeaders> putAllHeaders(Map<String, List<String>> defaultHeaders) { |
||||
return (httpHeaders) -> httpHeaders.putAll(defaultHeaders); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
/* |
||||
* Copyright 2012-present 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 |
||||
* |
||||
* https://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.boot.webclient.autoconfigure; |
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
import org.springframework.web.reactive.function.client.WebClient; |
||||
|
||||
/** |
||||
* Properties for {@link WebClient}. |
||||
* |
||||
* @author Phillip Webb |
||||
* @since 4.0.0 |
||||
*/ |
||||
@ConfigurationProperties("spring.http.reactiveclient.webclient") |
||||
public class WebClientProperties extends AbstractWebClientProperties { |
||||
|
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
/* |
||||
* Copyright 2012-present 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 |
||||
* |
||||
* https://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.boot.webclient.autoconfigure; |
||||
|
||||
import java.net.URI; |
||||
import java.util.List; |
||||
import java.util.Locale; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.test.util.ReflectionTestUtils; |
||||
import org.springframework.web.client.ApiVersionFormatter; |
||||
import org.springframework.web.client.ApiVersionInserter; |
||||
import org.springframework.web.reactive.function.client.WebClient; |
||||
import org.springframework.web.util.UriBuilderFactory; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Tests for {@link PropertiesWebClientCustomizer}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
class PropertiesWebClientCustomizerTests { |
||||
|
||||
@Test |
||||
void customizeAppliesPropertiesInOrder() throws Exception { |
||||
ApiVersionInserter delegateApiVersionInserter = ApiVersionInserter.useQueryParam("v"); |
||||
ApiVersionFormatter apiVersionFormatter = (version) -> String.valueOf(version).toUpperCase(Locale.ROOT); |
||||
TestWebClientProperties properties1 = new TestWebClientProperties(); |
||||
properties1.setBaseUrl("https://example.com/b1"); |
||||
properties1.getDefaultHeader().put("x-h1", List.of("v1")); |
||||
properties1.getApiversion().setDefaultVersion("dv1"); |
||||
properties1.getApiversion().getInsert().setQueryParameter("p1"); |
||||
TestWebClientProperties properties2 = new TestWebClientProperties(); |
||||
properties2.setBaseUrl("https://example.com/b2"); |
||||
properties1.getDefaultHeader().put("x-h2", List.of("v2")); |
||||
properties2.getApiversion().setDefaultVersion("dv2"); |
||||
PropertiesWebClientCustomizer customizer = new PropertiesWebClientCustomizer(delegateApiVersionInserter, |
||||
apiVersionFormatter, properties1, properties2); |
||||
WebClient.Builder builder = WebClient.builder(); |
||||
customizer.customize(builder); |
||||
WebClient client = builder.build(); |
||||
assertThat(client).extracting("defaultApiVersion").isEqualTo("dv1"); |
||||
UriBuilderFactory uriBuilderFactory = (UriBuilderFactory) ReflectionTestUtils.getField(client, |
||||
"uriBuilderFactory"); |
||||
assertThat(uriBuilderFactory.builder().build()).hasToString("https://example.com/b1"); |
||||
HttpHeaders defaultHeaders = (HttpHeaders) ReflectionTestUtils.getField(client, "defaultHeaders"); |
||||
assertThat(defaultHeaders.get("x-h1")).containsExactly("v1"); |
||||
assertThat(defaultHeaders.get("x-h2")).containsExactly("v2"); |
||||
ApiVersionInserter apiVersionInserter = (ApiVersionInserter) ReflectionTestUtils.getField(client, |
||||
"apiVersionInserter"); |
||||
assertThat(apiVersionInserter.insertVersion("v123", new URI("https://example.com"))) |
||||
.hasToString("https://example.com?v=v123&p1=V123"); |
||||
} |
||||
|
||||
static class TestWebClientProperties extends AbstractWebClientProperties { |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue