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
pull/17129/head
Phillip Webb 6 years ago
parent 43b1a667ce
commit aad21d1996

@ -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
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 {
@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 @@
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
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 {
}); });
} }
@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 {
} }
@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 {
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 {
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 {
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();

@ -1,36 +0,0 @@
/*
* 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;
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
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
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; }
} }
} }

@ -1,58 +0,0 @@
/*
* 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.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;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.Assert;
/**
* {@link ClientHttpRequestFactory} to apply default headers to a request unless header
* values were provided.
*
* @author Ilya Lukyanovich
* @author Dmytro Nosan
*/
class HttpHeadersCustomizingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {
private final Collection<? extends HttpHeadersCustomizer> customizers;
HttpHeadersCustomizingClientHttpRequestFactory(Collection<? extends HttpHeadersCustomizer> customizers,
ClientHttpRequestFactory clientHttpRequestFactory) {
super(clientHttpRequestFactory);
Assert.notEmpty(customizers, "Customizers must not be empty");
this.customizers = customizers;
}
@NotNull
@Override
protected ClientHttpRequest createRequest(@NotNull URI uri, @NotNull HttpMethod httpMethod,
ClientHttpRequestFactory requestFactory) throws IOException {
ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod);
this.customizers.forEach((customizer) -> customizer.applyTo(request.getHeaders()));
return request;
}
}

@ -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;
*/ */
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 {
*/ */
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 {
* @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 {
* @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 {
*/ */
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 {
*/ */
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 {
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 {
* @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 {
*/ */
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 {
*/ */
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 {
/** /**
* 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 {
*/ */
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 {
*/ */
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 {
* 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 {
* 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 {
*/ */
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 {
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 {
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 {
*/ */
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 {
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 {
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 {
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;

@ -0,0 +1,70 @@
/*
* 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.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
/**
* {@link ClientHttpRequestFactory} to apply customizations from the
* {@link RestTemplateBuilder}.
*
* @author Dmytro Nosan
* @author Ilya Lukyanovich
*/
class RestTemplateBuilderClientHttpRequestFactoryWrapper extends AbstractClientHttpRequestFactoryWrapper {
private final BasicAuthentication basicAuthentication;
private final Map<String, String> defaultHeaders;
private final Set<RestTemplateRequestCustomizer<?>> requestCustomizers;
RestTemplateBuilderClientHttpRequestFactoryWrapper(ClientHttpRequestFactory requestFactory,
BasicAuthentication basicAuthentication, Map<String, String> defaultHeaders,
Set<RestTemplateRequestCustomizer<?>> requestCustomizers) {
super(requestFactory);
this.basicAuthentication = basicAuthentication;
this.defaultHeaders = defaultHeaders;
this.requestCustomizers = requestCustomizers;
}
@Override
@SuppressWarnings("unchecked")
protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory)
throws IOException {
ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod);
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;
}
}

@ -1,5 +1,5 @@
/* /*
* 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 @@
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);
} }

@ -1,96 +0,0 @@
/*
* 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);
}
}

@ -1,94 +0,0 @@
/*
* 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.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;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
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;
/**
* Tests for {@link HttpHeadersCustomizingClientHttpRequestFactory}.
*
* @author Dmytro Nosan
* @author Ilya Lukyanovich
*/
public class HttpHeadersCustomizingClientHttpRequestFactoryTests {
private final HttpHeaders headers = new HttpHeaders();
private ClientHttpRequestFactory requestFactory;
@BeforeEach
public void setUp() throws IOException {
this.requestFactory = mock(ClientHttpRequestFactory.class);
ClientHttpRequest request = mock(ClientHttpRequest.class);
given(this.requestFactory.createRequest(any(), any())).willReturn(request);
given(request.getHeaders()).willReturn(this.headers);
}
@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=");
}
@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);
}
}

@ -0,0 +1,115 @@
/*
* 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.io.IOException;
import java.net.URI;
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.Test;
import org.mockito.InOrder;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
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.inOrder;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link RestTemplateBuilderClientHttpRequestFactoryWrapper}.
*
* @author Dmytro Nosan
* @author Ilya Lukyanovich
* @author Phillip Webb
*/
public class RestTemplateBuilderClientHttpRequestFactoryWrapperTests {
private ClientHttpRequestFactory requestFactory;
private final HttpHeaders headers = new HttpHeaders();
@BeforeEach
public void setUp() throws IOException {
this.requestFactory = mock(ClientHttpRequestFactory.class);
ClientHttpRequest request = mock(ClientHttpRequest.class);
given(this.requestFactory.createRequest(any(), any())).willReturn(request);
given(request.getHeaders()).willReturn(this.headers);
}
@Test
void createRequestWhenHasBasicAuthAndNoAuthHeaderAddsHeader() throws IOException {
this.requestFactory = new RestTemplateBuilderClientHttpRequestFactoryWrapper(this.requestFactory,
new BasicAuthentication("spring", "boot", null), Collections.emptyMap(), Collections.emptySet());
ClientHttpRequest request = createRequest();
assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic c3ByaW5nOmJvb3Q=");
}
@Test
void createRequestWhenHasBasicAuthAndExistingAuthHeaderDoesNotAddHeader() throws IOException {
this.headers.setBasicAuth("boot", "spring");
this.requestFactory = new RestTemplateBuilderClientHttpRequestFactoryWrapper(this.requestFactory,
new BasicAuthentication("spring", "boot", null), Collections.emptyMap(), Collections.emptySet());
ClientHttpRequest request = createRequest();
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
@SuppressWarnings("unchecked")
void createRequestWhenHasRequestCustomizersAppliesThemInOrder() throws IOException {
Set<RestTemplateRequestCustomizer<?>> customizers = new LinkedHashSet<>();
customizers.add(mock(RestTemplateRequestCustomizer.class));
customizers.add(mock(RestTemplateRequestCustomizer.class));
customizers.add(mock(RestTemplateRequestCustomizer.class));
this.requestFactory = new RestTemplateBuilderClientHttpRequestFactoryWrapper(this.requestFactory, null,
Collections.emptyMap(), customizers);
ClientHttpRequest request = createRequest();
InOrder inOrder = inOrder(customizers.toArray());
for (RestTemplateRequestCustomizer<?> customizer : customizers) {
inOrder.verify((RestTemplateRequestCustomizer<ClientHttpRequest>) customizer).customize(request);
}
}
private ClientHttpRequest createRequest() throws IOException {
return this.requestFactory.createRequest(URI.create("https://localhost:8080"), HttpMethod.POST);
}
}

@ -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;
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
* @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 {
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 {
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 {
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();
} }

@ -1,75 +0,0 @@
/*
* 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");
}
}
Loading…
Cancel
Save