Skip to content

Commit

Permalink
spring-projects#17072 Add default header support to RestTemplateBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
L00kian committed Jun 11, 2019
1 parent b3d5cd5 commit d905761
Show file tree
Hide file tree
Showing 10 changed files with 562 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
Expand All @@ -43,6 +44,7 @@
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.Base64Utils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.web.client.ResponseErrorHandler;
Expand Down Expand Up @@ -97,7 +99,8 @@ void useTheSameRequestFactoryClassWithBasicAuth() {
RestTemplateBuilder builder = new RestTemplateBuilder().requestFactory(() -> customFactory);
TestRestTemplate testRestTemplate = new TestRestTemplate(builder).withBasicAuth("test", "test");
RestTemplate restTemplate = testRestTemplate.getRestTemplate();
assertThat(restTemplate.getRequestFactory().getClass().getName()).contains("BasicAuth");
assertThat(restTemplate.getRequestFactory().getClass().getName())
.contains("HttpHeadersCustomizingClientHttpRequestFactory");
Object requestFactory = ReflectionTestUtils.getField(restTemplate.getRequestFactory(), "requestFactory");
assertThat(requestFactory).isEqualTo(customFactory).hasSameClassAs(customFactory);
}
Expand Down Expand Up @@ -125,10 +128,9 @@ void getRootUriRootUriNotSet() {
}

@Test
void authenticated() {
RestTemplate restTemplate = new TestRestTemplate("user", "password").getRestTemplate();
ClientHttpRequestFactory factory = restTemplate.getRequestFactory();
assertThat(factory.getClass().getName()).contains("BasicAuthentication");
void authenticated() throws Exception {
TestRestTemplate restTemplate = new TestRestTemplate("user", "password");
assertBasicAuthorizationCredentials(restTemplate, "user", "password");
}

@Test
Expand Down Expand Up @@ -201,23 +203,25 @@ private Object mockArgument(Class<?> type) throws Exception {
}

@Test
void withBasicAuthAddsBasicAuthClientFactoryWhenNotAlreadyPresent() {
void withBasicAuthAddsBasicAuthClientFactoryWhenNotAlreadyPresent() throws Exception {
TestRestTemplate original = new TestRestTemplate();
TestRestTemplate basicAuth = original.withBasicAuth("user", "password");
assertThat(getConverterClasses(original)).containsExactlyElementsOf(getConverterClasses(basicAuth));
assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName()).contains("BasicAuth");
assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName())
.contains("HttpHeadersCustomizingClientHttpRequestFactory");
assertThat(ReflectionTestUtils.getField(basicAuth.getRestTemplate().getRequestFactory(), "requestFactory"))
.isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class);
assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty();
assertBasicAuthorizationCredentials(basicAuth, "user", "password");
}

@Test
void withBasicAuthReplacesBasicAuthClientFactoryWhenAlreadyPresent() {
void withBasicAuthReplacesBasicAuthClientFactoryWhenAlreadyPresent() throws Exception {
TestRestTemplate original = new TestRestTemplate("foo", "bar").withBasicAuth("replace", "replace");
TestRestTemplate basicAuth = original.withBasicAuth("user", "password");
assertThat(getConverterClasses(basicAuth)).containsExactlyElementsOf(getConverterClasses(original));
assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName()).contains("BasicAuth");
assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName())
.contains("HttpHeadersCustomizingClientHttpRequestFactory");
assertThat(ReflectionTestUtils.getField(basicAuth.getRestTemplate().getRequestFactory(), "requestFactory"))
.isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class);
assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty();
Expand Down Expand Up @@ -342,11 +346,12 @@ private void verifyRelativeUriHandling(TestRestTemplateCallback callback) throws
}

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

}

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

}

