Add RestTemplateBuilder support

Add a RestTemplateBuilder that allows RestTemplates to be easily created
and configured.

See gh-5507
This commit is contained in:
Phillip Webb 2016-05-31 09:28:14 -07:00
parent e664d585d9
commit b641e63466
10 changed files with 1210 additions and 33 deletions

View File

@ -16,6 +16,7 @@
package org.springframework.boot.test.web.client;
import org.springframework.boot.web.client.RootUriTemplateHandler;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.web.util.DefaultUriTemplateHandler;
@ -28,17 +29,18 @@ import org.springframework.web.util.UriTemplateHandler;
* @author Phillip Webb
* @since 1.4.0
*/
public class LocalHostUriTemplateHandler extends DefaultUriTemplateHandler {
public class LocalHostUriTemplateHandler extends RootUriTemplateHandler {
private final Environment environment;
public LocalHostUriTemplateHandler(Environment environment) {
super(new DefaultUriTemplateHandler());
Assert.notNull(environment, "Environment must not be null");
this.environment = environment;
}
@Override
public String getBaseUrl() {
public String getRootUri() {
String port = this.environment.getProperty("local.server.port", "8080");
return "http://localhost:" + port;
}

View File

@ -18,7 +18,6 @@ package org.springframework.boot.test.web.client;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@ -37,20 +36,18 @@ import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.boot.web.client.BasicAuthorizationInterceptor;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
import org.springframework.util.ClassUtils;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RequestCallback;
@ -76,8 +73,6 @@ import org.springframework.web.util.UriTemplateHandler;
*/
public class TestRestTemplate {
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final RestTemplate restTemplate;
/**
@ -912,29 +907,6 @@ public class TestRestTemplate {
}
private static class BasicAuthorizationInterceptor
implements ClientHttpRequestInterceptor {
private final String username;
private final String password;
BasicAuthorizationInterceptor(String username, String password) {
this.username = username;
this.password = (password == null ? "" : password);
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
String token = Base64Utils.encodeToString(
(this.username + ":" + this.password).getBytes(UTF_8));
request.getHeaders().add("Authorization", "Basic " + token);
return execution.execute(request, body);
}
}
/**
* {@link HttpComponentsClientHttpRequestFactory} to apply customizations.
*/

View File

@ -47,7 +47,7 @@ public class LocalHostUriTemplateHandlerTests {
environment.setProperty("local.server.port", "1234");
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(
environment);
assertThat(handler.getBaseUrl()).isEqualTo("http://localhost:1234");
assertThat(handler.getRootUri()).isEqualTo("http://localhost:1234");
}
@Test
@ -55,7 +55,7 @@ public class LocalHostUriTemplateHandlerTests {
MockEnvironment environment = new MockEnvironment();
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(
environment);
assertThat(handler.getBaseUrl()).isEqualTo("http://localhost:8080");
assertThat(handler.getRootUri()).isEqualTo("http://localhost:8080");
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2012-2016 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
*
* http://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.nio.charset.Charset;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
/**
* {@link ClientHttpRequestInterceptor} to apply a BASIC authorization header.
*
* @author Phillip Webb
* @since 1.4.0
*/
public class BasicAuthorizationInterceptor implements ClientHttpRequestInterceptor {
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final String username;
private final String password;
public BasicAuthorizationInterceptor(String username, String password) {
Assert.hasLength(username, "Username must not be empty");
this.username = username;
this.password = (password == null ? "" : password);
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
String token = Base64Utils
.encodeToString((this.username + ":" + this.password).getBytes(UTF_8));
request.getHeaders().add("Authorization", "Basic " + token);
return execution.execute(request, body);
}
}

View File

@ -0,0 +1,438 @@
/*
* Copyright 2012-2016 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
*
* http://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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeanUtils;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplateHandler;
/**
* Builder that can be used to configure and create a {@link RestTemplate}. Provides
* convenience methods to register {@link #messageConverters(HttpMessageConverter...)
* converters}, {@link #errorHandler(ResponseErrorHandler) error handlers} and
* {@link #uriTemplateHandler(UriTemplateHandler) UriTemplateHandlers}.
* <p>
* By default the built {@link RestTemplate} will attempt to use the most suitable
* {@link ClientHttpRequestFactory}, call {@link #detectRequestFactory(boolean)
* detectRequestFactory(false)} if you prefer to keep the default. In a typical
* auto-configured Spring Boot application this builder is available as a bean and can be
* injected whenever a {@link RestTemplate} is needed.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.4.0
*/
public class RestTemplateBuilder {
private static final Map<String, String> REQUEST_FACTORY_CANDIDATES;
static {
Map<String, String> candidates = new LinkedHashMap<String, String>();
candidates.put("org.apache.http.client.HttpClient",
"org.springframework.http.client.HttpComponentsClientHttpRequestFactory");
candidates.put("okhttp3.OkHttpClient",
"org.springframework.http.client.OkHttp3ClientHttpRequestFactory");
candidates.put("com.squareup.okhttp.OkHttpClient",
"org.springframework.http.client.OkHttpClientHttpRequestFactory");
candidates.put("io.netty.channel.EventLoopGroup",
"org.springframework.http.client.Netty4ClientHttpRequestFactory");
REQUEST_FACTORY_CANDIDATES = Collections.unmodifiableMap(candidates);
}
private final boolean detectRequestFactory;
private final String rootUri;
private final Set<HttpMessageConverter<?>> messageConverters;
private final ClientHttpRequestFactory requestFactory;
private final UriTemplateHandler uriTemplateHandler;
private final ResponseErrorHandler errorHandler;
private final BasicAuthorizationInterceptor basicAuthorization;
private final Set<RestTemplateCustomizer> customizers;
/**
* Create a new {@link RestTemplateBuilder} instance.
* @param customizers any {@link RestTemplateCustomizer RestTemplateCustomizers} that
* should be applied when the {@link RestTemplate} is built
*/
public RestTemplateBuilder(RestTemplateCustomizer... customizers) {
Assert.notNull(customizers, "Customizers must not be null");
this.detectRequestFactory = true;
this.rootUri = null;
this.messageConverters = null;
this.requestFactory = null;
this.uriTemplateHandler = null;
this.errorHandler = null;
this.basicAuthorization = null;
this.customizers = Collections.unmodifiableSet(
new LinkedHashSet<RestTemplateCustomizer>(Arrays.asList(customizers)));
}
private RestTemplateBuilder(boolean detectRequestFactory, String rootUri,
Set<HttpMessageConverter<?>> messageConverters,
ClientHttpRequestFactory requestFactory,
UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler,
BasicAuthorizationInterceptor basicAuthorization,
Set<RestTemplateCustomizer> customizers) {
super();
this.detectRequestFactory = detectRequestFactory;
this.rootUri = rootUri;
this.messageConverters = messageConverters;
this.requestFactory = requestFactory;
this.uriTemplateHandler = uriTemplateHandler;
this.errorHandler = errorHandler;
this.basicAuthorization = basicAuthorization;
this.customizers = customizers;
}
/**
* Set if the {@link ClientHttpRequestFactory} should be detected based on the
* classpath. Default if {@code true}.
* @param detectRequestFactory if the {@link ClientHttpRequestFactory} should be
* detected
* @return a new builder instance
*/
public RestTemplateBuilder detectRequestFactory(boolean detectRequestFactory) {
return new RestTemplateBuilder(detectRequestFactory, this.rootUri,
this.messageConverters, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthorization, this.customizers);
}
/**
* Set a root URL that should be applied to each request that starts with {@code '/'}.
* See {@link RootUriTemplateHandler} for details.
* @param rootUri the root URI or {@code null}
* @return a new builder instance
*/
public RestTemplateBuilder rootUri(String rootUri) {
return new RestTemplateBuilder(this.detectRequestFactory, rootUri,
this.messageConverters, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthorization, this.customizers);
}
/**
* Set the {@link HttpMessageConverter HttpMessageConverters} that should be used with
* the {@link RestTemplate}. Setting this value will replace any previously calls.
* @param messageConverters the converters to set
* @return a new builder instance
* @see #additionalMessageConverters(HttpMessageConverter...)
*/
public RestTemplateBuilder messageConverters(
HttpMessageConverter<?>... messageConverters) {
Assert.notNull(messageConverters, "MessageConverters must not be null");
return messageConverters(Arrays.asList(messageConverters));
}
/**
* Set the {@link HttpMessageConverter HttpMessageConverters} that should be used with
* the {@link RestTemplate}. Setting this value will replace any previously calls.
* @param messageConverters the converters to set
* @return a new builder instance
* @see #additionalMessageConverters(HttpMessageConverter...)
*/
public RestTemplateBuilder messageConverters(
Collection<? extends HttpMessageConverter<?>> messageConverters) {
Assert.notNull(messageConverters, "MessageConverters must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
Collections.unmodifiableSet(
new LinkedHashSet<HttpMessageConverter<?>>(messageConverters)),
this.requestFactory, this.uriTemplateHandler, this.errorHandler,
this.basicAuthorization, this.customizers);
}
/**
* Add additional {@link HttpMessageConverter HttpMessageConverters} that should be
* used with the {@link RestTemplate}.
* @param messageConverters the converters to add
* @return a new builder instance
* @see #messageConverters(HttpMessageConverter...)
*/
public RestTemplateBuilder additionalMessageConverters(
HttpMessageConverter<?>... messageConverters) {
Assert.notNull(messageConverters, "MessageConverters must not be null");
return additionalMessageConverters(Arrays.asList(messageConverters));
}
/**
* Add additional {@link HttpMessageConverter HttpMessageConverters} that should be
* used with the {@link RestTemplate}.
* @param messageConverters the converters to add
* @return a new builder instance
* @see #messageConverters(HttpMessageConverter...)
*/
public RestTemplateBuilder additionalMessageConverters(
Collection<? extends HttpMessageConverter<?>> messageConverters) {
Assert.notNull(messageConverters, "MessageConverters must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
append(this.messageConverters, messageConverters), this.requestFactory,
this.uriTemplateHandler, this.errorHandler, this.basicAuthorization,
this.customizers);
}
/**
* Set the {@link HttpMessageConverter HttpMessageConverters} that should be used with
* the {@link RestTemplate} to the default set. Calling this method will replace any
* previously defined converters.
* @return a new builder instance
* @see #messageConverters(HttpMessageConverter...)
*/
public RestTemplateBuilder defaultMessageConverters() {
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
Collections.unmodifiableSet(new LinkedHashSet<HttpMessageConverter<?>>(
new RestTemplate().getMessageConverters())),
this.requestFactory, this.uriTemplateHandler, this.errorHandler,
this.basicAuthorization, this.customizers);
}
/**
* Set the {@link ClientHttpRequestFactory} class that should be used with the
* {@link RestTemplate}.
* @param requestFactory the request factory to use
* @return a new builder instance
*/
public RestTemplateBuilder requestFactory(
Class<? extends ClientHttpRequestFactory> requestFactory) {
Assert.notNull(requestFactory, "RequestFactory must not be null");
return requestFactory(BeanUtils.instantiate(requestFactory));
}
/**
* Set the {@link ClientHttpRequestFactory} that should be used with the
* {@link RestTemplate}.
* @param requestFactory the request factory to use
* @return a new builder instance
*/
public RestTemplateBuilder requestFactory(ClientHttpRequestFactory requestFactory) {
Assert.notNull(requestFactory, "RequestFactory must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
this.messageConverters, requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthorization, this.customizers);
}
/**
* Set the {@link UriTemplateHandler} that should be used with the
* {@link RestTemplate}.
* @param uriTemplateHandler the URI template handler to use
* @return a new builder instance
*/
public RestTemplateBuilder uriTemplateHandler(UriTemplateHandler uriTemplateHandler) {
Assert.notNull(uriTemplateHandler, "UriTemplateHandler must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
this.messageConverters, this.requestFactory, uriTemplateHandler,
this.errorHandler, this.basicAuthorization, this.customizers);
}
/**
* Set the {@link ResponseErrorHandler} that should be used with the
* {@link RestTemplate}.
* @param errorHandler the error hander to use
* @return a new builder instance
*/
public RestTemplateBuilder errorHandler(ResponseErrorHandler errorHandler) {
Assert.notNull(errorHandler, "ErrorHandler must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
this.messageConverters, this.requestFactory, this.uriTemplateHandler,
errorHandler, this.basicAuthorization, this.customizers);
}
/**
* Add HTTP basic authentication to requests. See
* {@link BasicAuthorizationInterceptor} for details.
* @param username the user name
* @param password the password
* @return a new builder instance
*/
public RestTemplateBuilder basicAuthorization(String username, String password) {
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
this.messageConverters, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, new BasicAuthorizationInterceptor(username, password),
this.customizers);
}
/**
* Set the {@link HttpMessageConverter HttpMessageConverters} that should be 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 replace
* any previously configured customizers.
* @param restTemplateCustomizers the customizers to set
* @return a new builder instance
* @see #additionalCustomizers(RestTemplateCustomizer...)
*/
public RestTemplateBuilder customizers(
RestTemplateCustomizer... restTemplateCustomizers) {
Assert.notNull(restTemplateCustomizers,
"RestTemplateCustomizers must not be null");
return customizers(Arrays.asList(restTemplateCustomizers));
}
/**
* Set the {@link HttpMessageConverter HttpMessageConverters} that should be 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 replace
* any previously configured customizers.
* @param restTemplateCustomizers the customizers to set
* @return a new builder instance
* @see #additionalCustomizers(RestTemplateCustomizer...)
*/
public RestTemplateBuilder customizers(
Collection<? extends RestTemplateCustomizer> restTemplateCustomizers) {
Assert.notNull(restTemplateCustomizers,
"RestTemplateCustomizers must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
this.messageConverters, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthorization,
Collections.unmodifiableSet(new LinkedHashSet<RestTemplateCustomizer>(
restTemplateCustomizers)));
}
/**
* Add {@link HttpMessageConverter HttpMessageConverters} 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(Arrays.asList(restTemplateCustomizers));
}
/**
* Add {@link HttpMessageConverter HttpMessageConverters} 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 customizers the customizers to add
* @return a new builder instance
* @see #customizers(RestTemplateCustomizer...)
*/
public RestTemplateBuilder additionalCustomizers(
Collection<? extends RestTemplateCustomizer> customizers) {
Assert.notNull(customizers, "RestTemplateCustomizers must not be null");
return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
this.messageConverters, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthorization,
append(this.customizers, customizers));
}
/**
* Build a new {@link RestTemplate} instance and configure it using this builder.
* @return a configured {@link RestTemplate} instance.
* @see #build(Class)
* @see #configure(RestTemplate)
*/
public RestTemplate build() {
return build(RestTemplate.class);
}
/**
* Build a new {@link RestTemplate} instance of the specified type and configure it
* using this builder.
* @param <T> the type of rest template
* @param restTemplateClass the template type to create
* @return a configured {@link RestTemplate} instance.
* @see RestTemplateBuilder#build()
* @see #configure(RestTemplate)
*/
public <T extends RestTemplate> T build(Class<T> restTemplateClass) {
return configure(BeanUtils.instantiate(restTemplateClass));
}
/**
* Configure the provided {@link RestTemplate} instance using this builder.
* @param <T> the type of rest template
* @param restTemplate the {@link RestTemplate} to configure
* @return the rest template instance
* @see RestTemplateBuilder#build()
* @see RestTemplateBuilder#build(Class)
*/
public <T extends RestTemplate> T configure(T restTemplate) {
if (this.requestFactory != null) {
restTemplate.setRequestFactory(this.requestFactory);
}
else if (this.detectRequestFactory) {
restTemplate.setRequestFactory(detectRequestFactory());
}
if (!CollectionUtils.isEmpty(this.messageConverters)) {
restTemplate.setMessageConverters(
new ArrayList<HttpMessageConverter<?>>(this.messageConverters));
}
if (this.uriTemplateHandler != null) {
restTemplate.setUriTemplateHandler(this.uriTemplateHandler);
}
if (this.errorHandler != null) {
restTemplate.setErrorHandler(this.errorHandler);
}
if (this.rootUri != null) {
RootUriTemplateHandler.addTo(restTemplate, this.rootUri);
}
if (this.basicAuthorization != null) {
restTemplate.getInterceptors().add(this.basicAuthorization);
}
if (!CollectionUtils.isEmpty(this.customizers)) {
for (RestTemplateCustomizer customizer : this.customizers) {
customizer.customize(restTemplate);
}
}
return restTemplate;
}
private ClientHttpRequestFactory detectRequestFactory() {
for (Map.Entry<String, String> candidate : REQUEST_FACTORY_CANDIDATES
.entrySet()) {
ClassLoader classLoader = getClass().getClassLoader();
if (ClassUtils.isPresent(candidate.getKey(), classLoader)) {
Class<?> factoryClass = ClassUtils.resolveClassName(candidate.getValue(),
classLoader);
return (ClientHttpRequestFactory) BeanUtils.instantiate(factoryClass);
}
}
return new SimpleClientHttpRequestFactory();
}
private <T> Set<T> append(Set<T> set, Collection<? extends T> additions) {
Set<T> result = new LinkedHashSet<T>(
set == null ? Collections.<T>emptySet() : set);
result.addAll(additions);
return Collections.unmodifiableSet(result);
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2012-2016 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
*
* http://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.web.client.RestTemplate;
/**
* Callback interface that can be used to customize a {@link RestTemplate}.
*
* @author Phillip Webb
* @since 1.4.0
* @see RestTemplateBuilder
*/
public interface RestTemplateCustomizer {
/**
* Callback to customize a {@link RestTemplate} instance.
* @param restTemplate the template to customize
*/
void customize(RestTemplate restTemplate);
}

View File

@ -0,0 +1,101 @@
/*
* Copyright 2012-2016 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
*
* http://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.net.URI;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriTemplateHandler;
import org.springframework.web.util.UriTemplateHandler;
/**
* {@link UriTemplateHandler} to set the root for URI that start with {@code '/'}.
*
* @author Phillip Webb
* @since 1.4.0
*/
public class RootUriTemplateHandler implements UriTemplateHandler {
private final String rootUri;
private final UriTemplateHandler handler;
protected RootUriTemplateHandler(UriTemplateHandler handler) {
this.rootUri = null;
this.handler = handler;
}
/**
* Create a new {@link RootUriTemplateHandler} instance.
* @param rootUri the root URI to used to prefix relative URLs
*/
public RootUriTemplateHandler(String rootUri) {
this(rootUri, new DefaultUriTemplateHandler());
}
/**
* Create a new {@link RootUriTemplateHandler} instance.
* @param rootUri the root URI to used to prefix relative URLs
* @param handler the delegate handler
*/
public RootUriTemplateHandler(String rootUri, UriTemplateHandler handler) {
Assert.notNull(rootUri, "RootUri must not be null");
Assert.notNull(handler, "Handler must not be null");
this.rootUri = rootUri;
this.handler = handler;
}
@Override
public URI expand(String uriTemplate, Map<String, ?> uriVariables) {
return this.handler.expand(apply(uriTemplate), uriVariables);
}
@Override
public URI expand(String uriTemplate, Object... uriVariables) {
return this.handler.expand(apply(uriTemplate), uriVariables);
}
private String apply(String uriTemplate) {
if (StringUtils.startsWithIgnoreCase(uriTemplate, "/")) {
return getRootUri() + uriTemplate;
}
return uriTemplate;
}
public String getRootUri() {
return this.rootUri;
}
/**
* Add a {@link RootUriTemplateHandler} instance to the given {@link RestTemplate}.
* @param restTemplate the {@link RestTemplate} to add the handler to
* @param rootUri the root URI
* @return the added {@link RootUriTemplateHandler}.
*/
public static RootUriTemplateHandler addTo(RestTemplate restTemplate,
String rootUri) {
Assert.notNull(restTemplate, "RestTemplate must not be null");
RootUriTemplateHandler handler = new RootUriTemplateHandler(rootUri,
restTemplate.getUriTemplateHandler());
restTemplate.setUriTemplateHandler(handler);
return handler;
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2012-2016 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
*
* http://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.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.mock.http.client.MockClientHttpRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link BasicAuthorizationInterceptor}.
*
* @author Phillip Webb
*/
public class BasicAuthorizationInterceptorTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenUsernameIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Username must not be empty");
new BasicAuthorizationInterceptor(null, "password");
}
@Test
public void createWhenUsernameIsEmptyShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Username must not be empty");
new BasicAuthorizationInterceptor("", "password");
}
@Test
public void createWhenPasswordIsNullShouldUseEmptyPassword() throws Exception {
BasicAuthorizationInterceptor interceptor = new BasicAuthorizationInterceptor(
"username", "");
assertThat(interceptor).extracting("password").containsExactly("");
}
@Test
public void interceptShouldAddHeader() throws Exception {
MockClientHttpRequest request = new MockClientHttpRequest();
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
byte[] body = new byte[] {};
new BasicAuthorizationInterceptor("spring", "boot").intercept(request, body,
execution);
verify(execution).execute(request, body);
assertThat(request.getHeaders().getFirst("Authorization"))
.isEqualTo("Basic c3ByaW5nOmJvb3Q=");
}
}

View File

@ -0,0 +1,370 @@
/*
* Copyright 2012-2016 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
*
* http://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.util.Collections;
import java.util.Set;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplateHandler;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
/**
* Tests for {@link RestTemplateBuilder}.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
public class RestTemplateBuilderTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private RestTemplateBuilder builder = new RestTemplateBuilder();
@Mock
private HttpMessageConverter<Object> messageConverter;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void createWhenCustomizersAreNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Customizers must not be null");
RestTemplateCustomizer[] customizers = null;
new RestTemplateBuilder(customizers);
}
@Test
public void createWithCustomizersShouldApplyCustomizers() throws Exception {
RestTemplateCustomizer customizer = mock(RestTemplateCustomizer.class);
RestTemplate template = new RestTemplateBuilder(customizer).build();
verify(customizer).customize(template);
}
@Test
public void buildShouldDetectRequestFactory() throws Exception {
RestTemplate restTemplate = this.builder.build();
assertThat(restTemplate.getRequestFactory())
.isInstanceOf(HttpComponentsClientHttpRequestFactory.class);
}
@Test
public void detectRequestFactoryWhenFalseShouldDisableDetection() throws Exception {
RestTemplate restTemplate = this.builder.detectRequestFactory(false).build();
assertThat(restTemplate.getRequestFactory())
.isInstanceOf(SimpleClientHttpRequestFactory.class);
}
@Test
public void rootUriShouldApply() throws Exception {
RestTemplate restTemplate = this.builder.rootUri("http://example.com").build();
MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build();
server.expect(requestTo("http://example.com/hello")).andRespond(withSuccess());
restTemplate.getForEntity("/hello", String.class);
server.verify();
}
@Test
public void rootUriShouldApplyAfterUriTemplateHandler() throws Exception {
UriTemplateHandler uriTemplateHandler = mock(UriTemplateHandler.class);
RestTemplate template = this.builder.uriTemplateHandler(uriTemplateHandler)
.rootUri("http://example.com").build();
UriTemplateHandler handler = template.getUriTemplateHandler();
handler.expand("/hello");
assertThat(handler).isInstanceOf(RootUriTemplateHandler.class);
verify(uriTemplateHandler).expand("http://example.com/hello");
}
@Test
public void messageConvertersWhenConvertersAreNullShouldThrowException()
throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("MessageConverters must not be null");
this.builder.messageConverters((HttpMessageConverter<?>[]) null);
}
@Test
public void messageConvertersCollectionWhenConvertersAreNullShouldThrowException()
throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("MessageConverters must not be null");
this.builder.messageConverters((Set<HttpMessageConverter<?>>) null);
}
@Test
public void messageConvertersShouldApply() throws Exception {
RestTemplate template = this.builder.messageConverters(this.messageConverter)
.build();
assertThat(template.getMessageConverters()).containsOnly(this.messageConverter);
}
@Test
public void messageConvertersShouldRepalceExisting() throws Exception {
RestTemplate template = this.builder
.messageConverters(new ResourceHttpMessageConverter())
.messageConverters(Collections.singleton(this.messageConverter)).build();
assertThat(template.getMessageConverters()).containsOnly(this.messageConverter);
}
@Test
public void additionalMessageConvertersWhenConvertersAreNullShouldThrowException()
throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("MessageConverters must not be null");
this.builder.additionalMessageConverters((HttpMessageConverter<?>[]) null);
}
@Test
public void additionalMessageConvertersCollectionWhenConvertersAreNullShouldThrowException()
throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("MessageConverters must not be null");
this.builder.additionalMessageConverters((Set<HttpMessageConverter<?>>) null);
}
@Test
public void additionalMessageConvertersShouldAddToExisting() throws Exception {
HttpMessageConverter<?> resourceConverter = new ResourceHttpMessageConverter();
RestTemplate template = this.builder.messageConverters(resourceConverter)
.additionalMessageConverters(this.messageConverter).build();
assertThat(template.getMessageConverters()).containsOnly(resourceConverter,
this.messageConverter);
}
@Test
public void defaultMessageConvertersShouldSetDefaultList() throws Exception {
RestTemplate template = new RestTemplate(
Collections.<HttpMessageConverter<?>>singletonList(
new StringHttpMessageConverter()));
this.builder.defaultMessageConverters().configure(template);
assertThat(template.getMessageConverters())
.hasSameSizeAs(new RestTemplate().getMessageConverters());
}
@Test
public void defaultMessageConvertersShouldClearExisting() throws Exception {
RestTemplate template = new RestTemplate(
Collections.<HttpMessageConverter<?>>singletonList(
new StringHttpMessageConverter()));
this.builder.additionalMessageConverters(this.messageConverter)
.defaultMessageConverters().configure(template);
assertThat(template.getMessageConverters())
.hasSameSizeAs(new RestTemplate().getMessageConverters());
}
@Test
public void requestFactoryClassWhenFactoryIsNullShouldThrowException()
throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("RequestFactory must not be null");
this.builder.requestFactory((Class<ClientHttpRequestFactory>) null);
}
@Test
public void requestFactoryClassShouldApply() throws Exception {
RestTemplate template = this.builder
.requestFactory(SimpleClientHttpRequestFactory.class).build();
assertThat(template.getRequestFactory())
.isInstanceOf(SimpleClientHttpRequestFactory.class);
}
@Test
public void requestFactoryWhenFactoryIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("RequestFactory must not be null");
this.builder.requestFactory((ClientHttpRequestFactory) null);
}
@Test
public void requestFactoryShouldApply() throws Exception {
ClientHttpRequestFactory requestFactory = mock(ClientHttpRequestFactory.class);
RestTemplate template = this.builder.requestFactory(requestFactory).build();
assertThat(template.getRequestFactory()).isSameAs(requestFactory);
}
@Test
public void uriTemplateHandlerWhenHandlerIsNullShouldThrowException()
throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("UriTemplateHandler must not be null");
this.builder.uriTemplateHandler(null);
}
@Test
public void uriTemplateHandlerShouldApply() throws Exception {
UriTemplateHandler uriTemplateHandler = mock(UriTemplateHandler.class);
RestTemplate template = this.builder.uriTemplateHandler(uriTemplateHandler)
.build();
assertThat(template.getUriTemplateHandler()).isSameAs(uriTemplateHandler);
}
@Test
public void errorHandlerWhenHandlerIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("ErrorHandler must not be null");
this.builder.errorHandler(null);
}
@Test
public void errorHandlerShouldApply() throws Exception {
ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
RestTemplate template = this.builder.errorHandler(errorHandler).build();
assertThat(template.getErrorHandler()).isSameAs(errorHandler);
}
@Test
public void basicAuthorizationShouldApply() throws Exception {
RestTemplate template = this.builder.basicAuthorization("spring", "boot").build();
ClientHttpRequestInterceptor interceptor = template.getInterceptors().get(0);
assertThat(interceptor).isInstanceOf(BasicAuthorizationInterceptor.class);
assertThat(interceptor).extracting("username").containsExactly("spring");
assertThat(interceptor).extracting("password").containsExactly("boot");
}
@Test
public void customizersWhenNullArrayShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Customizers must not be null");
RestTemplateCustomizer[] customizers = null;
this.builder.customizers(customizers);
}
@Test
public void customizersWhenConvertersAreNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("RestTemplateCustomizers must not be null");
this.builder.customizers((RestTemplateCustomizer[]) null);
}
@Test
public void customizersCollectionWhenConvertersAreNullShouldThrowException()
throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("RestTemplateCustomizers must not be null");
this.builder.customizers((Set<RestTemplateCustomizer>) null);
}
@Test
public void customizersShouldApply() throws Exception {
RestTemplateCustomizer customizer = mock(RestTemplateCustomizer.class);
RestTemplate template = this.builder.customizers(customizer).build();
verify(customizer).customize(template);
}
@Test
public void customizersShouldBeAppliedLast() throws Exception {
RestTemplate template = spy(new RestTemplate());
this.builder.additionalCustomizers(new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
verify(restTemplate).setRequestFactory((ClientHttpRequestFactory) any());
}
});
this.builder.configure(template);
}
@Test
public void customizersShouldRepalceExisting() throws Exception {
RestTemplateCustomizer customizer1 = mock(RestTemplateCustomizer.class);
RestTemplateCustomizer customizer2 = mock(RestTemplateCustomizer.class);
RestTemplate template = this.builder.customizers(customizer1)
.customizers(Collections.singleton(customizer2)).build();
verifyZeroInteractions(customizer1);
verify(customizer2).customize(template);
}
@Test
public void additionalCustomizersWhenConvertersAreNullShouldThrowException()
throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("RestTemplateCustomizers must not be null");
this.builder.additionalCustomizers((RestTemplateCustomizer[]) null);
}
@Test
public void additionalCustomizersCollectionWhenConvertersAreNullShouldThrowException()
throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("RestTemplateCustomizers must not be null");
this.builder.additionalCustomizers((Set<RestTemplateCustomizer>) null);
}
@Test
public void additionalCustomizersShouldAddToExisting() throws Exception {
RestTemplateCustomizer customizer1 = mock(RestTemplateCustomizer.class);
RestTemplateCustomizer customizer2 = mock(RestTemplateCustomizer.class);
RestTemplate template = this.builder.customizers(customizer1)
.additionalCustomizers(customizer2).build();
verify(customizer1).customize(template);
verify(customizer2).customize(template);
}
@Test
public void buildShouldReturnRestTemplate() throws Exception {
RestTemplate template = this.builder.build();
assertThat(template.getClass()).isEqualTo(RestTemplate.class);
}
@Test
public void buildClassShouldReturnClassInstance() throws Exception {
RestTemplateSubclass template = this.builder.build(RestTemplateSubclass.class);
assertThat(template.getClass()).isEqualTo(RestTemplateSubclass.class);
}
@Test
public void configureShouldApply() throws Exception {
RestTemplate template = new RestTemplate();
this.builder.configure(template);
assertThat(template.getRequestFactory())
.isInstanceOf(HttpComponentsClientHttpRequestFactory.class);
}
public static class RestTemplateSubclass extends RestTemplate {
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright 2012-2016 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
*
* http://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.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplateHandler;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.anyMap;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.anyVararg;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link RootUriTemplateHandler}.
*
* @author Phillip Webb
*/
public class RootUriTemplateHandlerTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private URI uri;
@Mock
public UriTemplateHandler delegate;
public UriTemplateHandler handler;
@Before
@SuppressWarnings("unchecked")
public void setup() throws URISyntaxException {
MockitoAnnotations.initMocks(this);
this.uri = new URI("http://example.com/hello");
this.handler = new RootUriTemplateHandler("http://example.com", this.delegate);
given(this.delegate.expand(anyString(), anyMap())).willReturn(this.uri);
given(this.delegate.expand(anyString(), (Object[]) anyVararg()))
.willReturn(this.uri);
}
@Test
public void createWithNullRootUriShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("RootUri must not be null");
new RootUriTemplateHandler((String) null);
}
@Test
public void createWithNullHandlerShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Handler must not be null");
new RootUriTemplateHandler("http://example.com", null);
}
@Test
public void expandMapVariablesShouldPrefixRoot() throws Exception {
HashMap<String, Object> uriVariables = new HashMap<String, Object>();
URI expanded = this.handler.expand("/hello", uriVariables);
verify(this.delegate).expand("http://example.com/hello", uriVariables);
assertThat(expanded).isEqualTo(this.uri);
}
@Test
public void expandMapVariablesWhenPathDoesNotStartWithSlashShouldNotPrefixRoot()
throws Exception {
HashMap<String, Object> uriVariables = new HashMap<String, Object>();
URI expanded = this.handler.expand("http://spring.io/hello", uriVariables);
verify(this.delegate).expand("http://spring.io/hello", uriVariables);
assertThat(expanded).isEqualTo(this.uri);
}
@Test
public void expandArrayVariablesShouldPrefixRoot() throws Exception {
Object[] uriVariables = new Object[0];
URI expanded = this.handler.expand("/hello", uriVariables);
verify(this.delegate).expand("http://example.com/hello", uriVariables);
assertThat(expanded).isEqualTo(this.uri);
}
@Test
public void expandArrayVariablesWhenPathDoesNotStartWithSlashShouldNotPrefixRoot()
throws Exception {
Object[] uriVariables = new Object[0];
URI expanded = this.handler.expand("http://spring.io/hello", uriVariables);
verify(this.delegate).expand("http://spring.io/hello", uriVariables);
assertThat(expanded).isEqualTo(this.uri);
}
@Test
public void applyShouldWrapExistingTemplate() throws Exception {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(this.delegate);
this.handler = RootUriTemplateHandler.addTo(restTemplate, "http://example.com");
Object[] uriVariables = new Object[0];
URI expanded = this.handler.expand("/hello", uriVariables);
verify(this.delegate).expand("http://example.com/hello", uriVariables);
assertThat(expanded).isEqualTo(this.uri);
}
}