Commit 43b1a667 authored by Ilya Lukyanovich's avatar Ilya Lukyanovich Committed by Phillip Webb

Support default headers with RestTemplateBuilder

Update `RestTemplateBuilder` so that it is easier to apply custom
headers to the outgoing request. The update is particularly useful
for setting the `User-Agent` header, for example so that a GitHub
username can be used when calling `api.github.com`.

See gh-17091
parent 9b5cb4f9
...@@ -31,6 +31,7 @@ import org.springframework.boot.test.web.client.TestRestTemplate.HttpClientOptio ...@@ -31,6 +31,7 @@ import org.springframework.boot.test.web.client.TestRestTemplate.HttpClientOptio
import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity; import org.springframework.http.RequestEntity;
...@@ -43,6 +44,7 @@ import org.springframework.mock.env.MockEnvironment; ...@@ -43,6 +44,7 @@ import org.springframework.mock.env.MockEnvironment;
import org.springframework.mock.http.client.MockClientHttpRequest; import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.Base64Utils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback; import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.ResponseErrorHandler;
...@@ -97,7 +99,8 @@ class TestRestTemplateTests { ...@@ -97,7 +99,8 @@ class TestRestTemplateTests {
RestTemplateBuilder builder = new RestTemplateBuilder().requestFactory(() -> customFactory); RestTemplateBuilder builder = new RestTemplateBuilder().requestFactory(() -> customFactory);
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()).contains("BasicAuth"); assertThat(restTemplate.getRequestFactory().getClass().getName())
.contains("HttpHeadersCustomizingClientHttpRequestFactory");
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);
} }
...@@ -125,10 +128,9 @@ class TestRestTemplateTests { ...@@ -125,10 +128,9 @@ class TestRestTemplateTests {
} }
@Test @Test
void authenticated() { void authenticated() throws Exception {
RestTemplate restTemplate = new TestRestTemplate("user", "password").getRestTemplate(); TestRestTemplate restTemplate = new TestRestTemplate("user", "password");
ClientHttpRequestFactory factory = restTemplate.getRequestFactory(); assertBasicAuthorizationCredentials(restTemplate, "user", "password");
assertThat(factory.getClass().getName()).contains("BasicAuthentication");
} }
@Test @Test
...@@ -201,11 +203,12 @@ class TestRestTemplateTests { ...@@ -201,11 +203,12 @@ class TestRestTemplateTests {
} }
@Test @Test
void withBasicAuthAddsBasicAuthClientFactoryWhenNotAlreadyPresent() { void withBasicAuthAddsBasicAuthClientFactoryWhenNotAlreadyPresent() throws Exception {
TestRestTemplate original = new TestRestTemplate(); TestRestTemplate original = new TestRestTemplate();
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()).contains("BasicAuth"); assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName())
.contains("HttpHeadersCustomizingClientHttpRequestFactory");
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();
...@@ -213,11 +216,12 @@ class TestRestTemplateTests { ...@@ -213,11 +216,12 @@ class TestRestTemplateTests {
} }
@Test @Test
void withBasicAuthReplacesBasicAuthClientFactoryWhenAlreadyPresent() { void withBasicAuthReplacesBasicAuthClientFactoryWhenAlreadyPresent() throws Exception {
TestRestTemplate original = new TestRestTemplate("foo", "bar").withBasicAuth("replace", "replace"); TestRestTemplate original = new TestRestTemplate("foo", "bar").withBasicAuth("replace", "replace");
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()).contains("BasicAuth"); assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName())
.contains("HttpHeadersCustomizingClientHttpRequestFactory");
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();
...@@ -342,11 +346,12 @@ class TestRestTemplateTests { ...@@ -342,11 +346,12 @@ class TestRestTemplateTests {
} }
private void assertBasicAuthorizationCredentials(TestRestTemplate testRestTemplate, String username, private void assertBasicAuthorizationCredentials(TestRestTemplate testRestTemplate, String username,
String password) { String password) throws Exception {
ClientHttpRequestFactory requestFactory = testRestTemplate.getRestTemplate().getRequestFactory(); ClientHttpRequestFactory requestFactory = testRestTemplate.getRestTemplate().getRequestFactory();
Object authentication = ReflectionTestUtils.getField(requestFactory, "authentication"); ClientHttpRequest request = requestFactory.createRequest(URI.create("http://localhost"), HttpMethod.POST);
assertThat(authentication).hasFieldOrPropertyWithValue("username", username); assertThat(request.getHeaders()).containsKeys(HttpHeaders.AUTHORIZATION);
assertThat(authentication).hasFieldOrPropertyWithValue("password", password); assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly(
"Basic " + Base64Utils.encodeToString(String.format("%s:%s", username, password).getBytes()));
} }
...@@ -356,16 +361,4 @@ class TestRestTemplateTests { ...@@ -356,16 +361,4 @@ class TestRestTemplateTests {
} }
static class TestClientHttpRequestFactory implements ClientHttpRequestFactory {
TestClientHttpRequestFactory(String value) {
}
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
return null;
}
}
} }
/*
* 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();
}
...@@ -19,17 +19,17 @@ package org.springframework.boot.web.client; ...@@ -19,17 +19,17 @@ package org.springframework.boot.web.client;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* Basic authentication properties which are used by * {@link AbstractHttpHeadersDefaultingCustomizer} that applies basic authentication
* {@link BasicAuthenticationClientHttpRequestFactory}. * header unless it was provided in the request.
* *
* @author Dmytro Nosan * @author Dmytro Nosan
* @see BasicAuthenticationClientHttpRequestFactory * @author Ilya Lukyanovich
* @see HttpHeadersCustomizingClientHttpRequestFactory
*/ */
class BasicAuthentication { class BasicAuthenticationHeaderDefaultingCustomizer extends AbstractHttpHeadersDefaultingCustomizer {
private final String username; private final String username;
...@@ -37,7 +37,7 @@ class BasicAuthentication { ...@@ -37,7 +37,7 @@ class BasicAuthentication {
private final Charset charset; private final Charset charset;
BasicAuthentication(String username, String password, Charset charset) { BasicAuthenticationHeaderDefaultingCustomizer(String username, String password, Charset charset) {
Assert.notNull(username, "Username must not be null"); Assert.notNull(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 +45,11 @@ class BasicAuthentication { ...@@ -45,11 +45,11 @@ class BasicAuthentication {
this.charset = charset; this.charset = charset;
} }
void applyTo(ClientHttpRequest request) { @Override
HttpHeaders headers = request.getHeaders(); 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;
} }
} }
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import org.springframework.http.HttpHeaders;
/**
* Callback interface that can be used to customize a {@link HttpHeaders}.
*
* @author Ilya Lukyanovich
* @see HttpHeadersCustomizingClientHttpRequestFactory
*/
@FunctionalInterface
public interface HttpHeadersCustomizer {
/**
* Callback to customize a {@link HttpHeaders} instance.
* @param headers the headers to customize
*/
void applyTo(HttpHeaders headers);
}
...@@ -18,6 +18,9 @@ package org.springframework.boot.web.client; ...@@ -18,6 +18,9 @@ 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 org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper; import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
...@@ -26,27 +29,29 @@ import org.springframework.http.client.ClientHttpRequestFactory; ...@@ -26,27 +29,29 @@ import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* {@link ClientHttpRequestFactory} to apply a given HTTP Basic Authentication * {@link ClientHttpRequestFactory} to apply default headers to a request unless header
* username/password pair, unless a custom Authorization header has been set before. * values were provided.
* *
* @author Ilya Lukyanovich
* @author Dmytro Nosan * @author Dmytro Nosan
*/ */
class BasicAuthenticationClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper { class HttpHeadersCustomizingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {
private final BasicAuthentication authentication; private final Collection<? extends HttpHeadersCustomizer> customizers;
BasicAuthenticationClientHttpRequestFactory(BasicAuthentication authentication, HttpHeadersCustomizingClientHttpRequestFactory(Collection<? extends HttpHeadersCustomizer> customizers,
ClientHttpRequestFactory clientHttpRequestFactory) { ClientHttpRequestFactory clientHttpRequestFactory) {
super(clientHttpRequestFactory); super(clientHttpRequestFactory);
Assert.notNull(authentication, "Authentication must not be null"); Assert.notEmpty(customizers, "Customizers must not be empty");
this.authentication = authentication; this.customizers = customizers;
} }
@NotNull
@Override @Override
protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) protected ClientHttpRequest createRequest(@NotNull URI uri, @NotNull HttpMethod httpMethod,
throws IOException { ClientHttpRequestFactory requestFactory) throws IOException {
ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod); ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod);
this.authentication.applyTo(request); this.customizers.forEach((customizer) -> customizer.applyTo(request.getHeaders()));
return request; return request;
} }
......
...@@ -32,6 +32,7 @@ import java.util.function.Consumer; ...@@ -32,6 +32,7 @@ import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
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;
...@@ -64,6 +65,7 @@ import org.springframework.web.util.UriTemplateHandler; ...@@ -64,6 +65,7 @@ import org.springframework.web.util.UriTemplateHandler;
* @author Brian Clozel * @author Brian Clozel
* @author Dmytro Nosan * @author Dmytro Nosan
* @author Kevin Strijbos * @author Kevin Strijbos
* @author Ilya Lukyanovich
* @since 1.4.0 * @since 1.4.0
*/ */
public class RestTemplateBuilder { public class RestTemplateBuilder {
...@@ -80,14 +82,14 @@ public class RestTemplateBuilder { ...@@ -80,14 +82,14 @@ public class RestTemplateBuilder {
private final ResponseErrorHandler errorHandler; private final ResponseErrorHandler errorHandler;
private final BasicAuthentication basicAuthentication;
private final Set<RestTemplateCustomizer> restTemplateCustomizers; private final Set<RestTemplateCustomizer> restTemplateCustomizers;
private final RequestFactoryCustomizer requestFactoryCustomizer; private final RequestFactoryCustomizer requestFactoryCustomizer;
private final Set<ClientHttpRequestInterceptor> interceptors; private final Set<ClientHttpRequestInterceptor> interceptors;
private final Set<HttpHeadersCustomizer> httpHeadersCustomizers;
/** /**
* Create a new {@link RestTemplateBuilder} instance. * Create a new {@link RestTemplateBuilder} instance.
* @param customizers any {@link RestTemplateCustomizer RestTemplateCustomizers} that * @param customizers any {@link RestTemplateCustomizer RestTemplateCustomizers} that
...@@ -101,27 +103,27 @@ public class RestTemplateBuilder { ...@@ -101,27 +103,27 @@ public class RestTemplateBuilder {
this.requestFactorySupplier = null; this.requestFactorySupplier = null;
this.uriTemplateHandler = null; this.uriTemplateHandler = null;
this.errorHandler = null; this.errorHandler = null;
this.basicAuthentication = null;
this.restTemplateCustomizers = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(customizers))); this.restTemplateCustomizers = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(customizers)));
this.requestFactoryCustomizer = new RequestFactoryCustomizer(); this.requestFactoryCustomizer = new RequestFactoryCustomizer();
this.interceptors = Collections.emptySet(); this.interceptors = Collections.emptySet();
this.httpHeadersCustomizers = Collections.emptySet();
} }
private RestTemplateBuilder(boolean detectRequestFactory, String rootUri, private RestTemplateBuilder(boolean detectRequestFactory, String rootUri,
Set<HttpMessageConverter<?>> messageConverters, Supplier<ClientHttpRequestFactory> requestFactorySupplier, Set<HttpMessageConverter<?>> messageConverters, Supplier<ClientHttpRequestFactory> requestFactorySupplier,
UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler, UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler,
BasicAuthentication basicAuthentication, Set<RestTemplateCustomizer> restTemplateCustomizers, Set<RestTemplateCustomizer> restTemplateCustomizers, RequestFactoryCustomizer requestFactoryCustomizer,
RequestFactoryCustomizer requestFactoryCustomizer, Set<ClientHttpRequestInterceptor> interceptors) { Set<ClientHttpRequestInterceptor> interceptors, Set<HttpHeadersCustomizer> httpHeadersCustomizers) {
this.detectRequestFactory = detectRequestFactory; this.detectRequestFactory = detectRequestFactory;
this.rootUri = rootUri; this.rootUri = rootUri;
this.messageConverters = messageConverters; this.messageConverters = messageConverters;
this.requestFactorySupplier = requestFactorySupplier; this.requestFactorySupplier = requestFactorySupplier;
this.uriTemplateHandler = uriTemplateHandler; this.uriTemplateHandler = uriTemplateHandler;
this.errorHandler = errorHandler; this.errorHandler = errorHandler;
this.basicAuthentication = basicAuthentication;
this.restTemplateCustomizers = restTemplateCustomizers; this.restTemplateCustomizers = restTemplateCustomizers;
this.requestFactoryCustomizer = requestFactoryCustomizer; this.requestFactoryCustomizer = requestFactoryCustomizer;
this.interceptors = interceptors; this.interceptors = interceptors;
this.httpHeadersCustomizers = httpHeadersCustomizers;
} }
/** /**
...@@ -133,8 +135,8 @@ public class RestTemplateBuilder { ...@@ -133,8 +135,8 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder detectRequestFactory(boolean detectRequestFactory) { public RestTemplateBuilder detectRequestFactory(boolean detectRequestFactory) {
return new RestTemplateBuilder(detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(detectRequestFactory, this.rootUri, this.messageConverters,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers,
this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers);
} }
/** /**
...@@ -145,8 +147,8 @@ public class RestTemplateBuilder { ...@@ -145,8 +147,8 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder rootUri(String rootUri) { public RestTemplateBuilder rootUri(String rootUri) {
return new RestTemplateBuilder(this.detectRequestFactory, rootUri, this.messageConverters, return new RestTemplateBuilder(this.detectRequestFactory, rootUri, this.messageConverters,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers,
this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers);
} }
/** /**
...@@ -176,8 +178,8 @@ public class RestTemplateBuilder { ...@@ -176,8 +178,8 @@ public class RestTemplateBuilder {
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.detectRequestFactory, this.rootUri,
Collections.unmodifiableSet(new LinkedHashSet<HttpMessageConverter<?>>(messageConverters)), Collections.unmodifiableSet(new LinkedHashSet<HttpMessageConverter<?>>(messageConverters)),
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers,
this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers);
} }
/** /**
...@@ -190,7 +192,22 @@ public class RestTemplateBuilder { ...@@ -190,7 +192,22 @@ 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(Arrays.asList(messageConverters)); return additionalMessageConverters(true, messageConverters);
}
/**
* Add additional {@link HttpMessageConverter HttpMessageConverters} that should be
* used with the {@link RestTemplate}. Any converters configured on the builder will
* replace RestTemplate's default converters.
* @param append if true adds converters to the end otherwise to the beginning
* @param messageConverters the converters to add
* @return a new builder instance
* @see #messageConverters(HttpMessageConverter...)
*/
public RestTemplateBuilder additionalMessageConverters(boolean append,
HttpMessageConverter<?>... messageConverters) {
Assert.notNull(messageConverters, "MessageConverters must not be null");
return additionalMessageConverters(append, Arrays.asList(messageConverters));
} }
/** /**
...@@ -204,10 +221,26 @@ public class RestTemplateBuilder { ...@@ -204,10 +221,26 @@ 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);
}
/**
* 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, return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
append(this.messageConverters, messageConverters), this.requestFactorySupplier, this.uriTemplateHandler, append ? append(this.messageConverters, messageConverters)
this.errorHandler, this.basicAuthentication, this.restTemplateCustomizers, : append(messageConverters, this.messageConverters),
this.requestFactoryCustomizer, this.interceptors); this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers,
this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers);
} }
/** /**
...@@ -220,8 +253,8 @@ public class RestTemplateBuilder { ...@@ -220,8 +253,8 @@ public class RestTemplateBuilder {
public RestTemplateBuilder defaultMessageConverters() { public RestTemplateBuilder defaultMessageConverters() {
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
Collections.unmodifiableSet(new LinkedHashSet<>(new RestTemplate().getMessageConverters())), Collections.unmodifiableSet(new LinkedHashSet<>(new RestTemplate().getMessageConverters())),
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers,
this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers);
} }
/** /**
...@@ -250,9 +283,9 @@ public class RestTemplateBuilder { ...@@ -250,9 +283,9 @@ 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.detectRequestFactory, this.rootUri, this.messageConverters,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers,
this.restTemplateCustomizers, this.requestFactoryCustomizer, this.requestFactoryCustomizer, Collections.unmodifiableSet(new LinkedHashSet<>(interceptors)),
Collections.unmodifiableSet(new LinkedHashSet<>(interceptors))); this.httpHeadersCustomizers);
} }
/** /**
...@@ -279,8 +312,8 @@ public class RestTemplateBuilder { ...@@ -279,8 +312,8 @@ 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.detectRequestFactory, this.rootUri, this.messageConverters,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers,
this.restTemplateCustomizers, this.requestFactoryCustomizer, append(this.interceptors, interceptors)); this.requestFactoryCustomizer, append(this.interceptors, interceptors), this.httpHeadersCustomizers);
} }
/** /**
...@@ -315,8 +348,8 @@ public class RestTemplateBuilder { ...@@ -315,8 +348,8 @@ public class RestTemplateBuilder {
public RestTemplateBuilder requestFactory(Supplier<ClientHttpRequestFactory> requestFactorySupplier) { public RestTemplateBuilder requestFactory(Supplier<ClientHttpRequestFactory> requestFactorySupplier) {
Assert.notNull(requestFactorySupplier, "RequestFactory Supplier must not be null"); Assert.notNull(requestFactorySupplier, "RequestFactory Supplier must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters,
requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers,
this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers);
} }
/** /**
...@@ -328,8 +361,8 @@ public class RestTemplateBuilder { ...@@ -328,8 +361,8 @@ 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.detectRequestFactory, this.rootUri, this.messageConverters,
this.requestFactorySupplier, uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.requestFactorySupplier, uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers,
this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers);
} }
/** /**
...@@ -341,8 +374,123 @@ public class RestTemplateBuilder { ...@@ -341,8 +374,123 @@ 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.detectRequestFactory, this.rootUri, this.messageConverters,
this.requestFactorySupplier, this.uriTemplateHandler, errorHandler, this.basicAuthentication, this.requestFactorySupplier, this.uriTemplateHandler, errorHandler, this.restTemplateCustomizers,
this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors); this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers);
}
/**
* Add a header to requests with the given value unless a custom header has been set
* before.
* @param header the header
* @param value the value
* @return a new builder instance
* @see #additionalHttpHeadersCustomizers(HttpHeadersCustomizer...)
*/
public RestTemplateBuilder defaultHeader(String header, String value) {
return additionalHttpHeadersCustomizers(SimpleHttpHeaderDefaultingCustomizer.singleHeader(header, value));
}
/**
* Add a headers to requests with the given value unless a custom header has been set
* before.
* @param headers the headers
* @return a new builder instance
* @see #additionalHttpHeadersCustomizers(HttpHeadersCustomizer...)
*/
public RestTemplateBuilder defaultHeaders(HttpHeaders headers) {
return additionalHttpHeadersCustomizers(new SimpleHttpHeaderDefaultingCustomizer(headers));
}
/**
* Set the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied
* to the {@link HttpHeaders} on the request. Customizers are applied in the order
* that they were added. Setting this value will replace any previously configured
* http headers customizers.
* @param httpHeadersCustomizers the customizers to set
* @return a new builder instance
* @see #additionalHttpHeadersCustomizers(HttpHeadersCustomizer...)
*/
public RestTemplateBuilder httpHeadersCustomizers(HttpHeadersCustomizer... httpHeadersCustomizers) {
Assert.notNull(httpHeadersCustomizers, "HttpHeadersCustomizers must not be null");
return httpHeadersCustomizers(Arrays.asList(httpHeadersCustomizers));
}
/**
* Set the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied
* to the {@link HttpHeaders} on the request. Customizers are applied in the order
* that they were added. Setting this value will replace any previously configured
* http headers customizers.
* @param httpHeadersCustomizers the customizers to set
* @return a new builder instance
* @see #additionalHttpHeadersCustomizers(HttpHeadersCustomizer...)
*/
public RestTemplateBuilder httpHeadersCustomizers(
Collection<? 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
* @see #httpHeadersCustomizers(HttpHeadersCustomizer...)
*/
public RestTemplateBuilder additionalHttpHeadersCustomizers(HttpHeadersCustomizer... httpHeadersCustomizers) {
Assert.notNull(httpHeadersCustomizers, "HttpHeadersCustomizers must not be null");
return additionalHttpHeadersCustomizers(true, httpHeadersCustomizers);
}
/**
* Add the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied
* to the {@link HttpHeaders} on the request. Customizers are applied in the order
* that they were added.
* @param append if true adds customizers to the end otherwise to the beginning
* @param httpHeadersCustomizers the customizers to set
* @return a new builder instance
* @see #httpHeadersCustomizers(HttpHeadersCustomizer...)
*/
public RestTemplateBuilder additionalHttpHeadersCustomizers(boolean append,
HttpHeadersCustomizer... httpHeadersCustomizers) {
Assert.notNull(httpHeadersCustomizers, "HttpHeadersCustomizers must not be null");
return additionalHttpHeadersCustomizers(append, Arrays.asList(httpHeadersCustomizers));
}
/**
* Add the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied
* to the {@link HttpHeaders} on the request. Customizers are applied in the order
* that they were added.
* @param httpHeadersCustomizers the customizers to set
* @return a new builder instance
* @see #httpHeadersCustomizers(HttpHeadersCustomizer...)
*/
public RestTemplateBuilder additionalHttpHeadersCustomizers(
Collection<? extends HttpHeadersCustomizer> httpHeadersCustomizers) {
return additionalHttpHeadersCustomizers(true, httpHeadersCustomizers);
}
/**
* Add the {@link HttpHeadersCustomizer HttpHeadersCustomizers} that should be applied
* to the {@link HttpHeaders} on the request. Customizers are applied in the order
* that they were added.
* @param append if true adds customizers to the end otherwise to the beginning
* @param httpHeadersCustomizers the customizers to set
* @return a new builder instance
* @see #httpHeadersCustomizers(HttpHeadersCustomizer...)
*/
public RestTemplateBuilder additionalHttpHeadersCustomizers(boolean append,
Collection<? 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,
append ? append(this.httpHeadersCustomizers, httpHeadersCustomizers)
: append(httpHeadersCustomizers, this.httpHeadersCustomizers));
} }
/** /**
...@@ -360,19 +508,21 @@ public class RestTemplateBuilder { ...@@ -360,19 +508,21 @@ public class RestTemplateBuilder {
/** /**
* Add HTTP Basic Authentication to requests with the given username/password pair, * Add HTTP Basic Authentication to requests with the given username/password pair,
* unless a custom Authorization header has been set before. * unless a custom Authorization header has been set before. Customizer is added to
* the beginning of the {@link HttpHeadersCustomizer HttpHeadersCustomizers}
* collection.
* @param username the user name * @param username the user name
* @param password the password * @param password the password
* @param charset the charset to use * @param charset the charset to use
* @return a new builder instance * @return a new builder instance
* @since 2.2.0 * @since 2.2.0
* @see #basicAuthentication(String, String) * @see #additionalHttpHeadersCustomizers(HttpHeadersCustomizer...)
* @see SimpleHttpHeaderDefaultingCustomizer#basicAuthentication(String, String,
* Charset)
*/ */
public RestTemplateBuilder basicAuthentication(String username, String password, Charset charset) { public RestTemplateBuilder basicAuthentication(String username, String password, Charset charset) {
BasicAuthentication basicAuthentication = new BasicAuthentication(username, password, charset); return additionalHttpHeadersCustomizers(false,
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, SimpleHttpHeaderDefaultingCustomizer.basicAuthentication(username, password, charset));
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, basicAuthentication,
this.restTemplateCustomizers, this.requestFactoryCustomizer, this.interceptors);
} }
/** /**
...@@ -401,9 +551,10 @@ public class RestTemplateBuilder { ...@@ -401,9 +551,10 @@ public class RestTemplateBuilder {
public RestTemplateBuilder customizers(Collection<? extends RestTemplateCustomizer> restTemplateCustomizers) { public RestTemplateBuilder customizers(Collection<? extends RestTemplateCustomizer> restTemplateCustomizers) {
Assert.notNull(restTemplateCustomizers, "RestTemplateCustomizers must not be null"); Assert.notNull(restTemplateCustomizers, "RestTemplateCustomizers must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler,
Collections.unmodifiableSet(new LinkedHashSet<RestTemplateCustomizer>(restTemplateCustomizers)), Collections.unmodifiableSet(new LinkedHashSet<RestTemplateCustomizer>(restTemplateCustomizers)),
this.requestFactoryCustomizer, this.interceptors); this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers);
} }
/** /**
...@@ -416,7 +567,22 @@ public class RestTemplateBuilder { ...@@ -416,7 +567,22 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder additionalCustomizers(RestTemplateCustomizer... restTemplateCustomizers) { public RestTemplateBuilder additionalCustomizers(RestTemplateCustomizer... restTemplateCustomizers) {
Assert.notNull(restTemplateCustomizers, "RestTemplateCustomizers must not be null"); Assert.notNull(restTemplateCustomizers, "RestTemplateCustomizers must not be null");
return additionalCustomizers(Arrays.asList(restTemplateCustomizers)); return additionalCustomizers(true, restTemplateCustomizers);
}
/**
* Add {@link RestTemplateCustomizer RestTemplateCustomizers} that should be applied
* to the {@link RestTemplate}. Customizers are applied in the order that they were
* added after builder configuration has been applied.
* @param append if true adds customizers to the end otherwise to the beginning
* @param restTemplateCustomizers the customizers to add
* @return a new builder instance
* @see #customizers(RestTemplateCustomizer...)
*/
public RestTemplateBuilder additionalCustomizers(boolean append,
RestTemplateCustomizer... restTemplateCustomizers) {
Assert.notNull(restTemplateCustomizers, "RestTemplateCustomizers must not be null");
return additionalCustomizers(append, Arrays.asList(restTemplateCustomizers));
} }
/** /**
...@@ -429,9 +595,26 @@ public class RestTemplateBuilder { ...@@ -429,9 +595,26 @@ 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);
}
/**
* Add {@link RestTemplateCustomizer RestTemplateCustomizers} that should be applied
* to the {@link RestTemplate}. Customizers are applied in the order that they were
* added after builder configuration has been applied.
* @param append if true adds customizers to the end otherwise to the beginning
* @param customizers the customizers to add
* @return a new builder instance
* @see #customizers(RestTemplateCustomizer...)
*/
public RestTemplateBuilder additionalCustomizers(boolean append,
Collection<? extends RestTemplateCustomizer> customizers) {
Assert.notNull(customizers, "RestTemplateCustomizers must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler,
append(this.restTemplateCustomizers, customizers), this.requestFactoryCustomizer, this.interceptors); append ? append(this.restTemplateCustomizers, customizers)
: append(customizers, this.restTemplateCustomizers),
this.requestFactoryCustomizer, this.interceptors, this.httpHeadersCustomizers);
} }
/** /**
...@@ -442,9 +625,9 @@ public class RestTemplateBuilder { ...@@ -442,9 +625,9 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder setConnectTimeout(Duration connectTimeout) { public RestTemplateBuilder setConnectTimeout(Duration connectTimeout) {
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers,
this.restTemplateCustomizers, this.requestFactoryCustomizer.connectTimeout(connectTimeout), this.requestFactoryCustomizer.connectTimeout(connectTimeout), this.interceptors,
this.interceptors); this.httpHeadersCustomizers);
} }
/** /**
...@@ -455,9 +638,8 @@ public class RestTemplateBuilder { ...@@ -455,9 +638,8 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder setReadTimeout(Duration readTimeout) { public RestTemplateBuilder setReadTimeout(Duration readTimeout) {
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers,
this.restTemplateCustomizers, this.requestFactoryCustomizer.readTimeout(readTimeout), this.requestFactoryCustomizer.readTimeout(readTimeout), this.interceptors, this.httpHeadersCustomizers);
this.interceptors);
} }
/** /**
...@@ -471,9 +653,9 @@ public class RestTemplateBuilder { ...@@ -471,9 +653,9 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder setBufferRequestBody(boolean bufferRequestBody) { public RestTemplateBuilder setBufferRequestBody(boolean bufferRequestBody) {
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters,
this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.requestFactorySupplier, this.uriTemplateHandler, this.errorHandler, this.restTemplateCustomizers,
this.restTemplateCustomizers, this.requestFactoryCustomizer.bufferRequestBody(bufferRequestBody), this.requestFactoryCustomizer.bufferRequestBody(bufferRequestBody), this.interceptors,
this.interceptors); this.httpHeadersCustomizers);
} }
/** /**
...@@ -512,8 +694,8 @@ public class RestTemplateBuilder { ...@@ -512,8 +694,8 @@ public class RestTemplateBuilder {
if (requestFactory != null) { if (requestFactory != null) {
restTemplate.setRequestFactory(requestFactory); restTemplate.setRequestFactory(requestFactory);
} }
if (this.basicAuthentication != null) { if (!CollectionUtils.isEmpty(this.httpHeadersCustomizers)) {
configureBasicAuthentication(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));
...@@ -558,7 +740,7 @@ public class RestTemplateBuilder { ...@@ -558,7 +740,7 @@ public class RestTemplateBuilder {
return requestFactory; return requestFactory;
} }
private void configureBasicAuthentication(RestTemplate restTemplate) { private void configureHttpHeadersCustomizers(RestTemplate restTemplate) {
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
...@@ -567,16 +749,18 @@ public class RestTemplateBuilder { ...@@ -567,16 +749,18 @@ public class RestTemplateBuilder {
} }
ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory(); ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory();
restTemplate.setRequestFactory( restTemplate.setRequestFactory(
new BasicAuthenticationClientHttpRequestFactory(this.basicAuthentication, requestFactory)); new HttpHeadersCustomizingClientHttpRequestFactory(this.httpHeadersCustomizers, requestFactory));
// Restore the original interceptors // Restore the original interceptors
if (interceptors != null) { if (interceptors != null) {
restTemplate.getInterceptors().addAll(interceptors); restTemplate.getInterceptors().addAll(interceptors);
} }
} }
private <T> Set<T> append(Set<T> set, Collection<? extends T> additions) { private static <T> Set<T> append(Collection<? extends T> collection, Collection<? extends T> additions) {
Set<T> result = new LinkedHashSet<>((set != null) ? set : Collections.emptySet()); Set<T> result = new LinkedHashSet<>((collection != null) ? collection : Collections.emptySet());
result.addAll(additions); if (additions != null) {
result.addAll(additions);
}
return Collections.unmodifiableSet(result); return Collections.unmodifiableSet(result);
} }
......
/*
* 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,6 +18,8 @@ package org.springframework.boot.web.client; ...@@ -18,6 +18,8 @@ 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 org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
...@@ -33,29 +35,30 @@ import static org.mockito.BDDMockito.given; ...@@ -33,29 +35,30 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link BasicAuthenticationClientHttpRequestFactory}. * Tests for {@link HttpHeadersCustomizingClientHttpRequestFactory}.
* *
* @author Dmytro Nosan * @author Dmytro Nosan
* @author Ilya Lukyanovich
*/ */
class BasicAuthenticationClientHttpRequestFactoryTests { public class HttpHeadersCustomizingClientHttpRequestFactoryTests {
private final HttpHeaders headers = new HttpHeaders(); private final HttpHeaders headers = new HttpHeaders();
private final BasicAuthentication authentication = new BasicAuthentication("spring", "boot", null);
private ClientHttpRequestFactory requestFactory; private ClientHttpRequestFactory requestFactory;
@BeforeEach @BeforeEach
public void setUp() throws IOException { public void setUp() throws IOException {
ClientHttpRequestFactory requestFactory = mock(ClientHttpRequestFactory.class); this.requestFactory = mock(ClientHttpRequestFactory.class);
ClientHttpRequest request = mock(ClientHttpRequest.class); ClientHttpRequest request = mock(ClientHttpRequest.class);
given(requestFactory.createRequest(any(), any())).willReturn(request); given(this.requestFactory.createRequest(any(), any())).willReturn(request);
given(request.getHeaders()).willReturn(this.headers); given(request.getHeaders()).willReturn(this.headers);
this.requestFactory = new BasicAuthenticationClientHttpRequestFactory(this.authentication, requestFactory);
} }
@Test @Test
void shouldAddAuthorizationHeader() throws IOException { void shouldAddAuthorizationHeader() throws IOException {
this.requestFactory = new HttpHeadersCustomizingClientHttpRequestFactory(
Collections.singleton(SimpleHttpHeaderDefaultingCustomizer.basicAuthentication("spring", "boot", null)),
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=");
} }
...@@ -63,11 +66,27 @@ class BasicAuthenticationClientHttpRequestFactoryTests { ...@@ -63,11 +66,27 @@ class BasicAuthenticationClientHttpRequestFactoryTests {
@Test @Test
void shouldNotAddAuthorizationHeaderAuthorizationAlreadySet() throws IOException { void shouldNotAddAuthorizationHeaderAuthorizationAlreadySet() throws IOException {
this.headers.setBasicAuth("boot", "spring"); this.headers.setBasicAuth("boot", "spring");
this.requestFactory = new HttpHeadersCustomizingClientHttpRequestFactory(
Collections.singleton(SimpleHttpHeaderDefaultingCustomizer.basicAuthentication("spring", "boot", null)),
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 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 { private ClientHttpRequest createRequest() throws IOException {
return this.requestFactory.createRequest(URI.create("https://localhost:8080"), HttpMethod.POST); return this.requestFactory.createRequest(URI.create("https://localhost:8080"), HttpMethod.POST);
} }
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.web.client; package org.springframework.boot.web.client;
import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.util.Collections; import java.util.Collections;
...@@ -29,7 +30,10 @@ import org.mockito.InOrder; ...@@ -29,7 +30,10 @@ import org.mockito.InOrder;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.BufferingClientHttpRequestFactory; import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
...@@ -298,12 +302,12 @@ class RestTemplateBuilderTests { ...@@ -298,12 +302,12 @@ class RestTemplateBuilderTests {
} }
@Test @Test
void basicAuthenticationShouldApply() { void basicAuthenticationShouldApply() throws Exception {
RestTemplate template = this.builder.basicAuthentication("spring", "boot", StandardCharsets.UTF_8).build(); RestTemplate template = this.builder.basicAuthentication("spring", "boot", StandardCharsets.UTF_8).build();
ClientHttpRequestFactory requestFactory = template.getRequestFactory(); ClientHttpRequestFactory requestFactory = template.getRequestFactory();
Object authentication = ReflectionTestUtils.getField(requestFactory, "authentication"); ClientHttpRequest request = requestFactory.createRequest(URI.create("http://localhost"), HttpMethod.POST);
assertThat(authentication).extracting("username", "password", "charset").containsExactly("spring", "boot", assertThat(request.getHeaders()).containsOnlyKeys(HttpHeaders.AUTHORIZATION);
StandardCharsets.UTF_8); assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic c3ByaW5nOmJvb3Q=");
} }
@Test @Test
...@@ -383,7 +387,7 @@ class RestTemplateBuilderTests { ...@@ -383,7 +387,7 @@ 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(BasicAuthenticationClientHttpRequestFactory.class); assertThat(authRequestFactory).isInstanceOf(HttpHeadersCustomizingClientHttpRequestFactory.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