Skip to content

Commit 20f5026

Browse files
committed
#17072 Add default header support to RestTemplateBuilder
1 parent d9c93bd commit 20f5026

10 files changed

+563
-115
lines changed

spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java

+18-25
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.boot.web.client.RestTemplateBuilder;
3232
import org.springframework.core.ParameterizedTypeReference;
3333
import org.springframework.http.HttpEntity;
34+
import org.springframework.http.HttpHeaders;
3435
import org.springframework.http.HttpMethod;
3536
import org.springframework.http.HttpStatus;
3637
import org.springframework.http.RequestEntity;
@@ -43,6 +44,7 @@
4344
import org.springframework.mock.http.client.MockClientHttpRequest;
4445
import org.springframework.mock.http.client.MockClientHttpResponse;
4546
import org.springframework.test.util.ReflectionTestUtils;
47+
import org.springframework.util.Base64Utils;
4648
import org.springframework.util.ReflectionUtils;
4749
import org.springframework.util.ReflectionUtils.MethodCallback;
4850
import org.springframework.web.client.ResponseErrorHandler;
@@ -97,7 +99,8 @@ void useTheSameRequestFactoryClassWithBasicAuth() {
9799
RestTemplateBuilder builder = new RestTemplateBuilder().requestFactory(() -> customFactory);
98100
TestRestTemplate testRestTemplate = new TestRestTemplate(builder).withBasicAuth("test", "test");
99101
RestTemplate restTemplate = testRestTemplate.getRestTemplate();
100-
assertThat(restTemplate.getRequestFactory().getClass().getName()).contains("BasicAuth");
102+
assertThat(restTemplate.getRequestFactory().getClass().getName())
103+
.contains("HttpHeadersCustomizingClientHttpRequestFactory");
101104
Object requestFactory = ReflectionTestUtils.getField(restTemplate.getRequestFactory(), "requestFactory");
102105
assertThat(requestFactory).isEqualTo(customFactory).hasSameClassAs(customFactory);
103106
}
@@ -125,10 +128,9 @@ void getRootUriRootUriNotSet() {
125128
}
126129

127130
@Test
128-
void authenticated() {
129-
RestTemplate restTemplate = new TestRestTemplate("user", "password").getRestTemplate();
130-
ClientHttpRequestFactory factory = restTemplate.getRequestFactory();
131-
assertThat(factory.getClass().getName()).contains("BasicAuthentication");
131+
public void authenticated() throws Exception {
132+
TestRestTemplate restTemplate = new TestRestTemplate("user", "password");
133+
assertBasicAuthorizationCredentials(restTemplate, "user", "password");
132134
}
133135

134136
@Test
@@ -201,23 +203,25 @@ private Object mockArgument(Class<?> type) throws Exception {
201203
}
202204

203205
@Test
204-
void withBasicAuthAddsBasicAuthClientFactoryWhenNotAlreadyPresent() {
206+
public void withBasicAuthAddsBasicAuthClientFactoryWhenNotAlreadyPresent() throws Exception {
205207
TestRestTemplate original = new TestRestTemplate();
206208
TestRestTemplate basicAuth = original.withBasicAuth("user", "password");
207209
assertThat(getConverterClasses(original)).containsExactlyElementsOf(getConverterClasses(basicAuth));
208-
assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName()).contains("BasicAuth");
210+
assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName())
211+
.contains("HttpHeadersCustomizingClientHttpRequestFactory");
209212
assertThat(ReflectionTestUtils.getField(basicAuth.getRestTemplate().getRequestFactory(), "requestFactory"))
210213
.isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class);
211214
assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty();
212215
assertBasicAuthorizationCredentials(basicAuth, "user", "password");
213216
}
214217

215218
@Test
216-
void withBasicAuthReplacesBasicAuthClientFactoryWhenAlreadyPresent() {
219+
public void withBasicAuthReplacesBasicAuthClientFactoryWhenAlreadyPresent() throws Exception {
217220
TestRestTemplate original = new TestRestTemplate("foo", "bar").withBasicAuth("replace", "replace");
218221
TestRestTemplate basicAuth = original.withBasicAuth("user", "password");
219222
assertThat(getConverterClasses(basicAuth)).containsExactlyElementsOf(getConverterClasses(original));
220-
assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName()).contains("BasicAuth");
223+
assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName())
224+
.contains("HttpHeadersCustomizingClientHttpRequestFactory");
221225
assertThat(ReflectionTestUtils.getField(basicAuth.getRestTemplate().getRequestFactory(), "requestFactory"))
222226
.isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class);
223227
assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty();
@@ -342,11 +346,12 @@ private void verifyRelativeUriHandling(TestRestTemplateCallback callback) throws
342346
}
343347

344348
private void assertBasicAuthorizationCredentials(TestRestTemplate testRestTemplate, String username,
345-
String password) {
349+
String password) throws Exception {
346350
ClientHttpRequestFactory requestFactory = testRestTemplate.getRestTemplate().getRequestFactory();
347-
Object authentication = ReflectionTestUtils.getField(requestFactory, "authentication");
348-
assertThat(authentication).hasFieldOrPropertyWithValue("username", username);
349-
assertThat(authentication).hasFieldOrPropertyWithValue("password", password);
351+
ClientHttpRequest request = requestFactory.createRequest(URI.create("http://localhost"), HttpMethod.POST);
352+
assertThat(request.getHeaders()).containsKeys(HttpHeaders.AUTHORIZATION);
353+
assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly(
354+
"Basic " + Base64Utils.encodeToString(String.format("%s:%s", username, password).getBytes()));
350355

351356
}
352357

