Move tests that use Tomcat out of spring-boot-all

This commit is contained in:
Andy Wilkinson
2025-03-11 15:50:22 +00:00
committed by Phillip Webb
parent bc398e6dd7
commit b00c8510a7
27 changed files with 28 additions and 1 deletions

View File

@@ -1,243 +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;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
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.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.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Base class for {@link ClientHttpRequestFactoryBuilder} tests.
*
* @param <T> The {@link ClientHttpRequestFactory} type
* @author Phillip Webb
* @author Andy Wilkinson
*/
@DirtiesUrlFactories
abstract class AbstractClientHttpRequestFactoryBuilderTests<T extends ClientHttpRequestFactory> {
private static final Function<HttpMethod, HttpStatus> ALWAYS_FOUND = (method) -> HttpStatus.FOUND;
private final Class<T> requestFactoryType;
private final ClientHttpRequestFactoryBuilder<T> builder;
AbstractClientHttpRequestFactoryBuilderTests(Class<T> requestFactoryType,
ClientHttpRequestFactoryBuilder<T> builder) {
this.requestFactoryType = requestFactoryType;
this.builder = builder;
}
@Test
void buildReturnsRequestFactoryOfExpectedType() {
T requestFactory = this.builder.build();
assertThat(requestFactory).isInstanceOf(this.requestFactoryType);
}
@Test
void buildWhenHasConnectTimeout() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withConnectTimeout(Duration.ofSeconds(60));
T requestFactory = this.builder.build(settings);
assertThat(connectTimeout(requestFactory)).isEqualTo(Duration.ofSeconds(60).toMillis());
}
@Test
void buildWhenHadReadTimeout() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withReadTimeout(Duration.ofSeconds(120));
T requestFactory = this.builder.build(settings);
assertThat(readTimeout(requestFactory)).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));
ClientHttpRequestFactory insecureRequestFactory = this.builder.build();
ClientHttpRequest insecureRequest = request(insecureRequestFactory, uri, httpMethod);
assertThatExceptionOfType(SSLHandshakeException.class)
.isThrownBy(() -> insecureRequest.execute().getBody());
ClientHttpRequestFactory secureRequestFactory = this.builder
.build(ClientHttpRequestFactorySettings.ofSslBundle(sslBundle()));
ClientHttpRequest secureRequest = request(secureRequestFactory, uri, httpMethod);
String secureResponse = StreamUtils.copyToString(secureRequest.execute().getBody(), StandardCharsets.UTF_8);
assertThat(secureResponse).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));
ClientHttpRequestFactory requestFactory = this.builder.build(ClientHttpRequestFactorySettings
.ofSslBundle(sslBundle(SslOptions.of(Set.of("TLS_AES_256_GCM_SHA384"), null))));
ClientHttpRequest secureRequest = request(requestFactory, uri, httpMethod);
assertThatExceptionOfType(SSLHandshakeException.class).isThrownBy(() -> secureRequest.execute().getBody());
}
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 {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.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 {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withRedirects(HttpRedirects.DONT_FOLLOW);
testRedirect(settings, HttpMethod.valueOf(httpMethod), ALWAYS_FOUND);
}
protected final void testRedirect(ClientHttpRequestFactorySettings settings, HttpMethod httpMethod,
Function<HttpMethod, HttpStatus> expectedStatusForMethod) throws URISyntaxException, IOException {
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");
ClientHttpRequestFactory requestFactory = this.builder.build(settings);
ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod);
ClientHttpResponse response = request.execute();
assertThat(response.getStatusCode()).isEqualTo(expectedStatus);
if (expectedStatus == HttpStatus.OK) {
assertThat(response.getBody()).asString(StandardCharsets.UTF_8).contains("request to /redirected");
}
}
finally {
webServer.stop();
}
}
private ClientHttpRequest request(ClientHttpRequestFactory factory, URI uri, String method) throws IOException {
return factory.createRequest(uri, HttpMethod.valueOf(method));
}
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 requestFactory);
protected abstract long readTimeout(T requestFactory);
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,112 +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;
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.config.RequestConfig;
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.junit.jupiter.api.Test;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HttpComponentsClientHttpRequestFactoryBuilder} and
* {@link HttpComponentsHttpClientBuilder}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class HttpComponentsClientHttpRequestFactoryBuilderTests
extends AbstractClientHttpRequestFactoryBuilderTests<HttpComponentsClientHttpRequestFactory> {
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<SocketConfig.Builder> socketConfigCustomizer = new TestCustomizer<>();
TestCustomizer<SocketConfig.Builder> socketConfigCustomizer1 = new TestCustomizer<>();
TestCustomizer<RequestConfig.Builder> defaultRequestConfigCustomizer = new TestCustomizer<>();
TestCustomizer<RequestConfig.Builder> defaultRequestConfigCustomizer1 = new TestCustomizer<>();
ClientHttpRequestFactoryBuilder.httpComponents()
.withHttpClientCustomizer(httpClientCustomizer1)
.withHttpClientCustomizer(httpClientCustomizer2)
.withConnectionManagerCustomizer(connectionManagerCustomizer)
.withSocketConfigCustomizer(socketConfigCustomizer)
.withSocketConfigCustomizer(socketConfigCustomizer1)
.withDefaultRequestConfigCustomizer(defaultRequestConfigCustomizer)
.withDefaultRequestConfigCustomizer(defaultRequestConfigCustomizer1)
.build();
httpClientCustomizer1.assertCalled();
httpClientCustomizer2.assertCalled();
connectionManagerCustomizer.assertCalled();
socketConfigCustomizer.assertCalled();
socketConfigCustomizer1.assertCalled();
defaultRequestConfigCustomizer.assertCalled();
defaultRequestConfigCustomizer1.assertCalled();
}
@Test
@WithPackageResources("test.jks")
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");
}
@Override
@SuppressWarnings("unchecked")
protected long readTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
HttpClient httpClient = requestFactory.getHttpClient();
Object connectionManager = ReflectionTestUtils.getField(httpClient, "connManager");
SocketConfig socketConfig = ((Resolver<HttpRoute, SocketConfig>) ReflectionTestUtils.getField(connectionManager,
"socketConfigResolver"))
.resolve(null);
return socketConfig.getSoTimeout().toMilliseconds();
}
}

