diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java index c22a89eded8c..edab82eb5e18 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java @@ -31,6 +31,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.RequestEntity; @@ -43,6 +44,7 @@ import org.springframework.mock.http.client.MockClientHttpRequest; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.util.Base64Utils; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.MethodCallback; import org.springframework.web.client.ResponseErrorHandler; @@ -97,7 +99,8 @@ void useTheSameRequestFactoryClassWithBasicAuth() { RestTemplateBuilder builder = new RestTemplateBuilder().requestFactory(() -> customFactory); TestRestTemplate testRestTemplate = new TestRestTemplate(builder).withBasicAuth("test", "test"); RestTemplate restTemplate = testRestTemplate.getRestTemplate(); - assertThat(restTemplate.getRequestFactory().getClass().getName()).contains("BasicAuth"); + assertThat(restTemplate.getRequestFactory().getClass().getName()) + .contains("HttpHeadersCustomizingClientHttpRequestFactory"); Object requestFactory = ReflectionTestUtils.getField(restTemplate.getRequestFactory(), "requestFactory"); assertThat(requestFactory).isEqualTo(customFactory).hasSameClassAs(customFactory); } @@ -125,10 +128,9 @@ void getRootUriRootUriNotSet() { } @Test - void authenticated() { - RestTemplate restTemplate = new TestRestTemplate("user", "password").getRestTemplate(); - ClientHttpRequestFactory factory = restTemplate.getRequestFactory(); - assertThat(factory.getClass().getName()).contains("BasicAuthentication"); + void authenticated() throws Exception { + TestRestTemplate restTemplate = new TestRestTemplate("user", "password"); + assertBasicAuthorizationCredentials(restTemplate, "user", "password"); } @Test @@ -201,11 +203,12 @@ private Object mockArgument(Class type) throws Exception { } @Test - void withBasicAuthAddsBasicAuthClientFactoryWhenNotAlreadyPresent() { + void withBasicAuthAddsBasicAuthClientFactoryWhenNotAlreadyPresent() throws Exception { TestRestTemplate original = new TestRestTemplate(); TestRestTemplate basicAuth = original.withBasicAuth("user", "password"); assertThat(getConverterClasses(original)).containsExactlyElementsOf(getConverterClasses(basicAuth)); - assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName()).contains("BasicAuth"); + assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName()) + .contains("HttpHeadersCustomizingClientHttpRequestFactory"); assertThat(ReflectionTestUtils.getField(basicAuth.getRestTemplate().getRequestFactory(), "requestFactory")) .isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class); assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty(); @@ -213,11 +216,12 @@ void withBasicAuthAddsBasicAuthClientFactoryWhenNotAlreadyPresent() { } @Test - void withBasicAuthReplacesBasicAuthClientFactoryWhenAlreadyPresent() { + void withBasicAuthReplacesBasicAuthClientFactoryWhenAlreadyPresent() throws Exception { TestRestTemplate original = new TestRestTemplate("foo", "bar").withBasicAuth("replace", "replace"); TestRestTemplate basicAuth = original.withBasicAuth("user", "password"); assertThat(getConverterClasses(basicAuth)).containsExactlyElementsOf(getConverterClasses(original)); - assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName()).contains("BasicAuth"); + assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName()) + .contains("HttpHeadersCustomizingClientHttpRequestFactory"); assertThat(ReflectionTestUtils.getField(basicAuth.getRestTemplate().getRequestFactory(), "requestFactory")) .isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class); assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty(); @@ -342,11 +346,12 @@ private void verifyRelativeUriHandling(TestRestTemplateCallback callback) throws } private void assertBasicAuthorizationCredentials(TestRestTemplate testRestTemplate, String username, - String password) { + String password) throws Exception { ClientHttpRequestFactory requestFactory = testRestTemplate.getRestTemplate().getRequestFactory(); - Object authentication = ReflectionTestUtils.getField(requestFactory, "authentication"); - assertThat(authentication).hasFieldOrPropertyWithValue("username", username); - assertThat(authentication).hasFieldOrPropertyWithValue("password", password); + ClientHttpRequest request = requestFactory.createRequest(URI.create("http://localhost"), HttpMethod.POST); + assertThat(request.getHeaders()).containsKeys(HttpHeaders.AUTHORIZATION); + assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly( + "Basic " + Base64Utils.encodeToString(String.format("%s:%s", username, password).getBytes())); } @@ -356,16 +361,4 @@ private interface TestRestTemplateCallback { } - static class TestClientHttpRequestFactory implements ClientHttpRequestFactory { - - TestClientHttpRequestFactory(String value) { - } - - @Override - public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { - return null; - } - - } - } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/AbstractHttpHeadersDefaultingCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/AbstractHttpHeadersDefaultingCustomizer.java new file mode 100644 index 000000000000..d4a85ab66925 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/AbstractHttpHeadersDefaultingCustomizer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2019 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.web.client; + +import org.springframework.http.HttpHeaders; + +/** + * {@link HttpHeadersCustomizer} that only adds headers that were not populated in the + * request. + * + * @author Ilya Lukyanovich + */ +public abstract class AbstractHttpHeadersDefaultingCustomizer implements HttpHeadersCustomizer { + + @Override + public void applyTo(HttpHeaders headers) { + createHeaders().forEach((key, value) -> headers.merge(key, value, (oldValue, ignored) -> oldValue)); + } + + protected abstract HttpHeaders createHeaders(); + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthentication.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthenticationHeaderDefaultingCustomizer.java similarity index 65% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthentication.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthenticationHeaderDefaultingCustomizer.java index 059499ccef09..5e9d2b49a329 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthentication.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthenticationHeaderDefaultingCustomizer.java @@ -19,17 +19,17 @@ import java.nio.charset.Charset; import org.springframework.http.HttpHeaders; -import org.springframework.http.client.ClientHttpRequest; import org.springframework.util.Assert; /** - * Basic authentication properties which are used by - * {@link BasicAuthenticationClientHttpRequestFactory}. + * {@link AbstractHttpHeadersDefaultingCustomizer} that applies basic authentication + * header unless it was provided in the request. * * @author Dmytro Nosan - * @see BasicAuthenticationClientHttpRequestFactory + * @author Ilya Lukyanovich + * @see HttpHeadersCustomizingClientHttpRequestFactory */ -class BasicAuthentication { +class BasicAuthenticationHeaderDefaultingCustomizer extends AbstractHttpHeadersDefaultingCustomizer { private final String username; @@ -37,7 +37,7 @@ class BasicAuthentication { private final Charset charset; - BasicAuthentication(String username, String password, Charset charset) { + BasicAuthenticationHeaderDefaultingCustomizer(String username, String password, Charset charset) { Assert.notNull(username, "Username must not be null"); Assert.notNull(password, "Password must not be null"); this.username = username; @@ -45,11 +45,11 @@ class BasicAuthentication { this.charset = charset; } - void applyTo(ClientHttpRequest request) { - HttpHeaders headers = request.getHeaders(); - if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) { - headers.setBasicAuth(this.username, this.password, this.charset); - } + @Override + protected HttpHeaders createHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.setBasicAuth(this.username, this.password, this.charset); + return headers; } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/HttpHeadersCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/HttpHeadersCustomizer.java new file mode 100644 index 000000000000..cfa68532c835 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/HttpHeadersCustomizer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2019 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.web.client; + +import org.springframework.http.HttpHeaders; + +/** + * Callback interface that can be used to customize a {@link HttpHeaders}. + * + * @author Ilya Lukyanovich + * @see HttpHeadersCustomizingClientHttpRequestFactory + */ +@FunctionalInterface +public interface HttpHeadersCustomizer { + + /** + * Callback to customize a {@link HttpHeaders} instance. + * @param headers the headers to customize + */ + void applyTo(HttpHeaders headers); + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthenticationClientHttpRequestFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/HttpHeadersCustomizingClientHttpRequestFactory.java similarity index 59% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthenticationClientHttpRequestFactory.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/HttpHeadersCustomizingClientHttpRequestFactory.java index 8e58a9ad0837..9611a6ec2a74 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthenticationClientHttpRequestFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/HttpHeadersCustomizingClientHttpRequestFactory.java @@ -18,6 +18,9 @@ import java.io.IOException; import java.net.URI; +import java.util.Collection; + +import org.jetbrains.annotations.NotNull; import org.springframework.http.HttpMethod; import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper; @@ -26,27 +29,29 @@ import org.springframework.util.Assert; /** - * {@link ClientHttpRequestFactory} to apply a given HTTP Basic Authentication - * username/password pair, unless a custom Authorization header has been set before. + * {@link ClientHttpRequestFactory} to apply default headers to a request unless header + * values were provided. * + * @author Ilya Lukyanovich * @author Dmytro Nosan */ -class BasicAuthenticationClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper { +class HttpHeadersCustomizingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper { - private final BasicAuthentication authentication; + private final Collection customizers; - BasicAuthenticationClientHttpRequestFactory(BasicAuthentication authentication, + HttpHeadersCustomizingClientHttpRequestFactory(Collection customizers, ClientHttpRequestFactory clientHttpRequestFactory) { super(clientHttpRequestFactory); - Assert.notNull(authentication, "Authentication must not be null"); - this.authentication = authentication; + Assert.notEmpty(customizers, "Customizers must not be empty"); + this.customizers = customizers; } + @NotNull @Override - protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) - throws IOException { + protected ClientHttpRequest createRequest(@NotNull URI uri, @NotNull HttpMethod httpMethod, + ClientHttpRequestFactory requestFactory) throws IOException { ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod); - this.authentication.applyTo(request); + this.customizers.forEach((customizer) -> customizer.applyTo(request.getHeaders())); return request; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java index 75bca79ff90a..77d327e7d2d1 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java @@ -32,6 +32,7 @@ import java.util.function.Supplier; import org.springframework.beans.BeanUtils; +import org.springframework.http.HttpHeaders; import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; @@ -64,6 +65,7 @@ * @author Brian Clozel * @author Dmytro Nosan * @author Kevin Strijbos + * @author Ilya Lukyanovich * @since 1.4.0 */ public class RestTemplateBuilder { @@ -80,14 +82,14 @@ public class RestTemplateBuilder { private final ResponseErrorHandler errorHandler; - private final BasicAuthentication basicAuthentication; - private final Set restTemplateCustomizers; private final RequestFactoryCustomizer requestFactoryCustomizer; private final Set interceptors; + private final Set httpHeadersCustomizers; + /** * Create a new {@link RestTemplateBuilder} instance. * @param customizers any {@link RestTemplateCustomizer RestTemplateCustomizers} that @@ -101,27 +103,27 @@ public RestTemplateBuilder(RestTemplateCustomizer... customizers) { this.requestFactorySupplier = null; this.uriTemplateHandler = null; this.errorHandler = null; - this.basicAuthentication = null; this.restTemplateCustomizers = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(customizers))); this.requestFactoryCustomizer = new RequestFactoryCustomizer(); this.interceptors = Collections.emptySet(); + this.httpHeadersCustomizers = Collections.emptySet(); } private RestTemplateBuilder(boolean detectRequestFactory, String rootUri, Set> messageConverters, Supplier requestFactorySupplier, UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler, - BasicAuthentication basicAuthentication, Set restTemplateCustomizers, - RequestFactoryCustomizer requestFactoryCustomizer, Set interceptors) { + Set restTemplateCustomizers, RequestFactoryCustomizer requestFactoryCustomizer, + Set interceptors, Set httpHeadersCustomizers) { this.detectRequestFactory = detectRequestFactory; this.rootUri = rootUri; this.messageConverters = messageConverters; this.requestFactorySupplier = requestFactorySupplier; this.uriTemplateHandler = uriTemplateHandler; this.errorHandler = errorHandler; - this.basicAuthentication = basicAuthentication; this.restTemplateCustomizers = restTemplateCustomizers; this.requestFactoryCustomizer = requestFactoryCustomizer; this.interceptors = interceptors; + this.httpHeadersCustomizers = httpHeadersCustomizers; } /** @@ -133,8 +135,8 @@ private RestTemplateBuilder(boolean detectRequestFactory, String rootUri, */ public RestTemplateBuilder detectRequestFactory(boolean detectRequestFactory) { return new RestTemplateBuilder(detectRequestFactory, this.rootUri, this.messageConverters, - this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, - this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); + this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, + this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); } /** @@ -145,8 +147,8 @@ public RestTemplateBuilder detectRequestFactory(boolean detectRequestFactory) { */ public RestTemplateBuilder rootUri(String rootUri) { return new RestTemplateBuilder(this.detectRequestFactory, rootUri, this.messageConverters, - this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, - this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); + this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, + this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); } /** @@ -176,8 +178,8 @@ public RestTemplateBuilder messageConverters(Collection>(messageConverters)), - this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, - this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); + this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, + this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); } /** @@ -190,7 +192,22 @@ public RestTemplateBuilder messageConverters(Collection... messageConverters) { Assert.notNull(messageConverters, "MessageConverters must not be null"); - return additionalMessageConverters(Arrays.asList(messageConverters)); + return additionalMessageConverters(true, messageConverters); + } + + /** + * Add additional {@link HttpMessageConverter HttpMessageConverters} that should be + * used with the {@link RestTemplate}. Any converters configured on the builder will + * replace RestTemplate's default converters. + * @param append if true adds converters to the end otherwise to the beginning + * @param messageConverters the converters to add + * @return a new builder instance + * @see #messageConverters(HttpMessageConverter...) + */ + public RestTemplateBuilder additionalMessageConverters(boolean append, + HttpMessageConverter... messageConverters) { + Assert.notNull(messageConverters, "MessageConverters must not be null"); + return additionalMessageConverters(append, Arrays.asList(messageConverters)); } /** @@ -204,10 +221,26 @@ public RestTemplateBuilder additionalMessageConverters(HttpMessageConverter.. public RestTemplateBuilder additionalMessageConverters( Collection> messageConverters) { Assert.notNull(messageConverters, "MessageConverters must not be null"); + return additionalMessageConverters(true, messageConverters); + } + + /** + * Add additional {@link HttpMessageConverter HttpMessageConverters} that should be + * used with the {@link RestTemplate}. Any converters configured on the builder will + * replace RestTemplate's default converters. + * @param append if true adds converters to the end otherwise to the beginning + * @param messageConverters the converters to add + * @return a new builder instance + * @see #messageConverters(HttpMessageConverter...) + */ + public RestTemplateBuilder additionalMessageConverters(boolean append, + Collection> messageConverters) { + Assert.notNull(messageConverters, "MessageConverters must not be null"); return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, - append(this.messageConverters, messageConverters), this.requestFactorySupplier, this.uriTemplateHandler, - this.errorHandler, this.basicAuthentication, this.restTemplateCustomizers, - this.requestFactoryCustomizer, this.interceptors); + append ? append(this.messageConverters, messageConverters) + : append(messageConverters, this.messageConverters), + this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, + this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); } /** @@ -220,8 +253,8 @@ public RestTemplateBuilder additionalMessageConverters( public RestTemplateBuilder defaultMessageConverters() { return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, Collections.unmodifiableSet(new LinkedHashSet<>(new RestTemplate().getMessageConverters())), - this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, - this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); + this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, + this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); } /** @@ -250,9 +283,9 @@ public RestTemplateBuilder interceptors(ClientHttpRequestInterceptor... intercep public RestTemplateBuilder interceptors(Collection interceptors) { Assert.notNull(interceptors, "interceptors must not be null"); return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, - this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, - this.restTemplateCustomizers, this.requestFactoryCustomizer, - Collections.unmodifiableSet(new LinkedHashSet<>(interceptors))); + this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, + this.requestFactoryCustomizer, Collections.unmodifiableSet(new LinkedHashSet<>(interceptors)), + this.httpHeadersCustomizers); } /** @@ -279,8 +312,8 @@ public RestTemplateBuilder additionalInterceptors(ClientHttpRequestInterceptor.. public RestTemplateBuilder additionalInterceptors(Collection interceptors) { Assert.notNull(interceptors, "interceptors must not be null"); return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, - this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, - this.restTemplateCustomizers, this.requestFactoryCustomizer, append(this.interceptors, interceptors)); + this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, + this.requestFactoryCustomizer, append(this.interceptors, interceptors), this.httpHeadersCustomizers); } /** @@ -315,8 +348,8 @@ private ClientHttpRequestFactory createRequestFactory(Class requestFactorySupplier) { Assert.notNull(requestFactorySupplier, "RequestFactory Supplier must not be null"); return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, - requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, - this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); + requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, + this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); } /** @@ -328,8 +361,8 @@ public RestTemplateBuilder requestFactory(Supplier req public RestTemplateBuilder uriTemplateHandler(UriTemplateHandler uriTemplateHandler) { Assert.notNull(uriTemplateHandler, "UriTemplateHandler must not be null"); return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, - this.requestFactorySupplier, uriTemplateHandler, this.errorHandler, this.basicAuthentication, - this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); + this.requestFactorySupplier, uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, + this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); } /** @@ -341,8 +374,123 @@ public RestTemplateBuilder uriTemplateHandler(UriTemplateHandler uriTemplateHand public RestTemplateBuilder errorHandler(ResponseErrorHandler errorHandler) { Assert.notNull(errorHandler, "ErrorHandler must not be null"); return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, - this.requestFactorySupplier, this.uriTemplateHandler, errorHandler, this.basicAuthentication, - this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); + this.requestFactorySupplier, this.uriTemplateHandler, errorHandler, this.restTemplateCustomizers, + this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); + } + + /** + * Add a header to requests with the given value unless a custom header has been set + * before. + * @param header the header + * @param value the value + * @return a new builder instance + * @see #additionalHttpHeadersCustomizers(HttpHeadersCustomizer...) + */ + public RestTemplateBuilder defaultHeader(String header, String value) { + return additionalHttpHeadersCustomizers(SimpleHttpHeaderDefaultingCustomizer.singleHeader(header, value)); + } + + /** + * Add a headers to requests with the given value unless a custom header has been set + * before. + * @param headers the headers + * @return a new builder instance + * @see #additionalHttpHeadersCustomizers(HttpHeadersCustomizer...) + */ + public RestTemplateBuilder defaultHeaders(HttpHeaders headers) { + return additionalHttpHeadersCustomizers(new SimpleHttpHeaderDefaultingCustomizer(headers)); + } + + /** + * Set the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied + * to the {@link HttpHeaders} on the request. Customizers are applied in the order + * that they were added. Setting this value will replace any previously configured + * http headers customizers. + * @param httpHeadersCustomizers the customizers to set + * @return a new builder instance + * @see #additionalHttpHeadersCustomizers(HttpHeadersCustomizer...) + */ + public RestTemplateBuilder httpHeadersCustomizers(HttpHeadersCustomizer... httpHeadersCustomizers) { + Assert.notNull(httpHeadersCustomizers, "HttpHeadersCustomizers must not be null"); + return httpHeadersCustomizers(Arrays.asList(httpHeadersCustomizers)); + } + + /** + * Set the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied + * to the {@link HttpHeaders} on the request. Customizers are applied in the order + * that they were added. Setting this value will replace any previously configured + * http headers customizers. + * @param httpHeadersCustomizers the customizers to set + * @return a new builder instance + * @see #additionalHttpHeadersCustomizers(HttpHeadersCustomizer...) + */ + public RestTemplateBuilder httpHeadersCustomizers( + Collection httpHeadersCustomizers) { + Assert.notNull(httpHeadersCustomizers, "HttpHeadersCustomizers must not be null"); + return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, + this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, + this.requestFactoryCustomizer, this.interceptors, + Collections.unmodifiableSet(new LinkedHashSet<>(this.httpHeadersCustomizers))); + } + + /** + * Add the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied + * to the {@link HttpHeaders} on the request. Customizers are applied in the order + * that they were added. + * @param httpHeadersCustomizers the customizers to set + * @return a new builder instance + * @see #httpHeadersCustomizers(HttpHeadersCustomizer...) + */ + public RestTemplateBuilder additionalHttpHeadersCustomizers(HttpHeadersCustomizer... httpHeadersCustomizers) { + Assert.notNull(httpHeadersCustomizers, "HttpHeadersCustomizers must not be null"); + return additionalHttpHeadersCustomizers(true, httpHeadersCustomizers); + } + + /** + * Add the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied + * to the {@link HttpHeaders} on the request. Customizers are applied in the order + * that they were added. + * @param append if true adds customizers to the end otherwise to the beginning + * @param httpHeadersCustomizers the customizers to set + * @return a new builder instance + * @see #httpHeadersCustomizers(HttpHeadersCustomizer...) + */ + public RestTemplateBuilder additionalHttpHeadersCustomizers(boolean append, + HttpHeadersCustomizer... httpHeadersCustomizers) { + Assert.notNull(httpHeadersCustomizers, "HttpHeadersCustomizers must not be null"); + return additionalHttpHeadersCustomizers(append, Arrays.asList(httpHeadersCustomizers)); + } + + /** + * Add the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied + * to the {@link HttpHeaders} on the request. Customizers are applied in the order + * that they were added. + * @param httpHeadersCustomizers the customizers to set + * @return a new builder instance + * @see #httpHeadersCustomizers(HttpHeadersCustomizer...) + */ + public RestTemplateBuilder additionalHttpHeadersCustomizers( + Collection httpHeadersCustomizers) { + return additionalHttpHeadersCustomizers(true, httpHeadersCustomizers); + } + + /** + * Add the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied + * to the {@link HttpHeaders} on the request. Customizers are applied in the order + * that they were added. + * @param append if true adds customizers to the end otherwise to the beginning + * @param httpHeadersCustomizers the customizers to set + * @return a new builder instance + * @see #httpHeadersCustomizers(HttpHeadersCustomizer...) + */ + public RestTemplateBuilder additionalHttpHeadersCustomizers(boolean append, + Collection httpHeadersCustomizers) { + Assert.notNull(httpHeadersCustomizers, "HttpHeadersCustomizers must not be null"); + return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, + this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, + this.requestFactoryCustomizer, this.interceptors, + append ? append(this.httpHeadersCustomizers, httpHeadersCustomizers) + : append(httpHeadersCustomizers, this.httpHeadersCustomizers)); } /** @@ -360,19 +508,21 @@ public RestTemplateBuilder basicAuthentication(String username, String password) /** * Add HTTP Basic Authentication to requests with the given username/password pair, - * unless a custom Authorization header has been set before. + * unless a custom Authorization header has been set before. Customizer is added to + * the beginning of the {@link HttpHeadersCustomizer HttpHeadersCustomizers} + * collection. * @param username the user name * @param password the password * @param charset the charset to use * @return a new builder instance * @since 2.2.0 - * @see #basicAuthentication(String, String) + * @see #additionalHttpHeadersCustomizers(HttpHeadersCustomizer...) + * @see SimpleHttpHeaderDefaultingCustomizer#basicAuthentication(String, String, + * Charset) */ public RestTemplateBuilder basicAuthentication(String username, String password, Charset charset) { - BasicAuthentication basicAuthentication = new BasicAuthentication(username, password, charset); - return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, - this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, basicAuthentication, - this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); + return additionalHttpHeadersCustomizers(false, + SimpleHttpHeaderDefaultingCustomizer.basicAuthentication(username, password, charset)); } /** @@ -401,9 +551,10 @@ public RestTemplateBuilder customizers(RestTemplateCustomizer... restTemplateCus public RestTemplateBuilder customizers(Collection restTemplateCustomizers) { Assert.notNull(restTemplateCustomizers, "RestTemplateCustomizers must not be null"); return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, - this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, + this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, + Collections.unmodifiableSet(new LinkedHashSet(restTemplateCustomizers)), - this.requestFactoryCustomizer, this.interceptors); + this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); } /** @@ -416,7 +567,22 @@ public RestTemplateBuilder customizers(Collection customizers) { Assert.notNull(customizers, "RestTemplateCustomizers must not be null"); + return additionalCustomizers(true, customizers); + } + + /** + * Add {@link RestTemplateCustomizer RestTemplateCustomizers} that should be applied + * to the {@link RestTemplate}. Customizers are applied in the order that they were + * added after builder configuration has been applied. + * @param append if true adds customizers to the end otherwise to the beginning + * @param customizers the customizers to add + * @return a new builder instance + * @see #customizers(RestTemplateCustomizer...) + */ + public RestTemplateBuilder additionalCustomizers(boolean append, + Collection customizers) { + Assert.notNull(customizers, "RestTemplateCustomizers must not be null"); return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, - this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, - append(this.restTemplateCustomizers, customizers), this.requestFactoryCustomizer, this.interceptors); + this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, + append ? append(this.restTemplateCustomizers, customizers) + : append(customizers, this.restTemplateCustomizers), + this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); } /** @@ -442,9 +625,9 @@ public RestTemplateBuilder additionalCustomizers(Collection T configure(T restTemplate) { if (requestFactory != null) { restTemplate.setRequestFactory(requestFactory); } - if (this.basicAuthentication != null) { - configureBasicAuthentication(restTemplate); + if (!CollectionUtils.isEmpty(this.httpHeadersCustomizers)) { + configureHttpHeadersCustomizers(restTemplate); } if (!CollectionUtils.isEmpty(this.messageConverters)) { restTemplate.setMessageConverters(new ArrayList<>(this.messageConverters)); @@ -558,7 +740,7 @@ else if (this.detectRequestFactory) { return requestFactory; } - private void configureBasicAuthentication(RestTemplate restTemplate) { + private void configureHttpHeadersCustomizers(RestTemplate restTemplate) { List interceptors = null; if (!restTemplate.getInterceptors().isEmpty()) { // Stash and clear the interceptors so we can access the real factory @@ -567,16 +749,18 @@ private void configureBasicAuthentication(RestTemplate restTemplate) { } ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory(); restTemplate.setRequestFactory( - new BasicAuthenticationClientHttpRequestFactory(this.basicAuthentication, requestFactory)); + new HttpHeadersCustomizingClientHttpRequestFactory(this.httpHeadersCustomizers, requestFactory)); // Restore the original interceptors if (interceptors != null) { restTemplate.getInterceptors().addAll(interceptors); } } - private Set append(Set set, Collection additions) { - Set result = new LinkedHashSet<>((set != null) ? set : Collections.emptySet()); - result.addAll(additions); + private static Set append(Collection collection, Collection additions) { + Set result = new LinkedHashSet<>((collection != null) ? collection : Collections.emptySet()); + if (additions != null) { + result.addAll(additions); + } return Collections.unmodifiableSet(result); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/SimpleHttpHeaderDefaultingCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/SimpleHttpHeaderDefaultingCustomizer.java new file mode 100644 index 000000000000..3a4e6734e145 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/SimpleHttpHeaderDefaultingCustomizer.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2019 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.web.client; + +import java.nio.charset.Charset; + +import org.springframework.http.HttpHeaders; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; + +/** + * A {@link AbstractHttpHeadersDefaultingCustomizer} that uses provided + * {@link HttpHeaders} instance as default headers. + * + * @author Ilya Lukyanovich + * @see HttpHeadersCustomizingClientHttpRequestFactory + */ +public class SimpleHttpHeaderDefaultingCustomizer extends AbstractHttpHeadersDefaultingCustomizer { + + private final HttpHeaders httpHeaders; + + public SimpleHttpHeaderDefaultingCustomizer(HttpHeaders httpHeaders) { + Assert.notNull(httpHeaders, "Header must not be null"); + this.httpHeaders = httpHeaders; + } + + @Override + protected HttpHeaders createHeaders() { + return new HttpHeaders(new LinkedMultiValueMap<>(this.httpHeaders)); + } + + /** + * A factory method that creates a {@link SimpleHttpHeaderDefaultingCustomizer} with a + * single header and a single value. + * @param header the header + * @param value the value + * @return new {@link SimpleHttpHeaderDefaultingCustomizer} instance + * @see HttpHeaders#set(String, String) + */ + public static HttpHeadersCustomizer singleHeader(@NonNull String header, @NonNull String value) { + Assert.notNull(header, "Header must not be null empty"); + Assert.notNull(value, "Value must not be null empty"); + HttpHeaders headers = new HttpHeaders(); + headers.set(header, value); + return new SimpleHttpHeaderDefaultingCustomizer(headers); + } + + /** + * A factory method that creates a {@link SimpleHttpHeaderDefaultingCustomizer} for + * {@link HttpHeaders#AUTHORIZATION} header with pre-defined username and password + * pair. + * @param username the username + * @param password the password + * @return new {@link SimpleHttpHeaderDefaultingCustomizer} instance + * @see #basicAuthentication(String, String, Charset) + */ + public static HttpHeadersCustomizer basicAuthentication(@NonNull String username, @NonNull String password) { + return basicAuthentication(username, password, null); + } + + /** + * A factory method that creates a {@link SimpleHttpHeaderDefaultingCustomizer} for + * {@link HttpHeaders#AUTHORIZATION} header with pre-defined username and password + * pair. + * @param username the username + * @param password the password + * @param charset the header encoding charset + * @return new {@link SimpleHttpHeaderDefaultingCustomizer} instance + * @see HttpHeaders#setBasicAuth(String, String, Charset) + */ + public static HttpHeadersCustomizer basicAuthentication(@NonNull String username, @NonNull String password, + @Nullable Charset charset) { + Assert.notNull(username, "Username must not be null"); + Assert.notNull(password, "Password must not be null"); + HttpHeaders headers = new HttpHeaders(); + headers.setBasicAuth(username, password, charset); + return new SimpleHttpHeaderDefaultingCustomizer(headers); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/BasicAuthenticationClientHttpRequestFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/HttpHeadersCustomizingClientHttpRequestFactoryTests.java similarity index 59% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/BasicAuthenticationClientHttpRequestFactoryTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/HttpHeadersCustomizingClientHttpRequestFactoryTests.java index cf8951052e84..e98a54bb7f50 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/BasicAuthenticationClientHttpRequestFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/HttpHeadersCustomizingClientHttpRequestFactoryTests.java @@ -18,6 +18,8 @@ import java.io.IOException; import java.net.URI; +import java.util.Arrays; +import java.util.Collections; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -33,29 +35,30 @@ import static org.mockito.Mockito.mock; /** - * Tests for {@link BasicAuthenticationClientHttpRequestFactory}. + * Tests for {@link HttpHeadersCustomizingClientHttpRequestFactory}. * * @author Dmytro Nosan + * @author Ilya Lukyanovich */ -class BasicAuthenticationClientHttpRequestFactoryTests { +public class HttpHeadersCustomizingClientHttpRequestFactoryTests { private final HttpHeaders headers = new HttpHeaders(); - private final BasicAuthentication authentication = new BasicAuthentication("spring", "boot", null); - private ClientHttpRequestFactory requestFactory; @BeforeEach public void setUp() throws IOException { - ClientHttpRequestFactory requestFactory = mock(ClientHttpRequestFactory.class); + this.requestFactory = mock(ClientHttpRequestFactory.class); ClientHttpRequest request = mock(ClientHttpRequest.class); - given(requestFactory.createRequest(any(), any())).willReturn(request); + given(this.requestFactory.createRequest(any(), any())).willReturn(request); given(request.getHeaders()).willReturn(this.headers); - this.requestFactory = new BasicAuthenticationClientHttpRequestFactory(this.authentication, requestFactory); } @Test void shouldAddAuthorizationHeader() throws IOException { + this.requestFactory = new HttpHeadersCustomizingClientHttpRequestFactory( + Collections.singleton(SimpleHttpHeaderDefaultingCustomizer.basicAuthentication("spring", "boot", null)), + this.requestFactory); ClientHttpRequest request = createRequest(); assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic c3ByaW5nOmJvb3Q="); } @@ -63,11 +66,27 @@ void shouldAddAuthorizationHeader() throws IOException { @Test void shouldNotAddAuthorizationHeaderAuthorizationAlreadySet() throws IOException { this.headers.setBasicAuth("boot", "spring"); + this.requestFactory = new HttpHeadersCustomizingClientHttpRequestFactory( + Collections.singleton(SimpleHttpHeaderDefaultingCustomizer.basicAuthentication("spring", "boot", null)), + this.requestFactory); ClientHttpRequest request = createRequest(); assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).doesNotContain("Basic c3ByaW5nOmJvb3Q="); } + @Test + void shouldApplyCustomizersInTheProvidedOrder() throws IOException { + this.requestFactory = new HttpHeadersCustomizingClientHttpRequestFactory( + Arrays.asList((headers) -> headers.add("foo", "bar"), + SimpleHttpHeaderDefaultingCustomizer.basicAuthentication("spring", "boot", null), + SimpleHttpHeaderDefaultingCustomizer.singleHeader(HttpHeaders.AUTHORIZATION, "won't do")), + this.requestFactory); + ClientHttpRequest request = createRequest(); + assertThat(request.getHeaders()).containsOnlyKeys("foo", HttpHeaders.AUTHORIZATION); + assertThat(request.getHeaders().get("foo")).containsExactly("bar"); + assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic c3ByaW5nOmJvb3Q="); + } + private ClientHttpRequest createRequest() throws IOException { return this.requestFactory.createRequest(URI.create("https://localhost:8080"), HttpMethod.POST); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java index c8158b233dac..cf562f94251a 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.web.client; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Collections; @@ -29,7 +30,10 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; @@ -298,12 +302,12 @@ void errorHandlerShouldApply() { } @Test - void basicAuthenticationShouldApply() { + void basicAuthenticationShouldApply() throws Exception { RestTemplate template = this.builder.basicAuthentication("spring", "boot", StandardCharsets.UTF_8).build(); ClientHttpRequestFactory requestFactory = template.getRequestFactory(); - Object authentication = ReflectionTestUtils.getField(requestFactory, "authentication"); - assertThat(authentication).extracting("username", "password", "charset").containsExactly("spring", "boot", - StandardCharsets.UTF_8); + ClientHttpRequest request = requestFactory.createRequest(URI.create("http://localhost"), HttpMethod.POST); + assertThat(request.getHeaders()).containsOnlyKeys(HttpHeaders.AUTHORIZATION); + assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic c3ByaW5nOmJvb3Q="); } @Test @@ -383,7 +387,7 @@ void customizerShouldBeAppliedAtTheEnd() { assertThat(actualRequestFactory).isInstanceOf(InterceptingClientHttpRequestFactory.class); ClientHttpRequestFactory authRequestFactory = (ClientHttpRequestFactory) ReflectionTestUtils .getField(actualRequestFactory, "requestFactory"); - assertThat(authRequestFactory).isInstanceOf(BasicAuthenticationClientHttpRequestFactory.class); + assertThat(authRequestFactory).isInstanceOf(HttpHeadersCustomizingClientHttpRequestFactory.class); assertThat(authRequestFactory).hasFieldOrPropertyWithValue("requestFactory", requestFactory); }).build(); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/SimpleHttpHeaderDefaultingCustomizerTest.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/SimpleHttpHeaderDefaultingCustomizerTest.java new file mode 100644 index 000000000000..2687da2956d2 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/SimpleHttpHeaderDefaultingCustomizerTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2019 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.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.http.HttpHeaders; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SimpleHttpHeaderDefaultingCustomizer}. + * + * @author Ilya Lukyanovich + */ +class SimpleHttpHeaderDefaultingCustomizerTest { + + @Test + void testApplyTo_shouldAddAllHeaders() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add("foo", "bar"); + httpHeaders.add("donut", "42"); + SimpleHttpHeaderDefaultingCustomizer customizer = new SimpleHttpHeaderDefaultingCustomizer(httpHeaders); + HttpHeaders provided = new HttpHeaders(); + customizer.applyTo(provided); + assertThat(provided).containsOnlyKeys("foo", "donut"); + assertThat(provided.get("foo")).containsExactly("bar"); + assertThat(provided.get("donut")).containsExactly("42"); + } + + @Test + void testApplyTo_shouldIgnoreProvided() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add("foo", "bar"); + httpHeaders.add("donut", "42"); + SimpleHttpHeaderDefaultingCustomizer customizer = new SimpleHttpHeaderDefaultingCustomizer(httpHeaders); + HttpHeaders provided = new HttpHeaders(); + provided.add("donut", "touchme"); + customizer.applyTo(provided); + assertThat(provided).containsOnlyKeys("foo", "donut"); + assertThat(provided.get("foo")).containsExactly("bar"); + assertThat(provided.get("donut")).containsExactly("touchme"); + } + + @Test + void testSingleHeader() { + HttpHeaders provided = new HttpHeaders(); + SimpleHttpHeaderDefaultingCustomizer.singleHeader("foo", "bar").applyTo(provided); + assertThat(provided).containsOnlyKeys("foo"); + assertThat(provided.get("foo")).containsExactly("bar"); + } + + @Test + void testBasicAuthentication() { + HttpHeaders provided = new HttpHeaders(); + SimpleHttpHeaderDefaultingCustomizer.basicAuthentication("spring", "boot").applyTo(provided); + assertThat(provided).containsOnlyKeys(HttpHeaders.AUTHORIZATION); + assertThat(provided.get(HttpHeaders.AUTHORIZATION)).containsExactly("bar"); + } + +}