Skip to content

Commit e1b5935

Browse files
committed
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
1 parent 97b20e9 commit e1b5935

10 files changed

+385
-40
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/AbstractClientHttpRequestFactoryBuilder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,20 @@
3535
abstract class AbstractClientHttpRequestFactoryBuilder<T extends ClientHttpRequestFactory>
3636
implements ClientHttpRequestFactoryBuilder<T> {
3737

38+
private static final Consumer<?> EMPTY_CUSTOMIZER = (t) -> {
39+
};
40+
3841
private final List<Consumer<T>> customizers;
3942

4043
protected AbstractClientHttpRequestFactoryBuilder(List<Consumer<T>> customizers) {
4144
this.customizers = (customizers != null) ? customizers : Collections.emptyList();
4245
}
4346

47+
@SuppressWarnings("unchecked")
48+
protected static <T> Consumer<T> emptyCustomizer() {
49+
return (Consumer<T>) EMPTY_CUSTOMIZER;
50+
}
51+
4452
protected final List<Consumer<T>> getCustomizers() {
4553
return this.customizers;
4654
}

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

Lines changed: 107 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import java.util.concurrent.TimeUnit;
2525
import java.util.function.Consumer;
26+
import java.util.function.Function;
2627

2728
import org.apache.hc.client5.http.classic.HttpClient;
2829
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
@@ -32,6 +33,7 @@
3233
import org.apache.hc.client5.http.protocol.RedirectStrategy;
3334
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
3435
import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier;
36+
import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
3537
import org.apache.hc.core5.http.HttpRequest;
3638
import org.apache.hc.core5.http.HttpResponse;
3739
import org.apache.hc.core5.http.io.SocketConfig;
@@ -42,6 +44,7 @@
4244
import org.springframework.boot.ssl.SslBundle;
4345
import org.springframework.boot.ssl.SslOptions;
4446
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
47+
import org.springframework.util.Assert;
4548
import org.springframework.util.ClassUtils;
4649

4750
/**
@@ -55,25 +58,104 @@
5558
public final class HttpComponentsClientHttpRequestFactoryBuilder
5659
extends AbstractClientHttpRequestFactoryBuilder<HttpComponentsClientHttpRequestFactory> {
5760

61+
private final Consumer<HttpClientBuilder> httpClientCustomizer;
62+
63+
private final Consumer<PoolingHttpClientConnectionManagerBuilder> connectionManagerCustomizer;
64+
65+
private final Consumer<SocketConfig.Builder> socketConfigCustomizer;
66+
67+
private final Function<SslBundle, TlsSocketStrategy> tlsSocketStrategyFactory;
68+
5869
HttpComponentsClientHttpRequestFactoryBuilder() {
59-
this(Collections.emptyList());
70+
this(Collections.emptyList(), emptyCustomizer(), emptyCustomizer(), emptyCustomizer(),
71+
HttpComponentsClientHttpRequestFactoryBuilder::createTlsSocketStrategy);
6072
}
6173

6274
private HttpComponentsClientHttpRequestFactoryBuilder(
63-
List<Consumer<HttpComponentsClientHttpRequestFactory>> customizers) {
75+
List<Consumer<HttpComponentsClientHttpRequestFactory>> customizers,
76+
Consumer<HttpClientBuilder> httpClientCustomizer,
77+
Consumer<PoolingHttpClientConnectionManagerBuilder> connectionManagerCustomizer,
78+
Consumer<SocketConfig.Builder> socketConfigCustomizer,
79+
Function<SslBundle, TlsSocketStrategy> tlsSocketStrategyFactory) {
6480
super(customizers);
81+
this.httpClientCustomizer = httpClientCustomizer;
82+
this.connectionManagerCustomizer = connectionManagerCustomizer;
83+
this.socketConfigCustomizer = socketConfigCustomizer;
84+
this.tlsSocketStrategyFactory = tlsSocketStrategyFactory;
6585
}
6686

6787
@Override
6888
public HttpComponentsClientHttpRequestFactoryBuilder withCustomizer(
6989
Consumer<HttpComponentsClientHttpRequestFactory> customizer) {
70-
return new HttpComponentsClientHttpRequestFactoryBuilder(mergedCustomizers(customizer));
90+
return new HttpComponentsClientHttpRequestFactoryBuilder(mergedCustomizers(customizer),
91+
this.httpClientCustomizer, this.connectionManagerCustomizer, this.socketConfigCustomizer,
92+
this.tlsSocketStrategyFactory);
7193
}
7294

7395
@Override
7496
public HttpComponentsClientHttpRequestFactoryBuilder withCustomizers(
7597
Collection<Consumer<HttpComponentsClientHttpRequestFactory>> customizers) {
76-
return new HttpComponentsClientHttpRequestFactoryBuilder(mergedCustomizers(customizers));
98+
return new HttpComponentsClientHttpRequestFactoryBuilder(mergedCustomizers(customizers),
99+
this.httpClientCustomizer, this.connectionManagerCustomizer, this.socketConfigCustomizer,
100+
this.tlsSocketStrategyFactory);
101+
}
102+
103+
/**
104+
* Return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} that applies
105+
* additional customization to the underlying {@link HttpClientBuilder}.
106+
* @param httpClientCustomizer the customizer to apply
107+
* @return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} instance
108+
*/
109+
public HttpComponentsClientHttpRequestFactoryBuilder withHttpClientCustomizer(
110+
Consumer<HttpClientBuilder> httpClientCustomizer) {
111+
Assert.notNull(httpClientCustomizer, "'httpClientCustomizer' must not be null");
112+
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(),
113+
this.httpClientCustomizer.andThen(httpClientCustomizer), this.connectionManagerCustomizer,
114+
this.socketConfigCustomizer, this.tlsSocketStrategyFactory);
115+
}
116+
117+
/**
118+
* Return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} that applies
119+
* additional customization to the underlying
120+
* {@link PoolingHttpClientConnectionManagerBuilder}.
121+
* @param connectionManagerCustomizer the customizer to apply
122+
* @return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} instance
123+
*/
124+
public HttpComponentsClientHttpRequestFactoryBuilder withConnectionManagerCustomizer(
125+
Consumer<PoolingHttpClientConnectionManagerBuilder> connectionManagerCustomizer) {
126+
Assert.notNull(connectionManagerCustomizer, "'connectionManagerCustomizer' must not be null");
127+
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(), this.httpClientCustomizer,
128+
this.connectionManagerCustomizer.andThen(connectionManagerCustomizer), this.socketConfigCustomizer,
129+
this.tlsSocketStrategyFactory);
130+
}
131+
132+
/**
133+
* Return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} that applies
134+
* additional customization to the underlying
135+
* {@link org.apache.hc.core5.http.io.SocketConfig.Builder}.
136+
* @param socketConfigCustomizer the customizer to apply
137+
* @return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} instance
138+
*/
139+
public HttpComponentsClientHttpRequestFactoryBuilder withSocketConfigCustomizer(
140+
Consumer<SocketConfig.Builder> socketConfigCustomizer) {
141+
Assert.notNull(socketConfigCustomizer, "'socketConfigCustomizer' must not be null");
142+
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(), this.httpClientCustomizer,
143+
this.connectionManagerCustomizer, this.socketConfigCustomizer.andThen(socketConfigCustomizer),
144+
this.tlsSocketStrategyFactory);
145+
}
146+
147+
/**
148+
* Return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} with a
149+
* replacement {@link TlsSocketStrategy} factory.
150+
* @param tlsSocketStrategyFactory the new factory used to create a
151+
* {@link TlsSocketStrategy} for a given {@link SslBundle}
152+
* @return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} instance
153+
*/
154+
public HttpComponentsClientHttpRequestFactoryBuilder withTlsSocketStrategyFactory(
155+
Function<SslBundle, TlsSocketStrategy> tlsSocketStrategyFactory) {
156+
Assert.notNull(tlsSocketStrategyFactory, "'tlsSocketStrategyFactory' must not be null");
157+
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(), this.httpClientCustomizer,
158+
this.connectionManagerCustomizer, this.socketConfigCustomizer, tlsSocketStrategyFactory);
77159
}
78160