View File

@@ -1,63 +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;
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;
/**
* Tests for {@link JdkClientHttpRequestFactoryBuilder} and {@link JdkHttpClientBuilder}.
*
* @author Phillip Webb
*/
class JdkClientHttpRequestFactoryBuilderTests
extends AbstractClientHttpRequestFactoryBuilderTests<JdkClientHttpRequestFactory> {
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");
return httpClient.connectTimeout().get().toMillis();
}
@Override
protected long readTimeout(JdkClientHttpRequestFactory requestFactory) {
Duration readTimeout = (Duration) ReflectionTestUtils.getField(requestFactory, "readTimeout");
return readTimeout.toMillis();
}
}

View File

@@ -1,68 +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;
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;
/**
* Tests for {@link JettyClientHttpRequestFactoryBuilder} and
* {@link JettyHttpClientBuilder}.
*
* @author Phillip Webb
*/
class JettyClientHttpRequestFactoryBuilderTests
extends AbstractClientHttpRequestFactoryBuilderTests<JettyClientHttpRequestFactory> {
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();
}
@Override
protected long readTimeout(JettyClientHttpRequestFactory requestFactory) {
return (long) ReflectionTestUtils.getField(requestFactory, "readTimeout");
}
}

View File

@@ -1,100 +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;
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;
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
* {@link ReactorHttpClientBuilder}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class ReactorClientHttpRequestFactoryBuilderTests
extends AbstractClientHttpRequestFactoryBuilderTests<ReactorClientHttpRequestFactory> {
ReactorClientHttpRequestFactoryBuilderTests() {
super(ReactorClientHttpRequestFactory.class, ClientHttpRequestFactoryBuilder.reactor());
}
@Test
void withHttpClientFactory() {
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<>();
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()
.options()
.get(ChannelOption.CONNECT_TIMEOUT_MILLIS);
}
@Override
protected long readTimeout(ReactorClientHttpRequestFactory requestFactory) {
return ((Duration) ReflectionTestUtils.getField(requestFactory, "readTimeout")).toMillis();
}
}

View File

