Create spring-boot-http-client-reactive module

This commit is contained in:
Andy Wilkinson
2025-05-07 11:31:34 +01:00
committed by Phillip Webb
parent 4471c0e644
commit 6e04fc0910
61 changed files with 95 additions and 382 deletions

View File

@@ -1,255 +0,0 @@
/*
* Copyright 2012-2025 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.reactive;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.Set;
import java.util.function.Function;
import javax.net.ssl.SSLHandshakeException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.boot.http.client.HttpRedirects;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundleKey;
import org.springframework.boot.ssl.SslOptions;
import org.springframework.boot.ssl.jks.JksSslStoreBundle;
import org.springframework.boot.ssl.jks.JksSslStoreDetails;
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.Ssl.ClientAuth;
import org.springframework.boot.web.server.WebServer;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFunctions;
import org.springframework.web.reactive.function.client.WebClientRequestException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Base class for {@link ClientHttpConnectorBuilder} tests.
*
* @param <T> The {@link ClientHttpConnector} type
* @author Phillip Webb
* @author Andy Wilkinson
*/
@DirtiesUrlFactories
abstract class AbstractClientHttpConnectorBuilderTests<T extends ClientHttpConnector> {
private static final Function<HttpMethod, HttpStatus> ALWAYS_FOUND = (method) -> HttpStatus.FOUND;
private final Class<T> connectorType;
private final ClientHttpConnectorBuilder<T> builder;
AbstractClientHttpConnectorBuilderTests(Class<T> connectorType, ClientHttpConnectorBuilder<T> builder) {
this.connectorType = connectorType;
this.builder = builder;
}
@Test
void buildReturnsConnectorOfExpectedType() {
T connector = this.builder.build();
assertThat(connector).isInstanceOf(this.connectorType);
}
@Test
void buildWhenHasConnectTimeout() {
ClientHttpConnectorSettings settings = ClientHttpConnectorSettings.defaults()
.withConnectTimeout(Duration.ofSeconds(60));
T connector = this.builder.build(settings);
assertThat(connectTimeout(connector)).isEqualTo(Duration.ofSeconds(60).toMillis());
}
@Test
void buildWhenHadReadTimeout() {
ClientHttpConnectorSettings settings = ClientHttpConnectorSettings.defaults()
.withReadTimeout(Duration.ofSeconds(120));
T connector = this.builder.build(settings);
assertThat(readTimeout(connector)).isEqualTo(Duration.ofSeconds(120).toMillis());
}
@ParameterizedTest
@WithPackageResources("test.jks")
@ValueSource(strings = { "GET", "POST" })
void connectWithSslBundle(String httpMethod) throws Exception {
TomcatServletWebServerFactory webServerFactory = new TomcatServletWebServerFactory(0);
webServerFactory.setSsl(ssl());
WebServer webServer = webServerFactory
.getWebServer((context) -> context.addServlet("test", TestServlet.class).addMapping("/"));
try {
webServer.start();
int port = webServer.getPort();
URI uri = new URI("https://localhost:%s".formatted(port));
ClientHttpConnector insecureConnector = this.builder.build();
ClientRequest insecureRequest = createRequest(httpMethod, uri);
assertThatExceptionOfType(WebClientRequestException.class)
.isThrownBy(() -> getResponse(insecureConnector, insecureRequest))
.withCauseInstanceOf(SSLHandshakeException.class);
ClientHttpConnector secureConnector = this.builder
.build(ClientHttpConnectorSettings.ofSslBundle(sslBundle()));
ClientRequest secureRequest = createRequest(httpMethod, uri);
ClientResponse secureResponse = getResponse(secureConnector, secureRequest);
assertThat(secureResponse.bodyToMono(String.class).block())
.contains("Received " + httpMethod + " request to /");
}
finally {
webServer.stop();
}
}
@ParameterizedTest
@WithPackageResources("test.jks")
@ValueSource(strings = { "GET", "POST" })
void connectWithSslBundleAndOptionsMismatch(String httpMethod) throws Exception {
TomcatServletWebServerFactory webServerFactory = new TomcatServletWebServerFactory(0);
webServerFactory.setSsl(ssl("TLS_AES_128_GCM_SHA256"));
WebServer webServer = webServerFactory
.getWebServer((context) -> context.addServlet("test", TestServlet.class).addMapping("/"));
try {
webServer.start();
int port = webServer.getPort();
URI uri = new URI("https://localhost:%s".formatted(port));
ClientHttpConnector secureConnector = this.builder.build(ClientHttpConnectorSettings
.ofSslBundle(sslBundle(SslOptions.of(Set.of("TLS_AES_256_GCM_SHA384"), null))));
ClientRequest secureRequest = createRequest(httpMethod, uri);
assertThatExceptionOfType(WebClientRequestException.class)
.isThrownBy(() -> getResponse(secureConnector, secureRequest))
.withCauseInstanceOf(SSLHandshakeException.class);
}
finally {
webServer.stop();
}
}
@ParameterizedTest
@ValueSource(strings = { "GET", "POST", "PUT", "PATCH", "DELETE" })
void redirectDefault(String httpMethod) throws Exception {
testRedirect(null, HttpMethod.valueOf(httpMethod), this::getExpectedRedirect);
}
@ParameterizedTest
@ValueSource(strings = { "GET", "POST", "PUT", "PATCH", "DELETE" })
void redirectFollow(String httpMethod) throws Exception {
ClientHttpConnectorSettings settings = ClientHttpConnectorSettings.defaults()
.withRedirects(HttpRedirects.FOLLOW);
testRedirect(settings, HttpMethod.valueOf(httpMethod), this::getExpectedRedirect);
}
@ParameterizedTest
@ValueSource(strings = { "GET", "POST", "PUT", "PATCH", "DELETE" })
void redirectDontFollow(String httpMethod) throws Exception {
ClientHttpConnectorSettings settings = ClientHttpConnectorSettings.defaults()
.withRedirects(HttpRedirects.DONT_FOLLOW);
testRedirect(settings, HttpMethod.valueOf(httpMethod), ALWAYS_FOUND);
}
protected final void testRedirect(ClientHttpConnectorSettings settings, HttpMethod httpMethod,
Function<HttpMethod, HttpStatus> expectedStatusForMethod) throws URISyntaxException {
HttpStatus expectedStatus = expectedStatusForMethod.apply(httpMethod);
TomcatServletWebServerFactory webServerFactory = new TomcatServletWebServerFactory(0);
WebServer webServer = webServerFactory
.getWebServer((context) -> context.addServlet("test", TestServlet.class).addMapping("/"));
try {
webServer.start();
int port = webServer.getPort();
URI uri = new URI("http://localhost:%s".formatted(port) + "/redirect");
ClientHttpConnector connector = this.builder.build(settings);
ClientRequest request = createRequest(httpMethod, uri);
ClientResponse response = getResponse(connector, request);
assertThat(response.statusCode()).isEqualTo(expectedStatus);
if (expectedStatus == HttpStatus.OK) {
assertThat(response.bodyToMono(String.class).block()).contains("request to /redirected");
}
}
finally {
webServer.stop();
}
}
private ClientRequest createRequest(String httpMethod, URI uri) {
return createRequest(HttpMethod.valueOf(httpMethod), uri);
}
private ClientRequest createRequest(HttpMethod httpMethod, URI uri) {
return ClientRequest.create(httpMethod, uri).build();
}
private ClientResponse getResponse(ClientHttpConnector connector, ClientRequest request) {
return ExchangeFunctions.create(connector).exchange(request).block();
}
private Ssl ssl(String... ciphers) {
Ssl ssl = new Ssl();
ssl.setClientAuth(ClientAuth.NEED);
ssl.setKeyPassword("password");
ssl.setKeyStore("classpath:test.jks");
ssl.setTrustStore("classpath:test.jks");
if (ciphers.length > 0) {
ssl.setCiphers(ciphers);
}
return ssl;
}
protected final SslBundle sslBundle() {
return sslBundle(SslOptions.NONE);
}
protected final SslBundle sslBundle(SslOptions sslOptions) {
JksSslStoreDetails storeDetails = JksSslStoreDetails.forLocation("classpath:test.jks");
JksSslStoreBundle stores = new JksSslStoreBundle(storeDetails, storeDetails);
return SslBundle.of(stores, SslBundleKey.of("password"), sslOptions);
}
protected HttpStatus getExpectedRedirect(HttpMethod httpMethod) {
return HttpStatus.OK;
}
protected abstract long connectTimeout(T connector);
protected abstract long readTimeout(T connector);
public static class TestServlet extends HttpServlet {
@Override
public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
if ("/redirect".equals(req.getRequestURI())) {
res.sendRedirect("/redirected");
return;
}
res.getWriter().println("Received " + req.getMethod() + " request to " + req.getRequestURI());
}
}
}