79161
@Override
@@ -87,11 +169,12 @@ protected HttpComponentsClientHttpRequestFactory createClientHttpRequestFactory(
87169
}
88170

89171
private HttpClient createHttpClient(ClientHttpRequestFactorySettings settings) {
90-
return HttpClientBuilder.create()
172+
HttpClientBuilder builder = HttpClientBuilder.create()
91173
.useSystemProperties()
92174
.setRedirectStrategy(asRedirectStrategy(settings.redirects()))
93-
.setConnectionManager(createConnectionManager(settings))
94-
.build();
175+
.setConnectionManager(createConnectionManager(settings));
176+
this.httpClientCustomizer.accept(builder);
177+
return builder.build();
95178
}
96179

97180
private RedirectStrategy asRedirectStrategy(Redirects redirects) {
@@ -102,23 +185,31 @@ private RedirectStrategy asRedirectStrategy(Redirects redirects) {
102185
}
103186

104187
private PoolingHttpClientConnectionManager createConnectionManager(ClientHttpRequestFactorySettings settings) {
105-
PoolingHttpClientConnectionManagerBuilder builder = PoolingHttpClientConnectionManagerBuilder.create();
188+
PoolingHttpClientConnectionManagerBuilder builder = PoolingHttpClientConnectionManagerBuilder.create()
189+
.useSystemProperties();
190+
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
191+
builder.setDefaultSocketConfig(createSocketConfig(settings));
192+
map.from(settings::sslBundle).as(this.tlsSocketStrategyFactory).to(builder::setTlsSocketStrategy);
193+
this.connectionManagerCustomizer.accept(builder);
194+
return builder.build();
195+
}
196+
197+
private SocketConfig createSocketConfig(ClientHttpRequestFactorySettings settings) {
198+
SocketConfig.Builder builder = SocketConfig.custom();
106199
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
107-
map.from(settings::readTimeout).as(this::createSocketConfig).to(builder::setDefaultSocketConfig);
108-
map.from(settings::sslBundle).as(this::createTlsSocketStrategy).to(builder::setTlsSocketStrategy);
109-
return builder.useSystemProperties().build();
200+
map.from(settings::readTimeout)
201+
.asInt(Duration::toMillis)
202+
.to((timeout) -> builder.setSoTimeout(timeout, TimeUnit.MILLISECONDS));
203+
this.socketConfigCustomizer.accept(builder);
204+
return builder.build();
110205
}
111206

112-
private DefaultClientTlsStrategy createTlsSocketStrategy(SslBundle sslBundle) {
207+
private static TlsSocketStrategy createTlsSocketStrategy(SslBundle sslBundle) {
113208
SslOptions options = sslBundle.getOptions();
114209
return new DefaultClientTlsStrategy(sslBundle.createSslContext(), options.getEnabledProtocols(),
115210
options.getCiphers(), null, new DefaultHostnameVerifier());
116211
}
117212

118-
private SocketConfig createSocketConfig(Duration readTimeout) {
119-
return SocketConfig.custom().setSoTimeout((int) readTimeout.toMillis(), TimeUnit.MILLISECONDS).build();
120-
}
121-
122213
/**
123214
* {@link RedirectStrategy} that never follows redirects.
124215
*/

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/JdkClientHttpRequestFactoryBuilder.java

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
2727
import org.springframework.boot.ssl.SslBundle;
2828
import org.springframework.http.client.JdkClientHttpRequestFactory;
29+
import org.springframework.util.Assert;
2930
import org.springframework.util.ClassUtils;
3031

3132
/**
@@ -39,23 +40,40 @@
3940
public class JdkClientHttpRequestFactoryBuilder
4041
extends AbstractClientHttpRequestFactoryBuilder<JdkClientHttpRequestFactory> {
4142

43+
private final Consumer<HttpClient.Builder> httpClientCustomizer;
44+
4245
JdkClientHttpRequestFactoryBuilder() {
43-
this(null);
46+
this(null, emptyCustomizer());
4447
}
4548

46-
private JdkClientHttpRequestFactoryBuilder(List<Consumer<JdkClientHttpRequestFactory>> customizers) {
49+
private JdkClientHttpRequestFactoryBuilder(List<Consumer<JdkClientHttpRequestFactory>> customizers,
50+
Consumer<HttpClient.Builder> httpClientCustomizer) {
4751
super(customizers);
52+
this.httpClientCustomizer = httpClientCustomizer;
4853
}
4954

5055
@Override
5156
public JdkClientHttpRequestFactoryBuilder withCustomizer(Consumer<JdkClientHttpRequestFactory> customizer) {
52-
return new JdkClientHttpRequestFactoryBuilder(mergedCustomizers(customizer));
57+
return new JdkClientHttpRequestFactoryBuilder(mergedCustomizers(customizer), this.httpClientCustomizer);
5358
}
5459

5560
@Override
5661
public JdkClientHttpRequestFactoryBuilder withCustomizers(
5762
Collection<Consumer<JdkClientHttpRequestFactory>> customizers) {
58-
return new JdkClientHttpRequestFactoryBuilder(mergedCustomizers(customizers));
63+
return new JdkClientHttpRequestFactoryBuilder(mergedCustomizers(customizers), this.httpClientCustomizer);
64+
}
65+
66+
/**
67+
* Return a new {@link JdkClientHttpRequestFactoryBuilder} that applies additional
68+
* customization to the underlying {@link java.net.http.HttpClient.Builder}.
69+
* @param httpClientCustomizer the customizer to apply
70+
* @return a new {@link JdkClientHttpRequestFactoryBuilder} instance
71+
*/
72+
public JdkClientHttpRequestFactoryBuilder withHttpClientCustomizer(
73+
Consumer<HttpClient.Builder> httpClientCustomizer) {
74+
Assert.notNull(httpClientCustomizer, "'httpClientCustomizer' must not be null");
75+
return new JdkClientHttpRequestFactoryBuilder(getCustomizers(),
76+
this.httpClientCustomizer.andThen(httpClientCustomizer));
5977
}
6078

6179
@Override
@@ -68,12 +86,13 @@ protected JdkClientHttpRequestFactory createClientHttpRequestFactory(ClientHttpR
6886
}
6987

7088
private HttpClient createHttpClient(ClientHttpRequestFactorySettings settings) {
71-
HttpClient.Builder httpClientBuilder = HttpClient.newBuilder();
89+
HttpClient.Builder builder = HttpClient.newBuilder();
7290
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
73-
map.from(settings::connectTimeout).to(httpClientBuilder::connectTimeout);
74-
map.from(settings::sslBundle).as(SslBundle::createSslContext).to(httpClientBuilder::sslContext);
75-
map.from(settings::redirects).as(this::asHttpClientRedirect).to(httpClientBuilder::followRedirects);
76-
return httpClientBuilder.build();
91+
map.from(settings::connectTimeout).to(builder::connectTimeout);
92+
map.from(settings::sslBundle).as(SslBundle::createSslContext).to(builder::sslContext);
93+
map.from(settings::redirects).as(this::asHttpClientRedirect).to(builder::followRedirects);
94+
this.httpClientCustomizer.accept(builder);
95+
return builder.build();
7796
}
7897

7998
private Redirect asHttpClientRedirect(Redirects redirects) {

0 commit comments

Comments
 (0)