@@ -1,274 +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;
import java.net.URI;
import java.time.Duration;
import org.eclipse.jetty.client.HttpClient;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.JettyClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link ReflectiveComponentsClientHttpRequestFactoryBuilder}.
*
* @author Phillip Webb
*/
class ReflectiveComponentsClientHttpRequestFactoryBuilderTests
extends AbstractClientHttpRequestFactoryBuilderTests<ClientHttpRequestFactory> {
ReflectiveComponentsClientHttpRequestFactoryBuilderTests() {
super(ClientHttpRequestFactory.class, ClientHttpRequestFactoryBuilder.of(JettyClientHttpRequestFactory::new));
}
@Override
void connectWithSslBundle(String httpMethod) throws Exception {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.ofSslBundle(sslBundle());
assertThatIllegalStateException().isThrownBy(() -> ofTestRequestFactory().build(settings))
.withMessage("Unable to set SSL bundler using reflection");
}
@Override
void redirectFollow(String httpMethod) throws Exception {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withRedirects(HttpRedirects.FOLLOW);
assertThatIllegalStateException().isThrownBy(() -> ofTestRequestFactory().build(settings))
.withMessage("Unable to set redirect follow using reflection");
}
@Override
void redirectDontFollow(String httpMethod) throws Exception {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withRedirects(HttpRedirects.DONT_FOLLOW);
assertThatIllegalStateException().isThrownBy(() -> ofTestRequestFactory().build(settings))
.withMessage("Unable to set redirect follow using reflection");
}
@Override
void connectWithSslBundleAndOptionsMismatch(String httpMethod) throws Exception {
assertThatIllegalStateException().isThrownBy(() -> super.connectWithSslBundleAndOptionsMismatch(httpMethod))
.withMessage("Unable to set SSL bundler using reflection");
}
@Test
void buildWithClassCreatesFactory() {
assertThat(ofTestRequestFactory().build()).isInstanceOf(TestClientHttpRequestFactory.class);
}
@Test
void buildWithClassWhenHasConnectTimeout() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withConnectTimeout(Duration.ofSeconds(60));
TestClientHttpRequestFactory requestFactory = ofTestRequestFactory().build(settings);
assertThat(requestFactory.connectTimeout).isEqualTo(Duration.ofSeconds(60).toMillis());
}
@Test
void buildWithClassWhenHasReadTimeout() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withReadTimeout(Duration.ofSeconds(90));
TestClientHttpRequestFactory requestFactory = ofTestRequestFactory().build(settings);
assertThat(requestFactory.readTimeout).isEqualTo(Duration.ofSeconds(90).toMillis());
}
@Test
void buildWithClassWhenUnconfigurableTypeWithConnectTimeoutThrowsException() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withConnectTimeout(Duration.ofSeconds(60));
assertThatIllegalStateException().isThrownBy(() -> ofUnconfigurableRequestFactory().build(settings))
.withMessageContaining("suitable setConnectTimeout method");
}
@Test
void buildWithClassWhenUnconfigurableTypeWithReadTimeoutThrowsException() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withReadTimeout(Duration.ofSeconds(60));
assertThatIllegalStateException().isThrownBy(() -> ofUnconfigurableRequestFactory().build(settings))
.withMessageContaining("suitable setReadTimeout method");
}
@Test
void buildWithClassWhenDeprecatedMethodsTypeWithConnectTimeoutThrowsException() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withConnectTimeout(Duration.ofSeconds(60));
assertThatIllegalStateException().isThrownBy(() -> ofDeprecatedMethodsRequestFactory().build(settings))
.withMessageContaining("setConnectTimeout method marked as deprecated");
}
@Test
void buildWithClassWhenDeprecatedMethodsTypeWithReadTimeoutThrowsException() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withReadTimeout(Duration.ofSeconds(60));
assertThatIllegalStateException().isThrownBy(() -> ofDeprecatedMethodsRequestFactory().build(settings))
.withMessageContaining("setReadTimeout method marked as deprecated");
}
@Test
void buildWithSupplierWhenWrappedRequestFactoryTypeWithConnectTimeout() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withConnectTimeout(Duration.ofMillis(1234));
SimpleClientHttpRequestFactory wrappedRequestFactory = new SimpleClientHttpRequestFactory();
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactoryBuilder
.of(() -> new BufferingClientHttpRequestFactory(wrappedRequestFactory))
.build(settings);
assertThat(requestFactory).extracting("requestFactory").isSameAs(wrappedRequestFactory);
assertThat(wrappedRequestFactory).hasFieldOrPropertyWithValue("connectTimeout", 1234);
}
@Test
void buildWithSupplierWhenWrappedRequestFactoryTypeWithReadTimeout() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withReadTimeout(Duration.ofMillis(1234));
SimpleClientHttpRequestFactory wrappedRequestFactory = new SimpleClientHttpRequestFactory();
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactoryBuilder
.of(() -> new BufferingClientHttpRequestFactory(wrappedRequestFactory))
.build(settings);
assertThat(requestFactory).extracting("requestFactory").isSameAs(wrappedRequestFactory);
assertThat(wrappedRequestFactory).hasFieldOrPropertyWithValue("readTimeout", 1234);
}
@Test
void buildWithClassWhenHasMultipleTimeoutSettersFavorsDurationMethods() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withConnectTimeout(Duration.ofSeconds(1))
.withReadTimeout(Duration.ofSeconds(2));
IntAndDurationTimeoutsClientHttpRequestFactory requestFactory = ClientHttpRequestFactoryBuilder
.of(IntAndDurationTimeoutsClientHttpRequestFactory.class)
.build(settings);
assertThat((requestFactory).connectTimeout).isZero();
assertThat((requestFactory).readTimeout).isZero();
assertThat((requestFactory).connectTimeoutDuration).isEqualTo(Duration.ofSeconds(1));
assertThat((requestFactory).readTimeoutDuration).isEqualTo(Duration.ofSeconds(2));
}
private ClientHttpRequestFactoryBuilder<TestClientHttpRequestFactory> ofTestRequestFactory() {
return ClientHttpRequestFactoryBuilder.of(TestClientHttpRequestFactory.class);
}
private ClientHttpRequestFactoryBuilder<UnconfigurableClientHttpRequestFactory> ofUnconfigurableRequestFactory() {
return ClientHttpRequestFactoryBuilder.of(UnconfigurableClientHttpRequestFactory.class);
}
private ClientHttpRequestFactoryBuilder<DeprecatedMethodsClientHttpRequestFactory> ofDeprecatedMethodsRequestFactory() {
return ClientHttpRequestFactoryBuilder.of(DeprecatedMethodsClientHttpRequestFactory.class);
}
@Override
protected long connectTimeout(ClientHttpRequestFactory requestFactory) {
return ((HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient")).getConnectTimeout();
}
@Override
protected long readTimeout(ClientHttpRequestFactory requestFactory) {
return (long) ReflectionTestUtils.getField(requestFactory, "readTimeout");
}
public static class TestClientHttpRequestFactory implements ClientHttpRequestFactory {
private int connectTimeout;
private int readTimeout;
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
throw new UnsupportedOperationException();
}
public void setConnectTimeout(int timeout) {
this.connectTimeout = timeout;
}
public void setReadTimeout(int timeout) {
this.readTimeout = timeout;
}
}
public static class UnconfigurableClientHttpRequestFactory implements ClientHttpRequestFactory {
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
throw new UnsupportedOperationException();
}
}
public static class DeprecatedMethodsClientHttpRequestFactory implements ClientHttpRequestFactory {
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
throw new UnsupportedOperationException();
}
@Deprecated(since = "3.0.0", forRemoval = false)
public void setConnectTimeout(int timeout) {
}
@Deprecated(since = "3.0.0", forRemoval = false)
public void setReadTimeout(int timeout) {
}
@Deprecated(since = "3.0.0", forRemoval = false)
public void setBufferRequestBody(boolean bufferRequestBody) {
}
}
public static class IntAndDurationTimeoutsClientHttpRequestFactory implements ClientHttpRequestFactory {
private int readTimeout;
private int connectTimeout;
private Duration readTimeoutDuration;
private Duration connectTimeoutDuration;
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
throw new UnsupportedOperationException();
}
public void setConnectTimeout(int timeout) {
this.connectTimeout = timeout;
}
public void setReadTimeout(int timeout) {
this.readTimeout = timeout;
}
public void setConnectTimeout(Duration timeout) {
this.connectTimeoutDuration = timeout;
}
public void setReadTimeout(Duration timeout) {
this.readTimeoutDuration = timeout;
}
}
}

