Browse Source

Add HttpClient factory support to ReactorHttpClientBuilder

Improve `ReactorHttpClientBuilder` support for creating the initial
`HttpClient`.

Fixes gh-45378
pull/45411/head
Phillip Webb 9 months ago
parent
commit
55ba0985d3
  1. 29
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ReactorClientHttpRequestFactoryBuilder.java
  2. 34
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ReactorHttpClientBuilder.java
  3. 26
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/reactive/ReactorClientHttpConnectorBuilder.java
  4. 23
      spring-boot-project/spring-boot/src/test/java/org/springframework/boot/http/client/ReactorClientHttpRequestFactoryBuilderTests.java
  5. 24
      spring-boot-project/spring-boot/src/test/java/org/springframework/boot/http/client/reactive/ReactorClientHttpConnectorBuilderTests.java

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

@ -20,12 +20,14 @@ import java.time.Duration; @@ -20,12 +20,14 @@ import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import reactor.netty.http.client.HttpClient;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.http.client.ReactorClientHttpRequestFactory;
import org.springframework.http.client.ReactorResourceFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@ -63,6 +65,33 @@ public final class ReactorClientHttpRequestFactoryBuilder @@ -63,6 +65,33 @@ public final class ReactorClientHttpRequestFactoryBuilder
return new ReactorClientHttpRequestFactoryBuilder(mergedCustomizers(customizers), this.httpClientBuilder);
}
/**
* Return a new {@link ReactorClientHttpRequestFactoryBuilder} that uses the given
* {@link ReactorResourceFactory} to create the underlying {@link HttpClient}.
* @param reactorResourceFactory the {@link ReactorResourceFactory} to use
* @return a new {@link ReactorClientHttpRequestFactoryBuilder} instance
* @since 3.5.0
*/
public ReactorClientHttpRequestFactoryBuilder withReactorResourceFactory(
ReactorResourceFactory reactorResourceFactory) {
Assert.notNull(reactorResourceFactory, "'reactorResourceFactory' must not be null");
return new ReactorClientHttpRequestFactoryBuilder(getCustomizers(),
this.httpClientBuilder.withReactorResourceFactory(reactorResourceFactory));
}
/**
* Return a new {@link ReactorClientHttpRequestFactoryBuilder} that uses the given
* factory to create the underlying {@link HttpClient}.
* @param factory the factory to use
* @return a new {@link ReactorClientHttpRequestFactoryBuilder} instance
* @since 3.5.0
*/
public ReactorClientHttpRequestFactoryBuilder withHttpClientFactory(Supplier<HttpClient> factory) {
Assert.notNull(factory, "'factory' must not be null");
return new ReactorClientHttpRequestFactoryBuilder(getCustomizers(),
this.httpClientBuilder.withHttpClientFactory(factory));
}
/**
* Return a new {@link ReactorClientHttpRequestFactoryBuilder} that applies additional
* customization to the underlying {@link HttpClient}.

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

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.boot.http.client;
import java.time.Duration;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import javax.net.ssl.SSLException;
@ -30,6 +31,7 @@ import org.springframework.boot.context.properties.PropertyMapper; @@ -30,6 +31,7 @@ import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslManagerBundle;
import org.springframework.boot.ssl.SslOptions;
import org.springframework.http.client.ReactorResourceFactory;
import org.springframework.util.Assert;
import org.springframework.util.function.ThrowingConsumer;
@ -43,16 +45,42 @@ import org.springframework.util.function.ThrowingConsumer; @@ -43,16 +45,42 @@ import org.springframework.util.function.ThrowingConsumer;
*/
public final class ReactorHttpClientBuilder {
private final Supplier<HttpClient> factory;
private final UnaryOperator<HttpClient> customizer;
public ReactorHttpClientBuilder() {
this(UnaryOperator.identity());
this(HttpClient::create, UnaryOperator.identity());
}
private ReactorHttpClientBuilder(UnaryOperator<HttpClient> customizer) {
private ReactorHttpClientBuilder(Supplier<HttpClient> httpClientFactory, UnaryOperator<HttpClient> customizer) {
this.factory = httpClientFactory;
this.customizer = customizer;
}
/**
* Return a new {@link ReactorHttpClientBuilder} that uses the given
* {@link ReactorResourceFactory} to create the {@link HttpClient}.
* @param reactorResourceFactory the {@link ReactorResourceFactory} to use
* @return a new {@link ReactorHttpClientBuilder} instance
*/
public ReactorHttpClientBuilder withReactorResourceFactory(ReactorResourceFactory reactorResourceFactory) {
Assert.notNull(reactorResourceFactory, "'reactorResourceFactory' must not be null");
return new ReactorHttpClientBuilder(() -> HttpClient.create(reactorResourceFactory.getConnectionProvider()),
(httpClient) -> this.customizer.apply(httpClient).runOn(reactorResourceFactory.getLoopResources()));
}
/**
* Return a new {@link ReactorHttpClientBuilder} that uses the given factory to create
* the {@link HttpClient}.
* @param factory the factory to use
* @return a new {@link ReactorHttpClientBuilder} instance
*/
public ReactorHttpClientBuilder withHttpClientFactory(Supplier<HttpClient> factory) {
Assert.notNull(factory, "'factory' must not be null");
return new ReactorHttpClientBuilder(factory, this.customizer);
}
/**
* Return a new {@link ReactorHttpClientBuilder} that applies additional customization
* to the underlying {@link HttpClient}.
@ -72,7 +100,7 @@ public final class ReactorHttpClientBuilder { @@ -72,7 +100,7 @@ public final class ReactorHttpClientBuilder {
*/
public HttpClient build(HttpClientSettings settings) {
settings = (settings != null) ? settings : HttpClientSettings.DEFAULTS;
HttpClient httpClient = applyDefaults(HttpClient.create());
HttpClient httpClient = applyDefaults(this.factory.get());
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
httpClient = map.from(settings::connectTimeout).to(httpClient, this::setConnectTimeout);
httpClient = map.from(settings::readTimeout).to(httpClient, HttpClient::responseTimeout);

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

@ -19,11 +19,13 @@ package org.springframework.boot.http.client.reactive; @@ -19,11 +19,13 @@ package org.springframework.boot.http.client.reactive;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import reactor.netty.http.client.HttpClient;
import org.springframework.boot.http.client.ReactorHttpClientBuilder;
import org.springframework.http.client.ReactorResourceFactory;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@ -60,6 +62,30 @@ public final class ReactorClientHttpConnectorBuilder @@ -60,6 +62,30 @@ public final class ReactorClientHttpConnectorBuilder
return new ReactorClientHttpConnectorBuilder(mergedCustomizers(customizers), this.httpClientBuilder);
}
/**
* Return a new {@link ReactorClientHttpConnectorBuilder} that uses the given
* {@link ReactorResourceFactory} to create the underlying {@link HttpClient}.
* @param reactorResourceFactory the {@link ReactorResourceFactory} to use
* @return a new {@link ReactorClientHttpConnectorBuilder} instance
*/
public ReactorClientHttpConnectorBuilder withReactorResourceFactory(ReactorResourceFactory reactorResourceFactory) {
Assert.notNull(reactorResourceFactory, "'reactorResourceFactory' must not be null");
return new ReactorClientHttpConnectorBuilder(getCustomizers(),
this.httpClientBuilder.withReactorResourceFactory(reactorResourceFactory));
}
/**
* Return a new {@link ReactorClientHttpConnectorBuilder} that uses the given factory
* to create the underlying {@link HttpClient}.
* @param factory the factory to use
* @return a new {@link ReactorClientHttpConnectorBuilder} instance
*/
public ReactorClientHttpConnectorBuilder withHttpClientFactory(Supplier<HttpClient> factory) {
Assert.notNull(factory, "'factory' must not be null");
return new ReactorClientHttpConnectorBuilder(getCustomizers(),
this.httpClientBuilder.withHttpClientFactory(factory));
}
/**
* Return a new {@link ReactorClientHttpConnectorBuilder} that applies additional
* customization to the underlying {@link HttpClient}.

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

@ -19,6 +19,7 @@ package org.springframework.boot.http.client; @@ -19,6 +19,7 @@ package org.springframework.boot.http.client;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import io.netty.channel.ChannelOption;
@ -26,9 +27,12 @@ import org.junit.jupiter.api.Test; @@ -26,9 +27,12 @@ import org.junit.jupiter.api.Test;
import reactor.netty.http.client.HttpClient;
import org.springframework.http.client.ReactorClientHttpRequestFactory;
import org.springframework.http.client.ReactorResourceFactory;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.spy;
/**
* Tests for {@link ReactorClientHttpRequestFactoryBuilder} and
@ -44,6 +48,25 @@ class ReactorClientHttpRequestFactoryBuilderTests @@ -44,6 +48,25 @@ class ReactorClientHttpRequestFactoryBuilderTests
super(ReactorClientHttpRequestFactory.class, ClientHttpRequestFactoryBuilder.reactor());
}
@Test
void withwithHttpClientFactory() {
boolean[] called = new boolean[1];
Supplier<HttpClient> httpClientFactory = () -> {
called[0] = true;
return HttpClient.create();
};
ClientHttpRequestFactoryBuilder.reactor().withHttpClientFactory(httpClientFactory).build();
assertThat(called).containsExactly(true);
}
@Test
void withReactorResourceFactory() {
ReactorResourceFactory resourceFactory = spy(new ReactorResourceFactory());
ClientHttpRequestFactoryBuilder.reactor().withReactorResourceFactory(resourceFactory).build();
then(resourceFactory).should().getConnectionProvider();
then(resourceFactory).should().getLoopResources();
}
@Test
void withCustomizers() {
List<HttpClient> httpClients = new ArrayList<>();

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

@ -18,18 +18,21 @@ package org.springframework.boot.http.client.reactive; @@ -18,18 +18,21 @@ package org.springframework.boot.http.client.reactive;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
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.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ReactorHttpClientBuilder;
import org.springframework.http.client.ReactorResourceFactory;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.spy;
/**
* Tests for {@link ReactorClientHttpConnectorBuilder} and
@ -44,6 +47,25 @@ class ReactorClientHttpConnectorBuilderTests @@ -44,6 +47,25 @@ class ReactorClientHttpConnectorBuilderTests
super(ReactorClientHttpConnector.class, ClientHttpConnectorBuilder.reactor());
}
@Test
void withwithHttpClientFactory() {
boolean[] called = new boolean[1];
Supplier<HttpClient> httpClientFactory = () -> {
called[0] = true;
return HttpClient.create();
};
ClientHttpConnectorBuilder.reactor().withHttpClientFactory(httpClientFactory).build();
assertThat(called).containsExactly(true);
}
@Test
void withReactorResourceFactory() {
ReactorResourceFactory resourceFactory = spy(new ReactorResourceFactory());
ClientHttpConnectorBuilder.reactor().withReactorResourceFactory(resourceFactory).build();
then(resourceFactory).should().getConnectionProvider();
then(resourceFactory).should().getLoopResources();
}
@Test
void withCustomizers() {
List<HttpClient> httpClients = new ArrayList<>();

Loading…
Cancel
Save