Allow customization of underlying ClientHttpRequestFactory components

Update `ClientHttpRequestFactoryBuilder` implementations for
`HttpComponents`, `Jdk`, `Jetty` and `Reactor` to allow customization
of the underlying components.

Closes gh-39035
This commit is contained in:
Phillip Webb
2024-10-25 16:52:22 -07:00
parent 97b20e9a98
commit e1b5935507
10 changed files with 385 additions and 40 deletions

View File

@@ -35,12 +35,20 @@ import org.springframework.util.Assert;
abstract class AbstractClientHttpRequestFactoryBuilder<T extends ClientHttpRequestFactory>
implements ClientHttpRequestFactoryBuilder<T> {
private static final Consumer<?> EMPTY_CUSTOMIZER = (t) -> {
};
private final List<Consumer<T>> customizers;
protected AbstractClientHttpRequestFactoryBuilder(List<Consumer<T>> customizers) {
this.customizers = (customizers != null) ? customizers : Collections.emptyList();
}
@SuppressWarnings("unchecked")
protected static <T> Consumer<T> emptyCustomizer() {
return (Consumer<T>) EMPTY_CUSTOMIZER;
}
protected final List<Consumer<T>> getCustomizers() {
return this.customizers;
}

View File

@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
@@ -32,6 +33,7 @@ import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuil
import org.apache.hc.client5.http.protocol.RedirectStrategy;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier;
import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.io.SocketConfig;
@@ -42,6 +44,7 @@ import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Red
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslOptions;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
@@ -55,25 +58,104 @@ import org.springframework.util.ClassUtils;
public final class HttpComponentsClientHttpRequestFactoryBuilder
extends AbstractClientHttpRequestFactoryBuilder<HttpComponentsClientHttpRequestFactory> {
private final Consumer<HttpClientBuilder> httpClientCustomizer;
private final Consumer<PoolingHttpClientConnectionManagerBuilder> connectionManagerCustomizer;
private final Consumer<SocketConfig.Builder> socketConfigCustomizer;
private final Function<SslBundle, TlsSocketStrategy> tlsSocketStrategyFactory;
HttpComponentsClientHttpRequestFactoryBuilder() {
this(Collections.emptyList());
this(Collections.emptyList(), emptyCustomizer(), emptyCustomizer(), emptyCustomizer(),
HttpComponentsClientHttpRequestFactoryBuilder::createTlsSocketStrategy);
}
private HttpComponentsClientHttpRequestFactoryBuilder(
List<Consumer<HttpComponentsClientHttpRequestFactory>> customizers) {
List<Consumer<HttpComponentsClientHttpRequestFactory>> customizers,
Consumer<HttpClientBuilder> httpClientCustomizer,
Consumer<PoolingHttpClientConnectionManagerBuilder> connectionManagerCustomizer,
Consumer<SocketConfig.Builder> socketConfigCustomizer,
Function<SslBundle, TlsSocketStrategy> tlsSocketStrategyFactory) {
super(customizers);
this.httpClientCustomizer = httpClientCustomizer;
this.connectionManagerCustomizer = connectionManagerCustomizer;
this.socketConfigCustomizer = socketConfigCustomizer;
this.tlsSocketStrategyFactory = tlsSocketStrategyFactory;
}
@Override
public HttpComponentsClientHttpRequestFactoryBuilder withCustomizer(
Consumer<HttpComponentsClientHttpRequestFactory> customizer) {
return new HttpComponentsClientHttpRequestFactoryBuilder(mergedCustomizers(customizer));
return new HttpComponentsClientHttpRequestFactoryBuilder(mergedCustomizers(customizer),
this.httpClientCustomizer, this.connectionManagerCustomizer, this.socketConfigCustomizer,
this.tlsSocketStrategyFactory);
}
@Override
public HttpComponentsClientHttpRequestFactoryBuilder withCustomizers(
Collection<Consumer<HttpComponentsClientHttpRequestFactory>> customizers) {
return new HttpComponentsClientHttpRequestFactoryBuilder(mergedCustomizers(customizers));
return new HttpComponentsClientHttpRequestFactoryBuilder(mergedCustomizers(customizers),
this.httpClientCustomizer, this.connectionManagerCustomizer, this.socketConfigCustomizer,
this.tlsSocketStrategyFactory);
}
/**
* Return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} that applies
* additional customization to the underlying {@link HttpClientBuilder}.
* @param httpClientCustomizer the customizer to apply
* @return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} instance
*/
public HttpComponentsClientHttpRequestFactoryBuilder withHttpClientCustomizer(
Consumer<HttpClientBuilder> httpClientCustomizer) {
Assert.notNull(httpClientCustomizer, "'httpClientCustomizer' must not be null");
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(),
this.httpClientCustomizer.andThen(httpClientCustomizer), this.connectionManagerCustomizer,
this.socketConfigCustomizer, this.tlsSocketStrategyFactory);
}
/**
* Return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} that applies
* additional customization to the underlying
* {@link PoolingHttpClientConnectionManagerBuilder}.
* @param connectionManagerCustomizer the customizer to apply
* @return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} instance
*/
public HttpComponentsClientHttpRequestFactoryBuilder withConnectionManagerCustomizer(
Consumer<PoolingHttpClientConnectionManagerBuilder> connectionManagerCustomizer) {
Assert.notNull(connectionManagerCustomizer, "'connectionManagerCustomizer' must not be null");
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(), this.httpClientCustomizer,
this.connectionManagerCustomizer.andThen(connectionManagerCustomizer), this.socketConfigCustomizer,
this.tlsSocketStrategyFactory);
}
/**
* Return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} that applies
* additional customization to the underlying
* {@link org.apache.hc.core5.http.io.SocketConfig.Builder}.
* @param socketConfigCustomizer the customizer to apply
* @return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} instance
*/
public HttpComponentsClientHttpRequestFactoryBuilder withSocketConfigCustomizer(
Consumer<SocketConfig.Builder> socketConfigCustomizer) {
Assert.notNull(socketConfigCustomizer, "'socketConfigCustomizer' must not be null");
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(), this.httpClientCustomizer,
this.connectionManagerCustomizer, this.socketConfigCustomizer.andThen(socketConfigCustomizer),
this.tlsSocketStrategyFactory);
}
/**
* Return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} with a
* replacement {@link TlsSocketStrategy} factory.
* @param tlsSocketStrategyFactory the new factory used to create a
* {@link TlsSocketStrategy} for a given {@link SslBundle}
* @return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} instance
*/
public HttpComponentsClientHttpRequestFactoryBuilder withTlsSocketStrategyFactory(
Function<SslBundle, TlsSocketStrategy> tlsSocketStrategyFactory) {
Assert.notNull(tlsSocketStrategyFactory, "'tlsSocketStrategyFactory' must not be null");
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(), this.httpClientCustomizer,
this.connectionManagerCustomizer, this.socketConfigCustomizer, tlsSocketStrategyFactory);
}
@Override
@@ -87,11 +169,12 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
}
private HttpClient createHttpClient(ClientHttpRequestFactorySettings settings) {
return HttpClientBuilder.create()
HttpClientBuilder builder = HttpClientBuilder.create()
.useSystemProperties()
.setRedirectStrategy(asRedirectStrategy(settings.redirects()))
.setConnectionManager(createConnectionManager(settings))
.build();
.setConnectionManager(createConnectionManager(settings));
this.httpClientCustomizer.accept(builder);
return builder.build();
}
private RedirectStrategy asRedirectStrategy(Redirects redirects) {
@@ -102,23 +185,31 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
}
private PoolingHttpClientConnectionManager createConnectionManager(ClientHttpRequestFactorySettings settings) {
PoolingHttpClientConnectionManagerBuilder builder = PoolingHttpClientConnectionManagerBuilder.create();
PoolingHttpClientConnectionManagerBuilder builder = PoolingHttpClientConnectionManagerBuilder.create()
.useSystemProperties();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(settings::readTimeout).as(this::createSocketConfig).to(builder::setDefaultSocketConfig);
map.from(settings::sslBundle).as(this::createTlsSocketStrategy).to(builder::setTlsSocketStrategy);
return builder.useSystemProperties().build();
builder.setDefaultSocketConfig(createSocketConfig(settings));
map.from(settings::sslBundle).as(this.tlsSocketStrategyFactory).to(builder::setTlsSocketStrategy);
this.connectionManagerCustomizer.accept(builder);
return builder.build();
}
private DefaultClientTlsStrategy createTlsSocketStrategy(SslBundle sslBundle) {
private SocketConfig createSocketConfig(ClientHttpRequestFactorySettings settings) {
SocketConfig.Builder builder = SocketConfig.custom();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(settings::readTimeout)
.asInt(Duration::toMillis)
.to((timeout) -> builder.setSoTimeout(timeout, TimeUnit.MILLISECONDS));
this.socketConfigCustomizer.accept(builder);
return builder.build();
}
private static TlsSocketStrategy createTlsSocketStrategy(SslBundle sslBundle) {
SslOptions options = sslBundle.getOptions();
return new DefaultClientTlsStrategy(sslBundle.createSslContext(), options.getEnabledProtocols(),
options.getCiphers(), null, new DefaultHostnameVerifier());
}
private SocketConfig createSocketConfig(Duration readTimeout) {
return SocketConfig.custom().setSoTimeout((int) readTimeout.toMillis(), TimeUnit.MILLISECONDS).build();
}
/**
* {@link RedirectStrategy} that never follows redirects.
*/

View File

@@ -26,6 +26,7 @@ import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
@@ -39,23 +40,40 @@ import org.springframework.util.ClassUtils;
public class JdkClientHttpRequestFactoryBuilder
extends AbstractClientHttpRequestFactoryBuilder<JdkClientHttpRequestFactory> {
private final Consumer<HttpClient.Builder> httpClientCustomizer;
JdkClientHttpRequestFactoryBuilder() {
this(null);
this(null, emptyCustomizer());
}
private JdkClientHttpRequestFactoryBuilder(List<Consumer<JdkClientHttpRequestFactory>> customizers) {
private JdkClientHttpRequestFactoryBuilder(List<Consumer<JdkClientHttpRequestFactory>> customizers,
Consumer<HttpClient.Builder> httpClientCustomizer) {
super(customizers);
this.httpClientCustomizer = httpClientCustomizer;
}
@Override
public JdkClientHttpRequestFactoryBuilder withCustomizer(Consumer<JdkClientHttpRequestFactory> customizer) {
return new JdkClientHttpRequestFactoryBuilder(mergedCustomizers(customizer));
return new JdkClientHttpRequestFactoryBuilder(mergedCustomizers(customizer), this.httpClientCustomizer);
}
@Override
public JdkClientHttpRequestFactoryBuilder withCustomizers(
Collection<Consumer<JdkClientHttpRequestFactory>> customizers) {
return new JdkClientHttpRequestFactoryBuilder(mergedCustomizers(customizers));
return new JdkClientHttpRequestFactoryBuilder(mergedCustomizers(customizers), this.httpClientCustomizer);
}
/**
* Return a new {@link JdkClientHttpRequestFactoryBuilder} that applies additional
* customization to the underlying {@link java.net.http.HttpClient.Builder}.
* @param httpClientCustomizer the customizer to apply
* @return a new {@link JdkClientHttpRequestFactoryBuilder} instance
*/
public JdkClientHttpRequestFactoryBuilder withHttpClientCustomizer(
Consumer<HttpClient.Builder> httpClientCustomizer) {
Assert.notNull(httpClientCustomizer, "'httpClientCustomizer' must not be null");
return new JdkClientHttpRequestFactoryBuilder(getCustomizers(),
this.httpClientCustomizer.andThen(httpClientCustomizer));
}
@Override
@@ -68,12 +86,13 @@ public class JdkClientHttpRequestFactoryBuilder
}
private HttpClient createHttpClient(ClientHttpRequestFactorySettings settings) {
HttpClient.Builder httpClientBuilder = HttpClient.newBuilder();
HttpClient.Builder builder = HttpClient.newBuilder();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(settings::connectTimeout).to(httpClientBuilder::connectTimeout);
map.from(settings::sslBundle).as(SslBundle::createSslContext).to(httpClientBuilder::sslContext);
map.from(settings::redirects).as(this::asHttpClientRedirect).to(httpClientBuilder::followRedirects);
return httpClientBuilder.build();
map.from(settings::connectTimeout).to(builder::connectTimeout);
map.from(settings::sslBundle).as(SslBundle::createSslContext).to(builder::sslContext);
map.from(settings::redirects).as(this::asHttpClientRedirect).to(builder::followRedirects);
this.httpClientCustomizer.accept(builder);
return builder.build();
}
private Redirect asHttpClientRedirect(Redirects redirects) {

View File

@@ -34,6 +34,7 @@ import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.http.client.JettyClientHttpRequestFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
@@ -47,23 +48,77 @@ import org.springframework.util.ClassUtils;
public final class JettyClientHttpRequestFactoryBuilder
extends AbstractClientHttpRequestFactoryBuilder<JettyClientHttpRequestFactory> {
private final Consumer<HttpClient> httpClientCustomizer;
private final Consumer<HttpClientTransport> httpClientTransportCustomizer;
private final Consumer<ClientConnector> clientConnectorCustomizerCustomizer;
JettyClientHttpRequestFactoryBuilder() {
this(null);
this(null, emptyCustomizer(), emptyCustomizer(), emptyCustomizer());
}
private JettyClientHttpRequestFactoryBuilder(List<Consumer<JettyClientHttpRequestFactory>> customizers) {
private JettyClientHttpRequestFactoryBuilder(List<Consumer<JettyClientHttpRequestFactory>> customizers,
Consumer<HttpClient> httpClientCustomizer, Consumer<HttpClientTransport> httpClientTransportCustomizer,
Consumer<ClientConnector> clientConnectorCustomizerCustomizer) {
super(customizers);
this.httpClientCustomizer = httpClientCustomizer;
this.httpClientTransportCustomizer = httpClientTransportCustomizer;
this.clientConnectorCustomizerCustomizer = clientConnectorCustomizerCustomizer;
}
@Override
public JettyClientHttpRequestFactoryBuilder withCustomizer(Consumer<JettyClientHttpRequestFactory> customizer) {
return new JettyClientHttpRequestFactoryBuilder(mergedCustomizers(customizer));
return new JettyClientHttpRequestFactoryBuilder(mergedCustomizers(customizer), this.httpClientCustomizer,
this.httpClientTransportCustomizer, this.clientConnectorCustomizerCustomizer);
}
@Override
public JettyClientHttpRequestFactoryBuilder withCustomizers(
Collection<Consumer<JettyClientHttpRequestFactory>> customizers) {
return new JettyClientHttpRequestFactoryBuilder(mergedCustomizers(customizers));
return new JettyClientHttpRequestFactoryBuilder(mergedCustomizers(customizers), this.httpClientCustomizer,
this.httpClientTransportCustomizer, this.clientConnectorCustomizerCustomizer);
}
/**
* Return a new {@link JettyClientHttpRequestFactoryBuilder} that applies additional
* customization to the underlying {@link HttpClient}.
* @param httpClientCustomizer the customizer to apply
* @return a new {@link JettyClientHttpRequestFactoryBuilder} instance
*/
public JettyClientHttpRequestFactoryBuilder withHttpClientCustomizer(Consumer<HttpClient> httpClientCustomizer) {
Assert.notNull(httpClientCustomizer, "'httpClientCustomizer' must not be null");
return new JettyClientHttpRequestFactoryBuilder(getCustomizers(),
this.httpClientCustomizer.andThen(httpClientCustomizer), this.httpClientTransportCustomizer,
this.clientConnectorCustomizerCustomizer);
}
/**
* Return a new {@link JettyClientHttpRequestFactoryBuilder} that applies additional
* customization to the underlying {@link HttpClientTransport}.
* @param httpClientTransportCustomizer the customizer to apply
* @return a new {@link JettyClientHttpRequestFactoryBuilder} instance
*/
public JettyClientHttpRequestFactoryBuilder withHttpClientTransportCustomizer(
Consumer<HttpClientTransport> httpClientTransportCustomizer) {
Assert.notNull(httpClientTransportCustomizer, "'httpClientTransportCustomizer' must not be null");
return new JettyClientHttpRequestFactoryBuilder(getCustomizers(), this.httpClientCustomizer,
this.httpClientTransportCustomizer.andThen(httpClientTransportCustomizer),
this.clientConnectorCustomizerCustomizer);
}
/**
* Return a new {@link JettyClientHttpRequestFactoryBuilder} that applies additional
* customization to the underlying {@link ClientConnector}.
* @param clientConnectorCustomizerCustomizer the customizer to apply
* @return a new {@link JettyClientHttpRequestFactoryBuilder} instance
*/
public JettyClientHttpRequestFactoryBuilder withClientConnectorCustomizerCustomizer(
Consumer<ClientConnector> clientConnectorCustomizerCustomizer) {
Assert.notNull(clientConnectorCustomizerCustomizer, "'clientConnectorCustomizerCustomizer' must not be null");
return new JettyClientHttpRequestFactoryBuilder(getCustomizers(), this.httpClientCustomizer,
this.httpClientTransportCustomizer,
this.clientConnectorCustomizerCustomizer.andThen(clientConnectorCustomizerCustomizer));
}
@Override
@@ -77,27 +132,34 @@ public final class JettyClientHttpRequestFactoryBuilder
private JettyClientHttpRequestFactory createRequestFactory(ClientHttpRequestFactorySettings settings) {
HttpClientTransport transport = createTransport(settings);
this.httpClientTransportCustomizer.accept(transport);
HttpClient httpClient = new HttpClient(transport);
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(settings::redirects).as(this::followRedirects).to(httpClient::setFollowRedirects);
this.httpClientCustomizer.accept(httpClient);
return new JettyClientHttpRequestFactory(httpClient);
}
private HttpClientTransport createTransport(ClientHttpRequestFactorySettings settings) {
if (settings.sslBundle() == null) {
return new HttpClientTransportOverHTTP();
}
ClientConnector connector = createClientConnector(settings.sslBundle());
return new HttpClientTransportDynamic(connector);
return (connector.getSslContextFactory() != null) ? new HttpClientTransportDynamic(connector)
: new HttpClientTransportOverHTTP(connector);
}
private ClientConnector createClientConnector(SslBundle sslBundle) {
ClientConnector connector = new ClientConnector();
if (sslBundle != null) {
connector.setSslContextFactory(createSslContextFactory(sslBundle));
}
this.clientConnectorCustomizerCustomizer.accept(connector);
return connector;
}
private SslContextFactory.Client createSslContextFactory(SslBundle sslBundle) {
SSLContext sslContext = sslBundle.createSslContext();
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
sslContextFactory.setSslContext(sslContext);
ClientConnector connector = new ClientConnector();
connector.setSslContextFactory(sslContextFactory);
return connector;
return sslContextFactory;
}
private boolean followRedirects(Redirects redirects) {

View File

@@ -20,6 +20,7 @@ import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import javax.net.ssl.SSLException;
@@ -33,6 +34,7 @@ import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslManagerBundle;
import org.springframework.boot.ssl.SslOptions;
import org.springframework.http.client.ReactorClientHttpRequestFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.function.ThrowingConsumer;
@@ -47,23 +49,40 @@ import org.springframework.util.function.ThrowingConsumer;
public final class ReactorClientHttpRequestFactoryBuilder
extends AbstractClientHttpRequestFactoryBuilder<ReactorClientHttpRequestFactory> {
private final UnaryOperator<HttpClient> httpClientCustomizer;
ReactorClientHttpRequestFactoryBuilder() {
this(null);
this(null, UnaryOperator.identity());
}
private ReactorClientHttpRequestFactoryBuilder(List<Consumer<ReactorClientHttpRequestFactory>> customizers) {
private ReactorClientHttpRequestFactoryBuilder(List<Consumer<ReactorClientHttpRequestFactory>> customizers,
UnaryOperator<HttpClient> httpClientCustomizer) {
super(customizers);
this.httpClientCustomizer = httpClientCustomizer;
}
@Override
public ReactorClientHttpRequestFactoryBuilder withCustomizer(Consumer<ReactorClientHttpRequestFactory> customizer) {
return new ReactorClientHttpRequestFactoryBuilder(mergedCustomizers(customizer));
return new ReactorClientHttpRequestFactoryBuilder(mergedCustomizers(customizer), this.httpClientCustomizer);
}
@Override
public ReactorClientHttpRequestFactoryBuilder withCustomizers(
Collection<Consumer<ReactorClientHttpRequestFactory>> customizers) {
return new ReactorClientHttpRequestFactoryBuilder(mergedCustomizers(customizers));
return new ReactorClientHttpRequestFactoryBuilder(mergedCustomizers(customizers), this.httpClientCustomizer);
}
/**
* Return a new {@link ReactorClientHttpRequestFactoryBuilder} that applies additional
* customization to the underlying {@link HttpClient}.
* @param httpClientCustomizer the customizer to apply
* @return a new {@link ReactorClientHttpRequestFactoryBuilder} instance
*/
public ReactorClientHttpRequestFactoryBuilder withHttpClientCustomizer(
UnaryOperator<HttpClient> httpClientCustomizer) {
Assert.notNull(httpClientCustomizer, "'httpClientCustomizer' must not be null");
return new ReactorClientHttpRequestFactoryBuilder(getCustomizers(),
(t) -> httpClientCustomizer.apply(this.httpClientCustomizer.apply(t)));
}
@Override
@@ -82,6 +101,7 @@ public final class ReactorClientHttpRequestFactoryBuilder
if (settings.sslBundle() != null) {
httpClient = httpClient.secure((ThrowingConsumer.of((spec) -> configureSsl(spec, settings.sslBundle()))));
}
httpClient = this.httpClientCustomizer.apply(httpClient);
return new ReactorClientHttpRequestFactory(httpClient);
}

View File

@@ -16,14 +16,26 @@
package org.springframework.boot.http.client;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
import org.apache.hc.core5.function.Resolver;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.io.SocketConfig.Builder;
import org.junit.jupiter.api.Test;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HttpComponentsClientHttpRequestFactoryBuilder}.
*
@@ -37,6 +49,38 @@ class HttpComponentsClientHttpRequestFactoryBuilderTests
super(HttpComponentsClientHttpRequestFactory.class, ClientHttpRequestFactoryBuilder.httpComponents());
}
@Test
void withCustomizers() {
TestCustomizer<HttpClientBuilder> httpClientCustomizer1 = new TestCustomizer<>();
TestCustomizer<HttpClientBuilder> httpClientCustomizer2 = new TestCustomizer<>();
TestCustomizer<PoolingHttpClientConnectionManagerBuilder> connectionManagerCustomizer = new TestCustomizer<>();
TestCustomizer<Builder> socketConfigCustomizer = new TestCustomizer<>();
ClientHttpRequestFactoryBuilder.httpComponents()
.withHttpClientCustomizer(httpClientCustomizer1)
.withHttpClientCustomizer(httpClientCustomizer2)
.withConnectionManagerCustomizer(connectionManagerCustomizer)
.withSocketConfigCustomizer(socketConfigCustomizer)
.build();
httpClientCustomizer1.assertCalled();
httpClientCustomizer2.assertCalled();
connectionManagerCustomizer.assertCalled();
socketConfigCustomizer.assertCalled();
}
@Test
void withTlsSocketStrategyFactory() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.ofSslBundle(sslBundle());
List<SslBundle> bundles = new ArrayList<>();
Function<SslBundle, TlsSocketStrategy> tlsSocketStrategyFactory = (bundle) -> {
bundles.add(bundle);
return (socket, target, port, attachment, context) -> null;
};
ClientHttpRequestFactoryBuilder.httpComponents()
.withTlsSocketStrategyFactory(tlsSocketStrategyFactory)
.build(settings);
assertThat(bundles).contains(settings.sslBundle());
}
@Override
protected long connectTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
return (long) ReflectionTestUtils.getField(requestFactory, "connectTimeout");

View File

@@ -19,6 +19,8 @@ package org.springframework.boot.http.client;
import java.net.http.HttpClient;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
@@ -34,6 +36,18 @@ class JdkClientHttpRequestFactoryBuilderTests
super(JdkClientHttpRequestFactory.class, ClientHttpRequestFactoryBuilder.jdk());
}
@Test
void withCustomizers() {
TestCustomizer<HttpClient.Builder> httpClientCustomizer1 = new TestCustomizer<>();
TestCustomizer<HttpClient.Builder> httpClientCustomizer2 = new TestCustomizer<>();
ClientHttpRequestFactoryBuilder.jdk()
.withHttpClientCustomizer(httpClientCustomizer1)
.withHttpClientCustomizer(httpClientCustomizer2)
.build();
httpClientCustomizer1.assertCalled();
httpClientCustomizer2.assertCalled();
}
@Override
protected long connectTimeout(JdkClientHttpRequestFactory requestFactory) {
HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient");

View File

@@ -17,6 +17,9 @@
package org.springframework.boot.http.client;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpClientTransport;
import org.eclipse.jetty.io.ClientConnector;
import org.junit.jupiter.api.Test;
import org.springframework.http.client.JettyClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
@@ -33,6 +36,24 @@ class JettyClientHttpRequestFactoryBuilderTests
super(JettyClientHttpRequestFactory.class, ClientHttpRequestFactoryBuilder.jetty());
}
@Test
void withCustomizers() {
TestCustomizer<HttpClient> httpClientCustomizer1 = new TestCustomizer<>();
TestCustomizer<HttpClient> httpClientCustomizer2 = new TestCustomizer<>();
TestCustomizer<HttpClientTransport> httpClientTransportCustomizer = new TestCustomizer<>();
TestCustomizer<ClientConnector> clientConnectorCustomizerCustomizer = new TestCustomizer<>();
ClientHttpRequestFactoryBuilder.jetty()
.withHttpClientCustomizer(httpClientCustomizer1)
.withHttpClientCustomizer(httpClientCustomizer2)
.withHttpClientTransportCustomizer(httpClientTransportCustomizer)
.withClientConnectorCustomizerCustomizer(clientConnectorCustomizerCustomizer)
.build();
httpClientCustomizer1.assertCalled();
httpClientCustomizer2.assertCalled();
httpClientTransportCustomizer.assertCalled();
clientConnectorCustomizerCustomizer.assertCalled();
}
@Override
protected long connectTimeout(JettyClientHttpRequestFactory requestFactory) {
return ((HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient")).getConnectTimeout();

View File

@@ -17,13 +17,19 @@
package org.springframework.boot.http.client;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.function.UnaryOperator;
import io.netty.channel.ChannelOption;
import org.junit.jupiter.api.Test;
import reactor.netty.http.client.HttpClient;
import org.springframework.http.client.ReactorClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ReactorClientHttpRequestFactoryBuilder}.
*
@@ -37,6 +43,24 @@ class ReactorClientHttpRequestFactoryBuilderTests
super(ReactorClientHttpRequestFactory.class, ClientHttpRequestFactoryBuilder.reactor());
}
@Test
void withCustomizers() {
List<HttpClient> httpClients = new ArrayList<>();
UnaryOperator<HttpClient> httpClientCustomizer1 = (httpClient) -> {
httpClients.add(httpClient);
return httpClient;
};
UnaryOperator<HttpClient> httpClientCustomizer2 = (httpClient) -> {
httpClients.add(httpClient);
return httpClient;
};
ClientHttpRequestFactoryBuilder.reactor()
.withHttpClientCustomizer(httpClientCustomizer1)
.withHttpClientCustomizer(httpClientCustomizer2)
.build();
assertThat(httpClients).hasSize(2);
}
@Override
protected long connectTimeout(ReactorClientHttpRequestFactory requestFactory) {
return (int) ((HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient")).configuration()

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2024 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.http.client;
import java.util.function.Consumer;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test customizer that can assert that it has been called.
*
* @param <T> type being customized
* @author Phillip Webb
*/
class TestCustomizer<T> implements Consumer<T> {
private boolean called;
@Override
public void accept(T t) {
this.called = true;
}
void assertCalled() {
assertThat(this.called).isTrue();
}
}