Add support for reactive JDK HTTP Client.

Closes gh-737
This commit is contained in:
Mark Paluch
2022-11-16 15:42:08 +01:00
parent 73d6777dad
commit 0b442cfe32
3 changed files with 80 additions and 15 deletions

View File

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

View File

@@ -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

View File

@@ -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