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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user