Browse Source

Merge branch '3.5.x'

Closes gh-47946
pull/47947/head
Phillip Webb 2 months ago
parent
commit
00bd0efc56
  1. 24
      module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/HttpComponentsClientHttpRequestFactoryBuilder.java
  2. 56
      module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/HttpComponentsHttpClientBuilder.java
  3. 30
      module/spring-boot-http-client/src/test/java/org/springframework/boot/http/client/HttpComponentsClientHttpRequestFactoryBuilderTests.java
  4. 4
      module/spring-boot-http-client/src/test/java/org/springframework/boot/http/client/autoconfigure/imperative/ImperativeHttpClientAutoConfigurationTests.java
  5. 17
      module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/RestTemplateBuilderTests.java
  6. 27
      module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfigurationTests.java

24
module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/HttpComponentsClientHttpRequestFactoryBuilder.java

@ -16,13 +16,13 @@ @@ -16,13 +16,13 @@
package org.springframework.boot.http.client;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
@ -30,7 +30,6 @@ import org.apache.hc.client5.http.ssl.TlsSocketStrategy; @@ -30,7 +30,6 @@ import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
import org.apache.hc.core5.http.io.SocketConfig;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.http.client.HttpComponentsHttpClientBuilder.TlsSocketStrategyFactory;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
@ -115,6 +114,20 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder @@ -115,6 +114,20 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
this.httpClientBuilder.withSocketConfigCustomizer(socketConfigCustomizer));
}
/**
* Return a new {@link HttpComponentsHttpClientBuilder} that applies additional
* customization to the underlying
* {@link org.apache.hc.client5.http.config.ConnectionConfig.Builder}.
* @param connectionConfigCustomizer the customizer to apply
* @return a new {@link HttpComponentsHttpClientBuilder} instance
*/
public HttpComponentsClientHttpRequestFactoryBuilder withConnectionConfigCustomizer(
Consumer<ConnectionConfig.Builder> connectionConfigCustomizer) {
Assert.notNull(connectionConfigCustomizer, "'connectionConfigCustomizer' must not be null");
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(),
this.httpClientBuilder.withConnectionConfigCustomizer(connectionConfigCustomizer));
}
/**
* Return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} with a
* replacement {@link TlsSocketStrategy} factory.
@ -158,11 +171,8 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder @@ -158,11 +171,8 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
@Override
protected HttpComponentsClientHttpRequestFactory createClientHttpRequestFactory(HttpClientSettings settings) {
HttpClient httpClient = this.httpClientBuilder.build(settings.withConnectTimeout(null));
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
PropertyMapper map = PropertyMapper.get();
map.from(settings::connectTimeout).asInt(Duration::toMillis).to(factory::setConnectTimeout);
return factory;
HttpClient httpClient = this.httpClientBuilder.build(settings);
return new HttpComponentsClientHttpRequestFactory(httpClient);
}
static class Classes {

56
module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/HttpComponentsHttpClientBuilder.java

@ -21,6 +21,7 @@ import java.time.Duration; @@ -21,6 +21,7 @@ import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
@ -52,23 +53,27 @@ public final class HttpComponentsHttpClientBuilder { @@ -52,23 +53,27 @@ public final class HttpComponentsHttpClientBuilder {
private final Consumer<SocketConfig.Builder> socketConfigCustomizer;
private final Consumer<ConnectionConfig.Builder> connectionConfigCustomizer;
private final Consumer<RequestConfig.Builder> defaultRequestConfigCustomizer;
private final TlsSocketStrategyFactory tlsSocketStrategyFactory;
public HttpComponentsHttpClientBuilder() {
this(Empty.consumer(), Empty.consumer(), Empty.consumer(), Empty.consumer(),
this(Empty.consumer(), Empty.consumer(), Empty.consumer(), Empty.consumer(), Empty.consumer(),
HttpComponentsSslBundleTlsStrategy::get);
}
private HttpComponentsHttpClientBuilder(Consumer<HttpClientBuilder> customizer,
Consumer<PoolingHttpClientConnectionManagerBuilder> connectionManagerCustomizer,
Consumer<SocketConfig.Builder> socketConfigCustomizer,
Consumer<ConnectionConfig.Builder> connectionConfigCustomizer,
Consumer<RequestConfig.Builder> defaultRequestConfigCustomizer,
TlsSocketStrategyFactory tlsSocketStrategyFactory) {
this.customizer = customizer;
this.connectionManagerCustomizer = connectionManagerCustomizer;
this.socketConfigCustomizer = socketConfigCustomizer;
this.connectionConfigCustomizer = connectionConfigCustomizer;
this.defaultRequestConfigCustomizer = defaultRequestConfigCustomizer;
this.tlsSocketStrategyFactory = tlsSocketStrategyFactory;
}
@ -82,8 +87,8 @@ public final class HttpComponentsHttpClientBuilder { @@ -82,8 +87,8 @@ public final class HttpComponentsHttpClientBuilder {
public HttpComponentsHttpClientBuilder withCustomizer(Consumer<HttpClientBuilder> customizer) {
Assert.notNull(customizer, "'customizer' must not be null");
return new HttpComponentsHttpClientBuilder(this.customizer.andThen(customizer),
this.connectionManagerCustomizer, this.socketConfigCustomizer, this.defaultRequestConfigCustomizer,
this.tlsSocketStrategyFactory);
this.connectionManagerCustomizer, this.socketConfigCustomizer, this.connectionConfigCustomizer,
this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
}
/**
@ -97,7 +102,7 @@ public final class HttpComponentsHttpClientBuilder { @@ -97,7 +102,7 @@ public final class HttpComponentsHttpClientBuilder {
Assert.notNull(connectionManagerCustomizer, "'connectionManagerCustomizer' must not be null");
return new HttpComponentsHttpClientBuilder(this.customizer,
this.connectionManagerCustomizer.andThen(connectionManagerCustomizer), this.socketConfigCustomizer,
this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
this.connectionConfigCustomizer, this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
}
/**
@ -111,8 +116,23 @@ public final class HttpComponentsHttpClientBuilder { @@ -111,8 +116,23 @@ public final class HttpComponentsHttpClientBuilder {
Consumer<SocketConfig.Builder> socketConfigCustomizer) {
Assert.notNull(socketConfigCustomizer, "'socketConfigCustomizer' must not be null");
return new HttpComponentsHttpClientBuilder(this.customizer, this.connectionManagerCustomizer,
this.socketConfigCustomizer.andThen(socketConfigCustomizer), this.defaultRequestConfigCustomizer,
this.tlsSocketStrategyFactory);
this.socketConfigCustomizer.andThen(socketConfigCustomizer), this.connectionConfigCustomizer,
this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
}
/**
* Return a new {@link HttpComponentsHttpClientBuilder} that applies additional
* customization to the underlying
* {@link org.apache.hc.client5.http.config.ConnectionConfig.Builder}.
* @param connectionConfigCustomizer the customizer to apply
* @return a new {@link HttpComponentsHttpClientBuilder} instance
*/
public HttpComponentsHttpClientBuilder withConnectionConfigCustomizer(
Consumer<ConnectionConfig.Builder> connectionConfigCustomizer) {
Assert.notNull(connectionConfigCustomizer, "'connectionConfigCustomizer' must not be null");
return new HttpComponentsHttpClientBuilder(this.customizer, this.connectionManagerCustomizer,
this.socketConfigCustomizer.andThen(this.socketConfigCustomizer), this.connectionConfigCustomizer,
this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
}
/**
@ -128,7 +148,8 @@ public final class HttpComponentsHttpClientBuilder { @@ -128,7 +148,8 @@ public final class HttpComponentsHttpClientBuilder {
TlsSocketStrategyFactory tlsSocketStrategyFactory) {
Assert.notNull(tlsSocketStrategyFactory, "'tlsSocketStrategyFactory' must not be null");
return new HttpComponentsHttpClientBuilder(this.customizer, this.connectionManagerCustomizer,
this.socketConfigCustomizer, this.defaultRequestConfigCustomizer, tlsSocketStrategyFactory);
this.socketConfigCustomizer, this.connectionConfigCustomizer, this.defaultRequestConfigCustomizer,
tlsSocketStrategyFactory);
}
/**
@ -143,7 +164,7 @@ public final class HttpComponentsHttpClientBuilder { @@ -143,7 +164,7 @@ public final class HttpComponentsHttpClientBuilder {
Consumer<RequestConfig.Builder> defaultRequestConfigCustomizer) {
Assert.notNull(defaultRequestConfigCustomizer, "'defaultRequestConfigCustomizer' must not be null");
return new HttpComponentsHttpClientBuilder(this.customizer, this.connectionManagerCustomizer,
this.socketConfigCustomizer,
this.socketConfigCustomizer, this.connectionConfigCustomizer,
this.defaultRequestConfigCustomizer.andThen(defaultRequestConfigCustomizer),
this.tlsSocketStrategyFactory);
}
@ -155,7 +176,6 @@ public final class HttpComponentsHttpClientBuilder { @@ -155,7 +176,6 @@ public final class HttpComponentsHttpClientBuilder {
*/
public CloseableHttpClient build(@Nullable HttpClientSettings settings) {
settings = (settings != null) ? settings : HttpClientSettings.defaults();
Assert.isTrue(settings.connectTimeout() == null, "'settings' must not have a 'connectTimeout'");
HttpClientBuilder builder = HttpClientBuilder.create()
.useSystemProperties()
.setRedirectStrategy(HttpComponentsRedirectStrategy.get(settings.redirects()))
@ -169,7 +189,8 @@ public final class HttpComponentsHttpClientBuilder { @@ -169,7 +189,8 @@ public final class HttpComponentsHttpClientBuilder {
PoolingHttpClientConnectionManagerBuilder builder = PoolingHttpClientConnectionManagerBuilder.create()
.useSystemProperties();
PropertyMapper map = PropertyMapper.get();
builder.setDefaultSocketConfig(createSocketConfig(settings));
builder.setDefaultSocketConfig(createSocketConfig());
builder.setDefaultConnectionConfig(createConnectionConfig(settings));
map.from(settings::sslBundle)
.always()
.as(this.tlsSocketStrategyFactory::getTlsSocketStrategy)
@ -178,13 +199,22 @@ public final class HttpComponentsHttpClientBuilder { @@ -178,13 +199,22 @@ public final class HttpComponentsHttpClientBuilder {
return builder.build();
}
private SocketConfig createSocketConfig(HttpClientSettings settings) {
private SocketConfig createSocketConfig() {
SocketConfig.Builder builder = SocketConfig.custom();
this.socketConfigCustomizer.accept(builder);
return builder.build();
}
private ConnectionConfig createConnectionConfig(HttpClientSettings settings) {
ConnectionConfig.Builder builder = ConnectionConfig.custom();
PropertyMapper map = PropertyMapper.get();
map.from(settings::connectTimeout)
.as(Duration::toMillis)
.to((timeout) -> builder.setConnectTimeout(timeout, TimeUnit.MILLISECONDS));
map.from(settings::readTimeout)
.asInt(Duration::toMillis)
.to((timeout) -> builder.setSoTimeout(timeout, TimeUnit.MILLISECONDS));
this.socketConfigCustomizer.accept(builder);
.to((timeout) -> builder.setSocketTimeout(timeout, TimeUnit.MILLISECONDS));
this.connectionConfigCustomizer.accept(builder);
return builder.build();
}

30
module/spring-boot-http-client/src/test/java/org/springframework/boot/http/client/HttpComponentsClientHttpRequestFactoryBuilderTests.java

@ -20,8 +20,9 @@ import java.util.ArrayList; @@ -20,8 +20,9 @@ import java.util.ArrayList;
import java.util.List;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.core5.function.Resolver;
@ -103,22 +104,25 @@ class HttpComponentsClientHttpRequestFactoryBuilderTests @@ -103,22 +104,25 @@ class HttpComponentsClientHttpRequestFactoryBuilderTests
@Override
protected long connectTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
Object field = ReflectionTestUtils.getField(requestFactory, "connectTimeout");
assertThat(field).isNotNull();
return (long) field;
return getConnectorConfig(requestFactory).getConnectTimeout().toMilliseconds();
}
@Override
@SuppressWarnings("unchecked")
protected long readTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
HttpClient httpClient = requestFactory.getHttpClient();
Object connectionManager = ReflectionTestUtils.getField(httpClient, "connManager");
assertThat(connectionManager).isNotNull();
Resolver<HttpRoute, SocketConfig> socketConfigResolver = (Resolver<HttpRoute, SocketConfig>) ReflectionTestUtils
.getField(connectionManager, "socketConfigResolver");
assertThat(socketConfigResolver).isNotNull();
SocketConfig socketConfig = socketConfigResolver.resolve(null);
return socketConfig.getSoTimeout().toMilliseconds();
return getConnectorConfig(requestFactory).getSocketTimeout().toMilliseconds();
}
@SuppressWarnings("unchecked")
private ConnectionConfig getConnectorConfig(HttpComponentsClientHttpRequestFactory requestFactory) {
CloseableHttpClient httpClient = (CloseableHttpClient) ReflectionTestUtils.getField(requestFactory,
"httpClient");
assertThat(httpClient).isNotNull();
Object manager = ReflectionTestUtils.getField(httpClient, "connManager");
assertThat(manager).isNotNull();
Resolver<HttpRoute, ConnectionConfig> resolver = (Resolver<HttpRoute, ConnectionConfig>) ReflectionTestUtils
.getField(manager, "connectionConfigResolver");
assertThat(resolver).isNotNull();
return resolver.resolve(null);
}
}

4
module/spring-boot-http-client/src/test/java/org/springframework/boot/http/client/autoconfigure/imperative/ImperativeHttpClientAutoConfigurationTests.java

@ -138,7 +138,7 @@ class ImperativeHttpClientAutoConfigurationTests { @@ -138,7 +138,7 @@ class ImperativeHttpClientAutoConfigurationTests {
this.contextRunner.withUserConfiguration(ClientHttpRequestFactoryBuilderCustomizersConfiguration.class)
.run((context) -> {
ClientHttpRequestFactory factory = context.getBean(ClientHttpRequestFactoryBuilder.class).build();
assertThat(factory).extracting("connectTimeout").isEqualTo(5L);
assertThat(factory).extracting("readTimeout").isEqualTo(5L);
});
}
@ -160,7 +160,7 @@ class ImperativeHttpClientAutoConfigurationTests { @@ -160,7 +160,7 @@ class ImperativeHttpClientAutoConfigurationTests {
@Bean
ClientHttpRequestFactoryBuilderCustomizer<HttpComponentsClientHttpRequestFactoryBuilder> httpComponentsCustomizer() {
return (builder) -> builder.withCustomizer((factory) -> factory.setConnectTimeout(5));
return (builder) -> builder.withCustomizer((factory) -> factory.setReadTimeout(5));
}
@Bean

17
module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/RestTemplateBuilderTests.java

@ -24,6 +24,11 @@ import java.util.Collections; @@ -24,6 +24,11 @@ import java.util.Collections;
import java.util.Set;
import java.util.function.Supplier;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.core5.function.Resolver;
import org.apache.hc.core5.util.Timeout;
import org.assertj.core.extractor.Extractors;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InOrder;
@ -354,10 +359,18 @@ class RestTemplateBuilderTests { @@ -354,10 +359,18 @@ class RestTemplateBuilderTests {
}
@Test
@SuppressWarnings("unchecked")
void clientSettingsAppliesSettings() {
HttpClientSettings settings = HttpClientSettings.defaults().withConnectTimeout(Duration.ofSeconds(1));
HttpClientSettings settings = HttpClientSettings.defaults()
.withConnectTimeout(Duration.ofSeconds(1))
.withReadTimeout(Duration.ofSeconds(2));
RestTemplate template = this.builder.clientSettings(settings).build();
assertThat(template.getRequestFactory()).extracting("connectTimeout").isEqualTo(1000L);
Resolver<HttpRoute, ConnectionConfig> resolver = (Resolver<HttpRoute, ConnectionConfig>) Extractors
.byName("httpClient.connManager.connectionConfigResolver")
.apply(template.getRequestFactory());
ConnectionConfig config = resolver.resolve(mock());
assertThat(config.getConnectTimeout()).isEqualTo(Timeout.of(Duration.ofSeconds(1)));
assertThat(config.getSocketTimeout()).isEqualTo(Timeout.of(Duration.ofSeconds(2)));
}
@Test

27
module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfigurationTests.java

@ -20,9 +20,15 @@ import java.lang.reflect.InvocationHandler; @@ -20,9 +20,15 @@ import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Redirect;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.core5.function.Resolver;
import org.apache.hc.core5.util.Timeout;
import org.assertj.core.extractor.Extractors;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
@ -40,6 +46,8 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -40,6 +46,8 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClient.Builder;
@ -128,8 +136,23 @@ class HttpServiceClientAutoConfigurationTests { @@ -128,8 +136,23 @@ class HttpServiceClientAutoConfigurationTests {
callback.withClient(group, builder);
ArgumentCaptor<ClientHttpRequestFactory> requestFactoryCaptor = ArgumentCaptor.captor();
then(builder).should().requestFactory(requestFactoryCaptor.capture());
ClientHttpRequestFactory client = requestFactoryCaptor.getValue();
assertThat(client).extracting("connectTimeout").isEqualTo(expectedReadTimeout);
HttpComponentsClientHttpRequestFactory client = (HttpComponentsClientHttpRequestFactory) requestFactoryCaptor
.getValue();
assertThat(getConnectorConfig(client).getConnectTimeout())
.isEqualTo(Timeout.of(Duration.ofMillis(expectedReadTimeout)));
}
@SuppressWarnings("unchecked")
private ConnectionConfig getConnectorConfig(HttpComponentsClientHttpRequestFactory requestFactory) {
CloseableHttpClient httpClient = (CloseableHttpClient) ReflectionTestUtils.getField(requestFactory,
"httpClient");
assertThat(httpClient).isNotNull();
Object manager = ReflectionTestUtils.getField(httpClient, "connManager");
assertThat(manager).isNotNull();
Resolver<HttpRoute, ConnectionConfig> resolver = (Resolver<HttpRoute, ConnectionConfig>) ReflectionTestUtils
.getField(manager, "connectionConfigResolver");
assertThat(resolver).isNotNull();
return resolver.resolve(null);
}
@Test

Loading…
Cancel
Save