diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandler.java b/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandler.java
index 1e4b4a3f86..84a4061050 100644
--- a/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandler.java
+++ b/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandler.java
@@ -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;
}
diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java b/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java
index 5759c3790c..08990f8665 100644
--- a/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java
+++ b/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java
@@ -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.
*/
diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandlerTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandlerTests.java
index c056cd4f41..7b9c5cf9ad 100644
--- a/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandlerTests.java
+++ b/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandlerTests.java
@@ -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");
}
}
diff --git a/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthorizationInterceptor.java b/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthorizationInterceptor.java
new file mode 100644
index 0000000000..e994245d08
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthorizationInterceptor.java
@@ -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);
+ }
+
+}
diff --git a/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java b/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java
new file mode 100644
index 0000000000..ee64a5ec55
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java
@@ -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}.
+ *
+ * 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 REQUEST_FACTORY_CANDIDATES;
+
+ static {
+ Map candidates = new LinkedHashMap();
+ 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> messageConverters;
+
+ private final ClientHttpRequestFactory requestFactory;
+
+ private final UriTemplateHandler uriTemplateHandler;
+
+ private final ResponseErrorHandler errorHandler;
+
+ private final BasicAuthorizationInterceptor basicAuthorization;
+
+ private final Set 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(Arrays.asList(customizers)));
+ }
+
+ private RestTemplateBuilder(boolean detectRequestFactory, String rootUri,
+ Set> messageConverters,
+ ClientHttpRequestFactory requestFactory,
+ UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler,
+ BasicAuthorizationInterceptor basicAuthorization,
+ Set 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>(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>(
+ 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(
+ 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 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 build(Class restTemplateClass) {
+ return configure(BeanUtils.instantiate(restTemplateClass));
+ }
+
+ /**
+ * Configure the provided {@link RestTemplate} instance using this builder.
+ * @param 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 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>(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 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 Set append(Set set, Collection extends T> additions) {
+ Set result = new LinkedHashSet(
+ set == null ? Collections.emptySet() : set);
+ result.addAll(additions);
+ return Collections.unmodifiableSet(result);
+ }
+
+}
diff --git a/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateCustomizer.java b/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateCustomizer.java
new file mode 100644
index 0000000000..fb902d41aa
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateCustomizer.java
@@ -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);
+
+}
diff --git a/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java b/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java
new file mode 100644
index 0000000000..7d704e3863
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java
@@ -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 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;
+ }
+
+}
diff --git a/spring-boot/src/test/java/org/springframework/boot/web/client/BasicAuthorizationInterceptorTests.java b/spring-boot/src/test/java/org/springframework/boot/web/client/BasicAuthorizationInterceptorTests.java
new file mode 100644
index 0000000000..75dc196c2d
--- /dev/null
+++ b/spring-boot/src/test/java/org/springframework/boot/web/client/BasicAuthorizationInterceptorTests.java
@@ -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=");
+ }
+
+}
diff --git a/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java b/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java
new file mode 100644
index 0000000000..73b2dd63b3
--- /dev/null
+++ b/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java
@@ -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