Rework HTTP client modules
This commit is contained in:
committed by
Phillip Webb
parent
4763ef2463
commit
7a9be5bd4a
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Basic authentication details to be applied to {@link HttpHeaders}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
* @author Ilya Lukyanovich
|
||||
*/
|
||||
class BasicAuthentication {
|
||||
|
||||
private final String username;
|
||||
|
||||
private final String password;
|
||||
|
||||
private final Charset charset;
|
||||
|
||||
BasicAuthentication(String username, String password, Charset charset) {
|
||||
Assert.notNull(username, "'username' must not be null");
|
||||
Assert.notNull(password, "'password' must not be null");
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
void applyTo(HttpHeaders headers) {
|
||||
if (!headers.containsHeader(HttpHeaders.AUTHORIZATION)) {
|
||||
headers.setBasicAuth(this.username, this.password, this.charset);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient;
|
||||
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* Callback interface that can be used to customize a
|
||||
* {@link org.springframework.web.client.RestClient.Builder RestClient.Builder}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RestClientCustomizer {
|
||||
|
||||
/**
|
||||
* Callback to customize a {@link org.springframework.web.client.RestClient.Builder
|
||||
* RestClient.Builder} instance.
|
||||
* @param restClientBuilder the client builder to customize
|
||||
*/
|
||||
void customize(RestClient.Builder restClientBuilder);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,789 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.Duration;
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.http.client.HttpRedirects;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
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
|
||||
* @author Andy Wilkinson
|
||||
* @author Brian Clozel
|
||||
* @author Dmytro Nosan
|
||||
* @author Kevin Strijbos
|
||||
* @author Ilya Lukyanovich
|
||||
* @author Scott Frederick
|
||||
* @author Yanming Zhou
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class RestTemplateBuilder {
|
||||
|
||||
private final ClientHttpRequestFactorySettings requestFactorySettings;
|
||||
|
||||
private final boolean detectRequestFactory;
|
||||
|
||||
private final String rootUri;
|
||||
|
||||
private final Set<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
private final Set<ClientHttpRequestInterceptor> interceptors;
|
||||
|
||||
private final ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder;
|
||||
|
||||
private final UriTemplateHandler uriTemplateHandler;
|
||||
|
||||
private final ResponseErrorHandler errorHandler;
|
||||
|
||||
private final BasicAuthentication basicAuthentication;
|
||||
|
||||
private final Map<String, List<String>> defaultHeaders;
|
||||
|
||||
private final Set<RestTemplateCustomizer> customizers;
|
||||
|
||||
private final Set<RestTemplateRequestCustomizer<?>> requestCustomizers;
|
||||
|
||||
/**
|
||||
* 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.requestFactorySettings = ClientHttpRequestFactorySettings.defaults();
|
||||
this.detectRequestFactory = true;
|
||||
this.rootUri = null;
|
||||
this.messageConverters = null;
|
||||
this.interceptors = Collections.emptySet();
|
||||
this.requestFactoryBuilder = null;
|
||||
this.uriTemplateHandler = null;
|
||||
this.errorHandler = null;
|
||||
this.basicAuthentication = null;
|
||||
this.defaultHeaders = Collections.emptyMap();
|
||||
this.customizers = copiedSetOf(customizers);
|
||||
this.requestCustomizers = Collections.emptySet();
|
||||
}
|
||||
|
||||
private RestTemplateBuilder(ClientHttpRequestFactorySettings requestFactorySettings, boolean detectRequestFactory,
|
||||
String rootUri, Set<HttpMessageConverter<?>> messageConverters,
|
||||
Set<ClientHttpRequestInterceptor> interceptors, ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder,
|
||||
UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler,
|
||||
BasicAuthentication basicAuthentication, Map<String, List<String>> defaultHeaders,
|
||||
Set<RestTemplateCustomizer> customizers, Set<RestTemplateRequestCustomizer<?>> requestCustomizers) {
|
||||
this.requestFactorySettings = requestFactorySettings;
|
||||
this.detectRequestFactory = detectRequestFactory;
|
||||
this.rootUri = rootUri;
|
||||
this.messageConverters = messageConverters;
|
||||
this.interceptors = interceptors;
|
||||
this.requestFactoryBuilder = requestFactoryBuilder;
|
||||
this.uriTemplateHandler = uriTemplateHandler;
|
||||
this.errorHandler = errorHandler;
|
||||
this.basicAuthentication = basicAuthentication;
|
||||
this.defaultHeaders = defaultHeaders;
|
||||
this.customizers = customizers;
|
||||
this.requestCustomizers = requestCustomizers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(this.requestFactorySettings, detectRequestFactory, this.rootUri,
|
||||
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
|
||||
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
|
||||
this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a root URL that should be applied to each request that starts with {@code '/'}.
|
||||
* The root URL will only apply when {@code String} variants of the
|
||||
* {@link RestTemplate} methods are used for specifying the request URL.
|
||||
* @param rootUri the root URI or {@code null}
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public RestTemplateBuilder rootUri(String rootUri) {
|
||||
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, rootUri,
|
||||
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
|
||||
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
|
||||
this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link HttpMessageConverter HttpMessageConverters} that should be used with
|
||||
* the {@link RestTemplate}. Setting this value will replace any previously configured
|
||||
* converters and any converters configured on the builder will replace RestTemplate's
|
||||
* default 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 and any converters configured on the builder will replace RestTemplate's
|
||||
* default 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.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
copiedSetOf(messageConverters), this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
|
||||
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
|
||||
this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional {@link HttpMessageConverter HttpMessageConverters} that should be
|
||||
* used with the {@link RestTemplate}. Any converters configured on the builder will
|
||||
* replace RestTemplate's default converters.
|
||||
* @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}. Any converters configured on the builder will
|
||||
* replace RestTemplate's default converters.
|
||||
* @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.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
append(this.messageConverters, messageConverters), this.interceptors, this.requestFactoryBuilder,
|
||||
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
|
||||
this.customizers, this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
copiedSetOf(new RestTemplate().getMessageConverters()), this.interceptors, this.requestFactoryBuilder,
|
||||
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
|
||||
this.customizers, this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
this.messageConverters, copiedSetOf(interceptors), this.requestFactoryBuilder, this.uriTemplateHandler,
|
||||
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
|
||||
this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
this.messageConverters, append(this.interceptors, interceptors), this.requestFactoryBuilder,
|
||||
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
|
||||
this.customizers, this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ClientHttpRequestFactory} class that should be used with the
|
||||
* {@link RestTemplate}.
|
||||
* @param requestFactoryType the request factory type to use
|
||||
* @return a new builder instance
|
||||
* @see ClientHttpRequestFactoryBuilder#of(Class)
|
||||
* @see #requestFactoryBuilder(ClientHttpRequestFactoryBuilder)
|
||||
*/
|
||||
public RestTemplateBuilder requestFactory(Class<? extends ClientHttpRequestFactory> requestFactoryType) {
|
||||
Assert.notNull(requestFactoryType, "'requestFactoryType' must not be null");
|
||||
return requestFactoryBuilder(ClientHttpRequestFactoryBuilder.of(requestFactoryType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@code Supplier} of {@link ClientHttpRequestFactory} that should be called
|
||||
* each time we {@link #build()} a new {@link RestTemplate} instance.
|
||||
* @param requestFactorySupplier the supplier for the request factory
|
||||
* @return a new builder instance
|
||||
* @since 2.0.0
|
||||
* @see ClientHttpRequestFactoryBuilder#of(Supplier)
|
||||
* @see #requestFactoryBuilder(ClientHttpRequestFactoryBuilder)
|
||||
*/
|
||||
public RestTemplateBuilder requestFactory(Supplier<ClientHttpRequestFactory> requestFactorySupplier) {
|
||||
Assert.notNull(requestFactorySupplier, "'requestFactorySupplier' must not be null");
|
||||
return requestFactoryBuilder(ClientHttpRequestFactoryBuilder.of(requestFactorySupplier));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ClientHttpRequestFactoryBuilder} that should be used each time we
|
||||
* {@link #build()} a new {@link RestTemplate} instance.
|
||||
* @param requestFactoryBuilder the {@link ClientHttpRequestFactoryBuilder} to use
|
||||
* @return a new builder instance
|
||||
* @since 3.4.0
|
||||
* @see ClientHttpRequestFactoryBuilder
|
||||
*/
|
||||
public RestTemplateBuilder requestFactoryBuilder(ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder) {
|
||||
Assert.notNull(requestFactoryBuilder, "'requestFactoryBuilder' must not be null");
|
||||
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
this.messageConverters, this.interceptors, requestFactoryBuilder, this.uriTemplateHandler,
|
||||
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
|
||||
this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
this.messageConverters, this.interceptors, this.requestFactoryBuilder, uriTemplateHandler,
|
||||
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
|
||||
this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
|
||||
errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers, this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add HTTP Basic Authentication to requests with the given username/password pair,
|
||||
* unless a custom Authorization header has been set before.
|
||||
* @param username the user name
|
||||
* @param password the password
|
||||
* @return a new builder instance
|
||||
* @since 2.1.0
|
||||
* @see #basicAuthentication(String, String, Charset)
|
||||
*/
|
||||
public RestTemplateBuilder basicAuthentication(String username, String password) {
|
||||
return basicAuthentication(username, password, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add HTTP Basic Authentication to requests with the given username/password pair,
|
||||
* unless a custom Authorization header has been set before.
|
||||
* @param username the user name
|
||||
* @param password the password
|
||||
* @param charset the charset to use
|
||||
* @return a new builder instance
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public RestTemplateBuilder basicAuthentication(String username, String password, Charset charset) {
|
||||
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
|
||||
this.errorHandler, new BasicAuthentication(username, password, charset), this.defaultHeaders,
|
||||
this.customizers, this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a default header that will be set if not already present on the outgoing
|
||||
* {@link ClientHttpRequest}.
|
||||
* @param name the name of the header
|
||||
* @param values the header values
|
||||
* @return a new builder instance
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public RestTemplateBuilder defaultHeader(String name, String... values) {
|
||||
Assert.notNull(name, "'name' must not be null");
|
||||
Assert.notNull(values, "'values' must not be null");
|
||||
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
|
||||
this.errorHandler, this.basicAuthentication, append(this.defaultHeaders, name, values),
|
||||
this.customizers, this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ClientHttpRequestFactorySettings}. This will replace any previously
|
||||
* set {@link #connectTimeout(Duration) connectTimeout}, {@link #readTimeout(Duration)
|
||||
* readTimeout} and {@link #sslBundle(SslBundle) sslBundle} values.
|
||||
* @param requestFactorySettings the request factory settings
|
||||
* @return a new builder instance
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public RestTemplateBuilder requestFactorySettings(ClientHttpRequestFactorySettings requestFactorySettings) {
|
||||
Assert.notNull(requestFactorySettings, "'requestFactorySettings' must not be null");
|
||||
return new RestTemplateBuilder(requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
|
||||
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
|
||||
this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the {@link ClientHttpRequestFactorySettings} using the given customizer.
|
||||
* @param requestFactorySettingsCustomizer a {@link UnaryOperator} to update request
|
||||
* factory settings
|
||||
* @return a new builder instance
|
||||
* @since 3.4.1
|
||||
*/
|
||||
public RestTemplateBuilder requestFactorySettings(
|
||||
UnaryOperator<ClientHttpRequestFactorySettings> requestFactorySettingsCustomizer) {
|
||||
Assert.notNull(requestFactorySettingsCustomizer, "'requestFactorySettingsCustomizer' must not be null");
|
||||
return new RestTemplateBuilder(requestFactorySettingsCustomizer.apply(this.requestFactorySettings),
|
||||
this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors,
|
||||
this.requestFactoryBuilder, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication,
|
||||
this.defaultHeaders, this.customizers, this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection timeout on the underlying {@link ClientHttpRequestFactory}.
|
||||
* @param connectTimeout the connection timeout
|
||||
* @return a new builder instance.
|
||||
* @since 2.1.0
|
||||
* @deprecated since 3.4.0 for removal in 4.0.0 in favor of
|
||||
* {@link #connectTimeout(Duration)}
|
||||
*/
|
||||
@Deprecated(since = "3.4.0", forRemoval = true)
|
||||
public RestTemplateBuilder setConnectTimeout(Duration connectTimeout) {
|
||||
return connectTimeout(connectTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection timeout on the underlying {@link ClientHttpRequestFactory}.
|
||||
* @param connectTimeout the connection timeout
|
||||
* @return a new builder instance.
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public RestTemplateBuilder connectTimeout(Duration connectTimeout) {
|
||||
return new RestTemplateBuilder(this.requestFactorySettings.withConnectTimeout(connectTimeout),
|
||||
this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors,
|
||||
this.requestFactoryBuilder, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication,
|
||||
this.defaultHeaders, this.customizers, this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the read timeout on the underlying {@link ClientHttpRequestFactory}.
|
||||
* @param readTimeout the read timeout
|
||||
* @return a new builder instance.
|
||||
* @since 2.1.0
|
||||
* @deprecated since 3.4.0 for removal in 4.0.0 in favor of
|
||||
* {@link #readTimeout(Duration)}
|
||||
*/
|
||||
@Deprecated(since = "3.4.0", forRemoval = true)
|
||||
public RestTemplateBuilder setReadTimeout(Duration readTimeout) {
|
||||
return readTimeout(readTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the read timeout on the underlying {@link ClientHttpRequestFactory}.
|
||||
* @param readTimeout the read timeout
|
||||
* @return a new builder instance.
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public RestTemplateBuilder readTimeout(Duration readTimeout) {
|
||||
return new RestTemplateBuilder(this.requestFactorySettings.withReadTimeout(readTimeout),
|
||||
this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors,
|
||||
this.requestFactoryBuilder, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication,
|
||||
this.defaultHeaders, this.customizers, this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the redirect strategy on the underlying {@link ClientHttpRequestFactory}.
|
||||
* @param redirects the redirect strategy
|
||||
* @return a new builder instance.
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public RestTemplateBuilder redirects(HttpRedirects redirects) {
|
||||
return new RestTemplateBuilder(this.requestFactorySettings.withRedirects(redirects), this.detectRequestFactory,
|
||||
this.rootUri, this.messageConverters, this.interceptors, this.requestFactoryBuilder,
|
||||
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
|
||||
this.customizers, this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SSL bundle on the underlying {@link ClientHttpRequestFactory}.
|
||||
* @param sslBundle the SSL bundle
|
||||
* @return a new builder instance
|
||||
* @since 3.1.0
|
||||
* @deprecated since 3.4.0 for removal in 4.0.0 in favor of
|
||||
* {@link #sslBundle(SslBundle)}
|
||||
*/
|
||||
@Deprecated(since = "3.4.0", forRemoval = true)
|
||||
public RestTemplateBuilder setSslBundle(SslBundle sslBundle) {
|
||||
return sslBundle(sslBundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SSL bundle on the underlying {@link ClientHttpRequestFactory}.
|
||||
* @param sslBundle the SSL bundle
|
||||
* @return a new builder instance
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public RestTemplateBuilder sslBundle(SslBundle sslBundle) {
|
||||
return new RestTemplateBuilder(this.requestFactorySettings.withSslBundle(sslBundle), this.detectRequestFactory,
|
||||
this.rootUri, this.messageConverters, this.interceptors, this.requestFactoryBuilder,
|
||||
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
|
||||
this.customizers, this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 customizers the customizers to set
|
||||
* @return a new builder instance
|
||||
* @see #additionalCustomizers(RestTemplateCustomizer...)
|
||||
*/
|
||||
public RestTemplateBuilder customizers(RestTemplateCustomizer... customizers) {
|
||||
Assert.notNull(customizers, "'customizers' must not be null");
|
||||
return customizers(Arrays.asList(customizers));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 customizers the customizers to set
|
||||
* @return a new builder instance
|
||||
* @see #additionalCustomizers(RestTemplateCustomizer...)
|
||||
*/
|
||||
public RestTemplateBuilder customizers(Collection<? extends RestTemplateCustomizer> customizers) {
|
||||
Assert.notNull(customizers, "'customizers' must not be null");
|
||||
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
|
||||
this.errorHandler, this.basicAuthentication, this.defaultHeaders, copiedSetOf(customizers),
|
||||
this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(RestTemplateCustomizer... customizers) {
|
||||
Assert.notNull(customizers, "'customizers' must not be null");
|
||||
return additionalCustomizers(Arrays.asList(customizers));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, "'customizers' must not be null");
|
||||
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
|
||||
this.errorHandler, this.basicAuthentication, this.defaultHeaders, append(this.customizers, customizers),
|
||||
this.requestCustomizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link RestTemplateRequestCustomizer RestTemplateRequestCustomizers} that
|
||||
* should be applied to the {@link ClientHttpRequest}. Customizers are applied in the
|
||||
* order that they were added. Setting this value will replace any previously
|
||||
* configured request customizers.
|
||||
* @param requestCustomizers the request customizers to set
|
||||
* @return a new builder instance
|
||||
* @since 2.2.0
|
||||
* @see #additionalRequestCustomizers(RestTemplateRequestCustomizer...)
|
||||
*/
|
||||
public RestTemplateBuilder requestCustomizers(RestTemplateRequestCustomizer<?>... requestCustomizers) {
|
||||
Assert.notNull(requestCustomizers, "'requestCustomizers' must not be null");
|
||||
return requestCustomizers(Arrays.asList(requestCustomizers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link RestTemplateRequestCustomizer RestTemplateRequestCustomizers} that
|
||||
* should be applied to the {@link ClientHttpRequest}. Customizers are applied in the
|
||||
* order that they were added. Setting this value will replace any previously
|
||||
* configured request customizers.
|
||||
* @param requestCustomizers the request customizers to set
|
||||
* @return a new builder instance
|
||||
* @since 2.2.0
|
||||
* @see #additionalRequestCustomizers(RestTemplateRequestCustomizer...)
|
||||
*/
|
||||
public RestTemplateBuilder requestCustomizers(
|
||||
Collection<? extends RestTemplateRequestCustomizer<?>> requestCustomizers) {
|
||||
Assert.notNull(requestCustomizers, "'requestCustomizers' must not be null");
|
||||
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
|
||||
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
|
||||
copiedSetOf(requestCustomizers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the {@link RestTemplateRequestCustomizer RestTemplateRequestCustomizers} that
|
||||
* should be applied to the {@link ClientHttpRequest}. Customizers are applied in the
|
||||
* order that they were added.
|
||||
* @param requestCustomizers the request customizers to add
|
||||
* @return a new builder instance
|
||||
* @since 2.2.0
|
||||
* @see #requestCustomizers(RestTemplateRequestCustomizer...)
|
||||
*/
|
||||
public RestTemplateBuilder additionalRequestCustomizers(RestTemplateRequestCustomizer<?>... requestCustomizers) {
|
||||
Assert.notNull(requestCustomizers, "'requestCustomizers' must not be null");
|
||||
return additionalRequestCustomizers(Arrays.asList(requestCustomizers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the {@link RestTemplateRequestCustomizer RestTemplateRequestCustomizers} that
|
||||
* should be applied to the {@link ClientHttpRequest}. Customizers are applied in the
|
||||
* order that they were added.
|
||||
* @param requestCustomizers the request customizers to add
|
||||
* @return a new builder instance
|
||||
* @since 2.2.0
|
||||
* @see #requestCustomizers(Collection)
|
||||
*/
|
||||
public RestTemplateBuilder additionalRequestCustomizers(
|
||||
Collection<? extends RestTemplateRequestCustomizer<?>> requestCustomizers) {
|
||||
Assert.notNull(requestCustomizers, "'requestCustomizers' must not be null");
|
||||
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
|
||||
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
|
||||
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
|
||||
append(this.requestCustomizers, requestCustomizers));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 configure(new RestTemplate());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
ClientHttpRequestFactory requestFactory = buildRequestFactory();
|
||||
if (requestFactory != null) {
|
||||
restTemplate.setRequestFactory(requestFactory);
|
||||
}
|
||||
addClientHttpRequestInitializer(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) {
|
||||
RootUriBuilderFactory.applyTo(restTemplate, this.rootUri);
|
||||
}
|
||||
restTemplate.getInterceptors().addAll(this.interceptors);
|
||||
if (!CollectionUtils.isEmpty(this.customizers)) {
|
||||
for (RestTemplateCustomizer customizer : this.customizers) {
|
||||
customizer.customize(restTemplate);
|
||||
}
|
||||
}
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new {@link ClientHttpRequestFactory} instance using the settings of this
|
||||
* builder.
|
||||
* @return a {@link ClientHttpRequestFactory} or {@code null}
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public ClientHttpRequestFactory buildRequestFactory() {
|
||||
ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder = requestFactoryBuilder();
|
||||
return (requestFactoryBuilder != null) ? requestFactoryBuilder.build(this.requestFactorySettings) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link ClientHttpRequestFactoryBuilder} instance using the settings of
|
||||
* this builder.
|
||||
* @return a {@link ClientHttpRequestFactoryBuilder} or {@code null}
|
||||
* @since 3.5.0
|
||||
*/
|
||||
public ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder() {
|
||||
if (this.requestFactoryBuilder != null) {
|
||||
return this.requestFactoryBuilder;
|
||||
}
|
||||
if (this.detectRequestFactory) {
|
||||
return ClientHttpRequestFactoryBuilder.detect();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addClientHttpRequestInitializer(RestTemplate restTemplate) {
|
||||
if (this.basicAuthentication == null && this.defaultHeaders.isEmpty() && this.requestCustomizers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
restTemplate.getClientHttpRequestInitializers()
|
||||
.add(new RestTemplateBuilderClientHttpRequestInitializer(this.basicAuthentication, this.defaultHeaders,
|
||||
this.requestCustomizers));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Set<T> copiedSetOf(T... items) {
|
||||
return copiedSetOf(Arrays.asList(items));
|
||||
}
|
||||
|
||||
private <T> Set<T> copiedSetOf(Collection<? extends T> collection) {
|
||||
return Collections.unmodifiableSet(new LinkedHashSet<>(collection));
|
||||
}
|
||||
|
||||
private static <T> List<T> copiedListOf(T[] items) {
|
||||
return Collections.unmodifiableList(Arrays.asList(Arrays.copyOf(items, items.length)));
|
||||
}
|
||||
|
||||
private static <T> Set<T> append(Collection<? extends T> collection, Collection<? extends T> additions) {
|
||||
Set<T> result = new LinkedHashSet<>((collection != null) ? collection : Collections.emptySet());
|
||||
if (additions != null) {
|
||||
result.addAll(additions);
|
||||
}
|
||||
return Collections.unmodifiableSet(result);
|
||||
}
|
||||
|
||||
private static <K, V> Map<K, List<V>> append(Map<K, List<V>> map, K key, V[] values) {
|
||||
Map<K, List<V>> result = new LinkedHashMap<>((map != null) ? map : Collections.emptyMap());
|
||||
if (values != null) {
|
||||
result.put(key, copiedListOf(values));
|
||||
}
|
||||
return Collections.unmodifiableMap(result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.util.LambdaSafe;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestInitializer;
|
||||
|
||||
/**
|
||||
* {@link ClientHttpRequestInitializer} to apply customizations from the
|
||||
* {@link RestTemplateBuilder}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
* @author Ilya Lukyanovich
|
||||
*/
|
||||
class RestTemplateBuilderClientHttpRequestInitializer implements ClientHttpRequestInitializer {
|
||||
|
||||
private final BasicAuthentication basicAuthentication;
|
||||
|
||||
private final Map<String, List<String>> defaultHeaders;
|
||||
|
||||
private final Set<RestTemplateRequestCustomizer<?>> requestCustomizers;
|
||||
|
||||
RestTemplateBuilderClientHttpRequestInitializer(BasicAuthentication basicAuthentication,
|
||||
Map<String, List<String>> defaultHeaders, Set<RestTemplateRequestCustomizer<?>> requestCustomizers) {
|
||||
this.basicAuthentication = basicAuthentication;
|
||||
this.defaultHeaders = defaultHeaders;
|
||||
this.requestCustomizers = requestCustomizers;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void initialize(ClientHttpRequest request) {
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
if (this.basicAuthentication != null) {
|
||||
this.basicAuthentication.applyTo(headers);
|
||||
}
|
||||
this.defaultHeaders.forEach(headers::putIfAbsent);
|
||||
LambdaSafe.callbacks(RestTemplateRequestCustomizer.class, this.requestCustomizers, request)
|
||||
.invoke((customizer) -> customizer.customize(request));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient;
|
||||
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Callback interface that can be used to customize a {@link RestTemplate}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
* @see RestTemplateBuilder
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RestTemplateCustomizer {
|
||||
|
||||
/**
|
||||
* Callback to customize a {@link RestTemplate} instance.
|
||||
* @param restTemplate the template to customize
|
||||
*/
|
||||
void customize(RestTemplate restTemplate);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient;
|
||||
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestInitializer;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Callback interface that can be used to customize the {@link ClientHttpRequest} sent
|
||||
* from a {@link RestTemplate}.
|
||||
*
|
||||
* @param <T> the {@link ClientHttpRequest} type
|
||||
* @author Ilya Lukyanovich
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
* @see RestTemplateBuilder
|
||||
* @see ClientHttpRequestInitializer
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RestTemplateRequestCustomizer<T extends ClientHttpRequest> {
|
||||
|
||||
/**
|
||||
* Customize the specified {@link ClientHttpRequest}.
|
||||
* @param request the request to customize
|
||||
*/
|
||||
void customize(T request);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriBuilder;
|
||||
import org.springframework.web.util.UriBuilderFactory;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriTemplateHandler;
|
||||
|
||||
/**
|
||||
* {@link UriBuilderFactory} to set the root for URI that starts with {@code '/'}.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class RootUriBuilderFactory extends RootUriTemplateHandler implements UriBuilderFactory {
|
||||
|
||||
RootUriBuilderFactory(String rootUri, UriTemplateHandler delegate) {
|
||||
super(rootUri, delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriBuilder uriString(String uriTemplate) {
|
||||
return UriComponentsBuilder.fromUriString(apply(uriTemplate));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriBuilder builder() {
|
||||
return UriComponentsBuilder.newInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a {@link RootUriBuilderFactory} instance to the given {@link RestTemplate}.
|
||||
* @param restTemplate the {@link RestTemplate} to add the builder factory to
|
||||
* @param rootUri the root URI
|
||||
*/
|
||||
static void applyTo(RestTemplate restTemplate, String rootUri) {
|
||||
Assert.notNull(restTemplate, "'restTemplate' must not be null");
|
||||
RootUriBuilderFactory handler = new RootUriBuilderFactory(rootUri, restTemplate.getUriTemplateHandler());
|
||||
restTemplate.setUriTemplateHandler(handler);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriTemplateHandler;
|
||||
|
||||
/**
|
||||
* {@link UriTemplateHandler} to set the root for URI that starts with {@code '/'}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class RootUriTemplateHandler implements UriTemplateHandler {
|
||||
|
||||
private final String rootUri;
|
||||
|
||||
private final UriTemplateHandler handler;
|
||||
|
||||
protected RootUriTemplateHandler(UriTemplateHandler handler) {
|
||||
Assert.notNull(handler, "'handler' must not be null");
|
||||
this.rootUri = null;
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
String apply(String uriTemplate) {
|
||||
if (StringUtils.startsWithIgnoreCase(uriTemplate, "/")) {
|
||||
return getRootUri() + uriTemplate;
|
||||
}
|
||||
return uriTemplate;
|
||||
}
|
||||
|
||||
public String getRootUri() {
|
||||
return this.rootUri;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.actuate.observation;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.boot.restclient.RestClientCustomizer;
|
||||
import org.springframework.http.client.observation.ClientRequestObservationConvention;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.RestClient.Builder;
|
||||
|
||||
/**
|
||||
* {@link RestClientCustomizer} that configures the {@link Builder RestClient builder} to
|
||||
* record request observations.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class ObservationRestClientCustomizer implements RestClientCustomizer {
|
||||
|
||||
private final ObservationRegistry observationRegistry;
|
||||
|
||||
private final ClientRequestObservationConvention observationConvention;
|
||||
|
||||
/**
|
||||
* Create a new {@link ObservationRestClientCustomizer}.
|
||||
* @param observationRegistry the observation registry
|
||||
* @param observationConvention the observation convention
|
||||
*/
|
||||
public ObservationRestClientCustomizer(ObservationRegistry observationRegistry,
|
||||
ClientRequestObservationConvention observationConvention) {
|
||||
Assert.notNull(observationConvention, "'observationConvention' must not be null");
|
||||
Assert.notNull(observationRegistry, "'observationRegistry' must not be null");
|
||||
this.observationRegistry = observationRegistry;
|
||||
this.observationConvention = observationConvention;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(Builder restClientBuilder) {
|
||||
restClientBuilder.observationRegistry(this.observationRegistry);
|
||||
restClientBuilder.observationConvention(this.observationConvention);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.actuate.observation;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.boot.restclient.RestTemplateCustomizer;
|
||||
import org.springframework.http.client.observation.ClientRequestObservationConvention;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* {@link RestTemplateCustomizer} that configures the {@link RestTemplate} to record
|
||||
* request observations.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class ObservationRestTemplateCustomizer implements RestTemplateCustomizer {
|
||||
|
||||
private final ObservationRegistry observationRegistry;
|
||||
|
||||
private final ClientRequestObservationConvention observationConvention;
|
||||
|
||||
/**
|
||||
* Create a new {@code ObservationRestTemplateCustomizer}.
|
||||
* @param observationConvention the observation convention
|
||||
* @param observationRegistry the observation registry
|
||||
*/
|
||||
public ObservationRestTemplateCustomizer(ObservationRegistry observationRegistry,
|
||||
ClientRequestObservationConvention observationConvention) {
|
||||
this.observationConvention = observationConvention;
|
||||
this.observationRegistry = observationRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(RestTemplate restTemplate) {
|
||||
restTemplate.setObservationConvention(this.observationConvention);
|
||||
restTemplate.setObservationRegistry(this.observationRegistry);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Observation integration for RestClient and RestTemplate.
|
||||
*/
|
||||
package org.springframework.boot.restclient.actuate.observation;
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* An auto-configured {@link RestClientSsl} implementation.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dmytro Nosan
|
||||
*/
|
||||
class AutoConfiguredRestClientSsl implements RestClientSsl {
|
||||
|
||||
private final ClientHttpRequestFactoryBuilder<?> builder;
|
||||
|
||||
private final ClientHttpRequestFactorySettings settings;
|
||||
|
||||
private final SslBundles sslBundles;
|
||||
|
||||
AutoConfiguredRestClientSsl(ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder,
|
||||
ClientHttpRequestFactorySettings clientHttpRequestFactorySettings, SslBundles sslBundles) {
|
||||
this.builder = clientHttpRequestFactoryBuilder;
|
||||
this.settings = clientHttpRequestFactorySettings;
|
||||
this.sslBundles = sslBundles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<RestClient.Builder> fromBundle(String bundleName) {
|
||||
return fromBundle(this.sslBundles.getBundle(bundleName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<RestClient.Builder> fromBundle(SslBundle bundle) {
|
||||
return (builder) -> builder.requestFactory(requestFactory(bundle));
|
||||
}
|
||||
|
||||
private ClientHttpRequestFactory requestFactory(SslBundle bundle) {
|
||||
return this.builder.build(this.settings.withSslBundle(bundle));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters;
|
||||
import org.springframework.boot.restclient.RestClientCustomizer;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* {@link RestClientCustomizer} to apply {@link HttpMessageConverter
|
||||
* HttpMessageConverters}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class HttpMessageConvertersRestClientCustomizer implements RestClientCustomizer {
|
||||
|
||||
private final Iterable<? extends HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
public HttpMessageConvertersRestClientCustomizer(HttpMessageConverter<?>... messageConverters) {
|
||||
Assert.notNull(messageConverters, "'messageConverters' must not be null");
|
||||
this.messageConverters = Arrays.asList(messageConverters);
|
||||
}
|
||||
|
||||
HttpMessageConvertersRestClientCustomizer(HttpMessageConverters messageConverters) {
|
||||
this.messageConverters = messageConverters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(RestClient.Builder restClientBuilder) {
|
||||
restClientBuilder.messageConverters(this::configureMessageConverters);
|
||||
}
|
||||
|
||||
private void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
|
||||
if (this.messageConverters != null) {
|
||||
messageConverters.clear();
|
||||
this.messageConverters.forEach(messageConverters::add);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
|
||||
/**
|
||||
* {@link SpringBootCondition} that applies only when running in a non-reactive web
|
||||
* application.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class NotReactiveWebApplicationCondition extends NoneNestedConditions {
|
||||
|
||||
NotReactiveWebApplicationCondition() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||
private static final class ReactiveWebApplication {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.thread.Threading;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
|
||||
/**
|
||||
* {@link SpringBootCondition} that applies when running in a non-reactive web application
|
||||
* or virtual threads are enabled.
|
||||
*
|
||||
* @author Dmitry Sulman
|
||||
*/
|
||||
class NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition extends AnyNestedCondition {
|
||||
|
||||
NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@Conditional(NotReactiveWebApplicationCondition.class)
|
||||
private static final class NotReactiveWebApplication {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnThreading(Threading.VIRTUAL)
|
||||
@ConditionalOnBean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
|
||||
private static final class VirtualThreadsExecutorEnabled {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration;
|
||||
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters;
|
||||
import org.springframework.boot.restclient.RestClientCustomizer;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestClient.Builder;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for {@link RestClient}.
|
||||
* <p>
|
||||
* This will produce a {@link Builder RestClient.Builder} bean with the {@code prototype}
|
||||
* scope, meaning each injection point will receive a newly cloned instance of the
|
||||
* builder.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Moritz Halbritter
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@AutoConfiguration(
|
||||
after = { HttpClientAutoConfiguration.class, TaskExecutionAutoConfiguration.class, SslAutoConfiguration.class })
|
||||
@ConditionalOnClass(RestClient.class)
|
||||
@Conditional(NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition.class)
|
||||
public class RestClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(RestClientSsl.class)
|
||||
@ConditionalOnBean(SslBundles.class)
|
||||
AutoConfiguredRestClientSsl restClientSsl(
|
||||
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientHttpRequestFactoryBuilder,
|
||||
ObjectProvider<ClientHttpRequestFactorySettings> clientHttpRequestFactorySettings, SslBundles sslBundles) {
|
||||
return new AutoConfiguredRestClientSsl(
|
||||
clientHttpRequestFactoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect),
|
||||
clientHttpRequestFactorySettings.getIfAvailable(ClientHttpRequestFactorySettings::defaults),
|
||||
sslBundles);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
RestClientBuilderConfigurer restClientBuilderConfigurer(
|
||||
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientHttpRequestFactoryBuilder,
|
||||
ObjectProvider<ClientHttpRequestFactorySettings> clientHttpRequestFactorySettings,
|
||||
ObjectProvider<RestClientCustomizer> customizerProvider) {
|
||||
return new RestClientBuilderConfigurer(
|
||||
clientHttpRequestFactoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect),
|
||||
clientHttpRequestFactorySettings.getIfAvailable(ClientHttpRequestFactorySettings::defaults),
|
||||
customizerProvider.orderedStream().toList());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||
@ConditionalOnMissingBean
|
||||
RestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) {
|
||||
return restClientBuilderConfigurer.configure(RestClient.builder());
|
||||
}
|
||||
|
||||
@ConditionalOnClass(HttpMessageConverters.class)
|
||||
static class HttpMessageConvertersConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
HttpMessageConvertersRestClientCustomizer httpMessageConvertersRestClientCustomizer(
|
||||
ObjectProvider<HttpMessageConverters> messageConverters) {
|
||||
return new HttpMessageConvertersRestClientCustomizer(messageConverters.getIfUnique());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.restclient.RestClientCustomizer;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestClient.Builder;
|
||||
|
||||
/**
|
||||
* Configure {@link Builder RestClient.Builder} with sensible defaults.
|
||||
* <p>
|
||||
* Can be injected into application code and used to define a custom
|
||||
* {@code RestClient.Builder} whose configuration is based upon that produced by
|
||||
* auto-configuration.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class RestClientBuilderConfigurer {
|
||||
|
||||
private final ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder;
|
||||
|
||||
private final ClientHttpRequestFactorySettings requestFactorySettings;
|
||||
|
||||
private final List<RestClientCustomizer> customizers;
|
||||
|
||||
public RestClientBuilderConfigurer() {
|
||||
this(ClientHttpRequestFactoryBuilder.detect(), ClientHttpRequestFactorySettings.defaults(),
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
RestClientBuilderConfigurer(ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder,
|
||||
ClientHttpRequestFactorySettings requestFactorySettings, List<RestClientCustomizer> customizers) {
|
||||
this.requestFactoryBuilder = requestFactoryBuilder;
|
||||
this.requestFactorySettings = requestFactorySettings;
|
||||
this.customizers = customizers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the specified {@link Builder RestClient.Builder}. The builder can be
|
||||
* further tuned and default settings can be overridden.
|
||||
* @param builder the {@link Builder RestClient.Builder} instance to configure
|
||||
* @return the configured builder
|
||||
*/
|
||||
public RestClient.Builder configure(RestClient.Builder builder) {
|
||||
builder.requestFactory(this.requestFactoryBuilder.build(this.requestFactorySettings));
|
||||
applyCustomizers(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private void applyCustomizers(Builder builder) {
|
||||
for (RestClientCustomizer customizer : this.customizers) {
|
||||
customizer.customize(builder);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.ssl.NoSuchSslBundleException;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* Interface that can be used to {@link RestClient.Builder#apply apply} SSL configuration
|
||||
* to a {@link org.springframework.web.client.RestClient.Builder RestClient.Builder}.
|
||||
* <p>
|
||||
* Typically used as follows: <pre class="code">
|
||||
* @Bean
|
||||
* public MyBean myBean(RestClient.Builder restClientBuilder, RestClientSsl ssl) {
|
||||
* RestClient restClient = restClientBuilder.apply(ssl.fromBundle("mybundle")).build();
|
||||
* return new MyBean(restClient);
|
||||
* }
|
||||
* </pre> NOTE: Applying SSL configuration will replace any previously
|
||||
* {@link RestClient.Builder#requestFactory configured} {@link ClientHttpRequestFactory}.
|
||||
* The replacement {@link ClientHttpRequestFactory} will apply only configured
|
||||
* {@link ClientHttpRequestFactorySettings} and the appropriate {@link SslBundle}.
|
||||
* <p>
|
||||
* If you need to configure {@link ClientHttpRequestFactory} with more than just SSL
|
||||
* consider using a {@link ClientHttpRequestFactoryBuilder}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public interface RestClientSsl {
|
||||
|
||||
/**
|
||||
* Return a {@link Consumer} that will apply SSL configuration for the named
|
||||
* {@link SslBundle} to a {@link org.springframework.web.client.RestClient.Builder
|
||||
* RestClient.Builder}.
|
||||
* @param bundleName the name of the SSL bundle to apply
|
||||
* @return a {@link Consumer} to apply the configuration
|
||||
* @throws NoSuchSslBundleException if a bundle with the provided name does not exist
|
||||
*/
|
||||
Consumer<RestClient.Builder> fromBundle(String bundleName) throws NoSuchSslBundleException;
|
||||
|
||||
/**
|
||||
* Return a {@link Consumer} that will apply SSL configuration for the
|
||||
* {@link SslBundle} to a {@link org.springframework.web.client.RestClient.Builder
|
||||
* RestClient.Builder}.
|
||||
* @param bundle the SSL bundle to apply
|
||||
* @return a {@link Consumer} to apply the configuration
|
||||
*/
|
||||
Consumer<RestClient.Builder> fromBundle(SslBundle bundle);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration;
|
||||
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters;
|
||||
import org.springframework.boot.restclient.RestTemplateBuilder;
|
||||
import org.springframework.boot.restclient.RestTemplateCustomizer;
|
||||
import org.springframework.boot.restclient.RestTemplateRequestCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for {@link RestTemplate} (via
|
||||
* {@link RestTemplateBuilder}).
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@AutoConfiguration(after = HttpClientAutoConfiguration.class)
|
||||
@ConditionalOnClass({ RestTemplate.class, HttpMessageConverters.class })
|
||||
@Conditional(NotReactiveWebApplicationCondition.class)
|
||||
public class RestTemplateAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public RestTemplateBuilderConfigurer restTemplateBuilderConfigurer(
|
||||
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientHttpRequestFactoryBuilder,
|
||||
ObjectProvider<ClientHttpRequestFactorySettings> clientHttpRequestFactorySettings,
|
||||
ObjectProvider<HttpMessageConverters> messageConverters,
|
||||
ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers,
|
||||
ObjectProvider<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
|
||||
RestTemplateBuilderConfigurer configurer = new RestTemplateBuilderConfigurer();
|
||||
configurer.setRequestFactoryBuilder(clientHttpRequestFactoryBuilder.getIfAvailable());
|
||||
configurer.setRequestFactorySettings(clientHttpRequestFactorySettings.getIfAvailable());
|
||||
configurer.setHttpMessageConverters(messageConverters.getIfUnique());
|
||||
configurer.setRestTemplateCustomizers(restTemplateCustomizers.orderedStream().toList());
|
||||
configurer.setRestTemplateRequestCustomizers(restTemplateRequestCustomizers.orderedStream().toList());
|
||||
return configurer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
|
||||
return restTemplateBuilderConfigurer.configure(new RestTemplateBuilder());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters;
|
||||
import org.springframework.boot.restclient.RestTemplateBuilder;
|
||||
import org.springframework.boot.restclient.RestTemplateCustomizer;
|
||||
import org.springframework.boot.restclient.RestTemplateRequestCustomizer;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Configure {@link RestTemplateBuilder} with sensible defaults.
|
||||
* <p>
|
||||
* Can be injected into application code and used to define a custom
|
||||
* {@code RestTemplateBuilder} whose configuration is based upon that produced by
|
||||
* auto-configuration.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public final class RestTemplateBuilderConfigurer {
|
||||
|
||||
private ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder;
|
||||
|
||||
private ClientHttpRequestFactorySettings requestFactorySettings;
|
||||
|
||||
private HttpMessageConverters httpMessageConverters;
|
||||
|
||||
private List<RestTemplateCustomizer> restTemplateCustomizers;
|
||||
|
||||
private List<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers;
|
||||
|
||||
void setRequestFactoryBuilder(ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder) {
|
||||
this.requestFactoryBuilder = requestFactoryBuilder;
|
||||
}
|
||||
|
||||
void setRequestFactorySettings(ClientHttpRequestFactorySettings requestFactorySettings) {
|
||||
this.requestFactorySettings = requestFactorySettings;
|
||||
}
|
||||
|
||||
void setHttpMessageConverters(HttpMessageConverters httpMessageConverters) {
|
||||
this.httpMessageConverters = httpMessageConverters;
|
||||
}
|
||||
|
||||
void setRestTemplateCustomizers(List<RestTemplateCustomizer> restTemplateCustomizers) {
|
||||
this.restTemplateCustomizers = restTemplateCustomizers;
|
||||
}
|
||||
|
||||
void setRestTemplateRequestCustomizers(List<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
|
||||
this.restTemplateRequestCustomizers = restTemplateRequestCustomizers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the specified {@link RestTemplateBuilder}. The builder can be further
|
||||
* tuned and default settings can be overridden.
|
||||
* @param builder the {@link RestTemplateBuilder} instance to configure
|
||||
* @return the configured builder
|
||||
*/
|
||||
public RestTemplateBuilder configure(RestTemplateBuilder builder) {
|
||||
if (this.requestFactoryBuilder != null) {
|
||||
builder = builder.requestFactoryBuilder(this.requestFactoryBuilder);
|
||||
}
|
||||
if (this.requestFactorySettings != null) {
|
||||
builder = builder.requestFactorySettings(this.requestFactorySettings);
|
||||
}
|
||||
if (this.httpMessageConverters != null) {
|
||||
builder = builder.messageConverters(this.httpMessageConverters.getConverters());
|
||||
}
|
||||
builder = addCustomizers(builder, this.restTemplateCustomizers, RestTemplateBuilder::customizers);
|
||||
builder = addCustomizers(builder, this.restTemplateRequestCustomizers, RestTemplateBuilder::requestCustomizers);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private <T> RestTemplateBuilder addCustomizers(RestTemplateBuilder builder, List<T> customizers,
|
||||
BiFunction<RestTemplateBuilder, Collection<T>, RestTemplateBuilder> method) {
|
||||
if (!ObjectUtils.isEmpty(customizers)) {
|
||||
return method.apply(builder, customizers);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for RestClient and RestTemplate.
|
||||
*/
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Web client utilities.
|
||||
*/
|
||||
package org.springframework.boot.restclient;
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.service.autoconfigure;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.http.client.autoconfigure.AbstractHttpRequestFactoryProperties;
|
||||
|
||||
/**
|
||||
* {@link AbstractHttpRequestFactoryProperties} for HTTP Service clients.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public abstract class AbstractHttpClientServiceProperties extends AbstractHttpRequestFactoryProperties {
|
||||
|
||||
/**
|
||||
* Base url to set in the underlying HTTP client group. By default, set to
|
||||
* {@code null}.
|
||||
*/
|
||||
private String baseUrl;
|
||||
|
||||
/**
|
||||
* Default request headers for interface client group. By default, set to empty
|
||||
* {@link Map}.
|
||||
*/
|
||||
private Map<String, List<String>> defaultHeader = new LinkedHashMap<>();
|
||||
|
||||
public String getBaseUrl() {
|
||||
return this.baseUrl;
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getDefaultHeader() {
|
||||
return this.defaultHeader;
|
||||
}
|
||||
|
||||
public void setDefaultHeader(Map<String, List<String>> defaultHeaders) {
|
||||
this.defaultHeader = defaultHeaders;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.service.autoconfigure;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Properties for HTTP Service clients.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.http.client.service")
|
||||
public class HttpClientServiceProperties extends AbstractHttpClientServiceProperties {
|
||||
|
||||
/**
|
||||
* Group settings.
|
||||
*/
|
||||
private Map<String, Group> group = new LinkedHashMap<>();
|
||||
|
||||
public Map<String, Group> getGroup() {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
public void setGroup(Map<String, Group> group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties for a single HTTP Service client group.
|
||||
*/
|
||||
public static class Group extends AbstractHttpClientServiceProperties {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.service.autoconfigure;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration;
|
||||
import org.springframework.boot.http.client.autoconfigure.HttpClientProperties;
|
||||
import org.springframework.boot.restclient.RestClientCustomizer;
|
||||
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.web.client.support.RestClientAdapter;
|
||||
import org.springframework.web.service.registry.HttpServiceProxyRegistry;
|
||||
import org.springframework.web.service.registry.ImportHttpServices;
|
||||
|
||||
/**
|
||||
* AutoConfiguration for Spring HTTP Service clients.
|
||||
* <p>
|
||||
* This will result in the creation of blocking HTTP Service client beans defined by
|
||||
* {@link ImportHttpServices @ImportHttpServices} annotations.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@AutoConfiguration(after = { HttpClientAutoConfiguration.class, RestClientAutoConfiguration.class })
|
||||
@ConditionalOnClass(RestClientAdapter.class)
|
||||
@ConditionalOnBean(HttpServiceProxyRegistry.class)
|
||||
@Conditional(NotReactiveWebApplicationCondition.class)
|
||||
@EnableConfigurationProperties(HttpClientServiceProperties.class)
|
||||
public class HttpServiceClientAutoConfiguration implements BeanClassLoaderAware {
|
||||
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
HttpServiceClientAutoConfiguration() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
}
|
||||
|
||||
@Bean
|
||||
RestClientPropertiesHttpServiceGroupConfigurer restClientPropertiesHttpServiceGroupConfigurer(
|
||||
ObjectProvider<SslBundles> sslBundles, ObjectProvider<HttpClientProperties> httpClientProperties,
|
||||
HttpClientServiceProperties serviceProperties,
|
||||
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientFactoryBuilder,
|
||||
ObjectProvider<ClientHttpRequestFactorySettings> clientHttpRequestFactorySettings) {
|
||||
return new RestClientPropertiesHttpServiceGroupConfigurer(this.beanClassLoader, sslBundles,
|
||||
httpClientProperties.getIfAvailable(), serviceProperties, clientFactoryBuilder,
|
||||
clientHttpRequestFactorySettings);
|
||||
}
|
||||
|
||||
@Bean
|
||||
RestClientCustomizerHttpServiceGroupConfigurer restClientCustomizerHttpServiceGroupConfigurer(
|
||||
ObjectProvider<RestClientCustomizer> customizers) {
|
||||
return new RestClientCustomizerHttpServiceGroupConfigurer(customizers);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.service.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
|
||||
/**
|
||||
* {@link SpringBootCondition} that applies only when running in a non-reactive web
|
||||
* application.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class NotReactiveWebApplicationCondition extends NoneNestedConditions {
|
||||
|
||||
NotReactiveWebApplicationCondition() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||
private static final class ReactiveWebApplication {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.service.autoconfigure;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.restclient.RestClientCustomizer;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
|
||||
import org.springframework.web.service.registry.HttpServiceGroup;
|
||||
|
||||
/**
|
||||
* A {@link RestClientHttpServiceGroupConfigurer} to apply auto-configured
|
||||
* {@link RestClientCustomizer} beans to the group's {@link RestClient}.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class RestClientCustomizerHttpServiceGroupConfigurer implements RestClientHttpServiceGroupConfigurer {
|
||||
|
||||
/**
|
||||
* Allow user defined configurers to apply before / after ours.
|
||||
*/
|
||||
private static final int ORDER = 0;
|
||||
|
||||
private final ObjectProvider<RestClientCustomizer> customizers;
|
||||
|
||||
RestClientCustomizerHttpServiceGroupConfigurer(ObjectProvider<RestClientCustomizer> customizers) {
|
||||
this.customizers = customizers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return ORDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureGroups(Groups<RestClient.Builder> groups) {
|
||||
groups.forEachClient(this::configureClient);
|
||||
}
|
||||
|
||||
private void configureClient(HttpServiceGroup group, RestClient.Builder builder) {
|
||||
this.customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.service.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.http.client.autoconfigure.ClientHttpRequestFactories;
|
||||
import org.springframework.boot.http.client.autoconfigure.HttpClientProperties;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
|
||||
import org.springframework.web.service.registry.HttpServiceGroup;
|
||||
|
||||
/**
|
||||
* A {@link RestClientHttpServiceGroupConfigurer} that configures the group and its
|
||||
* underlying {@link RestClient} using {@link HttpClientProperties}.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class RestClientPropertiesHttpServiceGroupConfigurer implements RestClientHttpServiceGroupConfigurer {
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private final ObjectProvider<SslBundles> sslBundles;
|
||||
|
||||
private final HttpClientProperties clientProperties;
|
||||
|
||||
private final HttpClientServiceProperties serviceProperties;
|
||||
|
||||
private final ObjectProvider<ClientHttpRequestFactoryBuilder<?>> requestFactoryBuilder;
|
||||
|
||||
private final ObjectProvider<ClientHttpRequestFactorySettings> requestFactorySettings;
|
||||
|
||||
RestClientPropertiesHttpServiceGroupConfigurer(ClassLoader classLoader, ObjectProvider<SslBundles> sslBundles,
|
||||
HttpClientProperties clientProperties, HttpClientServiceProperties serviceProperties,
|
||||
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> requestFactoryBuilder,
|
||||
ObjectProvider<ClientHttpRequestFactorySettings> requestFactorySettings) {
|
||||
this.classLoader = classLoader;
|
||||
this.sslBundles = sslBundles;
|
||||
this.clientProperties = clientProperties;
|
||||
this.serviceProperties = serviceProperties;
|
||||
this.requestFactoryBuilder = requestFactoryBuilder;
|
||||
this.requestFactorySettings = requestFactorySettings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureGroups(Groups<RestClient.Builder> groups) {
|
||||
groups.forEachClient(this::configureClient);
|
||||
}
|
||||
|
||||
private void configureClient(HttpServiceGroup group, RestClient.Builder builder) {
|
||||
HttpClientServiceProperties.Group groupProperties = this.serviceProperties.getGroup().get(group.name());
|
||||
builder.requestFactory(getRequestFactory(groupProperties));
|
||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||
map.from(this.serviceProperties::getBaseUrl).whenHasText().to(builder::baseUrl);
|
||||
map.from(this.serviceProperties::getDefaultHeader).as(this::putAllHeaders).to(builder::defaultHeaders);
|
||||
if (groupProperties != null) {
|
||||
map.from(groupProperties::getBaseUrl).whenHasText().to(builder::baseUrl);
|
||||
map.from(groupProperties::getDefaultHeader).as(this::putAllHeaders).to(builder::defaultHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
private Consumer<HttpHeaders> putAllHeaders(Map<String, List<String>> defaultHeaders) {
|
||||
return (httpHeaders) -> httpHeaders.putAll(defaultHeaders);
|
||||
}
|
||||
|
||||
private ClientHttpRequestFactory getRequestFactory(HttpClientServiceProperties.Group groupProperties) {
|
||||
ClientHttpRequestFactories factories = new ClientHttpRequestFactories(this.sslBundles, groupProperties,
|
||||
this.serviceProperties, this.clientProperties);
|
||||
ClientHttpRequestFactoryBuilder<?> builder = this.requestFactoryBuilder
|
||||
.getIfAvailable(() -> factories.builder(this.classLoader));
|
||||
ClientHttpRequestFactorySettings settings = this.requestFactorySettings.getIfAvailable(factories::settings);
|
||||
return builder.build(settings);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-Configuration for Spring's Blocking HTTP Service Interface Clients.
|
||||
*/
|
||||
package org.springframework.boot.restclient.service.autoconfigure;
|
||||
@@ -0,0 +1,3 @@
|
||||
org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration
|
||||
org.springframework.boot.restclient.autoconfigure.RestTemplateAutoConfiguration
|
||||
org.springframework.boot.restclient.service.autoconfigure.HttpServiceClientAutoConfiguration
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.mock.http.client.MockClientHttpRequest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link RestTemplateBuilderClientHttpRequestInitializer}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
* @author Ilya Lukyanovich
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class RestTemplateBuilderClientHttpRequestInitializerTests {
|
||||
|
||||
private final MockClientHttpRequest request = new MockClientHttpRequest();
|
||||
|
||||
@Test
|
||||
void createRequestWhenHasBasicAuthAndNoAuthHeaderAddsHeader() {
|
||||
new RestTemplateBuilderClientHttpRequestInitializer(new BasicAuthentication("spring", "boot", null),
|
||||
Collections.emptyMap(), Collections.emptySet())
|
||||
.initialize(this.request);
|
||||
assertThat(this.request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic c3ByaW5nOmJvb3Q=");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createRequestWhenHasBasicAuthAndExistingAuthHeaderDoesNotAddHeader() {
|
||||
this.request.getHeaders().setBasicAuth("boot", "spring");
|
||||
new RestTemplateBuilderClientHttpRequestInitializer(new BasicAuthentication("spring", "boot", null),
|
||||
Collections.emptyMap(), Collections.emptySet())
|
||||
.initialize(this.request);
|
||||
assertThat(this.request.getHeaders().get(HttpHeaders.AUTHORIZATION)).doesNotContain("Basic c3ByaW5nOmJvb3Q=");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createRequestWhenHasDefaultHeadersAddsMissing() {
|
||||
this.request.getHeaders().add("one", "existing");
|
||||
Map<String, List<String>> defaultHeaders = new LinkedHashMap<>();
|
||||
defaultHeaders.put("one", Collections.singletonList("1"));
|
||||
defaultHeaders.put("two", Arrays.asList("2", "3"));
|
||||
defaultHeaders.put("three", Collections.singletonList("4"));
|
||||
new RestTemplateBuilderClientHttpRequestInitializer(null, defaultHeaders, Collections.emptySet())
|
||||
.initialize(this.request);
|
||||
assertThat(this.request.getHeaders().get("one")).containsExactly("existing");
|
||||
assertThat(this.request.getHeaders().get("two")).containsExactly("2", "3");
|
||||
assertThat(this.request.getHeaders().get("three")).containsExactly("4");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void createRequestWhenHasRequestCustomizersAppliesThemInOrder() {
|
||||
Set<RestTemplateRequestCustomizer<?>> customizers = new LinkedHashSet<>();
|
||||
customizers.add(mock(RestTemplateRequestCustomizer.class));
|
||||
customizers.add(mock(RestTemplateRequestCustomizer.class));
|
||||
customizers.add(mock(RestTemplateRequestCustomizer.class));
|
||||
new RestTemplateBuilderClientHttpRequestInitializer(null, Collections.emptyMap(), customizers)
|
||||
.initialize(this.request);
|
||||
InOrder inOrder = inOrder(customizers.toArray());
|
||||
for (RestTemplateRequestCustomizer<?> customizer : customizers) {
|
||||
inOrder.verify((RestTemplateRequestCustomizer<ClientHttpRequest>) customizer).customize(this.request);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.http.client.HttpRedirects;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.BufferingClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpRequestInitializer;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
|
||||
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.util.ReflectionTestUtils;
|
||||
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.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
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
|
||||
* @author Andy Wilkinson
|
||||
* @author Dmytro Nosan
|
||||
* @author Kevin Strijbos
|
||||
* @author Ilya Lukyanovich
|
||||
* @author Brian Clozel
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RestTemplateBuilderTests {
|
||||
|
||||
private final RestTemplateBuilder builder = new RestTemplateBuilder();
|
||||
|
||||
@Mock
|
||||
private HttpMessageConverter<Object> messageConverter;
|
||||
|
||||
@Mock
|
||||
private ClientHttpRequestInterceptor interceptor;
|
||||
|
||||
@Test
|
||||
void createWhenCustomizersAreNullShouldThrowException() {
|
||||
RestTemplateCustomizer[] customizers = null;
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new RestTemplateBuilder(customizers))
|
||||
.withMessageContaining("'customizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithCustomizersShouldApplyCustomizers() {
|
||||
RestTemplateCustomizer customizer = mock(RestTemplateCustomizer.class);
|
||||
RestTemplate template = new RestTemplateBuilder(customizer).build();
|
||||
then(customizer).should().customize(template);
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildShouldDetectRequestFactory() {
|
||||
RestTemplate restTemplate = this.builder.build();
|
||||
assertThat(restTemplate.getRequestFactory()).isInstanceOf(HttpComponentsClientHttpRequestFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectRequestFactoryWhenFalseShouldDisableDetection() {
|
||||
RestTemplate restTemplate = this.builder.detectRequestFactory(false).build();
|
||||
assertThat(restTemplate.getRequestFactory()).isInstanceOf(SimpleClientHttpRequestFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rootUriShouldApply() {
|
||||
RestTemplate restTemplate = this.builder.rootUri("https://example.com").build();
|
||||
MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build();
|
||||
server.expect(requestTo("https://example.com/hello")).andRespond(withSuccess());
|
||||
restTemplate.getForEntity("/hello", String.class);
|
||||
server.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void rootUriShouldApplyAfterUriTemplateHandler() {
|
||||
UriTemplateHandler uriTemplateHandler = mock(UriTemplateHandler.class);
|
||||
RestTemplate template = this.builder.uriTemplateHandler(uriTemplateHandler)
|
||||
.rootUri("https://example.com")
|
||||
.build();
|
||||
UriTemplateHandler handler = template.getUriTemplateHandler();
|
||||
handler.expand("/hello");
|
||||
assertThat(handler).isInstanceOf(RootUriBuilderFactory.class);
|
||||
then(uriTemplateHandler).should().expand("https://example.com/hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void messageConvertersWhenConvertersAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.messageConverters((HttpMessageConverter<?>[]) null))
|
||||
.withMessageContaining("'messageConverters' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void messageConvertersCollectionWhenConvertersAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.messageConverters((Set<HttpMessageConverter<?>>) null))
|
||||
.withMessageContaining("'messageConverters' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void messageConvertersShouldApply() {
|
||||
RestTemplate template = this.builder.messageConverters(this.messageConverter).build();
|
||||
assertThat(template.getMessageConverters()).containsOnly(this.messageConverter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void messageConvertersShouldReplaceExisting() {
|
||||
RestTemplate template = this.builder.messageConverters(new ResourceHttpMessageConverter())
|
||||
.messageConverters(Collections.singleton(this.messageConverter))
|
||||
.build();
|
||||
assertThat(template.getMessageConverters()).containsOnly(this.messageConverter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalMessageConvertersWhenConvertersAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.additionalMessageConverters((HttpMessageConverter<?>[]) null))
|
||||
.withMessageContaining("'messageConverters' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalMessageConvertersCollectionWhenConvertersAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.additionalMessageConverters((Set<HttpMessageConverter<?>>) null))
|
||||
.withMessageContaining("'messageConverters' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalMessageConvertersShouldAddToExisting() {
|
||||
HttpMessageConverter<?> resourceConverter = new ResourceHttpMessageConverter();
|
||||
RestTemplate template = this.builder.messageConverters(resourceConverter)
|
||||
.additionalMessageConverters(this.messageConverter)
|
||||
.build();
|
||||
assertThat(template.getMessageConverters()).containsOnly(resourceConverter, this.messageConverter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultMessageConvertersShouldSetDefaultList() {
|
||||
RestTemplate template = new RestTemplate(Collections.singletonList(new StringHttpMessageConverter()));
|
||||
this.builder.defaultMessageConverters().configure(template);
|
||||
assertThat(template.getMessageConverters()).hasSameSizeAs(new RestTemplate().getMessageConverters());
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultMessageConvertersShouldClearExisting() {
|
||||
RestTemplate template = new RestTemplate(Collections.singletonList(new StringHttpMessageConverter()));
|
||||
this.builder.additionalMessageConverters(this.messageConverter).defaultMessageConverters().configure(template);
|
||||
assertThat(template.getMessageConverters()).hasSameSizeAs(new RestTemplate().getMessageConverters());
|
||||
}
|
||||
|
||||
@Test
|
||||
void interceptorsWhenInterceptorsAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.interceptors((ClientHttpRequestInterceptor[]) null))
|
||||
.withMessageContaining("'interceptors' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void interceptorsCollectionWhenInterceptorsAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.interceptors((Set<ClientHttpRequestInterceptor>) null))
|
||||
.withMessageContaining("'interceptors' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void interceptorsShouldApply() {
|
||||
RestTemplate template = this.builder.interceptors(this.interceptor).build();
|
||||
assertThat(template.getInterceptors()).containsOnly(this.interceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
void interceptorsShouldReplaceExisting() {
|
||||
RestTemplate template = this.builder.interceptors(mock(ClientHttpRequestInterceptor.class))
|
||||
.interceptors(Collections.singleton(this.interceptor))
|
||||
.build();
|
||||
assertThat(template.getInterceptors()).containsOnly(this.interceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalInterceptorsWhenInterceptorsAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.additionalInterceptors((ClientHttpRequestInterceptor[]) null))
|
||||
.withMessageContaining("'interceptors' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalInterceptorsCollectionWhenInterceptorsAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.additionalInterceptors((Set<ClientHttpRequestInterceptor>) null))
|
||||
.withMessageContaining("'interceptors' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalInterceptorsShouldAddToExisting() {
|
||||
ClientHttpRequestInterceptor interceptor = mock(ClientHttpRequestInterceptor.class);
|
||||
RestTemplate template = this.builder.interceptors(interceptor).additionalInterceptors(this.interceptor).build();
|
||||
assertThat(template.getInterceptors()).containsOnly(interceptor, this.interceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestFactoryClassWhenFactoryIsNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.requestFactory((Class<ClientHttpRequestFactory>) null))
|
||||
.withMessageContaining("'requestFactoryType' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestFactoryClassShouldApply() {
|
||||
RestTemplate template = this.builder.requestFactory(SimpleClientHttpRequestFactory.class).build();
|
||||
assertThat(template.getRequestFactory()).isInstanceOf(SimpleClientHttpRequestFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestFactoryPackagePrivateClassShouldApply() {
|
||||
RestTemplate template = this.builder.requestFactory(TestClientHttpRequestFactory.class).build();
|
||||
assertThat(template.getRequestFactory()).isInstanceOf(TestClientHttpRequestFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestFactoryWhenSupplierIsNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.requestFactory((Supplier<ClientHttpRequestFactory>) null))
|
||||
.withMessageContaining("requestFactorySupplier' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestFactoryShouldApply() {
|
||||
ClientHttpRequestFactory requestFactory = mock(ClientHttpRequestFactory.class);
|
||||
RestTemplate template = this.builder.requestFactory(() -> requestFactory).build();
|
||||
assertThat(template.getRequestFactory()).isSameAs(requestFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void uriTemplateHandlerWhenHandlerIsNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.builder.uriTemplateHandler(null))
|
||||
.withMessageContaining("'uriTemplateHandler' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void uriTemplateHandlerShouldApply() {
|
||||
UriTemplateHandler uriTemplateHandler = mock(UriTemplateHandler.class);
|
||||
RestTemplate template = this.builder.uriTemplateHandler(uriTemplateHandler).build();
|
||||
assertThat(template.getUriTemplateHandler()).isSameAs(uriTemplateHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorHandlerWhenHandlerIsNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.builder.errorHandler(null))
|
||||
.withMessageContaining("'errorHandler' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorHandlerShouldApply() {
|
||||
ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
|
||||
RestTemplate template = this.builder.errorHandler(errorHandler).build();
|
||||
assertThat(template.getErrorHandler()).isSameAs(errorHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
void basicAuthenticationShouldApply() {
|
||||
RestTemplate template = this.builder.basicAuthentication("spring", "boot", StandardCharsets.UTF_8).build();
|
||||
ClientHttpRequest request = createRequest(template);
|
||||
assertThat(request.getHeaders().headerNames()).containsOnly(HttpHeaders.AUTHORIZATION);
|
||||
assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic c3ByaW5nOmJvb3Q=");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultHeaderAddsHeader() {
|
||||
RestTemplate template = this.builder.defaultHeader("spring", "boot").build();
|
||||
ClientHttpRequest request = createRequest(template);
|
||||
assertThat(request.getHeaders().headerSet()).contains(entry("spring", Collections.singletonList("boot")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultHeaderAddsHeaderValues() {
|
||||
String name = HttpHeaders.ACCEPT;
|
||||
String[] values = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE };
|
||||
RestTemplate template = this.builder.defaultHeader(name, values).build();
|
||||
ClientHttpRequest request = createRequest(template);
|
||||
assertThat(request.getHeaders().headerSet()).contains(entry(name, Arrays.asList(values)));
|
||||
}
|
||||
|
||||
@Test // gh-17885
|
||||
void defaultHeaderWhenUsingMockRestServiceServerAddsHeader() {
|
||||
RestTemplate template = this.builder.defaultHeader("spring", "boot").build();
|
||||
MockRestServiceServer.bindTo(template).build();
|
||||
ClientHttpRequest request = createRequest(template);
|
||||
assertThat(request.getHeaders().headerSet()).contains(entry("spring", Collections.singletonList("boot")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestFactorySettingsAppliesSettings() {
|
||||
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
|
||||
.withConnectTimeout(Duration.ofSeconds(1));
|
||||
RestTemplate template = this.builder.requestFactorySettings(settings).build();
|
||||
assertThat(template.getRequestFactory()).extracting("connectTimeout").isEqualTo(1000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestCustomizersAddsCustomizers() {
|
||||
RestTemplate template = this.builder
|
||||
.requestCustomizers((request) -> request.getHeaders().add("spring", "framework"))
|
||||
.build();
|
||||
ClientHttpRequest request = createRequest(template);
|
||||
assertThat(request.getHeaders().headerSet()).contains(entry("spring", Collections.singletonList("framework")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalRequestCustomizersAddsCustomizers() {
|
||||
RestTemplate template = this.builder
|
||||
.requestCustomizers((request) -> request.getHeaders().add("spring", "framework"))
|
||||
.additionalRequestCustomizers((request) -> request.getHeaders().add("for", "java"))
|
||||
.build();
|
||||
ClientHttpRequest request = createRequest(template);
|
||||
assertThat(request.getHeaders().headerSet()).contains(entry("spring", Collections.singletonList("framework")))
|
||||
.contains(entry("for", Collections.singletonList("java")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersWhenCustomizersAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.builder.customizers((RestTemplateCustomizer[]) null))
|
||||
.withMessageContaining("'customizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersCollectionWhenCustomizersAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.customizers((Set<RestTemplateCustomizer>) null))
|
||||
.withMessageContaining("'customizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersShouldApply() {
|
||||
RestTemplateCustomizer customizer = mock(RestTemplateCustomizer.class);
|
||||
RestTemplate template = this.builder.customizers(customizer).build();
|
||||
then(customizer).should().customize(template);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersShouldBeAppliedLast() {
|
||||
RestTemplate template = spy(new RestTemplate());
|
||||
this.builder.additionalCustomizers(
|
||||
(restTemplate) -> then(restTemplate).should().setRequestFactory(any(ClientHttpRequestFactory.class)));
|
||||
this.builder.configure(template);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersShouldReplaceExisting() {
|
||||
RestTemplateCustomizer customizer1 = mock(RestTemplateCustomizer.class);
|
||||
RestTemplateCustomizer customizer2 = mock(RestTemplateCustomizer.class);
|
||||
RestTemplate template = this.builder.customizers(customizer1)
|
||||
.customizers(Collections.singleton(customizer2))
|
||||
.build();
|
||||
then(customizer1).shouldHaveNoInteractions();
|
||||
then(customizer2).should().customize(template);
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalCustomizersWhenCustomizersAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.additionalCustomizers((RestTemplateCustomizer[]) null))
|
||||
.withMessageContaining("'customizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalCustomizersCollectionWhenCustomizersAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.additionalCustomizers((Set<RestTemplateCustomizer>) null))
|
||||
.withMessageContaining("customizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalCustomizersShouldAddToExisting() {
|
||||
RestTemplateCustomizer customizer1 = mock(RestTemplateCustomizer.class);
|
||||
RestTemplateCustomizer customizer2 = mock(RestTemplateCustomizer.class);
|
||||
RestTemplate template = this.builder.customizers(customizer1).additionalCustomizers(customizer2).build();
|
||||
InOrder ordered = inOrder(customizer1, customizer2);
|
||||
ordered.verify(customizer1).customize(template);
|
||||
ordered.verify(customizer2).customize(template);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizerShouldBeAppliedAtTheEnd() {
|
||||
ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
|
||||
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
|
||||
this.builder.interceptors(this.interceptor)
|
||||
.messageConverters(this.messageConverter)
|
||||
.rootUri("http://localhost:8080")
|
||||
.errorHandler(errorHandler)
|
||||
.basicAuthentication("spring", "boot")
|
||||
.requestFactory(() -> requestFactory)
|
||||
.customizers((restTemplate) -> {
|
||||
assertThat(restTemplate.getInterceptors()).hasSize(1);
|
||||
assertThat(restTemplate.getMessageConverters()).contains(this.messageConverter);
|
||||
assertThat(restTemplate.getUriTemplateHandler()).isInstanceOf(RootUriBuilderFactory.class);
|
||||
assertThat(restTemplate.getErrorHandler()).isEqualTo(errorHandler);
|
||||
ClientHttpRequestFactory actualRequestFactory = restTemplate.getRequestFactory();
|
||||
assertThat(actualRequestFactory).isInstanceOf(InterceptingClientHttpRequestFactory.class);
|
||||
ClientHttpRequestInitializer initializer = restTemplate.getClientHttpRequestInitializers().get(0);
|
||||
assertThat(initializer).isInstanceOf(RestTemplateBuilderClientHttpRequestInitializer.class);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildShouldReturnRestTemplate() {
|
||||
RestTemplate template = this.builder.build();
|
||||
assertThat(template.getClass()).isEqualTo(RestTemplate.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildClassShouldReturnClassInstance() {
|
||||
RestTemplateSubclass template = this.builder.build(RestTemplateSubclass.class);
|
||||
assertThat(template.getClass()).isEqualTo(RestTemplateSubclass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureShouldApply() {
|
||||
RestTemplate template = new RestTemplate();
|
||||
this.builder.configure(template);
|
||||
assertThat(template.getRequestFactory()).isInstanceOf(HttpComponentsClientHttpRequestFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrappingDoesNotAffectRequestFactoryThatIsSetOnTheBuiltTemplate() {
|
||||
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
|
||||
RestTemplate template = this.builder.requestFactory(() -> new BufferingClientHttpRequestFactory(requestFactory))
|
||||
.build();
|
||||
assertThat(template.getRequestFactory()).isInstanceOf(BufferingClientHttpRequestFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureRedirects() {
|
||||
assertThat(this.builder.redirects(HttpRedirects.DONT_FOLLOW)).extracting("requestFactorySettings")
|
||||
.extracting("redirects")
|
||||
.isSameAs(HttpRedirects.DONT_FOLLOW);
|
||||
}
|
||||
|
||||
private ClientHttpRequest createRequest(RestTemplate template) {
|
||||
return ReflectionTestUtils.invokeMethod(template, "createRequest", URI.create("http://localhost"),
|
||||
HttpMethod.GET);
|
||||
}
|
||||
|
||||
static class RestTemplateSubclass extends RestTemplate {
|
||||
|
||||
}
|
||||
|
||||
static class TestClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
|
||||
|
||||
}
|
||||
|
||||
static class TestHttpComponentsClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.web.util.UriBuilder;
|
||||
import org.springframework.web.util.UriBuilderFactory;
|
||||
import org.springframework.web.util.UriTemplateHandler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link RootUriBuilderFactory}.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class RootUriBuilderFactoryTests {
|
||||
|
||||
@Test
|
||||
void uriStringPrefixesRoot() throws URISyntaxException {
|
||||
UriBuilderFactory builderFactory = new RootUriBuilderFactory("https://example.com",
|
||||
mock(UriTemplateHandler.class));
|
||||
UriBuilder builder = builderFactory.uriString("/hello");
|
||||
assertThat(builder.build()).isEqualTo(new URI("https://example.com/hello"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.web.util.UriTemplateHandler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link RootUriTemplateHandler}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RootUriTemplateHandlerTests {
|
||||
|
||||
private URI uri;
|
||||
|
||||
@Mock
|
||||
public UriTemplateHandler delegate;
|
||||
|
||||
public UriTemplateHandler handler;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws URISyntaxException {
|
||||
this.uri = new URI("https://example.com/hello");
|
||||
this.handler = new RootUriTemplateHandler("https://example.com", this.delegate);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithNullRootUriShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new RootUriTemplateHandler((String) null, mock(UriTemplateHandler.class)))
|
||||
.withMessageContaining("'rootUri' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithNullHandlerShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new RootUriTemplateHandler("https://example.com", null))
|
||||
.withMessageContaining("'handler' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void expandMapVariablesShouldPrefixRoot() {
|
||||
given(this.delegate.expand(anyString(), any(Map.class))).willReturn(this.uri);
|
||||
HashMap<String, Object> uriVariables = new HashMap<>();
|
||||
URI expanded = this.handler.expand("/hello", uriVariables);
|
||||
then(this.delegate).should().expand("https://example.com/hello", uriVariables);
|
||||
assertThat(expanded).isEqualTo(this.uri);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void expandMapVariablesWhenPathDoesNotStartWithSlashShouldNotPrefixRoot() {
|
||||
given(this.delegate.expand(anyString(), any(Map.class))).willReturn(this.uri);
|
||||
HashMap<String, Object> uriVariables = new HashMap<>();
|
||||
URI expanded = this.handler.expand("https://spring.io/hello", uriVariables);
|
||||
then(this.delegate).should().expand("https://spring.io/hello", uriVariables);
|
||||
assertThat(expanded).isEqualTo(this.uri);
|
||||
}
|
||||
|
||||
@Test
|
||||
void expandArrayVariablesShouldPrefixRoot() {
|
||||
given(this.delegate.expand(anyString(), any(Object[].class))).willReturn(this.uri);
|
||||
Object[] uriVariables = new Object[0];
|
||||
URI expanded = this.handler.expand("/hello", uriVariables);
|
||||
then(this.delegate).should().expand("https://example.com/hello", uriVariables);
|
||||
assertThat(expanded).isEqualTo(this.uri);
|
||||
}
|
||||
|
||||
@Test
|
||||
void expandArrayVariablesWhenPathDoesNotStartWithSlashShouldNotPrefixRoot() {
|
||||
given(this.delegate.expand(anyString(), any(Object[].class))).willReturn(this.uri);
|
||||
Object[] uriVariables = new Object[0];
|
||||
URI expanded = this.handler.expand("https://spring.io/hello", uriVariables);
|
||||
then(this.delegate).should().expand("https://spring.io/hello", uriVariables);
|
||||
assertThat(expanded).isEqualTo(this.uri);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.actuate.observation;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.client.observation.DefaultClientRequestObservationConvention;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ObservationRestClientCustomizer}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class ObservationRestClientCustomizerTests {
|
||||
|
||||
private static final String TEST_METRIC_NAME = "http.test.metric.name";
|
||||
|
||||
private final ObservationRegistry observationRegistry = TestObservationRegistry.create();
|
||||
|
||||
private final RestClient.Builder restClientBuilder = RestClient.builder();
|
||||
|
||||
private final ObservationRestClientCustomizer customizer = new ObservationRestClientCustomizer(
|
||||
this.observationRegistry, new DefaultClientRequestObservationConvention(TEST_METRIC_NAME));
|
||||
|
||||
@Test
|
||||
void shouldCustomizeObservationConfiguration() {
|
||||
this.customizer.customize(this.restClientBuilder);
|
||||
assertThat(this.restClientBuilder).hasFieldOrPropertyWithValue("observationRegistry", this.observationRegistry);
|
||||
assertThat(this.restClientBuilder).extracting("observationConvention")
|
||||
.isInstanceOf(DefaultClientRequestObservationConvention.class)
|
||||
.hasFieldOrPropertyWithValue("name", TEST_METRIC_NAME);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.actuate.observation;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.client.observation.DefaultClientRequestObservationConvention;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ObservationRestTemplateCustomizer}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class ObservationRestTemplateCustomizerTests {
|
||||
|
||||
private static final String TEST_METRIC_NAME = "http.test.metric.name";
|
||||
|
||||
private final ObservationRegistry observationRegistry = TestObservationRegistry.create();
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
private final ObservationRestTemplateCustomizer customizer = new ObservationRestTemplateCustomizer(
|
||||
this.observationRegistry, new DefaultClientRequestObservationConvention(TEST_METRIC_NAME));
|
||||
|
||||
@Test
|
||||
void shouldCustomizeObservationConfiguration() {
|
||||
this.customizer.customize(this.restTemplate);
|
||||
assertThat(this.restTemplate).hasFieldOrPropertyWithValue("observationRegistry", this.observationRegistry);
|
||||
assertThat(this.restTemplate).extracting("observationConvention")
|
||||
.isInstanceOf(DefaultClientRequestObservationConvention.class)
|
||||
.hasFieldOrPropertyWithValue("name", TEST_METRIC_NAME);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.http.client.HttpRedirects;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestClient.Builder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AutoConfiguredRestClientSsl}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AutoConfiguredRestClientSslTests {
|
||||
|
||||
private final ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings
|
||||
.ofSslBundle(mock(SslBundle.class, "Default SslBundle"))
|
||||
.withRedirects(HttpRedirects.DONT_FOLLOW)
|
||||
.withReadTimeout(Duration.ofSeconds(10))
|
||||
.withConnectTimeout(Duration.ofSeconds(30));
|
||||
|
||||
@Mock
|
||||
private SslBundles sslBundles;
|
||||
|
||||
@Mock
|
||||
private ClientHttpRequestFactoryBuilder<ClientHttpRequestFactory> factoryBuilder;
|
||||
|
||||
@Mock
|
||||
private ClientHttpRequestFactory factory;
|
||||
|
||||
private AutoConfiguredRestClientSsl restClientSsl;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
this.restClientSsl = new AutoConfiguredRestClientSsl(this.factoryBuilder, this.settings, this.sslBundles);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureRestClientUsingBundleName() {
|
||||
String bundleName = "test";
|
||||
SslBundle sslBundle = mock(SslBundle.class, "SslBundle named '%s'".formatted(bundleName));
|
||||
given(this.sslBundles.getBundle(bundleName)).willReturn(sslBundle);
|
||||
given(this.factoryBuilder.build(this.settings.withSslBundle(sslBundle))).willReturn(this.factory);
|
||||
RestClient restClient = build(this.restClientSsl.fromBundle(bundleName));
|
||||
assertThat(restClient).hasFieldOrPropertyWithValue("clientRequestFactory", this.factory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureRestClientUsingBundle() {
|
||||
SslBundle sslBundle = mock(SslBundle.class, "Custom SslBundle");
|
||||
given(this.factoryBuilder.build(this.settings.withSslBundle(sslBundle))).willReturn(this.factory);
|
||||
RestClient restClient = build(this.restClientSsl.fromBundle(sslBundle));
|
||||
assertThat(restClient).hasFieldOrPropertyWithValue("clientRequestFactory", this.factory);
|
||||
}
|
||||
|
||||
private RestClient build(Consumer<RestClient.Builder> customizer) {
|
||||
Builder builder = RestClient.builder();
|
||||
customizer.accept(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpMessageConvertersRestClientCustomizer}
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class HttpMessageConvertersRestClientCustomizerTests {
|
||||
|
||||
@Test
|
||||
void createWhenNullMessageConvertersArrayThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new HttpMessageConvertersRestClientCustomizer((HttpMessageConverter<?>[]) null))
|
||||
.withMessage("'messageConverters' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenNullMessageConvertersDoesNotCustomize() {
|
||||
HttpMessageConverter<?> c0 = mock();
|
||||
assertThat(apply(new HttpMessageConvertersRestClientCustomizer((HttpMessageConverters) null), c0))
|
||||
.containsExactly(c0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeConfiguresMessageConverters() {
|
||||
HttpMessageConverter<?> c0 = mock();
|
||||
HttpMessageConverter<?> c1 = mock();
|
||||
HttpMessageConverter<?> c2 = mock();
|
||||
assertThat(apply(new HttpMessageConvertersRestClientCustomizer(c1, c2), c0)).containsExactly(c1, c2);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<HttpMessageConverter<?>> apply(HttpMessageConvertersRestClientCustomizer customizer,
|
||||
HttpMessageConverter<?>... converters) {
|
||||
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(Arrays.asList(converters));
|
||||
RestClient.Builder restClientBuilder = mock();
|
||||
ArgumentCaptor<Consumer<List<HttpMessageConverter<?>>>> captor = ArgumentCaptor.forClass(Consumer.class);
|
||||
given(restClientBuilder.messageConverters(captor.capture())).willReturn(restClientBuilder);
|
||||
customizer.customize(restClientBuilder);
|
||||
captor.getValue().accept(messageConverters);
|
||||
return messageConverters;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.http.client.HttpRedirects;
|
||||
import org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration;
|
||||
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters;
|
||||
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.restclient.RestClientCustomizer;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestClient.Builder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link RestClientAutoConfiguration}
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Moritz Halbritter
|
||||
* @author Dmytro Nosan
|
||||
* @author Dmitry Sulman
|
||||
*/
|
||||
class RestClientAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class, HttpClientAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void shouldSupplyBeans() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).hasSingleBean(HttpMessageConvertersRestClientCustomizer.class);
|
||||
assertThat(context).hasSingleBean(RestClientBuilderConfigurer.class);
|
||||
assertThat(context).hasSingleBean(RestClient.Builder.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSupplyRestClientSslIfSslBundlesIsThereWithCustomHttpSettingsAndBuilder() {
|
||||
SslBundles sslBundles = mock(SslBundles.class);
|
||||
ClientHttpRequestFactorySettings clientHttpRequestFactorySettings = ClientHttpRequestFactorySettings.defaults()
|
||||
.withRedirects(HttpRedirects.DONT_FOLLOW)
|
||||
.withConnectTimeout(Duration.ofHours(1))
|
||||
.withReadTimeout(Duration.ofDays(1))
|
||||
.withSslBundle(mock(SslBundle.class));
|
||||
ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder = mock(
|
||||
ClientHttpRequestFactoryBuilder.class);
|
||||
this.contextRunner.withBean(SslBundles.class, () -> sslBundles)
|
||||
.withBean(ClientHttpRequestFactorySettings.class, () -> clientHttpRequestFactorySettings)
|
||||
.withBean(ClientHttpRequestFactoryBuilder.class, () -> clientHttpRequestFactoryBuilder)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(RestClientSsl.class);
|
||||
RestClientSsl restClientSsl = context.getBean(RestClientSsl.class);
|
||||
assertThat(restClientSsl).hasFieldOrPropertyWithValue("sslBundles", sslBundles);
|
||||
assertThat(restClientSsl).hasFieldOrPropertyWithValue("builder", clientHttpRequestFactoryBuilder);
|
||||
assertThat(restClientSsl).hasFieldOrPropertyWithValue("settings", clientHttpRequestFactorySettings);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSupplyRestClientSslIfSslBundlesIsThereWithAutoConfiguredHttpSettingsAndBuilder() {
|
||||
SslBundles sslBundles = mock(SslBundles.class);
|
||||
this.contextRunner.withBean(SslBundles.class, () -> sslBundles).run((context) -> {
|
||||
assertThat(context).hasSingleBean(RestClientSsl.class)
|
||||
.hasSingleBean(ClientHttpRequestFactorySettings.class)
|
||||
.hasSingleBean(ClientHttpRequestFactoryBuilder.class);
|
||||
RestClientSsl restClientSsl = context.getBean(RestClientSsl.class);
|
||||
assertThat(restClientSsl).hasFieldOrPropertyWithValue("sslBundles", sslBundles);
|
||||
assertThat(restClientSsl).hasFieldOrPropertyWithValue("builder",
|
||||
context.getBean(ClientHttpRequestFactoryBuilder.class));
|
||||
assertThat(restClientSsl).hasFieldOrPropertyWithValue("settings",
|
||||
context.getBean(ClientHttpRequestFactorySettings.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateBuilder() {
|
||||
this.contextRunner.run((context) -> {
|
||||
RestClient.Builder builder = context.getBean(RestClient.Builder.class);
|
||||
RestClient restClient = builder.build();
|
||||
assertThat(restClient).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void configurerShouldCallCustomizers() {
|
||||
this.contextRunner.withUserConfiguration(RestClientCustomizerConfig.class).run((context) -> {
|
||||
RestClientBuilderConfigurer configurer = context.getBean(RestClientBuilderConfigurer.class);
|
||||
RestClientCustomizer customizer = context.getBean("restClientCustomizer", RestClientCustomizer.class);
|
||||
Builder builder = RestClient.builder();
|
||||
configurer.configure(builder);
|
||||
then(customizer).should().customize(builder);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void restClientShouldApplyCustomizers() {
|
||||
this.contextRunner.withUserConfiguration(RestClientCustomizerConfig.class).run((context) -> {
|
||||
RestClient.Builder builder = context.getBean(RestClient.Builder.class);
|
||||
RestClientCustomizer customizer = context.getBean("restClientCustomizer", RestClientCustomizer.class);
|
||||
builder.build();
|
||||
then(customizer).should().customize(any(RestClient.Builder.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetPrototypeScopedBean() {
|
||||
this.contextRunner.withUserConfiguration(RestClientCustomizerConfig.class).run((context) -> {
|
||||
RestClient.Builder firstBuilder = context.getBean(RestClient.Builder.class);
|
||||
RestClient.Builder secondBuilder = context.getBean(RestClient.Builder.class);
|
||||
assertThat(firstBuilder).isNotEqualTo(secondBuilder);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotCreateClientBuilderIfAlreadyPresent() {
|
||||
this.contextRunner.withUserConfiguration(CustomRestClientBuilderConfig.class).run((context) -> {
|
||||
RestClient.Builder builder = context.getBean(RestClient.Builder.class);
|
||||
assertThat(builder).isInstanceOf(MyRestClientBuilder.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void restClientWhenMessageConvertersDefinedShouldHaveMessageConverters() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
|
||||
.withUserConfiguration(RestClientConfig.class)
|
||||
.run((context) -> {
|
||||
RestClient restClient = context.getBean(RestClient.class);
|
||||
List<HttpMessageConverter<?>> expectedConverters = context.getBean(HttpMessageConverters.class)
|
||||
.getConverters();
|
||||
List<HttpMessageConverter<?>> actualConverters = (List<HttpMessageConverter<?>>) ReflectionTestUtils
|
||||
.getField(restClient, "messageConverters");
|
||||
assertThat(actualConverters).containsExactlyElementsOf(expectedConverters);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void restClientWhenNoMessageConvertersDefinedShouldHaveDefaultMessageConverters() {
|
||||
this.contextRunner.withUserConfiguration(RestClientConfig.class).run((context) -> {
|
||||
RestClient restClient = context.getBean(RestClient.class);
|
||||
RestClient defaultRestClient = RestClient.builder().build();
|
||||
List<HttpMessageConverter<?>> actualConverters = (List<HttpMessageConverter<?>>) ReflectionTestUtils
|
||||
.getField(restClient, "messageConverters");
|
||||
List<HttpMessageConverter<?>> expectedConverters = (List<HttpMessageConverter<?>>) ReflectionTestUtils
|
||||
.getField(defaultRestClient, "messageConverters");
|
||||
assertThat(actualConverters).hasSameSizeAs(expectedConverters);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
void restClientWhenHasCustomMessageConvertersShouldHaveMessageConverters() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
|
||||
.withUserConfiguration(CustomHttpMessageConverter.class, RestClientConfig.class)
|
||||
.run((context) -> {
|
||||
RestClient restClient = context.getBean(RestClient.class);
|
||||
List<HttpMessageConverter<?>> actualConverters = (List<HttpMessageConverter<?>>) ReflectionTestUtils
|
||||
.getField(restClient, "messageConverters");
|
||||
assertThat(actualConverters).extracting(HttpMessageConverter::getClass)
|
||||
.contains((Class) CustomHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasFactoryProperty() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
|
||||
.withUserConfiguration(RestClientConfig.class)
|
||||
.withPropertyValues("spring.http.client.factory=simple")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(RestClient.class);
|
||||
RestClient restClient = context.getBean(RestClient.class);
|
||||
assertThat(restClient).extracting("clientRequestFactory")
|
||||
.isInstanceOf(SimpleClientHttpRequestFactory.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSupplyRestClientBuilderConfigurerWithCustomSettings() {
|
||||
ClientHttpRequestFactorySettings clientHttpRequestFactorySettings = ClientHttpRequestFactorySettings.defaults()
|
||||
.withRedirects(HttpRedirects.DONT_FOLLOW);
|
||||
ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder = mock(
|
||||
ClientHttpRequestFactoryBuilder.class);
|
||||
RestClientCustomizer customizer1 = mock(RestClientCustomizer.class);
|
||||
RestClientCustomizer customizer2 = mock(RestClientCustomizer.class);
|
||||
HttpMessageConvertersRestClientCustomizer httpMessageConverterCustomizer = mock(
|
||||
HttpMessageConvertersRestClientCustomizer.class);
|
||||
this.contextRunner.withBean(ClientHttpRequestFactorySettings.class, () -> clientHttpRequestFactorySettings)
|
||||
.withBean(ClientHttpRequestFactoryBuilder.class, () -> clientHttpRequestFactoryBuilder)
|
||||
.withBean("customizer1", RestClientCustomizer.class, () -> customizer1)
|
||||
.withBean("customizer2", RestClientCustomizer.class, () -> customizer2)
|
||||
.withBean("httpMessageConverterCustomizer", HttpMessageConvertersRestClientCustomizer.class,
|
||||
() -> httpMessageConverterCustomizer)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(RestClientBuilderConfigurer.class)
|
||||
.hasSingleBean(ClientHttpRequestFactorySettings.class)
|
||||
.hasSingleBean(ClientHttpRequestFactoryBuilder.class);
|
||||
RestClientBuilderConfigurer configurer = context.getBean(RestClientBuilderConfigurer.class);
|
||||
assertThat(configurer).hasFieldOrPropertyWithValue("requestFactoryBuilder",
|
||||
clientHttpRequestFactoryBuilder);
|
||||
assertThat(configurer).hasFieldOrPropertyWithValue("requestFactorySettings",
|
||||
clientHttpRequestFactorySettings);
|
||||
assertThat(configurer).hasFieldOrPropertyWithValue("customizers",
|
||||
List.of(customizer1, customizer2, httpMessageConverterCustomizer));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSupplyRestClientBuilderConfigurerWithAutoConfiguredHttpSettings() {
|
||||
RestClientCustomizer customizer1 = mock(RestClientCustomizer.class);
|
||||
RestClientCustomizer customizer2 = mock(RestClientCustomizer.class);
|
||||
this.contextRunner.withBean("customizer1", RestClientCustomizer.class, () -> customizer1)
|
||||
.withBean("customizer2", RestClientCustomizer.class, () -> customizer2)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(RestClientBuilderConfigurer.class)
|
||||
.hasSingleBean(ClientHttpRequestFactorySettings.class)
|
||||
.hasSingleBean(ClientHttpRequestFactoryBuilder.class)
|
||||
.hasSingleBean(HttpMessageConvertersRestClientCustomizer.class);
|
||||
RestClientBuilderConfigurer configurer = context.getBean(RestClientBuilderConfigurer.class);
|
||||
assertThat(configurer).hasFieldOrPropertyWithValue("requestFactoryBuilder",
|
||||
context.getBean(ClientHttpRequestFactoryBuilder.class));
|
||||
assertThat(configurer).hasFieldOrPropertyWithValue("requestFactorySettings",
|
||||
context.getBean(ClientHttpRequestFactorySettings.class));
|
||||
assertThat(configurer).hasFieldOrPropertyWithValue("customizers", List.of(customizer1, customizer2,
|
||||
context.getBean(HttpMessageConvertersRestClientCustomizer.class)));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenReactiveWebApplicationRestClientIsNotConfigured() {
|
||||
new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class))
|
||||
.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(HttpMessageConvertersRestClientCustomizer.class);
|
||||
assertThat(context).doesNotHaveBean(RestClientBuilderConfigurer.class);
|
||||
assertThat(context).doesNotHaveBean(RestClient.Builder.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenServletWebApplicationRestClientIsConfigured() {
|
||||
new WebApplicationContextRunner().withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class))
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(HttpMessageConvertersRestClientCustomizer.class);
|
||||
assertThat(context).hasSingleBean(RestClientBuilderConfigurer.class);
|
||||
assertThat(context).hasSingleBean(RestClient.Builder.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledForJreRange(min = JRE.JAVA_21)
|
||||
void whenReactiveWebApplicationAndVirtualThreadsEnabledAndTaskExecutorBean() {
|
||||
new ReactiveWebApplicationContextRunner().withPropertyValues("spring.threads.virtual.enabled=true")
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(RestClientAutoConfiguration.class, TaskExecutionAutoConfiguration.class))
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(HttpMessageConvertersRestClientCustomizer.class);
|
||||
assertThat(context).hasSingleBean(RestClientBuilderConfigurer.class);
|
||||
assertThat(context).hasSingleBean(RestClient.Builder.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledForJreRange(min = JRE.JAVA_21)
|
||||
void whenReactiveWebApplicationAndVirtualThreadsDisabled() {
|
||||
new ReactiveWebApplicationContextRunner().withPropertyValues("spring.threads.virtual.enabled=false")
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(RestClientAutoConfiguration.class, TaskExecutionAutoConfiguration.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(RestClient.Builder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledForJreRange(min = JRE.JAVA_21)
|
||||
void whenReactiveWebApplicationAndVirtualThreadsEnabledAndNoTaskExecutorBean() {
|
||||
new ReactiveWebApplicationContextRunner().withPropertyValues("spring.threads.virtual.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(RestClient.Builder.class));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class RestClientCustomizerConfig {
|
||||
|
||||
@Bean
|
||||
RestClientCustomizer restClientCustomizer() {
|
||||
return mock(RestClientCustomizer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomRestClientBuilderConfig {
|
||||
|
||||
@Bean
|
||||
MyRestClientBuilder myRestClientBuilder() {
|
||||
return mock(MyRestClientBuilder.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface MyRestClientBuilder extends RestClient.Builder {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class RestClientConfig {
|
||||
|
||||
@Bean
|
||||
RestClient restClient(RestClient.Builder restClientBuilder) {
|
||||
return restClientBuilder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class CustomHttpMessageConverter extends StringHttpMessageConverter {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.restclient.RestClientCustomizer;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link RestClientBuilderConfigurer}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RestClientBuilderConfigurerTests {
|
||||
|
||||
@Mock
|
||||
private ClientHttpRequestFactoryBuilder<ClientHttpRequestFactory> clientHttpRequestFactoryBuilder;
|
||||
|
||||
@Mock
|
||||
private ClientHttpRequestFactory clientHttpRequestFactory;
|
||||
|
||||
@Test
|
||||
void shouldConfigureRestClientBuilder() {
|
||||
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.ofSslBundle(mock(SslBundle.class));
|
||||
RestClientCustomizer customizer = mock(RestClientCustomizer.class);
|
||||
RestClientCustomizer customizer1 = mock(RestClientCustomizer.class);
|
||||
RestClientBuilderConfigurer configurer = new RestClientBuilderConfigurer(this.clientHttpRequestFactoryBuilder,
|
||||
settings, List.of(customizer, customizer1));
|
||||
given(this.clientHttpRequestFactoryBuilder.build(settings)).willReturn(this.clientHttpRequestFactory);
|
||||
|
||||
RestClient.Builder builder = RestClient.builder();
|
||||
configurer.configure(builder);
|
||||
assertThat(builder.build()).hasFieldOrPropertyWithValue("clientRequestFactory", this.clientHttpRequestFactory);
|
||||
then(customizer).should().customize(builder);
|
||||
then(customizer1).should().customize(builder);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.autoconfigure;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionOverrideException;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration;
|
||||
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters;
|
||||
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.restclient.RestTemplateBuilder;
|
||||
import org.springframework.boot.restclient.RestTemplateCustomizer;
|
||||
import org.springframework.boot.restclient.RestTemplateRequestCustomizer;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.mock.http.client.MockClientHttpRequest;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link RestTemplateAutoConfiguration}
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class RestTemplateAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
|
||||
AutoConfigurations.of(RestTemplateAutoConfiguration.class, HttpClientAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void restTemplateBuilderConfigurerShouldBeLazilyDefined() {
|
||||
this.contextRunner.run((context) -> assertThat(
|
||||
context.getBeanFactory().getBeanDefinition("restTemplateBuilderConfigurer").isLazyInit())
|
||||
.isTrue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailOnCustomRestTemplateBuilderConfigurer() {
|
||||
this.contextRunner.withUserConfiguration(RestTemplateBuilderConfigurerConfig.class)
|
||||
.run((context) -> assertThat(context).getFailure()
|
||||
.isInstanceOf(BeanDefinitionOverrideException.class)
|
||||
.hasMessageContaining("with name 'restTemplateBuilderConfigurer'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void restTemplateBuilderShouldBeLazilyDefined() {
|
||||
this.contextRunner
|
||||
.run((context) -> assertThat(context.getBeanFactory().getBeanDefinition("restTemplateBuilder").isLazyInit())
|
||||
.isTrue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void restTemplateWhenMessageConvertersDefinedShouldHaveMessageConverters() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
|
||||
.withUserConfiguration(RestTemplateConfig.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(RestTemplate.class);
|
||||
RestTemplate restTemplate = context.getBean(RestTemplate.class);
|
||||
List<HttpMessageConverter<?>> converters = context.getBean(HttpMessageConverters.class).getConverters();
|
||||
assertThat(restTemplate.getMessageConverters()).containsExactlyElementsOf(converters);
|
||||
assertThat(restTemplate.getRequestFactory()).isInstanceOf(HttpComponentsClientHttpRequestFactory.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void restTemplateWhenNoMessageConvertersDefinedShouldHaveDefaultMessageConverters() {
|
||||
this.contextRunner.withUserConfiguration(RestTemplateConfig.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(RestTemplate.class);
|
||||
RestTemplate restTemplate = context.getBean(RestTemplate.class);
|
||||
assertThat(restTemplate.getMessageConverters()).hasSameSizeAs(new RestTemplate().getMessageConverters());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
void restTemplateWhenHasCustomMessageConvertersShouldHaveMessageConverters() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
|
||||
.withUserConfiguration(CustomHttpMessageConverter.class, RestTemplateConfig.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(RestTemplate.class);
|
||||
RestTemplate restTemplate = context.getBean(RestTemplate.class);
|
||||
assertThat(restTemplate.getMessageConverters()).extracting(HttpMessageConverter::getClass)
|
||||
.contains((Class) CustomHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void restTemplateShouldApplyCustomizer() {
|
||||
this.contextRunner.withUserConfiguration(RestTemplateConfig.class, RestTemplateCustomizerConfig.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(RestTemplate.class);
|
||||
RestTemplate restTemplate = context.getBean(RestTemplate.class);
|
||||
RestTemplateCustomizer customizer = context.getBean(RestTemplateCustomizer.class);
|
||||
then(customizer).should().customize(restTemplate);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void restTemplateWhenHasCustomBuilderShouldUseCustomBuilder() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(RestTemplateConfig.class, CustomRestTemplateBuilderConfig.class,
|
||||
RestTemplateCustomizerConfig.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(RestTemplate.class);
|
||||
RestTemplate restTemplate = context.getBean(RestTemplate.class);
|
||||
assertThat(restTemplate.getMessageConverters()).hasSize(1);
|
||||
assertThat(restTemplate.getMessageConverters().get(0)).isInstanceOf(CustomHttpMessageConverter.class);
|
||||
then(context.getBean(RestTemplateCustomizer.class)).shouldHaveNoInteractions();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void restTemplateWhenHasCustomBuilderCouldReuseBuilderConfigurer() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(RestTemplateConfig.class, CustomRestTemplateBuilderWithConfigurerConfig.class,
|
||||
RestTemplateCustomizerConfig.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(RestTemplate.class);
|
||||
RestTemplate restTemplate = context.getBean(RestTemplate.class);
|
||||
assertThat(restTemplate.getMessageConverters()).hasSize(1);
|
||||
assertThat(restTemplate.getMessageConverters().get(0)).isInstanceOf(CustomHttpMessageConverter.class);
|
||||
RestTemplateCustomizer customizer = context.getBean(RestTemplateCustomizer.class);
|
||||
then(customizer).should().customize(restTemplate);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void restTemplateShouldApplyRequestCustomizer() {
|
||||
this.contextRunner.withUserConfiguration(RestTemplateRequestCustomizerConfig.class).run((context) -> {
|
||||
RestTemplateBuilder builder = context.getBean(RestTemplateBuilder.class);
|
||||
ClientHttpRequestFactory requestFactory = mock(ClientHttpRequestFactory.class);
|
||||
MockClientHttpRequest request = new MockClientHttpRequest();
|
||||
request.setResponse(new MockClientHttpResponse(new byte[0], HttpStatus.OK));
|
||||
given(requestFactory.createRequest(any(), any())).willReturn(request);
|
||||
RestTemplate restTemplate = builder.requestFactory(() -> requestFactory).build();
|
||||
restTemplate.getForEntity("http://localhost:8080/test", String.class);
|
||||
assertThat(request.getHeaders().headerSet()).contains(entry("spring", Collections.singletonList("boot")));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void builderShouldBeFreshForEachUse() {
|
||||
this.contextRunner.withUserConfiguration(DirtyRestTemplateConfig.class)
|
||||
.run((context) -> assertThat(context).hasNotFailed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenServletWebApplicationRestTemplateBuilderIsConfigured() {
|
||||
new WebApplicationContextRunner().withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class))
|
||||
.run((context) -> assertThat(context).hasSingleBean(RestTemplateBuilder.class)
|
||||
.hasSingleBean(RestTemplateBuilderConfigurer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenReactiveWebApplicationRestTemplateBuilderIsNotConfigured() {
|
||||
new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(RestTemplateBuilder.class)
|
||||
.doesNotHaveBean(RestTemplateBuilderConfigurer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasFactoryProperty() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
|
||||
.withUserConfiguration(RestTemplateConfig.class)
|
||||
.withPropertyValues("spring.http.client.factory=simple")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(RestTemplate.class);
|
||||
RestTemplate restTemplate = context.getBean(RestTemplate.class);
|
||||
assertThat(restTemplate.getRequestFactory()).isInstanceOf(SimpleClientHttpRequestFactory.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class RestTemplateConfig {
|
||||
|
||||
@Bean
|
||||
RestTemplate restTemplate(RestTemplateBuilder builder) {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class DirtyRestTemplateConfig {
|
||||
|
||||
@Bean
|
||||
RestTemplate restTemplateOne(RestTemplateBuilder builder) {
|
||||
try {
|
||||
return builder.build();
|
||||
}
|
||||
finally {
|
||||
breakBuilderOnNextCall(builder);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
RestTemplate restTemplateTwo(RestTemplateBuilder builder) {
|
||||
try {
|
||||
return builder.build();
|
||||
}
|
||||
finally {
|
||||
breakBuilderOnNextCall(builder);
|
||||
}
|
||||
}
|
||||
|
||||
private void breakBuilderOnNextCall(RestTemplateBuilder builder) {
|
||||
builder.additionalCustomizers((restTemplate) -> {
|
||||
throw new IllegalStateException();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomRestTemplateBuilderConfig {
|
||||
|
||||
@Bean
|
||||
RestTemplateBuilder restTemplateBuilder() {
|
||||
return new RestTemplateBuilder().messageConverters(new CustomHttpMessageConverter());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomRestTemplateBuilderWithConfigurerConfig {
|
||||
|
||||
@Bean
|
||||
RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) {
|
||||
return configurer.configure(new RestTemplateBuilder()).messageConverters(new CustomHttpMessageConverter());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class RestTemplateCustomizerConfig {
|
||||
|
||||
@Bean
|
||||
RestTemplateCustomizer restTemplateCustomizer() {
|
||||
return mock(RestTemplateCustomizer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class RestTemplateRequestCustomizerConfig {
|
||||
|
||||
@Bean
|
||||
RestTemplateRequestCustomizer<?> restTemplateRequestCustomizer() {
|
||||
return (request) -> request.getHeaders().add("spring", "boot");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class RestTemplateBuilderConfigurerConfig {
|
||||
|
||||
@Bean
|
||||
RestTemplateBuilderConfigurer restTemplateBuilderConfigurer() {
|
||||
return new RestTemplateBuilderConfigurer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class CustomHttpMessageConverter extends StringHttpMessageConverter {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.service.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.http.client.HttpRedirects;
|
||||
import org.springframework.boot.http.client.autoconfigure.AbstractHttpRequestFactoryProperties.Factory;
|
||||
import org.springframework.boot.restclient.service.autoconfigure.HttpClientServiceProperties.Group;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpClientServiceProperties}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class HttpClientServicePropertiesTests {
|
||||
|
||||
@Test
|
||||
void bindProperties() {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
environment.setProperty("spring.http.client.service.base-url", "https://example.com");
|
||||
environment.setProperty("spring.http.client.service.default-header.secure", "very,somewhat");
|
||||
environment.setProperty("spring.http.client.service.default-header.test", "true");
|
||||
environment.setProperty("spring.http.client.service.factory", "jetty");
|
||||
environment.setProperty("spring.http.client.service.redirects", "dont-follow");
|
||||
environment.setProperty("spring.http.client.service.connect-timeout", "1s");
|
||||
environment.setProperty("spring.http.client.service.read-timeout", "2s");
|
||||
environment.setProperty("spring.http.client.service.ssl.bundle", "usual");
|
||||
environment.setProperty("spring.http.client.service.group.olga.base-url", "https://example.com/olga");
|
||||
environment.setProperty("spring.http.client.service.group.olga.default-header.secure", "nope");
|
||||
environment.setProperty("spring.http.client.service.group.olga.factory", "reactor");
|
||||
environment.setProperty("spring.http.client.service.group.olga.redirects", "follow");
|
||||
environment.setProperty("spring.http.client.service.group.olga.connect-timeout", "10s");
|
||||
environment.setProperty("spring.http.client.service.group.olga.read-timeout", "20s");
|
||||
environment.setProperty("spring.http.client.service.group.olga.ssl.bundle", "unusual");
|
||||
environment.setProperty("spring.http.client.service.group.rossen.base-url", "https://example.com/rossen");
|
||||
environment.setProperty("spring.http.client.service.group.phil.base-url", "https://example.com/phil");
|
||||
try (AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext()) {
|
||||
applicationContext.setEnvironment(environment);
|
||||
applicationContext.register(PropertiesConfiguration.class);
|
||||
applicationContext.refresh();
|
||||
HttpClientServiceProperties properties = applicationContext.getBean(HttpClientServiceProperties.class);
|
||||
assertThat(properties.getBaseUrl()).isEqualTo("https://example.com");
|
||||
assertThat(properties.getDefaultHeader()).containsOnly(Map.entry("secure", List.of("very", "somewhat")),
|
||||
Map.entry("test", List.of("true")));
|
||||
assertThat(properties.getFactory()).isEqualTo(Factory.JETTY);
|
||||
assertThat(properties.getRedirects()).isEqualTo(HttpRedirects.DONT_FOLLOW);
|
||||
assertThat(properties.getConnectTimeout()).isEqualTo(Duration.ofSeconds(1));
|
||||
assertThat(properties.getReadTimeout()).isEqualTo(Duration.ofSeconds(2));
|
||||
assertThat(properties.getSsl().getBundle()).isEqualTo("usual");
|
||||
assertThat(properties.getGroup()).containsOnlyKeys("olga", "rossen", "phil");
|
||||
assertThat(properties.getGroup().get("olga").getBaseUrl()).isEqualTo("https://example.com/olga");
|
||||
assertThat(properties.getGroup().get("rossen").getBaseUrl()).isEqualTo("https://example.com/rossen");
|
||||
assertThat(properties.getGroup().get("phil").getBaseUrl()).isEqualTo("https://example.com/phil");
|
||||
Group groupProperties = properties.getGroup().get("olga");
|
||||
assertThat(groupProperties.getDefaultHeader()).containsOnly(Map.entry("secure", List.of("nope")));
|
||||
assertThat(groupProperties.getFactory()).isEqualTo(Factory.REACTOR);
|
||||
assertThat(groupProperties.getRedirects()).isEqualTo(HttpRedirects.FOLLOW);
|
||||
assertThat(groupProperties.getConnectTimeout()).isEqualTo(Duration.ofSeconds(10));
|
||||
assertThat(groupProperties.getReadTimeout()).isEqualTo(Duration.ofSeconds(20));
|
||||
assertThat(groupProperties.getSsl().getBundle()).isEqualTo("unusual");
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(HttpClientServiceProperties.class)
|
||||
static class PropertiesConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.restclient.service.autoconfigure;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpClient.Redirect;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.extractor.Extractors;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aop.Advisor;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.http.client.HttpRedirects;
|
||||
import org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration;
|
||||
import org.springframework.boot.restclient.RestClientCustomizer;
|
||||
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.web.client.MockRestServiceServer;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestClient.Builder;
|
||||
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
|
||||
import org.springframework.web.service.annotation.GetExchange;
|
||||
import org.springframework.web.service.registry.HttpServiceGroup;
|
||||
import org.springframework.web.service.registry.HttpServiceProxyRegistry;
|
||||
import org.springframework.web.service.registry.ImportHttpServices;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
|
||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
|
||||
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpServiceClientAutoConfiguration},
|
||||
* {@link RestClientPropertiesHttpServiceGroupConfigurer} and
|
||||
* {@link RestClientCustomizerHttpServiceGroupConfigurer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class HttpServiceClientAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(HttpServiceClientAutoConfiguration.class,
|
||||
HttpClientAutoConfiguration.class, RestClientAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void configuresClientFromProperties() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.http.client.service.base-url=https://example.com",
|
||||
"spring.http.client.service.default-header.test=true",
|
||||
"spring.http.client.service.group.one.base-url=https://example.com/one",
|
||||
"spring.http.client.service.group.two.default-header.two=iam2")
|
||||
.withUserConfiguration(HttpClientConfiguration.class, MockRestServiceServerConfiguration.class)
|
||||
.run((context) -> {
|
||||
HttpServiceProxyRegistry serviceProxyRegistry = context.getBean(HttpServiceProxyRegistry.class);
|
||||
assertThat(serviceProxyRegistry.getGroupNames()).containsOnly("one", "two");
|
||||
MockRestServiceServerConfiguration mockServers = context
|
||||
.getBean(MockRestServiceServerConfiguration.class);
|
||||
MockRestServiceServer serverOne = mockServers.getMock("one");
|
||||
serverOne.expect(requestTo("https://example.com/one/hello"))
|
||||
.andExpect(header("test", "true"))
|
||||
.andRespond(withSuccess().body("world!"));
|
||||
TestClientOne clientOne = context.getBean(TestClientOne.class);
|
||||
assertThat(clientOne.hello()).isEqualTo("world!");
|
||||
MockRestServiceServer serverTwo = mockServers.getMock("two");
|
||||
serverTwo.expect((request) -> request.getURI().toString().equals("https://example.com/"))
|
||||
.andExpect(header("test", "true"))
|
||||
.andExpect(header("two", "iam2"))
|
||||
.andRespond(withSuccess().body("boot!"));
|
||||
TestClientTwo clientTwo = context.getBean(TestClientTwo.class);
|
||||
assertThat(clientTwo.there()).isEqualTo("boot!");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasUserDefinedRequestFactoryBuilder() {
|
||||
this.contextRunner.withPropertyValues("spring.http.client.service.base-url=https://example.com")
|
||||
.withUserConfiguration(HttpClientConfiguration.class, RequestFactoryBuilderConfiguration.class)
|
||||
.run((context) -> {
|
||||
TestClientOne clientOne = context.getBean(TestClientOne.class);
|
||||
assertThat(getJdkHttpClient(clientOne).followRedirects()).isEqualTo(Redirect.NEVER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasUserDefinedRequestFactorySettings() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.http.client.service.base-url=https://example.com",
|
||||
"spring.http.client.factory=jdk")
|
||||
.withUserConfiguration(HttpClientConfiguration.class, RequestFactorySettingsConfiguration.class)
|
||||
.run((context) -> {
|
||||
TestClientOne clientOne = context.getBean(TestClientOne.class);
|
||||
assertThat(getJdkHttpClient(clientOne).followRedirects()).isEqualTo(Redirect.NEVER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasUserDefinedRestClientCustomizer() {
|
||||
this.contextRunner.withPropertyValues("spring.http.client.service.base-url=https://example.com")
|
||||
.withUserConfiguration(HttpClientConfiguration.class, MockRestServiceServerConfiguration.class,
|
||||
RestClientCustomizerConfiguration.class)
|
||||
.run((context) -> {
|
||||
MockRestServiceServerConfiguration mockServers = context
|
||||
.getBean(MockRestServiceServerConfiguration.class);
|
||||
MockRestServiceServer serverOne = mockServers.getMock("one");
|
||||
serverOne.expect(requestTo("https://example.com/hello"))
|
||||
.andExpect(header("customized", "true"))
|
||||
.andRespond(withSuccess().body("world!"));
|
||||
TestClientOne clientOne = context.getBean(TestClientOne.class);
|
||||
assertThat(clientOne.hello()).isEqualTo("world!");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasUserDefinedHttpServiceGroupConfigurer() {
|
||||
this.contextRunner.withPropertyValues("spring.http.client.service.base-url=https://example.com")
|
||||
.withUserConfiguration(HttpClientConfiguration.class, MockRestServiceServerConfiguration.class,
|
||||
HttpServiceGroupConfigurerConfiguration.class)
|
||||
.run((context) -> {
|
||||
MockRestServiceServerConfiguration mockServers = context
|
||||
.getBean(MockRestServiceServerConfiguration.class);
|
||||
MockRestServiceServer serverOne = mockServers.getMock("one");
|
||||
serverOne.expect(requestTo("https://example.com/hello"))
|
||||
.andExpect(header("customizedgroup", "true"))
|
||||
.andRespond(withSuccess().body("world!"));
|
||||
TestClientOne clientOne = context.getBean(TestClientOne.class);
|
||||
assertThat(clientOne.hello()).isEqualTo("world!");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasNoHttpServiceProxyRegistryBean() {
|
||||
this.contextRunner.withPropertyValues("spring.http.client.service.base-url=https://example.com")
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(HttpServiceProxyRegistry.class));
|
||||
}
|
||||
|
||||
private HttpClient getJdkHttpClient(Object proxy) {
|
||||
return (HttpClient) Extractors.byName("clientRequestFactory.httpClient").apply(getRestClient(proxy));
|
||||
}
|
||||
|
||||
private RestClient getRestClient(Object proxy) {
|
||||
InvocationHandler handler = Proxy.getInvocationHandler(proxy);
|
||||
Advisor[] advisors = (Advisor[]) Extractors.byName("advised.advisors").apply(handler);
|
||||
Map<?, ?> serviceMethods = (Map<?, ?>) Extractors.byName("advice.httpServiceMethods").apply(advisors[0]);
|
||||
Object serviceMethod = serviceMethods.values().iterator().next();
|
||||
return (RestClient) Extractors.byName("responseFunction.responseFunction.arg$1.restClient")
|
||||
.apply(serviceMethod);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class MockRestServiceServerConfiguration {
|
||||
|
||||
private Map<String, MockRestServiceServer> mocks = new HashMap<>();
|
||||
|
||||
@Bean
|
||||
RestClientHttpServiceGroupConfigurer mockServerConfigurer() {
|
||||
return (groups) -> groups.forEachClient(this::addMock);
|
||||
}
|
||||
|
||||
private MockRestServiceServer addMock(HttpServiceGroup group, Builder client) {
|
||||
return this.mocks.put(group.name(), MockRestServiceServer.bindTo(client).build());
|
||||
}
|
||||
|
||||
MockRestServiceServer getMock(String name) {
|
||||
return this.mocks.get(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ImportHttpServices(group = "one", types = TestClientOne.class)
|
||||
@ImportHttpServices(group = "two", types = TestClientTwo.class)
|
||||
static class HttpClientConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class RequestFactoryBuilderConfiguration {
|
||||
|
||||
@Bean
|
||||
ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder() {
|
||||
return ClientHttpRequestFactoryBuilder.jdk()
|
||||
.withHttpClientCustomizer((httpClient) -> httpClient.followRedirects(Redirect.NEVER));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class RequestFactorySettingsConfiguration {
|
||||
|
||||
@Bean
|
||||
ClientHttpRequestFactorySettings requestFactorySettings() {
|
||||
return ClientHttpRequestFactorySettings.defaults().withRedirects(HttpRedirects.DONT_FOLLOW);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class RestClientCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
RestClientCustomizer restClientCustomizer() {
|
||||
return (builder) -> builder.defaultHeader("customized", "true");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class HttpServiceGroupConfigurerConfiguration {
|
||||
|
||||
@Bean
|
||||
RestClientHttpServiceGroupConfigurer restClientHttpServiceGroupConfigurer() {
|
||||
return (groups) -> groups.filterByName("one")
|
||||
.forEachClient((group, builder) -> builder.defaultHeader("customizedgroup", "true"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface TestClientOne {
|
||||
|
||||
@GetExchange("/hello")
|
||||
String hello();
|
||||
|
||||
}
|
||||
|
||||
interface TestClientTwo {
|
||||
|
||||
@GetExchange("/there")
|
||||
String there();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user