View File

@@ -1,84 +0,0 @@
/*
* 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 org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link SimpleClientHttpRequestFactoryBuilder}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class SimpleClientHttpRequestFactoryBuilderTests
extends AbstractClientHttpRequestFactoryBuilderTests<SimpleClientHttpRequestFactory> {
SimpleClientHttpRequestFactoryBuilderTests() {
super(SimpleClientHttpRequestFactory.class, ClientHttpRequestFactoryBuilder.simple());
}
@Override
protected long connectTimeout(SimpleClientHttpRequestFactory requestFactory) {
return (int) ReflectionTestUtils.getField(requestFactory, "connectTimeout");
}
@Override
protected long readTimeout(SimpleClientHttpRequestFactory requestFactory) {
return (int) ReflectionTestUtils.getField(requestFactory, "readTimeout");
}
@Override
void connectWithSslBundleAndOptionsMismatch(String httpMethod) throws Exception {
assertThatIllegalStateException().isThrownBy(() -> super.connectWithSslBundleAndOptionsMismatch(httpMethod))
.withMessage("SSL Options cannot be specified with Java connections");
}
@ParameterizedTest
@ValueSource(strings = { "GET", "POST", "PUT", "DELETE" })
@Override
void redirectDefault(String httpMethod) throws Exception {
super.redirectDefault(httpMethod);
}
@ParameterizedTest
@ValueSource(strings = { "GET", "POST", "PUT", "DELETE" })
@Override
void redirectFollow(String httpMethod) throws Exception {
super.redirectFollow(httpMethod);
}
@ParameterizedTest
@ValueSource(strings = { "GET", "POST", "PUT", "DELETE" })
@Override
void redirectDontFollow(String httpMethod) throws Exception {
super.redirectDontFollow(httpMethod);
}
@Override
protected HttpStatus getExpectedRedirect(HttpMethod httpMethod) {
return (httpMethod != HttpMethod.GET) ? HttpStatus.FOUND : HttpStatus.OK;
}
}

View File

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

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();
}
}

View File

@@ -1,186 +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.web.client;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
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.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundleKey;
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.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Base classes for testing of {@link ClientHttpRequestFactories} with different HTTP
* clients on the classpath.
*
* @param <T> the {@link ClientHttpRequestFactory} to be produced
* @author Andy Wilkinson
*/
@DirtiesUrlFactories
@SuppressWarnings("removal")
abstract class AbstractClientHttpRequestFactoriesTests<T extends ClientHttpRequestFactory> {
private final Class<T> requestFactoryType;
protected AbstractClientHttpRequestFactoriesTests(Class<T> requestFactoryType) {
this.requestFactoryType = requestFactoryType;
}
@Test
@SuppressWarnings("deprecation")
void getReturnsRequestFactoryOfExpectedType() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories
.get(ClientHttpRequestFactorySettings.DEFAULTS);
assertThat(requestFactory).isInstanceOf(this.requestFactoryType);
}
@Test
@SuppressWarnings("deprecation")
void getOfGeneralTypeReturnsRequestFactoryOfExpectedType() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(ClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS);
assertThat(requestFactory).isInstanceOf(this.requestFactoryType);
}
@Test
@SuppressWarnings("deprecation")
void getOfSpecificTypeReturnsRequestFactoryOfExpectedType() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(this.requestFactoryType,
ClientHttpRequestFactorySettings.DEFAULTS);
assertThat(requestFactory).isInstanceOf(this.requestFactoryType);
}
@Test
@SuppressWarnings({ "deprecation", "unchecked" })
void getReturnsRequestFactoryWithConfiguredConnectTimeout() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories
.get(ClientHttpRequestFactorySettings.DEFAULTS.withConnectTimeout(Duration.ofSeconds(60)));
assertThat(connectTimeout((T) requestFactory)).isEqualTo(Duration.ofSeconds(60).toMillis());
}
@Test
@SuppressWarnings({ "deprecation", "unchecked" })
void getReturnsRequestFactoryWithConfiguredReadTimeout() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories
.get(ClientHttpRequestFactorySettings.DEFAULTS.withReadTimeout(Duration.ofSeconds(120)));
assertThat(readTimeout((T) requestFactory)).isEqualTo(Duration.ofSeconds(120).toMillis());
}
@Test
@SuppressWarnings("deprecation")
void shouldSetConnectTimeoutsWhenUsingReflective() {
Assumptions.assumeTrue(supportsSettingConnectTimeout());
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
.withConnectTimeout(Duration.ofSeconds(1));
T requestFactory = ClientHttpRequestFactories
.get(() -> ClientHttpRequestFactories.get(this.requestFactoryType, settings), settings);
assertThat(connectTimeout(requestFactory)).isEqualTo(1000);
}
@Test
@SuppressWarnings("deprecation")
void shouldSetReadTimeoutsWhenUsingReflective() {
Assumptions.assumeTrue(supportsSettingReadTimeout());
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
.withReadTimeout(Duration.ofSeconds(2));
T requestFactory = ClientHttpRequestFactories
.get(() -> ClientHttpRequestFactories.get(this.requestFactoryType, settings), settings);
assertThat(readTimeout(requestFactory)).isEqualTo(2000);
}
@ParameterizedTest
@SuppressWarnings("deprecation")
@ValueSource(strings = { "GET", "POST" })
@WithPackageResources("test.jks")
void connectWithSslBundle(String httpMethod) throws Exception {
TomcatServletWebServerFactory webServerFactory = new TomcatServletWebServerFactory(0);
Ssl ssl = new Ssl();
ssl.setClientAuth(ClientAuth.NEED);
ssl.setKeyPassword("password");
ssl.setKeyStore("classpath:test.jks");
ssl.setTrustStore("classpath:test.jks");
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));
ClientHttpRequestFactory insecureRequestFactory = ClientHttpRequestFactories
.get(ClientHttpRequestFactorySettings.DEFAULTS);
ClientHttpRequest insecureRequest = insecureRequestFactory.createRequest(uri, HttpMethod.GET);
assertThatExceptionOfType(SSLHandshakeException.class)
.isThrownBy(() -> insecureRequest.execute().getBody());
JksSslStoreDetails storeDetails = JksSslStoreDetails.forLocation("classpath:test.jks");
JksSslStoreBundle stores = new JksSslStoreBundle(storeDetails, storeDetails);
SslBundle sslBundle = SslBundle.of(stores, SslBundleKey.of("password"));
ClientHttpRequestFactory secureRequestFactory = ClientHttpRequestFactories
.get(ClientHttpRequestFactorySettings.DEFAULTS.withSslBundle(sslBundle));
ClientHttpRequest secureRequest = secureRequestFactory.createRequest(uri, HttpMethod.valueOf(httpMethod));
String secureResponse = StreamUtils.copyToString(secureRequest.execute().getBody(), StandardCharsets.UTF_8);
assertThat(secureResponse).contains("Received " + httpMethod + " request to /");
}
finally {
webServer.stop();
}
}
protected abstract boolean supportsSettingConnectTimeout();
protected abstract long connectTimeout(T requestFactory);
protected abstract boolean supportsSettingReadTimeout();
protected abstract long readTimeout(T requestFactory);
public static class TestServlet extends HttpServlet {
@Override
public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
res.getWriter().println("Received " + req.getMethod() + " request to " + req.getRequestURI());
}
}
}

