From 3bbfee5e938f03048c1b8b0cc976b536633df73f Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Thu, 29 Jun 2023 15:11:20 +0200 Subject: [PATCH] Support JDK HttpClient in ClientHttpRequestFactories See gh-36118 --- .../client/ClientHttpRequestFactories.java | 37 +++++++++++++ ...entHttpRequestFactoriesJdkClientTests.java | 52 +++++++++++++++++++ ...ClientHttpRequestFactoriesSimpleTests.java | 4 +- ...geSenderBuilderSimpleIntegrationTests.java | 2 +- 4 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJdkClientTests.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java index 347f6c4b0c5..63b6595ae9b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java @@ -49,6 +49,7 @@ import org.springframework.boot.ssl.SslOptions; import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.JdkClientHttpRequestFactory; import org.springframework.http.client.JettyClientHttpRequestFactory; import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; @@ -79,6 +80,10 @@ public final class ClientHttpRequestFactories { private static final boolean JETTY_CLIENT_PRESENT = ClassUtils.isPresent(JETTY_CLIENT_CLASS, null); + static final String JDK_CLIENT_CLASS = "java.net.http.HttpClient"; + + private static final boolean JDK_CLIENT_PRESENT = ClassUtils.isPresent(JDK_CLIENT_CLASS, null); + private ClientHttpRequestFactories() { } @@ -99,6 +104,9 @@ public final class ClientHttpRequestFactories { if (JETTY_CLIENT_PRESENT) { return Jetty.get(settings); } + if (JDK_CLIENT_PRESENT) { + return Jdk.get(settings); + } return Simple.get(settings); } @@ -126,6 +134,9 @@ public final class ClientHttpRequestFactories { if (requestFactoryType == JettyClientHttpRequestFactory.class) { return (T) Jetty.get(settings); } + if (requestFactoryType == JdkClientHttpRequestFactory.class) { + return (T) Jdk.get(settings); + } if (requestFactoryType == SimpleClientHttpRequestFactory.class) { return (T) Simple.get(settings); } @@ -254,6 +265,32 @@ public final class ClientHttpRequestFactories { } + /** + * Support for {@link JdkClientHttpRequestFactory}. + */ + static class Jdk { + + static JdkClientHttpRequestFactory get(ClientHttpRequestFactorySettings settings) { + java.net.http.HttpClient httpClient = createHttpClient(settings.connectTimeout(), settings.sslBundle()); + JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(httpClient); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout); + return requestFactory; + } + + private static java.net.http.HttpClient createHttpClient(Duration connectTimeout, SslBundle sslBundle) { + java.net.http.HttpClient.Builder builder = java.net.http.HttpClient.newBuilder(); + if (connectTimeout != null) { + builder.connectTimeout(connectTimeout); + } + if (sslBundle != null) { + builder.sslContext(sslBundle.createSslContext()); + } + return builder.build(); + } + + } + /** * Support for {@link SimpleClientHttpRequestFactory}. */ diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJdkClientTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJdkClientTests.java new file mode 100644 index 00000000000..a3dd693ec7b --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJdkClientTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2023 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.net.http.HttpClient; +import java.time.Duration; + +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.http.client.JdkClientHttpRequestFactory; +import org.springframework.test.util.ReflectionTestUtils; + +/** + * Tests for {@link ClientHttpRequestFactories} when JDK HttpClient is the + * predominant HTTP client. + * + * @author Andy Wilkinson + */ +@ClassPathExclusions({ "httpclient5-*.jar", "okhttp-*.jar" }) +class ClientHttpRequestFactoriesJdkClientTests + extends AbstractClientHttpRequestFactoriesTests { + + ClientHttpRequestFactoriesJdkClientTests() { + super(JdkClientHttpRequestFactory.class); + } + + @Override + protected long connectTimeout(JdkClientHttpRequestFactory requestFactory) { + HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient"); + return httpClient.connectTimeout().map(Duration::toMillis).orElse(-1L); + } + + @Override + @SuppressWarnings("unchecked") + protected long readTimeout(JdkClientHttpRequestFactory requestFactory) { + return ((Duration) ReflectionTestUtils.getField(requestFactory, "readTimeout")).toMillis(); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java index f00882bc7df..189901f20c4 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -26,7 +26,7 @@ import org.springframework.test.util.ReflectionTestUtils; * * @author Andy Wilkinson */ -@ClassPathExclusions({ "httpclient5-*.jar", "okhttp-*.jar", "jetty-client-*.jar" }) +@ClassPathExclusions(files = {"httpclient5-*.jar", "jetty-client-*.jar", "okhttp-*.jar"}, packages = "java.net.http") class ClientHttpRequestFactoriesSimpleTests extends AbstractClientHttpRequestFactoriesTests { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java index 0014d47cd7e..5872c3a092d 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java @@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Stephane Nicoll */ -@ClassPathExclusions(files = { "httpclient5-*.jar", "jetty-client-*.jar", "okhttp*.jar" }) +@ClassPathExclusions(files = { "httpclient5-*.jar", "jetty-client-*.jar", "okhttp*.jar" }, packages = "java.net.http") class HttpWebServiceMessageSenderBuilderSimpleIntegrationTests { private final HttpWebServiceMessageSenderBuilder builder = new HttpWebServiceMessageSenderBuilder();