View File

@@ -1,115 +0,0 @@
/*
* Copyright 2012-2025 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.reactive;
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.async.HttpAsyncClient;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
import org.apache.hc.core5.function.Resolver;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.junit.jupiter.api.Test;
import org.springframework.boot.http.client.HttpComponentsHttpAsyncClientBuilder;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HttpComponentsClientHttpConnectorBuilder} and
* {@link HttpComponentsHttpAsyncClientBuilder}.
*
* @author Phillip Webb
*/
class HttpComponentsClientHttpConnectorBuilderTests
extends AbstractClientHttpConnectorBuilderTests<HttpComponentsClientHttpConnector> {
HttpComponentsClientHttpConnectorBuilderTests() {
super(HttpComponentsClientHttpConnector.class, ClientHttpConnectorBuilder.httpComponents());
}
@Test
void withCustomizers() {
TestCustomizer<HttpAsyncClientBuilder> httpClientCustomizer1 = new TestCustomizer<>();
TestCustomizer<HttpAsyncClientBuilder> httpClientCustomizer2 = new TestCustomizer<>();
TestCustomizer<PoolingAsyncClientConnectionManagerBuilder> connectionManagerCustomizer = new TestCustomizer<>();
TestCustomizer<ConnectionConfig.Builder> connectionConfigCustomizer1 = new TestCustomizer<>();
TestCustomizer<ConnectionConfig.Builder> connectionConfigCustomizer2 = new TestCustomizer<>();
TestCustomizer<RequestConfig.Builder> defaultRequestConfigCustomizer = new TestCustomizer<>();
TestCustomizer<RequestConfig.Builder> defaultRequestConfigCustomizer1 = new TestCustomizer<>();
ClientHttpConnectorBuilder.httpComponents()
.withHttpClientCustomizer(httpClientCustomizer1)
.withHttpClientCustomizer(httpClientCustomizer2)
.withConnectionManagerCustomizer(connectionManagerCustomizer)
.withConnectionConfigCustomizer(connectionConfigCustomizer1)
.withConnectionConfigCustomizer(connectionConfigCustomizer2)
.withDefaultRequestConfigCustomizer(defaultRequestConfigCustomizer)
.withDefaultRequestConfigCustomizer(defaultRequestConfigCustomizer1)
.build();
httpClientCustomizer1.assertCalled();
httpClientCustomizer2.assertCalled();
connectionManagerCustomizer.assertCalled();
connectionConfigCustomizer1.assertCalled();
connectionConfigCustomizer2.assertCalled();
defaultRequestConfigCustomizer.assertCalled();
defaultRequestConfigCustomizer1.assertCalled();
}
@Test
@WithPackageResources("test.jks")
void withTlsSocketStrategyFactory() {
ClientHttpConnectorSettings settings = ClientHttpConnectorSettings.ofSslBundle(sslBundle());
List<SslBundle> bundles = new ArrayList<>();
Function<SslBundle, TlsStrategy> tlsSocketStrategyFactory = (bundle) -> {
bundles.add(bundle);
return (sessionLayer, host, localAddress, remoteAddress, attachment, handshakeTimeout) -> false;
};
ClientHttpConnectorBuilder.httpComponents()
.withTlsSocketStrategyFactory(tlsSocketStrategyFactory)
.build(settings);
assertThat(bundles).contains(settings.sslBundle());
}
@Override
protected long connectTimeout(HttpComponentsClientHttpConnector connector) {
return getConnectorConfig(connector).getConnectTimeout().toMilliseconds();
}
@Override
protected long readTimeout(HttpComponentsClientHttpConnector connector) {
return getConnectorConfig(connector).getSocketTimeout().toMilliseconds();
}
@SuppressWarnings("unchecked")
private ConnectionConfig getConnectorConfig(HttpComponentsClientHttpConnector connector) {
HttpAsyncClient httpClient = (HttpAsyncClient) ReflectionTestUtils.getField(connector, "client");
Object manager = ReflectionTestUtils.getField(httpClient, "manager");
ConnectionConfig connectorConfig = ((Resolver<HttpRoute, ConnectionConfig>) ReflectionTestUtils
.getField(manager, "connectionConfigResolver")).resolve(null);
return connectorConfig;
}
}

