/* * Copyright 2012-2017 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.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; 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.AbstractClientHttpRequestFactoryWrapper; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.client.support.BasicAuthorizationInterceptor; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ReflectionUtils; 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 * @author Andy Wilkinson * @since 1.4.0 */ public class RestTemplateBuilder { private static final Map<String, String> REQUEST_FACTORY_CANDIDATES; static { Map<String, String> 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"); 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> restTemplateCustomizers; private final Set<RequestFactoryCustomizer> requestFactoryCustomizers; private final Set<ClientHttpRequestInterceptor> interceptors; /** * 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.restTemplateCustomizers = Collections .unmodifiableSet(new LinkedHashSet<>(Arrays.asList(customizers))); this.requestFactoryCustomizers = Collections.<RequestFactoryCustomizer>emptySet(); this.interceptors = Collections.<ClientHttpRequestInterceptor>emptySet(); } private RestTemplateBuilder(boolean detectRequestFactory, String rootUri, Set<HttpMessageConverter<?>> messageConverters, ClientHttpRequestFactory requestFactory, UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler, BasicAuthorizationInterceptor basicAuthorization, Set<RestTemplateCustomizer> restTemplateCustomizers, Set<RequestFactoryCustomizer> requestFactoryCustomizers, Set<ClientHttpRequestInterceptor> interceptors) { super(); this.detectRequestFactory = detectRequestFactory; this.rootUri = rootUri; this.messageConverters = messageConverters; this.requestFactory = requestFactory; this.uriTemplateHandler = uriTemplateHandler; this.errorHandler = errorHandler; this.basicAuthorization = basicAuthorization; this.restTemplateCustomizers = restTemplateCustomizers; this.requestFactoryCustomizers = requestFactoryCustomizers; this.interceptors = interceptors; } /** * 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.restTemplateCustomizers, this.requestFactoryCustomizers, this.interceptors); } /** * 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.restTemplateCustomizers, this.requestFactoryCustomizers, this.interceptors); } /** * Set the {@link HttpMessageConverter HttpMessageConverters} that should be used with * the {@link RestTemplate}. Setting this value will replace any previously configured * converters. * @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 configured * converters. * @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.restTemplateCustomizers, this.requestFactoryCustomizers, this.interceptors); } /** * 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.restTemplateCustomizers, this.requestFactoryCustomizers, this.interceptors); } /** * 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.restTemplateCustomizers, this.requestFactoryCustomizers, this.interceptors); } /** * Set the {@link ClientHttpRequestInterceptor ClientHttpRequestInterceptors} that * should be used with the {@link RestTemplate}. Setting this value will replace any * previously defined interceptors. * @param interceptors the interceptors to set * @return a new builder instance * @since 1.4.1 * @see #additionalInterceptors(ClientHttpRequestInterceptor...) */ public RestTemplateBuilder interceptors( ClientHttpRequestInterceptor... interceptors) { Assert.notNull(interceptors, "interceptors must not be null"); return interceptors(Arrays.asList(interceptors)); } /** * Set the {@link ClientHttpRequestInterceptor ClientHttpRequestInterceptors} that * should be used with the {@link RestTemplate}. Setting this value will replace any * previously defined interceptors. * @param interceptors the interceptors to set * @return a new builder instance * @since 1.4.1 * @see #additionalInterceptors(ClientHttpRequestInterceptor...) */ public RestTemplateBuilder interceptors( Collection<ClientHttpRequestInterceptor> interceptors) { Assert.notNull(interceptors, "interceptors must not be null"); return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, this.requestFactory, this.uriTemplateHandler, this.errorHandler, this.basicAuthorization, this.restTemplateCustomizers, this.requestFactoryCustomizers, Collections.unmodifiableSet(new LinkedHashSet<>(interceptors))); } /** * Add additional {@link ClientHttpRequestInterceptor ClientHttpRequestInterceptors} * that should be used with the {@link RestTemplate}. * @param interceptors the interceptors to add * @return a new builder instance * @since 1.4.1 * @see #interceptors(ClientHttpRequestInterceptor...) */ public RestTemplateBuilder additionalInterceptors( ClientHttpRequestInterceptor... interceptors) { Assert.notNull(interceptors, "interceptors must not be null"); return additionalInterceptors(Arrays.asList(interceptors)); } /** * Add additional {@link ClientHttpRequestInterceptor ClientHttpRequestInterceptors} * that should be used with the {@link RestTemplate}. * @param interceptors the interceptors to add * @return a new builder instance * @since 1.4.1 * @see #interceptors(ClientHttpRequestInterceptor...) */ public RestTemplateBuilder additionalInterceptors( Collection<? extends ClientHttpRequestInterceptor> interceptors) { Assert.notNull(interceptors, "interceptors must not be null"); return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, this.requestFactory, this.uriTemplateHandler, this.errorHandler, this.basicAuthorization, this.restTemplateCustomizers, this.requestFactoryCustomizers, append(this.interceptors, interceptors)); } /** * 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(createRequestFactory(requestFactory)); } private ClientHttpRequestFactory createRequestFactory( Class<? extends ClientHttpRequestFactory> requestFactory) { try { Constructor<?> constructor = requestFactory.getDeclaredConstructor(); constructor.setAccessible(true); return (ClientHttpRequestFactory) constructor.newInstance(); } catch (Exception ex) { throw new IllegalStateException(ex); } } /** * 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.restTemplateCustomizers, this.requestFactoryCustomizers, this.interceptors); } /** * 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.restTemplateCustomizers, this.requestFactoryCustomizers, this.interceptors); } /** * Set the {@link ResponseErrorHandler} that should be used with the * {@link RestTemplate}. * @param errorHandler the error handler 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.restTemplateCustomizers, this.requestFactoryCustomizers, this.interceptors); } /** * 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.restTemplateCustomizers, this.requestFactoryCustomizers, this.interceptors); } /** * Set the {@link RestTemplateCustomizer RestTemplateCustomizers} that should be * applied to the {@link RestTemplate}. Customizers are applied in the order that they * were added after builder configuration has been applied. 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 RestTemplateCustomizer RestTemplateCustomizers} that should be * applied to the {@link RestTemplate}. Customizers are applied in the order that they * were added after builder configuration has been applied. 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)), this.requestFactoryCustomizers, this.interceptors); } /** * Add {@link RestTemplateCustomizer RestTemplateCustomizers} that should be applied * to the {@link RestTemplate}. Customizers are applied in the order that they were * added after builder configuration has been applied. * @param 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 RestTemplateCustomizer RestTemplateCustomizers} that should be applied * to the {@link RestTemplate}. Customizers are applied in the order that they were * added after builder configuration has been applied. * @param 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.restTemplateCustomizers, customizers), this.requestFactoryCustomizers, this.interceptors); } /** * Sets the connect timeout in milliseconds on the underlying * {@link ClientHttpRequestFactory}. * @param connectTimeout the connect timeout in milliseconds * @return a new builder instance. */ public RestTemplateBuilder setConnectTimeout(int connectTimeout) { return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, this.requestFactory, this.uriTemplateHandler, this.errorHandler, this.basicAuthorization, this.restTemplateCustomizers, append(this.requestFactoryCustomizers, new ConnectTimeoutRequestFactoryCustomizer(connectTimeout)), this.interceptors); } /** * Sets the read timeout in milliseconds on the underlying * {@link ClientHttpRequestFactory}. * @param readTimeout the read timeout in milliseconds * @return a new builder instance. */ public RestTemplateBuilder setReadTimeout(int readTimeout) { return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, this.messageConverters, this.requestFactory, this.uriTemplateHandler, this.errorHandler, this.basicAuthorization, this.restTemplateCustomizers, append(this.requestFactoryCustomizers, new ReadTimeoutRequestFactoryCustomizer(readTimeout)), this.interceptors); } /** * 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.instantiateClass(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) { configureRequestFactory(restTemplate); 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.restTemplateCustomizers)) { for (RestTemplateCustomizer customizer : this.restTemplateCustomizers) { customizer.customize(restTemplate); } } restTemplate.getInterceptors().addAll(this.interceptors); return restTemplate; } private void configureRequestFactory(RestTemplate restTemplate) { ClientHttpRequestFactory requestFactory = null; if (this.requestFactory != null) { requestFactory = this.requestFactory; } else if (this.detectRequestFactory) { requestFactory = detectRequestFactory(); } if (requestFactory != null) { ClientHttpRequestFactory unwrappedRequestFactory = unwrapRequestFactoryIfNecessary( requestFactory); for (RequestFactoryCustomizer customizer : this.requestFactoryCustomizers) { customizer.customize(unwrappedRequestFactory); } restTemplate.setRequestFactory(requestFactory); } } private ClientHttpRequestFactory unwrapRequestFactoryIfNecessary( ClientHttpRequestFactory requestFactory) { if (!(requestFactory instanceof AbstractClientHttpRequestFactoryWrapper)) { return requestFactory; } ClientHttpRequestFactory unwrappedRequestFactory = requestFactory; Field field = ReflectionUtils.findField( AbstractClientHttpRequestFactoryWrapper.class, "requestFactory"); ReflectionUtils.makeAccessible(field); do { unwrappedRequestFactory = (ClientHttpRequestFactory) ReflectionUtils .getField(field, unwrappedRequestFactory); } while (unwrappedRequestFactory instanceof AbstractClientHttpRequestFactoryWrapper); return unwrappedRequestFactory; } 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 .instantiateClass(factoryClass); } } return new SimpleClientHttpRequestFactory(); } private <T> Set<T> append(Set<T> set, T addition) { Set<T> result = new LinkedHashSet<>( set == null ? Collections.<T>emptySet() : set); result.add(addition); return Collections.unmodifiableSet(result); } private <T> Set<T> append(Set<T> set, Collection<? extends T> additions) { Set<T> result = new LinkedHashSet<>( set == null ? Collections.<T>emptySet() : set); result.addAll(additions); return Collections.unmodifiableSet(result); } /** * Strategy interface used to customize the {@link ClientHttpRequestFactory}. */ private interface RequestFactoryCustomizer { void customize(ClientHttpRequestFactory factory); } /** * {@link RequestFactoryCustomizer} to call a "set timeout" method. */ private static abstract class TimeoutRequestFactoryCustomizer implements RequestFactoryCustomizer { private final int timeout; private final String methodName; TimeoutRequestFactoryCustomizer(int timeout, String methodName) { this.timeout = timeout; this.methodName = methodName; } @Override public void customize(ClientHttpRequestFactory factory) { ReflectionUtils.invokeMethod(findMethod(factory), factory, this.timeout); } private Method findMethod(ClientHttpRequestFactory factory) { Method method = ReflectionUtils.findMethod(factory.getClass(), this.methodName, int.class); if (method != null) { return method; } throw new IllegalStateException("Request factory " + factory.getClass() + " does not have a " + this.methodName + "(int) method"); } } /** * {@link RequestFactoryCustomizer} to set the read timeout. */ private static class ReadTimeoutRequestFactoryCustomizer extends TimeoutRequestFactoryCustomizer { ReadTimeoutRequestFactoryCustomizer(int readTimeout) { super(readTimeout, "setReadTimeout"); } } /** * {@link RequestFactoryCustomizer} to set the connect timeout. */ private static class ConnectTimeoutRequestFactoryCustomizer extends TimeoutRequestFactoryCustomizer { ConnectTimeoutRequestFactoryCustomizer(int connectTimeout) { super(connectTimeout, "setConnectTimeout"); } } }