@@ -356,16 +361,4 @@ private interface TestRestTemplateCallback {
356361

357362
}
358363

359-
static class TestClientHttpRequestFactory implements ClientHttpRequestFactory {
360-
361-
TestClientHttpRequestFactory(String value) {
362-
}
363-
364-
@Override
365-
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
366-
return null;
367-
}
368-
369-
}
370-
371364
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.web.client;
18+
19+
import org.springframework.http.HttpHeaders;
20+
21+
/**
22+
* {@link HttpHeadersCustomizer} that only adds headers that were not populated in the
23+
* request.
24+
*
25+
* @author Ilya Lukyanovich
26+
*/
27+
public abstract class AbstractHttpHeadersDefaultingCustomizer implements HttpHeadersCustomizer {
28+
29+
@Override
30+
public void applyTo(HttpHeaders headers) {
31+
createHeaders().forEach((key, value) -> headers.merge(key, value, (oldValue, ignored) -> oldValue));
32+
}
33+
34+
protected abstract HttpHeaders createHeaders();
35+
36+
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthentication.java renamed to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthenticationHeaderDefaultingCustomizer.java

+11-11
Original file line numberDiff line numberDiff line change
@@ -19,37 +19,37 @@
1919
import java.nio.charset.Charset;
2020

2121
import org.springframework.http.HttpHeaders;
22-
import org.springframework.http.client.ClientHttpRequest;
2322
import org.springframework.util.Assert;
2423

2524
/**
26-
* Basic authentication properties which are used by
27-
* {@link BasicAuthenticationClientHttpRequestFactory}.
25+
* {@link AbstractHttpHeadersDefaultingCustomizer} that applies basic authentication
26+
* header unless it was provided in the request.
2827
*
2928
* @author Dmytro Nosan
30-
* @see BasicAuthenticationClientHttpRequestFactory
29+
* @author Ilya Lukyanovich
30+
* @see HttpHeadersCustomizingClientHttpRequestFactory
3131
*/
32-
class BasicAuthentication {
32+
class BasicAuthenticationHeaderDefaultingCustomizer extends AbstractHttpHeadersDefaultingCustomizer {
3333

3434
private final String username;
3535

3636
private final String password;
3737

3838
private final Charset charset;
3939

40-
BasicAuthentication(String username, String password, Charset charset) {
40+
BasicAuthenticationHeaderDefaultingCustomizer(String username, String password, Charset charset) {
4141
Assert.notNull(username, "Username must not be null");
4242
Assert.notNull(password, "Password must not be null");
4343
this.username = username;
4444
this.password = password;
4545
this.charset = charset;
4646
}
4747

48-
void applyTo(ClientHttpRequest request) {
49-
HttpHeaders headers = request.getHeaders();
50-
if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
51-
headers.setBasicAuth(this.username, this.password, this.charset);
52-
}
48+
@Override
49+
protected HttpHeaders createHeaders() {
50+
HttpHeaders headers = new HttpHeaders();
51+
headers.setBasicAuth(this.username, this.password, this.charset);
52+
return headers;
5353
}
5454

5555
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.web.client;
18+
19+
import org.springframework.http.HttpHeaders;
20+
21+
/**
22+
* Callback interface that can be used to customize a {@link HttpHeaders}.
23+
*
24+
* @author Ilya Lukyanovich
25+
* @see HttpHeadersCustomizingClientHttpRequestFactory
26+
*/
27+
@FunctionalInterface
28+
public interface HttpHeadersCustomizer {
29+
30+
/**
31+
* Callback to customize a {@link HttpHeaders} instance.
32+
* @param headers the headers to customize
33+
*/
34+
void applyTo(HttpHeaders headers);
35+
36+
}
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
import java.io.IOException;
2020
import java.net.URI;
21+
import java.util.Collection;
22+
23+
import org.jetbrains.annotations.NotNull;
2124

2225
import org.springframework.http.HttpMethod;
2326
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
@@ -26,27 +29,29 @@
2629
import org.springframework.util.Assert;
2730

2831
/**
29-
* {@link ClientHttpRequestFactory} to apply a given HTTP Basic Authentication
30-
* username/password pair, unless a custom Authorization header has been set before.
32+
* {@link ClientHttpRequestFactory} to apply default headers to a request unless header
33+
* values were provided.
3134
*
35+
* @author Ilya Lukyanovich
3236
* @author Dmytro Nosan
3337
*/
34-
class BasicAuthenticationClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {
38+
class HttpHeadersCustomizingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {
3539

36-
private final BasicAuthentication authentication;
40+
private final Collection<? extends HttpHeadersCustomizer> customizers;
3741

38-
BasicAuthenticationClientHttpRequestFactory(BasicAuthentication authentication,
42+
HttpHeadersCustomizingClientHttpRequestFactory(Collection<? extends HttpHeadersCustomizer> customizers,
3943
ClientHttpRequestFactory clientHttpRequestFactory) {
4044
super(clientHttpRequestFactory);
41-
Assert.notNull(authentication, "Authentication must not be null");
42-
this.authentication = authentication;
45+
Assert.notEmpty(customizers, "Customizers must not be empty");
46+
this.customizers = customizers;
4347
}
4448

49+
@NotNull
4550
@Override
46-
protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory)
47-
throws IOException {
51+
protected ClientHttpRequest createRequest(@NotNull URI uri, @NotNull HttpMethod httpMethod,
52+
ClientHttpRequestFactory requestFactory) throws IOException {
4853
ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod);
49-
this.authentication.applyTo(request);
54+
this.customizers.forEach((customizer) -> customizer.applyTo(request.getHeaders()));
5055
return request;
5156
}
5257

0 commit comments

Comments
 (0)