View File

@@ -1,64 +0,0 @@
/*
* Copyright 2012-2025 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.reactive;
import java.net.http.HttpClient;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.JdkHttpClientBuilder;
import org.springframework.http.client.reactive.JdkClientHttpConnector;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Tests for {@link JdkClientHttpConnectorBuilder} and {@link JdkHttpClientBuilder}.
*
* @author Phillip Webb
*/
class JdkClientHttpConnectorBuilderTests extends AbstractClientHttpConnectorBuilderTests<JdkClientHttpConnector> {
JdkClientHttpConnectorBuilderTests() {
super(JdkClientHttpConnector.class, ClientHttpConnectorBuilder.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(JdkClientHttpConnector connector) {
HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(connector, "httpClient");
return httpClient.connectTimeout().get().toMillis();
}
@Override
protected long readTimeout(JdkClientHttpConnector connector) {
Duration readTimeout = (Duration) ReflectionTestUtils.getField(connector, "readTimeout");
return readTimeout.toMillis();
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright 2012-2025 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.reactive;
import java.time.Duration;
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.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.JettyHttpClientBuilder;
import org.springframework.http.client.reactive.JettyClientHttpConnector;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Tests for {@link JettyClientHttpConnectorBuilder} and {@link JettyHttpClientBuilder}.
*
* @author Phillip Webb
*/
class JettyClientHttpConnectorBuilderTests extends AbstractClientHttpConnectorBuilderTests<JettyClientHttpConnector> {
JettyClientHttpConnectorBuilderTests() {
super(JettyClientHttpConnector.class, ClientHttpConnectorBuilder.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(JettyClientHttpConnector connector) {
return ((HttpClient) ReflectionTestUtils.getField(connector, "httpClient")).getConnectTimeout();
}
@Override
protected long readTimeout(JettyClientHttpConnector connector) {
HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(connector, "httpClient");
return ((Duration) ReflectionTestUtils.getField(httpClient, "readTimeout")).toMillis();
}
}

View File

@@ -1,101 +0,0 @@
/*
* Copyright 2012-2025 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.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.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
* {@link ReactorHttpClientBuilder}.
*
* @author Phillip Webb
*/
class ReactorClientHttpConnectorBuilderTests
extends AbstractClientHttpConnectorBuilderTests<ReactorClientHttpConnector> {
ReactorClientHttpConnectorBuilderTests() {
super(ReactorClientHttpConnector.class, ClientHttpConnectorBuilder.reactor());
}
@Test
void withHttpClientFactory() {
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<>();
UnaryOperator<HttpClient> httpClientCustomizer1 = (httpClient) -> {
httpClients.add(httpClient);
return httpClient;
};
UnaryOperator<HttpClient> httpClientCustomizer2 = (httpClient) -> {
httpClients.add(httpClient);
return httpClient;
};
ClientHttpConnectorBuilder.reactor()
.withHttpClientCustomizer(httpClientCustomizer1)
.withHttpClientCustomizer(httpClientCustomizer2)
.build();
assertThat(httpClients).hasSize(2);
}
@Override
protected long connectTimeout(ReactorClientHttpConnector connector) {
return (int) ((HttpClient) ReflectionTestUtils.getField(connector, "httpClient")).configuration()
.options()
.get(ChannelOption.CONNECT_TIMEOUT_MILLIS);
}
@Override
protected long readTimeout(ReactorClientHttpConnector connector) {
return (int) ((HttpClient) ReflectionTestUtils.getField(connector, "httpClient")).configuration()
.responseTimeout()
.toMillis();
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2012-2025 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.reactive;
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();
}
}