Skip to content

Commit 55ba098

Browse files
committed
Add HttpClient factory support to ReactorHttpClientBuilder
Improve `ReactorHttpClientBuilder` support for creating the initial `HttpClient`. Fixes gh-45378
1 parent 62c8d6b commit 55ba098

File tree

5 files changed

+132
-4
lines changed

5 files changed

+132
-4
lines changed

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
import java.util.Collection;
2121
import java.util.List;
2222
import java.util.function.Consumer;
23+
import java.util.function.Supplier;
2324
import java.util.function.UnaryOperator;
2425

2526
import reactor.netty.http.client.HttpClient;
2627

2728
import org.springframework.boot.context.properties.PropertyMapper;
2829
import org.springframework.http.client.ReactorClientHttpRequestFactory;
30+
import org.springframework.http.client.ReactorResourceFactory;
2931
import org.springframework.util.Assert;
3032
import org.springframework.util.ClassUtils;
3133

@@ -63,6 +65,33 @@ public ReactorClientHttpRequestFactoryBuilder withCustomizers(
6365
return new ReactorClientHttpRequestFactoryBuilder(mergedCustomizers(customizers), this.httpClientBuilder);
6466
}
6567

68+
/**
69+
* Return a new {@link ReactorClientHttpRequestFactoryBuilder} that uses the given
70+
* {@link ReactorResourceFactory} to create the underlying {@link HttpClient}.
71+
* @param reactorResourceFactory the {@link ReactorResourceFactory} to use
72+
* @return a new {@link ReactorClientHttpRequestFactoryBuilder} instance
73+
* @since 3.5.0
74+
*/
75+
public ReactorClientHttpRequestFactoryBuilder withReactorResourceFactory(
76+
ReactorResourceFactory reactorResourceFactory) {
77+
Assert.notNull(reactorResourceFactory, "'reactorResourceFactory' must not be null");
78+
return new ReactorClientHttpRequestFactoryBuilder(getCustomizers(),
79+
this.httpClientBuilder.withReactorResourceFactory(reactorResourceFactory));
80+
}
81+
82+
/**
83+
* Return a new {@link ReactorClientHttpRequestFactoryBuilder} that uses the given
84+
* factory to create the underlying {@link HttpClient}.
85+
* @param factory the factory to use
86+
* @return a new {@link ReactorClientHttpRequestFactoryBuilder} instance
87+
* @since 3.5.0
88+
*/
89+
public ReactorClientHttpRequestFactoryBuilder withHttpClientFactory(Supplier<HttpClient> factory) {
90+
Assert.notNull(factory, "'factory' must not be null");
91+
return new ReactorClientHttpRequestFactoryBuilder(getCustomizers(),
92+
this.httpClientBuilder.withHttpClientFactory(factory));
93+
}
94+
6695
/**
6796
* Return a new {@link ReactorClientHttpRequestFactoryBuilder} that applies additional
6897
* customization to the underlying {@link HttpClient}.

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

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.http.client;
1818

1919
import java.time.Duration;
20+
import java.util.function.Supplier;
2021
import java.util.function.UnaryOperator;
2122

2223
import javax.net.ssl.SSLException;
@@ -30,6 +31,7 @@
3031
import org.springframework.boot.ssl.SslBundle;
3132
import org.springframework.boot.ssl.SslManagerBundle;
3233
import org.springframework.boot.ssl.SslOptions;
34+
import org.springframework.http.client.ReactorResourceFactory;
3335
import org.springframework.util.Assert;
3436
import org.springframework.util.function.ThrowingConsumer;
3537

@@ -43,16 +45,42 @@
4345
*/
4446
public final class ReactorHttpClientBuilder {
4547

48+
private final Supplier<HttpClient> factory;
49+
4650
private final UnaryOperator<HttpClient> customizer;
4751

4852
public ReactorHttpClientBuilder() {
49-
this(UnaryOperator.identity());
53+
this(HttpClient::create, UnaryOperator.identity());
5054
}
5155

52-
private ReactorHttpClientBuilder(UnaryOperator<HttpClient> customizer) {
56+
private ReactorHttpClientBuilder(Supplier<HttpClient> httpClientFactory, UnaryOperator<HttpClient> customizer) {
57+
this.factory = httpClientFactory;
5358
this.customizer = customizer;
5459
}
5560

61+
/**
62+
* Return a new {@link ReactorHttpClientBuilder} that uses the given
63+
* {@link ReactorResourceFactory} to create the {@link HttpClient}.
64+
* @param reactorResourceFactory the {@link ReactorResourceFactory} to use
65+
* @return a new {@link ReactorHttpClientBuilder} instance
66+
*/
67+
public ReactorHttpClientBuilder withReactorResourceFactory(ReactorResourceFactory reactorResourceFactory) {
68+
Assert.notNull(reactorResourceFactory, "'reactorResourceFactory' must not be null");
69+
return new ReactorHttpClientBuilder(() -> HttpClient.create(reactorResourceFactory.getConnectionProvider()),
70+
(httpClient) -> this.customizer.apply(httpClient).runOn(reactorResourceFactory.getLoopResources()));
71+
}
72+
73+
/**
74+
* Return a new {@link ReactorHttpClientBuilder} that uses the given factory to create
75+
* the {@link HttpClient}.
76+
* @param factory the factory to use
77+
* @return a new {@link ReactorHttpClientBuilder} instance
78+
*/
79+
public ReactorHttpClientBuilder withHttpClientFactory(Supplier<HttpClient> factory) {
80+
Assert.notNull(factory, "'factory' must not be null");
81+
return new ReactorHttpClientBuilder(factory, this.customizer);
82+
}
83+
5684
/**
5785
* Return a new {@link ReactorHttpClientBuilder} that applies additional customization
5886
* to the underlying {@link HttpClient}.
@@ -72,7 +100,7 @@ public ReactorHttpClientBuilder withHttpClientCustomizer(UnaryOperator<HttpClien
72100
*/
73101
public HttpClient build(HttpClientSettings settings) {
74102
settings = (settings != null) ? settings : HttpClientSettings.DEFAULTS;
75-
HttpClient httpClient = applyDefaults(HttpClient.create());
103+
HttpClient httpClient = applyDefaults(this.factory.get());
76104
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
77105
httpClient = map.from(settings::connectTimeout).to(httpClient, this::setConnectTimeout);
78106
httpClient = map.from(settings::readTimeout).to(httpClient, HttpClient::responseTimeout);

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
import java.util.Collection;
2020
import java.util.List;
2121
import java.util.function.Consumer;
22+
import java.util.function.Supplier;
2223
import java.util.function.UnaryOperator;
2324

2425
import reactor.netty.http.client.HttpClient;
2526

2627
import org.springframework.boot.http.client.ReactorHttpClientBuilder;
28+
import org.springframework.http.client.ReactorResourceFactory;
2729
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
2830
import org.springframework.util.Assert;
2931
import org.springframework.util.ClassUtils;
@@ -60,6 +62,30 @@ public ReactorClientHttpConnectorBuilder withCustomizers(
6062
return new ReactorClientHttpConnectorBuilder(mergedCustomizers(customizers), this.httpClientBuilder);
6163
}
6264

65+
/**
66+
* Return a new {@link ReactorClientHttpConnectorBuilder} that uses the given
67+
* {@link ReactorResourceFactory} to create the underlying {@link HttpClient}.
68+
* @param reactorResourceFactory the {@link ReactorResourceFactory} to use
69+
* @return a new {@link ReactorClientHttpConnectorBuilder} instance
70+
*/
71+
public ReactorClientHttpConnectorBuilder withReactorResourceFactory(ReactorResourceFactory reactorResourceFactory) {
72+
Assert.notNull(reactorResourceFactory, "'reactorResourceFactory' must not be null");
73+
return new ReactorClientHttpConnectorBuilder(getCustomizers(),
74+
this.httpClientBuilder.withReactorResourceFactory(reactorResourceFactory));
75+
}
76+
77+
/**
78+
* Return a new {@link ReactorClientHttpConnectorBuilder} that uses the given factory
79+
* to create the underlying {@link HttpClient}.
80+
* @param factory the factory to use
81+
* @return a new {@link ReactorClientHttpConnectorBuilder} instance
82+
*/
83+
public ReactorClientHttpConnectorBuilder withHttpClientFactory(Supplier<HttpClient> factory) {
84+
Assert.notNull(factory, "'factory' must not be null");
85+
return new ReactorClientHttpConnectorBuilder(getCustomizers(),
86+
this.httpClientBuilder.withHttpClientFactory(factory));
87+
}
88+
6389
/**
6490
* Return a new {@link ReactorClientHttpConnectorBuilder} that applies additional
6591
* customization to the underlying {@link HttpClient}.

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/http/client/ReactorClientHttpRequestFactoryBuilderTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@
1919
import java.time.Duration;
2020
import java.util.ArrayList;
2121
import java.util.List;
22+
import java.util.function.Supplier;
2223
import java.util.function.UnaryOperator;
2324

2425
import io.netty.channel.ChannelOption;
2526
import org.junit.jupiter.api.Test;
2627
import reactor.netty.http.client.HttpClient;
2728

2829
import org.springframework.http.client.ReactorClientHttpRequestFactory;
30+
import org.springframework.http.client.ReactorResourceFactory;
2931
import org.springframework.test.util.ReflectionTestUtils;
3032

3133
import static org.assertj.core.api.Assertions.assertThat;
34+
import static org.mockito.BDDMockito.then;
35+
import static org.mockito.Mockito.spy;
3236

3337
/**
3438
* Tests for {@link ReactorClientHttpRequestFactoryBuilder} and
@@ -44,6 +48,25 @@ class ReactorClientHttpRequestFactoryBuilderTests
4448
super(ReactorClientHttpRequestFactory.class, ClientHttpRequestFactoryBuilder.reactor());
4549
}
4650

51+
@Test
52+
void withwithHttpClientFactory() {
53+
boolean[] called = new boolean[1];
54+
Supplier<HttpClient> httpClientFactory = () -> {
55+
called[0] = true;
56+
return HttpClient.create();
57+
};
58+
ClientHttpRequestFactoryBuilder.reactor().withHttpClientFactory(httpClientFactory).build();
59+
assertThat(called).containsExactly(true);
60+
}
61+
62+
@Test
63+
void withReactorResourceFactory() {
64+
ReactorResourceFactory resourceFactory = spy(new ReactorResourceFactory());
65+
ClientHttpRequestFactoryBuilder.reactor().withReactorResourceFactory(resourceFactory).build();
66+
then(resourceFactory).should().getConnectionProvider();
67+
then(resourceFactory).should().getLoopResources();
68+
}
69+
4770
@Test
4871
void withCustomizers() {
4972
List<HttpClient> httpClients = new ArrayList<>();

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/http/client/reactive/ReactorClientHttpConnectorBuilderTests.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,21 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.List;
21+
import java.util.function.Supplier;
2122
import java.util.function.UnaryOperator;
2223

2324
import io.netty.channel.ChannelOption;
2425
import org.junit.jupiter.api.Test;
2526
import reactor.netty.http.client.HttpClient;
2627

27-
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
2828
import org.springframework.boot.http.client.ReactorHttpClientBuilder;
29+
import org.springframework.http.client.ReactorResourceFactory;
2930
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
3031
import org.springframework.test.util.ReflectionTestUtils;
3132

3233
import static org.assertj.core.api.Assertions.assertThat;
34+
import static org.mockito.BDDMockito.then;
35+
import static org.mockito.Mockito.spy;
3336

3437
/**
3538
* Tests for {@link ReactorClientHttpConnectorBuilder} and
@@ -44,6 +47,25 @@ class ReactorClientHttpConnectorBuilderTests
4447
super(ReactorClientHttpConnector.class, ClientHttpConnectorBuilder.reactor());
4548
}
4649

50+
@Test
51+
void withwithHttpClientFactory() {
52+
boolean[] called = new boolean[1];
53+
Supplier<HttpClient> httpClientFactory = () -> {
54+
called[0] = true;
55+
return HttpClient.create();
56+
};
57+
ClientHttpConnectorBuilder.reactor().withHttpClientFactory(httpClientFactory).build();
58+
assertThat(called).containsExactly(true);
59+
}
60+
61+
@Test
62+
void withReactorResourceFactory() {
63+
ReactorResourceFactory resourceFactory = spy(new ReactorResourceFactory());
64+
ClientHttpConnectorBuilder.reactor().withReactorResourceFactory(resourceFactory).build();
65+
then(resourceFactory).should().getConnectionProvider();
66+
then(resourceFactory).should().getLoopResources();
67+
}
68+
4769
@Test
4870
void withCustomizers() {
4971
List<HttpClient> httpClients = new ArrayList<>();

0 commit comments

Comments
 (0)