From 0b442cfe32a379f7bfebb6ce71a2867e3c2bddc4 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 16 Nov 2022 15:42:08 +0100 Subject: [PATCH] Add support for reactive JDK HTTP Client. Closes gh-737 --- .../client/ClientHttpConnectorFactory.java | 79 +++++++++++++++---- ...tHttpConnectorFactoryIntegrationTests.java | 13 +++ .../asciidoc/reference/client-support.adoc | 3 +- 3 files changed, 80 insertions(+), 15 deletions(-) diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java index e24c6dd8..a8eb7cb9 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java @@ -21,6 +21,7 @@ import java.security.GeneralSecurityException; import java.security.KeyStore; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; import io.netty.channel.ChannelOption; import io.netty.handler.ssl.SslContextBuilder; @@ -41,6 +42,7 @@ import reactor.netty.http.client.HttpClient; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector; +import org.springframework.http.client.reactive.JdkClientHttpConnector; import org.springframework.http.client.reactive.JettyClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.util.Assert; @@ -98,24 +100,25 @@ public class ClientHttpConnectorFactory { Assert.notNull(options, "ClientOptions must not be null"); Assert.notNull(sslConfiguration, "SslConfiguration must not be null"); - if (REACTOR_NETTY_PRESENT) { - return ReactorNetty.usingReactorNetty(options, sslConfiguration); - } + try { + if (REACTOR_NETTY_PRESENT) { + return ReactorNetty.usingReactorNetty(options, sslConfiguration); + } - if (HTTP_COMPONENTS_PRESENT) { - try { + if (HTTP_COMPONENTS_PRESENT) { return HttpComponents.usingHttpComponents(options, sslConfiguration); - } - catch (GeneralSecurityException | IOException e) { - throw new IllegalStateException(e); - } - } - if (JETTY_PRESENT) { - return JettyClient.usingJetty(options, sslConfiguration); - } + } - throw new IllegalStateException("No supported Reactive Http Client library available (Reactor Netty, Jetty)"); + if (JETTY_PRESENT) { + return JettyClient.usingJetty(options, sslConfiguration); + } + + return JdkHttpClient.usingJdkHttpClient(options, sslConfiguration); + } + catch (GeneralSecurityException | IOException e) { + throw new IllegalStateException(e); + } } private static void configureSsl(SslConfiguration sslConfiguration, SslContextBuilder sslContextBuilder) { @@ -297,4 +300,52 @@ public class ClientHttpConnectorFactory { } + /** + * {@link ClientHttpRequestFactory} using the JDK's HttpClient. + * + * @author Mark Paluch + */ + static class JdkHttpClient { + + static ClientHttpConnector usingJdkHttpClient(ClientOptions options, SslConfiguration sslConfiguration) + throws GeneralSecurityException, IOException { + + java.net.http.HttpClient.Builder builder = java.net.http.HttpClient.newBuilder(); + + if (hasSslConfiguration(sslConfiguration)) { + + SSLContext sslContext = getSSLContext(sslConfiguration, getTrustManagers(sslConfiguration)); + + String[] enabledProtocols = !sslConfiguration.getEnabledProtocols().isEmpty() + ? sslConfiguration.getEnabledProtocols().toArray(new String[0]) : null; + + String[] enabledCipherSuites = !sslConfiguration.getEnabledCipherSuites().isEmpty() + ? sslConfiguration.getEnabledCipherSuites().toArray(new String[0]) : null; + + BasicClientTlsStrategy tlsStrategy = new BasicClientTlsStrategy(sslContext, (endpoint, sslEngine) -> { + + if (enabledProtocols != null) { + sslEngine.setEnabledProtocols(enabledProtocols); + } + + if (enabledCipherSuites != null) { + sslEngine.setEnabledCipherSuites(enabledCipherSuites); + } + }, null); + + SSLParameters parameters = new SSLParameters(); + parameters.setProtocols(enabledProtocols); + parameters.setCipherSuites(enabledCipherSuites); + + builder.sslContext(sslContext).sslParameters(parameters); + } + + builder.proxy(ProxySelector.getDefault()).followRedirects(java.net.http.HttpClient.Redirect.ALWAYS) + .connectTimeout(options.getConnectionTimeout()); + + return new JdkClientHttpConnector(builder.build()); + } + + } + } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpConnectorFactoryIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpConnectorFactoryIntegrationTests.java index e5fdf7c2..c1e72ed6 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpConnectorFactoryIntegrationTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpConnectorFactoryIntegrationTests.java @@ -160,6 +160,19 @@ class ClientHttpConnectorFactoryIntegrationTests { assertThat(response).isNotNull().contains("initialized"); } + @Test + void jdkHttpClientShouldWork() throws Exception { + + ClientHttpConnector factory = JdkHttpClient.usingJdkHttpClient(new ClientOptions(), + Settings.createSslConfiguration()); + + WebClient webClient = WebClient.builder().clientConnector(factory).build(); + + String response = request(webClient); + + assertThat(response).isNotNull().contains("initialized"); + } + private String request(WebClient webClient) { // Uninitialized and sealed can cause status 500 diff --git a/src/main/asciidoc/reference/client-support.adoc b/src/main/asciidoc/reference/client-support.adoc index 00b22269..93d2bb20 100644 --- a/src/main/asciidoc/reference/client-support.adoc +++ b/src/main/asciidoc/reference/client-support.adoc @@ -8,12 +8,13 @@ that is scoped only to Spring Vault's client components. Spring Vault supports following HTTP imperative clients: -* Java's builtin `HttpURLConnection` (default client) +* Java's builtin `HttpURLConnection` (default client if no other is available) * Apache Http Components * OkHttp 3 Spring Vault's reactive integration supports the following reactive HTTP clients: +* Java's builtin reactive `HttpClient` (default client if no other is available) * Reactor Netty * Apache Http Components * Jetty