static class TestClientHttpRequestFactory implements ClientHttpRequestFactory {

TestClientHttpRequestFactory(String value) {
}

@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
return null;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.web.client;

import org.springframework.http.HttpHeaders;

/**
* {@link HttpHeadersCustomizer} that only adds headers that were not populated in the
* request.
*
* @author Ilya Lukyanovich
*/
public abstract class AbstractHttpHeadersDefaultingCustomizer implements HttpHeadersCustomizer {

@Override
public void applyTo(HttpHeaders headers) {
createHeaders().forEach((key, value) -> headers.merge(key, value, (oldValue, ignored) -> oldValue));
}

protected abstract HttpHeaders createHeaders();

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,37 @@
import java.nio.charset.Charset;

import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.util.Assert;

/**
* Basic authentication properties which are used by
* {@link BasicAuthenticationClientHttpRequestFactory}.
* {@link AbstractHttpHeadersDefaultingCustomizer} that applies basic authentication
* header unless it was provided in the request.
*
* @author Dmytro Nosan
* @see BasicAuthenticationClientHttpRequestFactory
* @author Ilya Lukyanovich
* @see HttpHeadersCustomizingClientHttpRequestFactory
*/
class BasicAuthentication {
class BasicAuthenticationHeaderDefaultingCustomizer extends AbstractHttpHeadersDefaultingCustomizer {

private final String username;

private final String password;

private final Charset charset;

BasicAuthentication(String username, String password, Charset charset) {
BasicAuthenticationHeaderDefaultingCustomizer(String username, String password, Charset charset) {
Assert.notNull(username, "Username must not be null");
Assert.notNull(password, "Password must not be null");
this.username = username;
this.password = password;
this.charset = charset;
}

void applyTo(ClientHttpRequest request) {
HttpHeaders headers = request.getHeaders();
if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
headers.setBasicAuth(this.username, this.password, this.charset);
}
@Override
protected HttpHeaders createHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(this.username, this.password, this.charset);
return headers;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.web.client;

import org.springframework.http.HttpHeaders;

/**
* Callback interface that can be used to customize a {@link HttpHeaders}.
*
* @author Ilya Lukyanovich
* @see HttpHeadersCustomizingClientHttpRequestFactory
*/
@FunctionalInterface
public interface HttpHeadersCustomizer {

/**
* Callback to customize a {@link HttpHeaders} instance.
* @param headers the headers to customize
*/
void applyTo(HttpHeaders headers);

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

import java.io.IOException;
import java.net.URI;
import java.util.Collection;

import org.jetbrains.annotations.NotNull;

import org.springframework.http.HttpMethod;
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
Expand All @@ -26,27 +29,29 @@
import org.springframework.util.Assert;

/**
* {@link ClientHttpRequestFactory} to apply a given HTTP Basic Authentication
* username/password pair, unless a custom Authorization header has been set before.
* {@link ClientHttpRequestFactory} to apply default headers to a request unless header
* values were provided.
*
* @author Ilya Lukyanovich
* @author Dmytro Nosan
*/
class BasicAuthenticationClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {
class HttpHeadersCustomizingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {

private final BasicAuthentication authentication;
private final Collection<? extends HttpHeadersCustomizer> customizers;

BasicAuthenticationClientHttpRequestFactory(BasicAuthentication authentication,
HttpHeadersCustomizingClientHttpRequestFactory(Collection<? extends HttpHeadersCustomizer> customizers,
ClientHttpRequestFactory clientHttpRequestFactory) {
super(clientHttpRequestFactory);
Assert.notNull(authentication, "Authentication must not be null");
this.authentication = authentication;
Assert.notEmpty(customizers, "Customizers must not be empty");
this.customizers = customizers;
}

@NotNull
@Override
protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory)
throws IOException {
protected ClientHttpRequest createRequest(@NotNull URI uri, @NotNull HttpMethod httpMethod,
ClientHttpRequestFactory requestFactory) throws IOException {
ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod);
this.authentication.applyTo(request);
this.customizers.forEach((customizer) -> customizer.applyTo(request.getHeaders()));
return request;
}

Expand Down
Loading

0 comments on commit d905761

Please sign in to comment.