View File

@@ -1,67 +0,0 @@
/*
* 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.web.client;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.core5.function.Resolver;
import org.apache.hc.core5.http.io.SocketConfig;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Tests for {@link ClientHttpRequestFactories} when Apache Http Components is the
* predominant HTTP client.
*
* @author Andy Wilkinson
*/
@SuppressWarnings("removal")
class ClientHttpRequestFactoriesHttpComponentsTests
extends AbstractClientHttpRequestFactoriesTests<HttpComponentsClientHttpRequestFactory> {
ClientHttpRequestFactoriesHttpComponentsTests() {
super(HttpComponentsClientHttpRequestFactory.class);
}
@Override
protected long connectTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
return (long) ReflectionTestUtils.getField(requestFactory, "connectTimeout");
}
@Override
@SuppressWarnings("unchecked")
protected long readTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
HttpClient httpClient = requestFactory.getHttpClient();
Object connectionManager = ReflectionTestUtils.getField(httpClient, "connManager");
SocketConfig socketConfig = ((Resolver<HttpRoute, SocketConfig>) ReflectionTestUtils.getField(connectionManager,
"socketConfigResolver"))
.resolve(null);
return socketConfig.getSoTimeout().toMilliseconds();
}
@Override
protected boolean supportsSettingConnectTimeout() {
return true;
}
@Override
protected boolean supportsSettingReadTimeout() {
return false;
}
}

