Commit aad21d19 authored by Phillip Webb's avatar Phillip Webb

Polish "Support default headers with RestTemplateBuilder"

Broaden the scope of customizer support so that instead of focusing
just on headers, we can now customize any outgoing `HttpClientRequest`.
Also update auto-configuration to automatically add any
`RestTemplateRequestCustomizer` beans to the builder.

See gh-17091
parent 43b1a667
...@@ -16,7 +16,9 @@ ...@@ -16,7 +16,9 @@
package org.springframework.boot.autoconfigure.web.client; package org.springframework.boot.autoconfigure.web.client;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
...@@ -32,6 +34,7 @@ import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConf ...@@ -32,6 +34,7 @@ import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConf
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.NotReactiveWebApplicationCondition; import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.NotReactiveWebApplicationCondition;
import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.boot.web.client.RestTemplateRequestCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -53,15 +56,23 @@ public class RestTemplateAutoConfiguration { ...@@ -53,15 +56,23 @@ public class RestTemplateAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder(ObjectProvider<HttpMessageConverters> messageConverters, public RestTemplateBuilder restTemplateBuilder(ObjectProvider<HttpMessageConverters> messageConverters,
ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers) { ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers,
ObjectProvider<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
RestTemplateBuilder builder = new RestTemplateBuilder(); RestTemplateBuilder builder = new RestTemplateBuilder();
HttpMessageConverters converters = messageConverters.getIfUnique(); HttpMessageConverters converters = messageConverters.getIfUnique();
if (converters != null) { if (converters != null) {
builder = builder.messageConverters(converters.getConverters()); builder = builder.messageConverters(converters.getConverters());
} }
List<RestTemplateCustomizer> customizers = restTemplateCustomizers.orderedStream().collect(Collectors.toList()); builder = addCustomizers(builder, restTemplateCustomizers, RestTemplateBuilder::customizers);
builder = addCustomizers(builder, restTemplateRequestCustomizers, RestTemplateBuilder::requestCustomizers);
return builder;
}
private <T> RestTemplateBuilder addCustomizers(RestTemplateBuilder builder, ObjectProvider<T> objectProvider,
BiFunction<RestTemplateBuilder, Collection<T>, RestTemplateBuilder> method) {
List<T> customizers = objectProvider.orderedStream().collect(Collectors.toList());
if (!customizers.isEmpty()) { if (!customizers.isEmpty()) {
builder = builder.customizers(customizers); return method.apply(builder, customizers);
} }
return builder; return builder;
} }
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.web.client; package org.springframework.boot.autoconfigure.web.client;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
...@@ -28,14 +29,21 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContex ...@@ -28,14 +29,21 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContex
import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.boot.web.client.RestTemplateRequestCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
...@@ -109,6 +117,20 @@ class RestTemplateAutoConfigurationTests { ...@@ -109,6 +117,20 @@ class RestTemplateAutoConfigurationTests {
}); });
} }
@Test
void restTemplateShouldApplyRequestCustomizer() {
this.contextRunner.withUserConfiguration(RestTemplateRequestCustomizerConfig.class).run((context) -> {
RestTemplateBuilder builder = context.getBean(RestTemplateBuilder.class);
ClientHttpRequestFactory requestFactory = mock(ClientHttpRequestFactory.class);
MockClientHttpRequest request = new MockClientHttpRequest();
request.setResponse(new MockClientHttpResponse(new byte[0], HttpStatus.OK));
given(requestFactory.createRequest(any(), any())).willReturn(request);
RestTemplate restTemplate = builder.requestFactory(() -> requestFactory).build();
restTemplate.getForEntity("http://localhost:8080/test", String.class);
assertThat(request.getHeaders()).containsEntry("spring", Collections.singletonList("boot"));
});
}
@Test @Test
void builderShouldBeFreshForEachUse() { void builderShouldBeFreshForEachUse() {
this.contextRunner.withUserConfiguration(DirtyRestTemplateConfig.class) this.contextRunner.withUserConfiguration(DirtyRestTemplateConfig.class)
...@@ -189,6 +211,16 @@ class RestTemplateAutoConfigurationTests { ...@@ -189,6 +211,16 @@ class RestTemplateAutoConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
static class RestTemplateRequestCustomizerConfig {
@Bean
public RestTemplateRequestCustomizer<?> restTemplateRequestCustomizer() {
return (request) -> request.getHeaders().add("spring", "boot");
}
}
static class CustomHttpMessageConverter extends StringHttpMessageConverter { static class CustomHttpMessageConverter extends StringHttpMessageConverter {
} }
......
...@@ -100,7 +100,7 @@ class TestRestTemplateTests { ...@@ -100,7 +100,7 @@ class TestRestTemplateTests {
TestRestTemplate testRestTemplate = new TestRestTemplate(builder).withBasicAuth("test", "test"); TestRestTemplate testRestTemplate = new TestRestTemplate(builder).withBasicAuth("test", "test");
RestTemplate restTemplate = testRestTemplate.getRestTemplate(); RestTemplate restTemplate = testRestTemplate.getRestTemplate();
assertThat(restTemplate.getRequestFactory().getClass().getName()) assertThat(restTemplate.getRequestFactory().getClass().getName())
.contains("HttpHeadersCustomizingClientHttpRequestFactory"); .contains("RestTemplateBuilderClientHttpRequestFactoryWrapper");
Object requestFactory = ReflectionTestUtils.getField(restTemplate.getRequestFactory(), "requestFactory"); Object requestFactory = ReflectionTestUtils.getField(restTemplate.getRequestFactory(), "requestFactory");
assertThat(requestFactory).isEqualTo(customFactory).hasSameClassAs(customFactory); assertThat(requestFactory).isEqualTo(customFactory).hasSameClassAs(customFactory);
} }
...@@ -208,7 +208,7 @@ class TestRestTemplateTests { ...@@ -208,7 +208,7 @@ class TestRestTemplateTests {
TestRestTemplate basicAuth = original.withBasicAuth("user", "password"); TestRestTemplate basicAuth = original.withBasicAuth("user", "password");
assertThat(getConverterClasses(original)).containsExactlyElementsOf(getConverterClasses(basicAuth)); assertThat(getConverterClasses(original)).containsExactlyElementsOf(getConverterClasses(basicAuth));
assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName()) assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName())
.contains("HttpHeadersCustomizingClientHttpRequestFactory"); .contains("RestTemplateBuilderClientHttpRequestFactoryWrapper");
assertThat(ReflectionTestUtils.getField(basicAuth.getRestTemplate().getRequestFactory(), "requestFactory")) assertThat(ReflectionTestUtils.getField(basicAuth.getRestTemplate().getRequestFactory(), "requestFactory"))
.isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class); .isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class);
assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty(); assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty();
...@@ -221,7 +221,7 @@ class TestRestTemplateTests { ...@@ -221,7 +221,7 @@ class TestRestTemplateTests {
TestRestTemplate basicAuth = original.withBasicAuth("user", "password"); TestRestTemplate basicAuth = original.withBasicAuth("user", "password");
assertThat(getConverterClasses(basicAuth)).containsExactlyElementsOf(getConverterClasses(original)); assertThat(getConverterClasses(basicAuth)).containsExactlyElementsOf(getConverterClasses(original));
assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName()) assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName())
.contains("HttpHeadersCustomizingClientHttpRequestFactory"); .contains("RestTemplateBuilderClientHttpRequestFactoryWrapper");
assertThat(ReflectionTestUtils.getField(basicAuth.getRestTemplate().getRequestFactory(), "requestFactory")) assertThat(ReflectionTestUtils.getField(basicAuth.getRestTemplate().getRequestFactory(), "requestFactory"))
.isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class); .isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class);
assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty(); assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty();
......
/*
* 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();
}
...@@ -22,14 +22,12 @@ import org.springframework.http.HttpHeaders; ...@@ -22,14 +22,12 @@ import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* {@link AbstractHttpHeadersDefaultingCustomizer} that applies basic authentication * Basic authentication details to be applied to {@link HttpHeaders}.
* header unless it was provided in the request.
* *
* @author Dmytro Nosan * @author Dmytro Nosan
* @author Ilya Lukyanovich * @author Ilya Lukyanovich
* @see HttpHeadersCustomizingClientHttpRequestFactory
*/ */
class BasicAuthenticationHeaderDefaultingCustomizer extends AbstractHttpHeadersDefaultingCustomizer { class BasicAuthentication {
private final String username; private final String username;
...@@ -37,7 +35,7 @@ class BasicAuthenticationHeaderDefaultingCustomizer extends AbstractHttpHeadersD ...@@ -37,7 +35,7 @@ class BasicAuthenticationHeaderDefaultingCustomizer extends AbstractHttpHeadersD
private final Charset charset; private final Charset charset;
BasicAuthenticationHeaderDefaultingCustomizer(String username, String password, Charset charset) { BasicAuthentication(String username, String password, Charset charset) {
Assert.notNull(username, "Username must not be null"); Assert.notNull(username, "Username must not be null");
Assert.notNull(password, "Password must not be null"); Assert.notNull(password, "Password must not be null");
this.username = username; this.username = username;
...@@ -45,11 +43,10 @@ class BasicAuthenticationHeaderDefaultingCustomizer extends AbstractHttpHeadersD ...@@ -45,11 +43,10 @@ class BasicAuthenticationHeaderDefaultingCustomizer extends AbstractHttpHeadersD
this.charset = charset; this.charset = charset;
} }
@Override public void applyTo(HttpHeaders headers) {
protected HttpHeaders createHeaders() { if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
HttpHeaders headers = new HttpHeaders(); headers.setBasicAuth(this.username, this.password, this.charset);
headers.setBasicAuth(this.username, this.password, this.charset); }
return headers;
} }
} }
...@@ -25,14 +25,17 @@ import java.util.ArrayList; ...@@ -25,14 +25,17 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import reactor.netty.http.client.HttpClientRequest;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper; import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory;
...@@ -70,25 +73,29 @@ import org.springframework.web.util.UriTemplateHandler; ...@@ -70,25 +73,29 @@ import org.springframework.web.util.UriTemplateHandler;
*/ */
public class RestTemplateBuilder { public class RestTemplateBuilder {
private final RequestFactoryCustomizer requestFactoryCustomizer;
private final boolean detectRequestFactory; private final boolean detectRequestFactory;
private final String rootUri; private final String rootUri;
private final Set<HttpMessageConverter<?>> messageConverters; private final Set<HttpMessageConverter<?>> messageConverters;
private final Supplier<ClientHttpRequestFactory> requestFactorySupplier; private final Set<ClientHttpRequestInterceptor> interceptors;
private final Supplier<ClientHttpRequestFactory> requestFactory;
private final UriTemplateHandler uriTemplateHandler; private final UriTemplateHandler uriTemplateHandler;
private final ResponseErrorHandler errorHandler; private final ResponseErrorHandler errorHandler;
private final Set<RestTemplateCustomizer> restTemplateCustomizers; private final BasicAuthentication basicAuthentication;
private final RequestFactoryCustomizer requestFactoryCustomizer; private final Map<String, String> defaultHeaders;
private final Set<ClientHttpRequestInterceptor> interceptors; private final Set<RestTemplateCustomizer> customizers;
private final Set<HttpHeadersCustomizer> httpHeadersCustomizers; private final Set<RestTemplateRequestCustomizer<?>> requestCustomizers;
/** /**
* Create a new {@link RestTemplateBuilder} instance. * Create a new {@link RestTemplateBuilder} instance.
...@@ -97,33 +104,38 @@ public class RestTemplateBuilder { ...@@ -97,33 +104,38 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder(RestTemplateCustomizer... customizers) { public RestTemplateBuilder(RestTemplateCustomizer... customizers) {
Assert.notNull(customizers, "Customizers must not be null"); Assert.notNull(customizers, "Customizers must not be null");
this.requestFactoryCustomizer = new RequestFactoryCustomizer();
this.detectRequestFactory = true; this.detectRequestFactory = true;
this.rootUri = null; this.rootUri = null;
this.messageConverters = null; this.messageConverters = null;
this.requestFactorySupplier = null; this.interceptors = Collections.emptySet();
this.requestFactory = null;
this.uriTemplateHandler = null; this.uriTemplateHandler = null;
this.errorHandler = null; this.errorHandler = null;
this.restTemplateCustomizers = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(customizers))); this.basicAuthentication = null;
this.requestFactoryCustomizer = new RequestFactoryCustomizer(); this.defaultHeaders = Collections.emptyMap();
this.interceptors = Collections.emptySet(); this.customizers = setOf(customizers);
this.httpHeadersCustomizers = Collections.emptySet(); this.requestCustomizers = Collections.emptySet();
} }
private RestTemplateBuilder(boolean detectRequestFactory, String rootUri, private RestTemplateBuilder(RequestFactoryCustomizer requestFactoryCustomizer, boolean detectRequestFactory,
Set<HttpMessageConverter<?>> messageConverters, Supplier<ClientHttpRequestFactory> requestFactorySupplier, String rootUri, Set<HttpMessageConverter<?>> messageConverters,
Set<ClientHttpRequestInterceptor> interceptors, Supplier<ClientHttpRequestFactory> requestFactorySupplier,
UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler, UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler,
Set<RestTemplateCustomizer> restTemplateCustomizers, RequestFactoryCustomizer requestFactoryCustomizer, BasicAuthentication basicAuthentication, Map<String, String> defaultHeaders,
Set<ClientHttpRequestInterceptor> interceptors, Set<HttpHeadersCustomizer> httpHeadersCustomizers) { Set<RestTemplateCustomizer> customizers, Set<RestTemplateRequestCustomizer<?>> requestCustomizers) {
this.requestFactoryCustomizer = requestFactoryCustomizer;
this.detectRequestFactory = detectRequestFactory; this.detectRequestFactory = detectRequestFactory;
this.rootUri = rootUri; this.rootUri = rootUri;
this.messageConverters = messageConverters; this.messageConverters = messageConverters;
this.requestFactorySupplier = requestFactorySupplier; this.interceptors = interceptors;
this.requestFactory = requestFactorySupplier;
this.uriTemplateHandler = uriTemplateHandler; this.uriTemplateHandler = uriTemplateHandler;
this.errorHandler = errorHandler; this.errorHandler = errorHandler;
this.restTemplateCustomizers = restTemplateCustomizers; this.basicAuthentication = basicAuthentication;
this.requestFactoryCustomizer = requestFactoryCustomizer; this.defaultHeaders = defaultHeaders;
this.interceptors = interceptors; this.customizers = customizers;
this.httpHeadersCustomizers = httpHeadersCustomizers; this.requestCustomizers = requestCustomizers;
} }
/** /**
...@@ -134,9 +146,10 @@ public class RestTemplateBuilder { ...@@ -134,9 +146,10 @@ public class RestTemplateBuilder {
* @return a new builder instance * @return a new builder instance
*/ */
public RestTemplateBuilder detectRequestFactory(boolean detectRequestFactory) { public RestTemplateBuilder detectRequestFactory(boolean detectRequestFactory) {
return new RestTemplateBuilder(detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(this.requestFactoryCustomizer, detectRequestFactory, this.rootUri,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers);
} }
/** /**
...@@ -146,9 +159,10 @@ public class RestTemplateBuilder { ...@@ -146,9 +159,10 @@ public class RestTemplateBuilder {
* @return a new builder instance * @return a new builder instance
*/ */
public RestTemplateBuilder rootUri(String rootUri) { public RestTemplateBuilder rootUri(String rootUri) {
return new RestTemplateBuilder(this.detectRequestFactory, rootUri, this.messageConverters, return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, rootUri,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers);
} }
/** /**
...@@ -176,10 +190,10 @@ public class RestTemplateBuilder { ...@@ -176,10 +190,10 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder messageConverters(Collection<? extends HttpMessageConverter<?>> messageConverters) { public RestTemplateBuilder messageConverters(Collection<? extends HttpMessageConverter<?>> messageConverters) {
Assert.notNull(messageConverters, "MessageConverters must not be null"); Assert.notNull(messageConverters, "MessageConverters must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
Collections.unmodifiableSet(new LinkedHashSet<HttpMessageConverter<?>>(messageConverters)), setOf(messageConverters), this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); this.requestCustomizers);
} }
/** /**
...@@ -192,22 +206,7 @@ public class RestTemplateBuilder { ...@@ -192,22 +206,7 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder additionalMessageConverters(HttpMessageConverter<?>... messageConverters) { public RestTemplateBuilder additionalMessageConverters(HttpMessageConverter<?>... messageConverters) {
Assert.notNull(messageConverters, "MessageConverters must not be null"); Assert.notNull(messageConverters, "MessageConverters must not be null");
return additionalMessageConverters(true, messageConverters); return additionalMessageConverters(Arrays.asList(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));
} }
/** /**
...@@ -221,26 +220,10 @@ public class RestTemplateBuilder { ...@@ -221,26 +220,10 @@ public class RestTemplateBuilder {
public RestTemplateBuilder additionalMessageConverters( public RestTemplateBuilder additionalMessageConverters(
Collection<? extends HttpMessageConverter<?>> messageConverters) { Collection<? extends HttpMessageConverter<?>> messageConverters) {
Assert.notNull(messageConverters, "MessageConverters must not be null"); Assert.notNull(messageConverters, "MessageConverters must not be null");
return additionalMessageConverters(true, messageConverters); return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
} append(this.messageConverters, messageConverters), this.interceptors, this.requestFactory,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
/** this.customizers, this.requestCustomizers);
* 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<? extends HttpMessageConverter<?>> messageConverters) {
Assert.notNull(messageConverters, "MessageConverters must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
append ? append(this.messageConverters, messageConverters)
: append(messageConverters, this.messageConverters),
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers,
this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers);
} }
/** /**
...@@ -251,10 +234,10 @@ public class RestTemplateBuilder { ...@@ -251,10 +234,10 @@ public class RestTemplateBuilder {
* @see #messageConverters(HttpMessageConverter...) * @see #messageConverters(HttpMessageConverter...)
*/ */
public RestTemplateBuilder defaultMessageConverters() { public RestTemplateBuilder defaultMessageConverters() {
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
Collections.unmodifiableSet(new LinkedHashSet<>(new RestTemplate().getMessageConverters())), setOf(new RestTemplate().getMessageConverters()), this.interceptors, this.requestFactory,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); this.customizers, this.requestCustomizers);
} }
/** /**
...@@ -282,10 +265,10 @@ public class RestTemplateBuilder { ...@@ -282,10 +265,10 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder interceptors(Collection<ClientHttpRequestInterceptor> interceptors) { public RestTemplateBuilder interceptors(Collection<ClientHttpRequestInterceptor> interceptors) {
Assert.notNull(interceptors, "interceptors must not be null"); Assert.notNull(interceptors, "interceptors must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, this.messageConverters, setOf(interceptors), this.requestFactory, this.uriTemplateHandler,
this.requestFactoryCustomizer, Collections.unmodifiableSet(new LinkedHashSet<>(interceptors)), this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.httpHeadersCustomizers); this.requestCustomizers);
} }
/** /**
...@@ -311,9 +294,10 @@ public class RestTemplateBuilder { ...@@ -311,9 +294,10 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder additionalInterceptors(Collection<? extends ClientHttpRequestInterceptor> interceptors) { public RestTemplateBuilder additionalInterceptors(Collection<? extends ClientHttpRequestInterceptor> interceptors) {
Assert.notNull(interceptors, "interceptors must not be null"); Assert.notNull(interceptors, "interceptors must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, this.messageConverters, append(this.interceptors, interceptors), this.requestFactory,
this.requestFactoryCustomizer, append(this.interceptors, interceptors), this.httpHeadersCustomizers); this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers);
} }
/** /**
...@@ -341,15 +325,15 @@ public class RestTemplateBuilder { ...@@ -341,15 +325,15 @@ public class RestTemplateBuilder {
/** /**
* Set the {@code Supplier} of {@link ClientHttpRequestFactory} that should be called * Set the {@code Supplier} of {@link ClientHttpRequestFactory} that should be called
* each time we {@link #build()} a new {@link RestTemplate} instance. * each time we {@link #build()} a new {@link RestTemplate} instance.
* @param requestFactorySupplier the supplier for the request factory * @param requestFactory the supplier for the request factory
* @return a new builder instance * @return a new builder instance
* @since 2.0.0 * @since 2.0.0
*/ */
public RestTemplateBuilder requestFactory(Supplier<ClientHttpRequestFactory> requestFactorySupplier) { public RestTemplateBuilder requestFactory(Supplier<ClientHttpRequestFactory> requestFactory) {
Assert.notNull(requestFactorySupplier, "RequestFactory Supplier must not be null"); Assert.notNull(requestFactory, "RequestFactory Supplier must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, this.messageConverters, this.interceptors, requestFactory, this.uriTemplateHandler, this.errorHandler,
this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); this.basicAuthentication, this.defaultHeaders, this.customizers, this.requestCustomizers);
} }
/** /**
...@@ -360,9 +344,9 @@ public class RestTemplateBuilder { ...@@ -360,9 +344,9 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder uriTemplateHandler(UriTemplateHandler uriTemplateHandler) { public RestTemplateBuilder uriTemplateHandler(UriTemplateHandler uriTemplateHandler) {
Assert.notNull(uriTemplateHandler, "UriTemplateHandler must not be null"); Assert.notNull(uriTemplateHandler, "UriTemplateHandler must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
this.requestFactorySupplier, uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, this.messageConverters, this.interceptors, this.requestFactory, uriTemplateHandler, this.errorHandler,
this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); this.basicAuthentication, this.defaultHeaders, this.customizers, this.requestCustomizers);
} }
/** /**
...@@ -373,156 +357,95 @@ public class RestTemplateBuilder { ...@@ -373,156 +357,95 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder errorHandler(ResponseErrorHandler errorHandler) { public RestTemplateBuilder errorHandler(ResponseErrorHandler errorHandler) {
Assert.notNull(errorHandler, "ErrorHandler must not be null"); Assert.notNull(errorHandler, "ErrorHandler must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
this.requestFactorySupplier, this.uriTemplateHandler, errorHandler, this.restTemplateCustomizers, this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler, errorHandler,
this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers); this.basicAuthentication, this.defaultHeaders, this.customizers, this.requestCustomizers);
} }
/** /**
* Add a header to requests with the given value unless a custom header has been set * Add HTTP Basic Authentication to requests with the given username/password pair,
* before. * unless a custom Authorization header has been set before.
* @param header the header * @param username the user name
* @param value the value * @param password the password
* @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<? extends HttpHeadersCustomizer> 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 * @return a new builder instance
* @see #httpHeadersCustomizers(HttpHeadersCustomizer...) * @since 2.1.0
* @see #basicAuthentication(String, String, Charset)
*/ */
public RestTemplateBuilder additionalHttpHeadersCustomizers(HttpHeadersCustomizer... httpHeadersCustomizers) { public RestTemplateBuilder basicAuthentication(String username, String password) {
Assert.notNull(httpHeadersCustomizers, "HttpHeadersCustomizers must not be null"); return basicAuthentication(username, password, null);
return additionalHttpHeadersCustomizers(true, httpHeadersCustomizers);
} }
/** /**
* Add the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied * Add HTTP Basic Authentication to requests with the given username/password pair,
* to the {@link HttpHeaders} on the request. Customizers are applied in the order * unless a custom Authorization header has been set before.
* that they were added. * @param username the user name
* @param append if true adds customizers to the end otherwise to the beginning * @param password the password
* @param httpHeadersCustomizers the customizers to set * @param charset the charset to use
* @return a new builder instance * @return a new builder instance
* @see #httpHeadersCustomizers(HttpHeadersCustomizer...) * @since 2.2.0
*/ */
public RestTemplateBuilder additionalHttpHeadersCustomizers(boolean append, public RestTemplateBuilder basicAuthentication(String username, String password, Charset charset) {
HttpHeadersCustomizer... httpHeadersCustomizers) { return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
Assert.notNull(httpHeadersCustomizers, "HttpHeadersCustomizers must not be null"); this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
return additionalHttpHeadersCustomizers(append, Arrays.asList(httpHeadersCustomizers)); this.errorHandler, new BasicAuthentication(username, password, charset), this.defaultHeaders,
this.customizers, this.requestCustomizers);
} }
/** /**
* Add the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied * Add a default header that will be set if not already present on the outgoing
* to the {@link HttpHeaders} on the request. Customizers are applied in the order * {@link HttpClientRequest}.
* that they were added. * @param name the name of the header
* @param httpHeadersCustomizers the customizers to set * @param value the header value
* @return a new builder instance * @return a new builder instance
* @see #httpHeadersCustomizers(HttpHeadersCustomizer...) * @since 2.2.0
*/ */
public RestTemplateBuilder additionalHttpHeadersCustomizers( public RestTemplateBuilder defaultHeader(String name, String value) {
Collection<? extends HttpHeadersCustomizer> httpHeadersCustomizers) { return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
return additionalHttpHeadersCustomizers(true, httpHeadersCustomizers); this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, append(this.defaultHeaders, name, value), this.customizers,
this.requestCustomizers);
} }
/** /**
* Add the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied * Sets the connection timeout on the underlying {@link ClientHttpRequestFactory}.
* to the {@link HttpHeaders} on the request. Customizers are applied in the order * @param connectTimeout the connection timeout
* that they were added. * @return a new builder instance.
* @param append if true adds customizers to the end otherwise to the beginning * @since 2.1.0
* @param httpHeadersCustomizers the customizers to set
* @return a new builder instance
* @see #httpHeadersCustomizers(HttpHeadersCustomizer...)
*/ */
public RestTemplateBuilder additionalHttpHeadersCustomizers(boolean append, public RestTemplateBuilder setConnectTimeout(Duration connectTimeout) {
Collection<? extends HttpHeadersCustomizer> httpHeadersCustomizers) { return new RestTemplateBuilder(this.requestFactoryCustomizer.connectTimeout(connectTimeout),
Assert.notNull(httpHeadersCustomizers, "HttpHeadersCustomizers must not be null"); this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors, this.requestFactory,
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, this.customizers, this.requestCustomizers);
this.requestFactoryCustomizer, this.interceptors,
append ? append(this.httpHeadersCustomizers, httpHeadersCustomizers)
: append(httpHeadersCustomizers, this.httpHeadersCustomizers));
} }
/** /**
* Add HTTP Basic Authentication to requests with the given username/password pair, * Sets the read timeout on the underlying {@link ClientHttpRequestFactory}.
* unless a custom Authorization header has been set before. * @param readTimeout the read timeout
* @param username the user name * @return a new builder instance.
* @param password the password
* @return a new builder instance
* @since 2.1.0 * @since 2.1.0
* @see #basicAuthentication(String, String, Charset)
*/ */
public RestTemplateBuilder basicAuthentication(String username, String password) { public RestTemplateBuilder setReadTimeout(Duration readTimeout) {
return basicAuthentication(username, password, null); return new RestTemplateBuilder(this.requestFactoryCustomizer.readTimeout(readTimeout),
this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors, this.requestFactory,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers);
} }
/** /**
* Add HTTP Basic Authentication to requests with the given username/password pair, * Sets if the underling {@link ClientHttpRequestFactory} should buffer the
* unless a custom Authorization header has been set before. Customizer is added to * {@linkplain ClientHttpRequest#getBody() request body} internally.
* the beginning of the {@link HttpHeadersCustomizer HttpHeadersCustomizers} * @param bufferRequestBody value of the bufferRequestBody parameter
* collection. * @return a new builder instance.
* @param username the user name
* @param password the password
* @param charset the charset to use
* @return a new builder instance
* @since 2.2.0 * @since 2.2.0
* @see #additionalHttpHeadersCustomizers(HttpHeadersCustomizer...) * @see SimpleClientHttpRequestFactory#setBufferRequestBody(boolean)
* @see SimpleHttpHeaderDefaultingCustomizer#basicAuthentication(String, String, * @see HttpComponentsClientHttpRequestFactory#setBufferRequestBody(boolean)
* Charset)
*/ */
public RestTemplateBuilder basicAuthentication(String username, String password, Charset charset) { public RestTemplateBuilder setBufferRequestBody(boolean bufferRequestBody) {
return additionalHttpHeadersCustomizers(false, return new RestTemplateBuilder(this.requestFactoryCustomizer.bufferRequestBody(bufferRequestBody),
SimpleHttpHeaderDefaultingCustomizer.basicAuthentication(username, password, charset)); this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors, this.requestFactory,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers);
} }
/** /**
...@@ -530,13 +453,13 @@ public class RestTemplateBuilder { ...@@ -530,13 +453,13 @@ public class RestTemplateBuilder {
* applied to the {@link RestTemplate}. Customizers are applied in the order that they * applied to the {@link RestTemplate}. Customizers are applied in the order that they
* were added after builder configuration has been applied. Setting this value will * were added after builder configuration has been applied. Setting this value will
* replace any previously configured customizers. * replace any previously configured customizers.
* @param restTemplateCustomizers the customizers to set * @param customizers the customizers to set
* @return a new builder instance * @return a new builder instance
* @see #additionalCustomizers(RestTemplateCustomizer...) * @see #additionalCustomizers(RestTemplateCustomizer...)
*/ */
public RestTemplateBuilder customizers(RestTemplateCustomizer... restTemplateCustomizers) { public RestTemplateBuilder customizers(RestTemplateCustomizer... customizers) {
Assert.notNull(restTemplateCustomizers, "RestTemplateCustomizers must not be null"); Assert.notNull(customizers, "Customizers must not be null");
return customizers(Arrays.asList(restTemplateCustomizers)); return customizers(Arrays.asList(customizers));
} }
/** /**
...@@ -544,45 +467,29 @@ public class RestTemplateBuilder { ...@@ -544,45 +467,29 @@ public class RestTemplateBuilder {
* applied to the {@link RestTemplate}. Customizers are applied in the order that they * applied to the {@link RestTemplate}. Customizers are applied in the order that they
* were added after builder configuration has been applied. Setting this value will * were added after builder configuration has been applied. Setting this value will
* replace any previously configured customizers. * replace any previously configured customizers.
* @param restTemplateCustomizers the customizers to set * @param customizers the customizers to set
* @return a new builder instance * @return a new builder instance
* @see #additionalCustomizers(RestTemplateCustomizer...) * @see #additionalCustomizers(RestTemplateCustomizer...)
*/ */
public RestTemplateBuilder customizers(Collection<? extends RestTemplateCustomizer> restTemplateCustomizers) { public RestTemplateBuilder customizers(Collection<? extends RestTemplateCustomizer> customizers) {
Assert.notNull(restTemplateCustomizers, "RestTemplateCustomizers must not be null"); Assert.notNull(customizers, "Customizers must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, setOf(customizers),
Collections.unmodifiableSet(new LinkedHashSet<RestTemplateCustomizer>(restTemplateCustomizers)), this.requestCustomizers);
this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers);
}
/**
* 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 restTemplateCustomizers the customizers to add
* @return a new builder instance
* @see #customizers(RestTemplateCustomizer...)
*/
public RestTemplateBuilder additionalCustomizers(RestTemplateCustomizer... restTemplateCustomizers) {
Assert.notNull(restTemplateCustomizers, "RestTemplateCustomizers must not be null");
return additionalCustomizers(true, restTemplateCustomizers);
} }
/** /**
* Add {@link RestTemplateCustomizer RestTemplateCustomizers} that should be applied * Add {@link RestTemplateCustomizer RestTemplateCustomizers} that should be applied
* to the {@link RestTemplate}. Customizers are applied in the order that they were * to the {@link RestTemplate}. Customizers are applied in the order that they were
* added after builder configuration has been applied. * 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
* @param restTemplateCustomizers the customizers to add
* @return a new builder instance * @return a new builder instance
* @see #customizers(RestTemplateCustomizer...) * @see #customizers(RestTemplateCustomizer...)
*/ */
public RestTemplateBuilder additionalCustomizers(boolean append, public RestTemplateBuilder additionalCustomizers(RestTemplateCustomizer... customizers) {
RestTemplateCustomizer... restTemplateCustomizers) { Assert.notNull(customizers, "Customizers must not be null");
Assert.notNull(restTemplateCustomizers, "RestTemplateCustomizers must not be null"); return additionalCustomizers(Arrays.asList(customizers));
return additionalCustomizers(append, Arrays.asList(restTemplateCustomizers));
} }
/** /**
...@@ -595,67 +502,76 @@ public class RestTemplateBuilder { ...@@ -595,67 +502,76 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder additionalCustomizers(Collection<? extends RestTemplateCustomizer> customizers) { public RestTemplateBuilder additionalCustomizers(Collection<? extends RestTemplateCustomizer> customizers) {
Assert.notNull(customizers, "RestTemplateCustomizers must not be null"); Assert.notNull(customizers, "RestTemplateCustomizers must not be null");
return additionalCustomizers(true, customizers); return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, append(this.customizers, customizers),
this.requestCustomizers);
} }
/** /**
* Add {@link RestTemplateCustomizer RestTemplateCustomizers} that should be applied * Set the {@link RestTemplateRequestCustomizer RestTemplateRequestCustomizers} that
* to the {@link RestTemplate}. Customizers are applied in the order that they were * should be applied to the {@link ClientHttpRequest}. Customizers are applied in the
* added after builder configuration has been applied. * order that they were added. Setting this value will replace any previously
* @param append if true adds customizers to the end otherwise to the beginning * configured request customizers.
* @param customizers the customizers to add * @param requestCustomizers the request customizers to set
* @return a new builder instance * @return a new builder instance
* @see #customizers(RestTemplateCustomizer...) * @since 2.2.0
* @see #additionalRequestCustomizers(RestTemplateRequestCustomizer...)
*/ */
public RestTemplateBuilder additionalCustomizers(boolean append, public RestTemplateBuilder requestCustomizers(RestTemplateRequestCustomizer<?>... requestCustomizers) {
Collection<? extends RestTemplateCustomizer> customizers) { Assert.notNull(requestCustomizers, "RequestCustomizers must not be null");
Assert.notNull(customizers, "RestTemplateCustomizers must not be null"); return requestCustomizers(Arrays.asList(requestCustomizers));
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler,
append ? append(this.restTemplateCustomizers, customizers)
: append(customizers, this.restTemplateCustomizers),
this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers);
} }
/** /**
* Sets the connection timeout on the underlying {@link ClientHttpRequestFactory}. * Set the {@link RestTemplateRequestCustomizer RestTemplateRequestCustomizers} that
* @param connectTimeout the connection timeout * should be applied to the {@link ClientHttpRequest}. Customizers are applied in the
* @return a new builder instance. * order that they were added. Setting this value will replace any previously
* @since 2.1.0 * configured request customizers.
* @param requestCustomizers the request customizers to set
* @return a new builder instance
* @since 2.2.0
* @see #additionalRequestCustomizers(RestTemplateRequestCustomizer...)
*/ */
public RestTemplateBuilder setConnectTimeout(Duration connectTimeout) { public RestTemplateBuilder requestCustomizers(
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, Collection<? extends RestTemplateRequestCustomizer<?>> requestCustomizers) {
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, Assert.notNull(requestCustomizers, "RequestCustomizers must not be null");
this.requestFactoryCustomizer.connectTimeout(connectTimeout), this.interceptors, return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
this.httpHeadersCustomizers); this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
setOf(requestCustomizers));
} }
/** /**
* Sets the read timeout on the underlying {@link ClientHttpRequestFactory}. * Add the {@link RestTemplateRequestCustomizer RestTemplateRequestCustomizers} that
* @param readTimeout the read timeout * should be applied to the {@link ClientHttpRequest}. Customizers are applied in the
* @return a new builder instance. * order that they were added.
* @since 2.1.0 * @param requestCustomizers the request customizers to add
* @return a new builder instance
* @since 2.2.0
* @see #requestCustomizers(RestTemplateRequestCustomizer...)
*/ */
public RestTemplateBuilder setReadTimeout(Duration readTimeout) { public RestTemplateBuilder additionalRequestCustomizers(RestTemplateRequestCustomizer<?>... requestCustomizers) {
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, Assert.notNull(requestCustomizers, "RequestCustomizers must not be null");
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, return additionalRequestCustomizers(Arrays.asList(requestCustomizers));
this.requestFactoryCustomizer.readTimeout(readTimeout), this.interceptors, this.httpHeadersCustomizers);
} }
/** /**
* Sets if the underling {@link ClientHttpRequestFactory} should buffer the * Add the {@link RestTemplateRequestCustomizer RestTemplateRequestCustomizers} that
* {@linkplain ClientHttpRequest#getBody() request body} internally. * should be applied to the {@link ClientHttpRequest}. Customizers are applied in the
* @param bufferRequestBody value of the bufferRequestBody parameter * order that they were added.
* @return a new builder instance. * @param requestCustomizers the request customizers to add
* @return a new builder instance
* @since 2.2.0 * @since 2.2.0
* @see SimpleClientHttpRequestFactory#setBufferRequestBody(boolean) * @see #requestCustomizers(Collection)
* @see HttpComponentsClientHttpRequestFactory#setBufferRequestBody(boolean)
*/ */
public RestTemplateBuilder setBufferRequestBody(boolean bufferRequestBody) { public RestTemplateBuilder additionalRequestCustomizers(
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, Collection<? extends RestTemplateRequestCustomizer<?>> requestCustomizers) {
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers, Assert.notNull(requestCustomizers, "RequestCustomizers must not be null");
this.requestFactoryCustomizer.bufferRequestBody(bufferRequestBody), this.interceptors, return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri,
this.httpHeadersCustomizers); this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
append(this.requestCustomizers, requestCustomizers));
} }
/** /**
...@@ -694,9 +610,7 @@ public class RestTemplateBuilder { ...@@ -694,9 +610,7 @@ public class RestTemplateBuilder {
if (requestFactory != null) { if (requestFactory != null) {
restTemplate.setRequestFactory(requestFactory); restTemplate.setRequestFactory(requestFactory);
} }
if (!CollectionUtils.isEmpty(this.httpHeadersCustomizers)) { addClientHttpRequestFactoryWrapper(restTemplate);
configureHttpHeadersCustomizers(restTemplate);
}
if (!CollectionUtils.isEmpty(this.messageConverters)) { if (!CollectionUtils.isEmpty(this.messageConverters)) {
restTemplate.setMessageConverters(new ArrayList<>(this.messageConverters)); restTemplate.setMessageConverters(new ArrayList<>(this.messageConverters));
} }
...@@ -710,8 +624,8 @@ public class RestTemplateBuilder { ...@@ -710,8 +624,8 @@ public class RestTemplateBuilder {
RootUriTemplateHandler.addTo(restTemplate, this.rootUri); RootUriTemplateHandler.addTo(restTemplate, this.rootUri);
} }
restTemplate.getInterceptors().addAll(this.interceptors); restTemplate.getInterceptors().addAll(this.interceptors);
if (!CollectionUtils.isEmpty(this.restTemplateCustomizers)) { if (!CollectionUtils.isEmpty(this.customizers)) {
for (RestTemplateCustomizer customizer : this.restTemplateCustomizers) { for (RestTemplateCustomizer customizer : this.customizers) {
customizer.customize(restTemplate); customizer.customize(restTemplate);
} }
} }
...@@ -726,8 +640,8 @@ public class RestTemplateBuilder { ...@@ -726,8 +640,8 @@ public class RestTemplateBuilder {
*/ */
public ClientHttpRequestFactory buildRequestFactory() { public ClientHttpRequestFactory buildRequestFactory() {
ClientHttpRequestFactory requestFactory = null; ClientHttpRequestFactory requestFactory = null;
if (this.requestFactorySupplier != null) { if (this.requestFactory != null) {
requestFactory = this.requestFactorySupplier.get(); requestFactory = this.requestFactory.get();
} }
else if (this.detectRequestFactory) { else if (this.detectRequestFactory) {
requestFactory = new ClientHttpRequestFactorySupplier().get(); requestFactory = new ClientHttpRequestFactorySupplier().get();
...@@ -740,7 +654,10 @@ public class RestTemplateBuilder { ...@@ -740,7 +654,10 @@ public class RestTemplateBuilder {
return requestFactory; return requestFactory;
} }
private void configureHttpHeadersCustomizers(RestTemplate restTemplate) { private void addClientHttpRequestFactoryWrapper(RestTemplate restTemplate) {
if (this.basicAuthentication == null && this.defaultHeaders.isEmpty() && this.requestCustomizers.isEmpty()) {
return;
}
List<ClientHttpRequestInterceptor> interceptors = null; List<ClientHttpRequestInterceptor> interceptors = null;
if (!restTemplate.getInterceptors().isEmpty()) { if (!restTemplate.getInterceptors().isEmpty()) {
// Stash and clear the interceptors so we can access the real factory // Stash and clear the interceptors so we can access the real factory
...@@ -748,14 +665,24 @@ public class RestTemplateBuilder { ...@@ -748,14 +665,24 @@ public class RestTemplateBuilder {
restTemplate.getInterceptors().clear(); restTemplate.getInterceptors().clear();
} }
ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory(); ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory();
restTemplate.setRequestFactory( ClientHttpRequestFactory wrapper = new RestTemplateBuilderClientHttpRequestFactoryWrapper(requestFactory,
new HttpHeadersCustomizingClientHttpRequestFactory(this.httpHeadersCustomizers, requestFactory)); this.basicAuthentication, this.defaultHeaders, this.requestCustomizers);
restTemplate.setRequestFactory(wrapper);
// Restore the original interceptors // Restore the original interceptors
if (interceptors != null) { if (interceptors != null) {
restTemplate.getInterceptors().addAll(interceptors); restTemplate.getInterceptors().addAll(interceptors);
} }
} }
@SuppressWarnings("unchecked")
private <T> Set<T> setOf(T... items) {
return setOf(Arrays.asList(items));
}
private <T> Set<T> setOf(Collection<? extends T> collection) {
return Collections.unmodifiableSet(new LinkedHashSet<>(collection));
}
private static <T> Set<T> append(Collection<? extends T> collection, Collection<? extends T> additions) { private static <T> Set<T> append(Collection<? extends T> collection, Collection<? extends T> additions) {
Set<T> result = new LinkedHashSet<>((collection != null) ? collection : Collections.emptySet()); Set<T> result = new LinkedHashSet<>((collection != null) ? collection : Collections.emptySet());
if (additions != null) { if (additions != null) {
...@@ -764,6 +691,15 @@ public class RestTemplateBuilder { ...@@ -764,6 +691,15 @@ public class RestTemplateBuilder {
return Collections.unmodifiableSet(result); return Collections.unmodifiableSet(result);
} }
private static <K, V> Map<K, V> append(Map<K, V> map, K key, V value) {
Map<K, V> result = new LinkedHashMap<>((map != null) ? map : Collections.emptyMap());
result.put(key, value);
return Collections.unmodifiableMap(result);
}
/**
* Internal customizer used to apply {@link ClientHttpRequestFactory} settings.
*/
private static class RequestFactoryCustomizer implements Consumer<ClientHttpRequestFactory> { private static class RequestFactoryCustomizer implements Consumer<ClientHttpRequestFactory> {
private final Duration connectTimeout; private final Duration connectTimeout;
......
...@@ -18,40 +18,52 @@ package org.springframework.boot.web.client; ...@@ -18,40 +18,52 @@ package org.springframework.boot.web.client;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.Collection; import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper; import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.Assert;
/** /**
* {@link ClientHttpRequestFactory} to apply default headers to a request unless header * {@link ClientHttpRequestFactory} to apply customizations from the
* values were provided. * {@link RestTemplateBuilder}.
* *
* @author Ilya Lukyanovich
* @author Dmytro Nosan * @author Dmytro Nosan
* @author Ilya Lukyanovich
*/ */
class HttpHeadersCustomizingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper { class RestTemplateBuilderClientHttpRequestFactoryWrapper extends AbstractClientHttpRequestFactoryWrapper {
private final BasicAuthentication basicAuthentication;
private final Map<String, String> defaultHeaders;
private final Collection<? extends HttpHeadersCustomizer> customizers; private final Set<RestTemplateRequestCustomizer<?>> requestCustomizers;
HttpHeadersCustomizingClientHttpRequestFactory(Collection<? extends HttpHeadersCustomizer> customizers, RestTemplateBuilderClientHttpRequestFactoryWrapper(ClientHttpRequestFactory requestFactory,
ClientHttpRequestFactory clientHttpRequestFactory) { BasicAuthentication basicAuthentication, Map<String, String> defaultHeaders,
super(clientHttpRequestFactory); Set<RestTemplateRequestCustomizer<?>> requestCustomizers) {
Assert.notEmpty(customizers, "Customizers must not be empty"); super(requestFactory);
this.customizers = customizers; this.basicAuthentication = basicAuthentication;
this.defaultHeaders = defaultHeaders;
this.requestCustomizers = requestCustomizers;
} }
@NotNull
@Override @Override
protected ClientHttpRequest createRequest(@NotNull URI uri, @NotNull HttpMethod httpMethod, @SuppressWarnings("unchecked")
ClientHttpRequestFactory requestFactory) throws IOException { protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory)
throws IOException {
ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod); ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod);
this.customizers.forEach((customizer) -> customizer.applyTo(request.getHeaders())); HttpHeaders headers = request.getHeaders();
if (this.basicAuthentication != null) {
this.basicAuthentication.applyTo(headers);
}
this.defaultHeaders.forEach(headers::addIfAbsent);
LambdaSafe.callbacks(RestTemplateRequestCustomizer.class, this.requestCustomizers, request)
.invoke((customizer) -> customizer.customize(request));
return request; return request;
} }
......
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,21 +16,26 @@ ...@@ -16,21 +16,26 @@
package org.springframework.boot.web.client; package org.springframework.boot.web.client;
import org.springframework.http.HttpHeaders; import org.springframework.http.client.ClientHttpRequest;
import org.springframework.web.client.RestTemplate;
/** /**
* Callback interface that can be used to customize a {@link HttpHeaders}. * Callback interface that can be used to customize the {@link ClientHttpRequest} sent
* from a {@link RestTemplate}.
* *
* @param <T> the {@link ClientHttpRequest} type
* @author Ilya Lukyanovich * @author Ilya Lukyanovich
* @see HttpHeadersCustomizingClientHttpRequestFactory * @author Phillip Webb
* @since 2.2.0
* @see RestTemplateBuilder
*/ */
@FunctionalInterface @FunctionalInterface
public interface HttpHeadersCustomizer { public interface RestTemplateRequestCustomizer<T extends ClientHttpRequest> {
/** /**
* Callback to customize a {@link HttpHeaders} instance. * Customize the specified {@link ClientHttpRequest}.
* @param headers the headers to customize * @param request the request to customize
*/ */
void applyTo(HttpHeaders headers); void customize(T request);
} }
/*
* 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);
}
}
...@@ -18,11 +18,15 @@ package org.springframework.boot.web.client; ...@@ -18,11 +18,15 @@ package org.springframework.boot.web.client;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
...@@ -32,20 +36,22 @@ import org.springframework.http.client.ClientHttpRequestFactory; ...@@ -32,20 +36,22 @@ import org.springframework.http.client.ClientHttpRequestFactory;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link HttpHeadersCustomizingClientHttpRequestFactory}. * Tests for {@link RestTemplateBuilderClientHttpRequestFactoryWrapper}.
* *
* @author Dmytro Nosan * @author Dmytro Nosan
* @author Ilya Lukyanovich * @author Ilya Lukyanovich
* @author Phillip Webb
*/ */
public class HttpHeadersCustomizingClientHttpRequestFactoryTests { public class RestTemplateBuilderClientHttpRequestFactoryWrapperTests {
private final HttpHeaders headers = new HttpHeaders();
private ClientHttpRequestFactory requestFactory; private ClientHttpRequestFactory requestFactory;
private final HttpHeaders headers = new HttpHeaders();
@BeforeEach @BeforeEach
public void setUp() throws IOException { public void setUp() throws IOException {
this.requestFactory = mock(ClientHttpRequestFactory.class); this.requestFactory = mock(ClientHttpRequestFactory.class);
...@@ -55,36 +61,51 @@ public class HttpHeadersCustomizingClientHttpRequestFactoryTests { ...@@ -55,36 +61,51 @@ public class HttpHeadersCustomizingClientHttpRequestFactoryTests {
} }
@Test @Test
void shouldAddAuthorizationHeader() throws IOException { void createRequestWhenHasBasicAuthAndNoAuthHeaderAddsHeader() throws IOException {
this.requestFactory = new HttpHeadersCustomizingClientHttpRequestFactory( this.requestFactory = new RestTemplateBuilderClientHttpRequestFactoryWrapper(this.requestFactory,
Collections.singleton(SimpleHttpHeaderDefaultingCustomizer.basicAuthentication("spring", "boot", null)), new BasicAuthentication("spring", "boot", null), Collections.emptyMap(), Collections.emptySet());
this.requestFactory);
ClientHttpRequest request = createRequest(); ClientHttpRequest request = createRequest();
assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic c3ByaW5nOmJvb3Q="); assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic c3ByaW5nOmJvb3Q=");
} }
@Test @Test
void shouldNotAddAuthorizationHeaderAuthorizationAlreadySet() throws IOException { void createRequestWhenHasBasicAuthAndExistingAuthHeaderDoesNotAddHeader() throws IOException {
this.headers.setBasicAuth("boot", "spring"); this.headers.setBasicAuth("boot", "spring");
this.requestFactory = new HttpHeadersCustomizingClientHttpRequestFactory( this.requestFactory = new RestTemplateBuilderClientHttpRequestFactoryWrapper(this.requestFactory,
Collections.singleton(SimpleHttpHeaderDefaultingCustomizer.basicAuthentication("spring", "boot", null)), new BasicAuthentication("spring", "boot", null), Collections.emptyMap(), Collections.emptySet());
this.requestFactory);
ClientHttpRequest request = createRequest(); ClientHttpRequest request = createRequest();
assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).doesNotContain("Basic c3ByaW5nOmJvb3Q="); assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).doesNotContain("Basic c3ByaW5nOmJvb3Q=");
}
@Test
void createRequestWhenHasDefaultHeadersAddsMissing() throws IOException {
this.headers.add("one", "existing");
Map<String, String> defaultHeaders = new LinkedHashMap<>();
defaultHeaders.put("one", "1");
defaultHeaders.put("two", "2");
defaultHeaders.put("three", "3");
this.requestFactory = new RestTemplateBuilderClientHttpRequestFactoryWrapper(this.requestFactory, null,
defaultHeaders, Collections.emptySet());
ClientHttpRequest request = createRequest();
assertThat(request.getHeaders().get("one")).containsExactly("existing");
assertThat(request.getHeaders().get("two")).containsExactly("2");
assertThat(request.getHeaders().get("three")).containsExactly("3");
} }
@Test @Test
void shouldApplyCustomizersInTheProvidedOrder() throws IOException { @SuppressWarnings("unchecked")
this.requestFactory = new HttpHeadersCustomizingClientHttpRequestFactory( void createRequestWhenHasRequestCustomizersAppliesThemInOrder() throws IOException {
Arrays.asList((headers) -> headers.add("foo", "bar"), Set<RestTemplateRequestCustomizer<?>> customizers = new LinkedHashSet<>();
SimpleHttpHeaderDefaultingCustomizer.basicAuthentication("spring", "boot", null), customizers.add(mock(RestTemplateRequestCustomizer.class));
SimpleHttpHeaderDefaultingCustomizer.singleHeader(HttpHeaders.AUTHORIZATION, "won't do")), customizers.add(mock(RestTemplateRequestCustomizer.class));
this.requestFactory); customizers.add(mock(RestTemplateRequestCustomizer.class));
this.requestFactory = new RestTemplateBuilderClientHttpRequestFactoryWrapper(this.requestFactory, null,
Collections.emptyMap(), customizers);
ClientHttpRequest request = createRequest(); ClientHttpRequest request = createRequest();
assertThat(request.getHeaders()).containsOnlyKeys("foo", HttpHeaders.AUTHORIZATION); InOrder inOrder = inOrder(customizers.toArray());
assertThat(request.getHeaders().get("foo")).containsExactly("bar"); for (RestTemplateRequestCustomizer<?> customizer : customizers) {
assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic c3ByaW5nOmJvb3Q="); inOrder.verify((RestTemplateRequestCustomizer<ClientHttpRequest>) customizer).customize(request);
}
} }
private ClientHttpRequest createRequest() throws IOException { private ClientHttpRequest createRequest() throws IOException {
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.web.client; package org.springframework.boot.web.client;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
...@@ -52,6 +53,7 @@ import org.springframework.web.util.UriTemplateHandler; ...@@ -52,6 +53,7 @@ import org.springframework.web.util.UriTemplateHandler;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
...@@ -69,6 +71,7 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat ...@@ -69,6 +71,7 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Dmytro Nosan * @author Dmytro Nosan
* @author Kevin Strijbos * @author Kevin Strijbos
* @author Ilya Lukyanovich
*/ */
class RestTemplateBuilderTests { class RestTemplateBuilderTests {
...@@ -310,17 +313,45 @@ class RestTemplateBuilderTests { ...@@ -310,17 +313,45 @@ class RestTemplateBuilderTests {
assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic c3ByaW5nOmJvb3Q="); assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic c3ByaW5nOmJvb3Q=");
} }
@Test
void defaultHeaderAddsHeader() throws IOException {
RestTemplate template = this.builder.defaultHeader("spring", "boot").build();
ClientHttpRequestFactory requestFactory = template.getRequestFactory();
ClientHttpRequest request = requestFactory.createRequest(URI.create("http://localhost"), HttpMethod.GET);
assertThat(request.getHeaders()).contains(entry("spring", Collections.singletonList("boot")));
}
@Test
void requestCustomizersAddsCustomizers() throws IOException {
RestTemplate template = this.builder
.requestCustomizers((request) -> request.getHeaders().add("spring", "framework")).build();
ClientHttpRequestFactory requestFactory = template.getRequestFactory();
ClientHttpRequest request = requestFactory.createRequest(URI.create("http://localhost"), HttpMethod.GET);
assertThat(request.getHeaders()).contains(entry("spring", Collections.singletonList("framework")));
}
@Test
void additionalRequestCustomizersAddsCustomizers() throws IOException {
RestTemplate template = this.builder
.requestCustomizers((request) -> request.getHeaders().add("spring", "framework"))
.additionalRequestCustomizers((request) -> request.getHeaders().add("for", "java")).build();
ClientHttpRequestFactory requestFactory = template.getRequestFactory();
ClientHttpRequest request = requestFactory.createRequest(URI.create("http://localhost"), HttpMethod.GET);
assertThat(request.getHeaders()).contains(entry("spring", Collections.singletonList("framework")))
.contains(entry("for", Collections.singletonList("java")));
}
@Test @Test
void customizersWhenCustomizersAreNullShouldThrowException() { void customizersWhenCustomizersAreNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.builder.customizers((RestTemplateCustomizer[]) null)) assertThatIllegalArgumentException().isThrownBy(() -> this.builder.customizers((RestTemplateCustomizer[]) null))
.withMessageContaining("RestTemplateCustomizers must not be null"); .withMessageContaining("Customizers must not be null");
} }
@Test @Test
void customizersCollectionWhenCustomizersAreNullShouldThrowException() { void customizersCollectionWhenCustomizersAreNullShouldThrowException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> this.builder.customizers((Set<RestTemplateCustomizer>) null)) .isThrownBy(() -> this.builder.customizers((Set<RestTemplateCustomizer>) null))
.withMessageContaining("RestTemplateCustomizers must not be null"); .withMessageContaining("Customizers must not be null");
} }
@Test @Test
...@@ -352,7 +383,7 @@ class RestTemplateBuilderTests { ...@@ -352,7 +383,7 @@ class RestTemplateBuilderTests {
void additionalCustomizersWhenCustomizersAreNullShouldThrowException() { void additionalCustomizersWhenCustomizersAreNullShouldThrowException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> this.builder.additionalCustomizers((RestTemplateCustomizer[]) null)) .isThrownBy(() -> this.builder.additionalCustomizers((RestTemplateCustomizer[]) null))
.withMessageContaining("RestTemplateCustomizers must not be null"); .withMessageContaining("Customizers must not be null");
} }
@Test @Test
...@@ -387,7 +418,8 @@ class RestTemplateBuilderTests { ...@@ -387,7 +418,8 @@ class RestTemplateBuilderTests {
assertThat(actualRequestFactory).isInstanceOf(InterceptingClientHttpRequestFactory.class); assertThat(actualRequestFactory).isInstanceOf(InterceptingClientHttpRequestFactory.class);
ClientHttpRequestFactory authRequestFactory = (ClientHttpRequestFactory) ReflectionTestUtils ClientHttpRequestFactory authRequestFactory = (ClientHttpRequestFactory) ReflectionTestUtils
.getField(actualRequestFactory, "requestFactory"); .getField(actualRequestFactory, "requestFactory");
assertThat(authRequestFactory).isInstanceOf(HttpHeadersCustomizingClientHttpRequestFactory.class); assertThat(authRequestFactory)
.isInstanceOf(RestTemplateBuilderClientHttpRequestFactoryWrapper.class);
assertThat(authRequestFactory).hasFieldOrPropertyWithValue("requestFactory", requestFactory); assertThat(authRequestFactory).hasFieldOrPropertyWithValue("requestFactory", requestFactory);
}).build(); }).build();
} }
......
/*
* 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");
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment