Move tests that use Tomcat out of spring-boot-all
This commit is contained in:
committed by
Phillip Webb
parent
bc398e6dd7
commit
b00c8510a7
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user