View File

@@ -1,59 +0,0 @@
/*
* 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.web.client;
import org.eclipse.jetty.client.HttpClient;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.http.client.JettyClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Tests for {@link ClientHttpRequestFactories} when Jetty is the predominant HTTP client.
*
* @author Arjen Poutsma
*/
@ClassPathExclusions("httpclient5-*.jar")
@SuppressWarnings("removal")
class ClientHttpRequestFactoriesJettyTests
extends AbstractClientHttpRequestFactoriesTests<JettyClientHttpRequestFactory> {
ClientHttpRequestFactoriesJettyTests() {
super(JettyClientHttpRequestFactory.class);
}
@Override
protected long connectTimeout(JettyClientHttpRequestFactory requestFactory) {
return ((HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient")).getConnectTimeout();
}
@Override
protected long readTimeout(JettyClientHttpRequestFactory requestFactory) {
return (long) ReflectionTestUtils.getField(requestFactory, "readTimeout");
}
@Override
protected boolean supportsSettingConnectTimeout() {
return true;
}
@Override
protected boolean supportsSettingReadTimeout() {
return true;
}
}

View File

@@ -1,65 +0,0 @@
/*
* 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.web.client;
import java.time.Duration;
import io.netty.channel.ChannelOption;
import reactor.netty.http.client.HttpClient;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.http.client.ReactorClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Tests for {@link ClientHttpRequestFactories} when Reactor Netty is the predominant HTTP
* client.
*
* @author Andy Wilkinson
*/
@ClassPathExclusions({ "httpclient5-*.jar", "jetty-client-*.jar" })
@SuppressWarnings("removal")
class ClientHttpRequestFactoriesReactorTests
extends AbstractClientHttpRequestFactoriesTests<ReactorClientHttpRequestFactory> {
ClientHttpRequestFactoriesReactorTests() {
super(ReactorClientHttpRequestFactory.class);
}
@Override
protected long connectTimeout(ReactorClientHttpRequestFactory requestFactory) {
return (int) ((HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient")).configuration()
.options()
.get(ChannelOption.CONNECT_TIMEOUT_MILLIS);
}
@Override
protected long readTimeout(ReactorClientHttpRequestFactory requestFactory) {
return ((Duration) ReflectionTestUtils.getField(requestFactory, "readTimeout")).toMillis();
}
@Override
protected boolean supportsSettingConnectTimeout() {
return true;
}
@Override
protected boolean supportsSettingReadTimeout() {
return true;
}
}

View File

@@ -1,58 +0,0 @@
/*
* 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.web.client;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Tests for {@link ClientHttpRequestFactories} when the simple JDK-based client is the
* predominant HTTP client.
*
* @author Andy Wilkinson
*/
@ClassPathExclusions({ "httpclient5-*.jar", "jetty-client-*.jar", "reactor-netty-http-*.jar" })
@SuppressWarnings("removal")
class ClientHttpRequestFactoriesSimpleTests
extends AbstractClientHttpRequestFactoriesTests<SimpleClientHttpRequestFactory> {
ClientHttpRequestFactoriesSimpleTests() {
super(SimpleClientHttpRequestFactory.class);
}
@Override
protected long connectTimeout(SimpleClientHttpRequestFactory requestFactory) {
return (int) ReflectionTestUtils.getField(requestFactory, "connectTimeout");
}
@Override
protected long readTimeout(SimpleClientHttpRequestFactory requestFactory) {
return (int) ReflectionTestUtils.getField(requestFactory, "readTimeout");
}
@Override
protected boolean supportsSettingConnectTimeout() {
return true;
}
@Override
protected boolean supportsSettingReadTimeout() {
return true;
}
}

View File

@@ -1,214 +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.web.servlet.context;
import java.net.URI;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.servlet.ServletWebServerFactory;
import org.springframework.boot.web.server.servlet.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.server.servlet.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link ServletWebServerApplicationContext} and {@link WebServer}s
* running Spring MVC.
*
* @author Phillip Webb
* @author Ivan Sopov
*/
@DirtiesUrlFactories
class ServletWebServerMvcIntegrationTests {
private AnnotationConfigServletWebServerApplicationContext context;
@AfterEach
void closeContext() {
try {
this.context.close();
}
catch (Exception ex) {
// Ignore
}
}
@Test
void tomcat() throws Exception {
this.context = new AnnotationConfigServletWebServerApplicationContext(TomcatConfig.class);
doTest(this.context, "/hello");
}
@Test
void jetty() throws Exception {
this.context = new AnnotationConfigServletWebServerApplicationContext(JettyConfig.class);
doTest(this.context, "/hello");
}
@Test
void undertow() throws Exception {
this.context = new AnnotationConfigServletWebServerApplicationContext(UndertowConfig.class);
doTest(this.context, "/hello");
}
@Test
@WithResource(name = "conf.properties", content = "context=/example")
void advancedConfig() throws Exception {
this.context = new AnnotationConfigServletWebServerApplicationContext(AdvancedConfig.class);
doTest(this.context, "/example/spring/hello");
}
private void doTest(AnnotationConfigServletWebServerApplicationContext context, String resourcePath)
throws Exception {
SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();
ClientHttpRequest request = clientHttpRequestFactory.createRequest(
new URI("http://localhost:" + context.getWebServer().getPort() + resourcePath), HttpMethod.GET);
try (ClientHttpResponse response = request.execute()) {
assertThat(response.getBody()).hasContent("Hello World");
}
}
// Simple main method for testing in a browser
@SuppressWarnings("resource")
static void main(String[] args) {
new AnnotationConfigServletWebServerApplicationContext(JettyServletWebServerFactory.class, Config.class);
}
@Configuration(proxyBeanMethods = false)
@Import(Config.class)
static class TomcatConfig {
@Bean
ServletWebServerFactory webServerFactory() {
return new TomcatServletWebServerFactory(0);
}
}
@Configuration(proxyBeanMethods = false)
@Import(Config.class)
static class JettyConfig {
@Bean
ServletWebServerFactory webServerFactory() {
return new JettyServletWebServerFactory(0);
}
}
@Configuration(proxyBeanMethods = false)
@Import(Config.class)
static class UndertowConfig {
@Bean
ServletWebServerFactory webServerFactory() {
return new UndertowServletWebServerFactory(0);
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebMvc
static class Config {
@Bean
DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
// Alternatively you can use ServletContextInitializer beans including
// ServletRegistration and FilterRegistration. Read the
// EmbeddedWebApplicationContext Javadoc for details.
}
@Bean
HelloWorldController helloWorldController() {
return new HelloWorldController();
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebMvc
@PropertySource("classpath:conf.properties")
static class AdvancedConfig {
private final Environment env;
AdvancedConfig(Environment env) {
this.env = env;
}
@Bean
ServletWebServerFactory webServerFactory() {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory(0);
factory.setContextPath(this.env.getProperty("context"));
return factory;
}
@Bean
ServletRegistrationBean<DispatcherServlet> dispatcherRegistration(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(dispatcherServlet);
registration.addUrlMappings("/spring/*");
return registration;
}
@Bean
DispatcherServlet dispatcherServlet() {
// Can configure dispatcher servlet here as would usually do through
// init-params
return new DispatcherServlet();
}
@Bean
HelloWorldController helloWorldController() {
return new HelloWorldController();
}
}
@Controller
static class HelloWorldController {
@RequestMapping("/hello")
@ResponseBody
String sayHello() {
return "Hello World";
}
}
}

View File

@@ -1,201 +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.web.servlet.support;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.servlet.ServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.support.ErrorPageFilterIntegrationTests.EmbeddedWebContextLoader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.support.AbstractContextLoader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link ErrorPageFilter}.
*
* @author Dave Syer
* @author Phillip Webb
*/
@ExtendWith(SpringExtension.class)
@DirtiesContext
@ContextConfiguration(classes = ErrorPageFilterIntegrationTests.TomcatConfig.class,
loader = EmbeddedWebContextLoader.class)
class ErrorPageFilterIntegrationTests {
@Autowired
private HelloWorldController controller;
@Autowired
private AnnotationConfigServletWebServerApplicationContext context;
@AfterEach
void init() {
this.controller.reset();
}
@Test
void created() throws Exception {
doTest(this.context, "/create", HttpStatus.CREATED);
assertThat(this.controller.getStatus()).isEqualTo(201);
}
@Test
void ok() throws Exception {
doTest(this.context, "/hello", HttpStatus.OK);
assertThat(this.controller.getStatus()).isEqualTo(200);
}
private void doTest(AnnotationConfigServletWebServerApplicationContext context, String resourcePath,
HttpStatus status) throws Exception {
int port = context.getWebServer().getPort();
RestTemplate template = new RestTemplate();
ResponseEntity<String> entity = template.getForEntity(new URI("http://localhost:" + port + resourcePath),
String.class);
assertThat(entity.getBody()).isEqualTo("Hello World");
assertThat(entity.getStatusCode()).isEqualTo(status);
}
@Configuration(proxyBeanMethods = false)
@EnableWebMvc
static class TomcatConfig {
@Bean
ServletWebServerFactory webServerFactory() {
return new TomcatServletWebServerFactory(0);
}
@Bean
ErrorPageFilter errorPageFilter() {
return new ErrorPageFilter();
}
@Bean
DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
HelloWorldController helloWorldController() {
return new HelloWorldController();
}
}
@Controller
static class HelloWorldController implements WebMvcConfigurer {
private int status;
private CountDownLatch latch = new CountDownLatch(1);
int getStatus() throws InterruptedException {
assertThat(this.latch.await(1, TimeUnit.SECONDS)).as("Timed out waiting for latch").isTrue();
return this.status;
}
void setStatus(int status) {
this.status = status;
}
void reset() {
this.status = 0;
this.latch = new CountDownLatch(1);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) {
HelloWorldController.this.setStatus(response.getStatus());
HelloWorldController.this.latch.countDown();
}
});
}
@RequestMapping("/hello")
@ResponseBody
String sayHello() {
return "Hello World";
}
@RequestMapping("/create")
@ResponseBody
@ResponseStatus(HttpStatus.CREATED)
String created() {
return "Hello World";
}
}
static class EmbeddedWebContextLoader extends AbstractContextLoader {
private static final String[] EMPTY_RESOURCE_SUFFIXES = {};
@Override
public ApplicationContext loadContext(MergedContextConfiguration config) {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(
config.getClasses());
context.registerShutdownHook();
return context;
}
@Override
protected String[] getResourceSuffixes() {
return EMPTY_RESOURCE_SUFFIXES;
}
@Override
protected String getResourceSuffix() {
throw new UnsupportedOperationException();
}
}
}