Allow reuse of library-specific configuration code in ClientHttpRequestFactoryFactory and ClientHttpConnectorFactory.

Closes gh-760
This commit is contained in:
Mark Paluch
2023-03-02 09:27:57 +01:00
parent 4c962ecc72
commit 2d9463eb6f
3 changed files with 176 additions and 97 deletions

View File

@@ -107,41 +107,26 @@ public class ClientHttpConnectorFactory {
}
}
private static void configureSsl(SslConfiguration sslConfiguration, SslContextBuilder sslContextBuilder) {
try {
if (sslConfiguration.getTrustStoreConfiguration().isPresent()) {
sslContextBuilder
.trustManager(createTrustManagerFactory(sslConfiguration.getTrustStoreConfiguration()));
}
if (sslConfiguration.getKeyStoreConfiguration().isPresent()) {
sslContextBuilder.keyManager(createKeyManagerFactory(sslConfiguration.getKeyStoreConfiguration(),
sslConfiguration.getKeyConfiguration()));
}
if (!sslConfiguration.getEnabledProtocols().isEmpty()) {
sslContextBuilder.protocols(sslConfiguration.getEnabledProtocols());
}
if (!sslConfiguration.getEnabledCipherSuites().isEmpty()) {
sslContextBuilder.ciphers(sslConfiguration.getEnabledCipherSuites());
}
}
catch (GeneralSecurityException | IOException e) {
throw new IllegalStateException(e);
}
}
/**
* {@link ClientHttpConnector} for Reactor Netty.
*
* @author Mark Paluch
*/
static class ReactorNetty {
public static class ReactorNetty {
/**
* Create a {@link ClientHttpConnector} using Reactor Netty.
* @param options must not be {@literal null}
* @param sslConfiguration must not be {@literal null}
* @return a new and configured {@link ReactorClientHttpConnector} instance.
*/
public static ReactorClientHttpConnector usingReactorNetty(ClientOptions options,
SslConfiguration sslConfiguration) {
return new ReactorClientHttpConnector(createClient(options, sslConfiguration));
}
public static HttpClient createClient(ClientOptions options, SslConfiguration sslConfiguration) {
static ClientHttpConnector usingReactorNetty(ClientOptions options, SslConfiguration sslConfiguration) {
HttpClient client = HttpClient.create();
if (hasSslConfiguration(sslConfiguration)) {
@@ -155,20 +140,65 @@ public class ClientHttpConnectorFactory {
client = client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
Math.toIntExact(options.getConnectionTimeout().toMillis())).proxyWithSystemProperties();
return new ReactorClientHttpConnector(client);
return client;
}
private static void configureSsl(SslConfiguration sslConfiguration, SslContextBuilder sslContextBuilder) {
try {
if (sslConfiguration.getTrustStoreConfiguration().isPresent()) {
sslContextBuilder
.trustManager(createTrustManagerFactory(sslConfiguration.getTrustStoreConfiguration()));
}
if (sslConfiguration.getKeyStoreConfiguration().isPresent()) {
sslContextBuilder.keyManager(createKeyManagerFactory(sslConfiguration.getKeyStoreConfiguration(),
sslConfiguration.getKeyConfiguration()));
}
if (!sslConfiguration.getEnabledProtocols().isEmpty()) {
sslContextBuilder.protocols(sslConfiguration.getEnabledProtocols());
}
if (!sslConfiguration.getEnabledCipherSuites().isEmpty()) {
sslContextBuilder.ciphers(sslConfiguration.getEnabledCipherSuites());
}
}
catch (GeneralSecurityException | IOException e) {
throw new IllegalStateException(e);
}
}
}
/**
* {@link ClientHttpRequestFactory} for Apache Http Components.
* Utility methods to create {@link ClientHttpRequestFactory} using Apache Http
* Components.
*
* @author Mark Paluch
*/
static class HttpComponents {
public static class HttpComponents {
static ClientHttpConnector usingHttpComponents(ClientOptions options, SslConfiguration sslConfiguration)
throws GeneralSecurityException, IOException {
/**
* Create a {@link ClientHttpConnector} using Apache Http Components.
* @param options must not be {@literal null}
* @param sslConfiguration must not be {@literal null}
* @return a new and configured {@link HttpComponentsClientHttpConnector}
* instance.
* @throws GeneralSecurityException
* @throws IOException
*/
public static HttpComponentsClientHttpConnector usingHttpComponents(ClientOptions options,
SslConfiguration sslConfiguration) throws GeneralSecurityException, IOException {
HttpAsyncClientBuilder httpClientBuilder = createHttpAsyncClientBuilder(options, sslConfiguration);
return new HttpComponentsClientHttpConnector(httpClientBuilder.build());
}
public static HttpAsyncClientBuilder createHttpAsyncClientBuilder(ClientOptions options,
SslConfiguration sslConfiguration) throws GeneralSecurityException, IOException {
HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClientBuilder.create();
@@ -177,7 +207,7 @@ public class ClientHttpConnectorFactory {
if (hasSslConfiguration(sslConfiguration)) {
SSLContext sslContext = getSSLContext(sslConfiguration, getTrustManagers(sslConfiguration));
SSLContext sslContext = getSSLContext(sslConfiguration);
String[] enabledProtocols = !sslConfiguration.getEnabledProtocols().isEmpty()
? sslConfiguration.getEnabledProtocols().toArray(new String[0]) : null;
@@ -210,24 +240,32 @@ public class ClientHttpConnectorFactory {
httpClientBuilder.setDefaultRequestConfig(requestConfig);
return new HttpComponentsClientHttpConnector(httpClientBuilder.build());
return httpClientBuilder;
}
}
static class JettyClient {
/**
* Utility methods to create {@link ClientHttpRequestFactory} using the Jetty Client.
*
* @author Mark Paluch
*/
public static class JettyClient {
static ClientHttpConnector usingJetty(ClientOptions options, SslConfiguration sslConfiguration) {
try {
return new JettyClientHttpConnector(configureClient(getHttpClient(sslConfiguration), options));
}
catch (GeneralSecurityException | IOException e) {
throw new IllegalStateException(e);
}
/**
* Create a {@link ClientHttpConnector} using Jetty.
* @param options must not be {@literal null}
* @param sslConfiguration must not be {@literal null}
* @return a new and configured {@link JettyClientHttpConnector} instance.
* @throws GeneralSecurityException
* @throws IOException
*/
public static JettyClientHttpConnector usingJetty(ClientOptions options, SslConfiguration sslConfiguration)
throws GeneralSecurityException, IOException {
return new JettyClientHttpConnector(configureClient(getHttpClient(sslConfiguration), options));
}
private static org.eclipse.jetty.client.HttpClient configureClient(
public static org.eclipse.jetty.client.HttpClient configureClient(
org.eclipse.jetty.client.HttpClient httpClient, ClientOptions options) {
httpClient.setConnectTimeout(options.getConnectionTimeout().toMillis());
@@ -236,7 +274,7 @@ public class ClientHttpConnectorFactory {
return httpClient;
}
private static org.eclipse.jetty.client.HttpClient getHttpClient(SslConfiguration sslConfiguration)
public static org.eclipse.jetty.client.HttpClient getHttpClient(SslConfiguration sslConfiguration)
throws IOException, GeneralSecurityException {
if (hasSslConfiguration(sslConfiguration)) {
@@ -291,16 +329,32 @@ public class ClientHttpConnectorFactory {
*
* @author Mark Paluch
*/
static class JdkHttpClient {
public static class JdkHttpClient {
static ClientHttpConnector usingJdkHttpClient(ClientOptions options, SslConfiguration sslConfiguration)
throws GeneralSecurityException, IOException {
/**
* Create a {@link JdkClientHttpConnector} using the JDK's HttpClient.
* @param options must not be {@literal null}
* @param sslConfiguration must not be {@literal null}
* @return a new and configured {@link JdkClientHttpConnector} instance.
* @throws GeneralSecurityException
* @throws IOException
*/
public static JdkClientHttpConnector usingJdkHttpClient(ClientOptions options,
SslConfiguration sslConfiguration) throws GeneralSecurityException, IOException {
java.net.http.HttpClient.Builder builder = getBuilder(options, sslConfiguration);
return new JdkClientHttpConnector(builder.build());
}
public static java.net.http.HttpClient.Builder getBuilder(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));
SSLContext sslContext = getSSLContext(sslConfiguration);
String[] enabledProtocols = !sslConfiguration.getEnabledProtocols().isEmpty()
? sslConfiguration.getEnabledProtocols().toArray(new String[0]) : null;
@@ -308,17 +362,6 @@ public class ClientHttpConnectorFactory {
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);
@@ -328,8 +371,7 @@ public class ClientHttpConnectorFactory {
builder.proxy(ProxySelector.getDefault()).followRedirects(java.net.http.HttpClient.Redirect.ALWAYS)
.connectTimeout(options.getConnectionTimeout());
return new JdkClientHttpConnector(builder.build());
return builder;
}
}

View File

@@ -63,6 +63,7 @@ import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.client.reactive.JdkClientHttpConnector;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -131,14 +132,17 @@ public class ClientHttpRequestFactoryFactory {
return new SimpleClientHttpRequestFactory();
}
static SSLContext getSSLContext(SslConfiguration sslConfiguration, TrustManager[] trustManagers)
throws GeneralSecurityException, IOException {
static SSLContext getSSLContext(SslConfiguration sslConfiguration) throws GeneralSecurityException, IOException {
KeyConfiguration keyConfiguration = sslConfiguration.getKeyConfiguration();
KeyManager[] keyManagers = sslConfiguration.getKeyStoreConfiguration().isPresent()
? createKeyManagerFactory(sslConfiguration.getKeyStoreConfiguration(), keyConfiguration)
.getKeyManagers()
: null;
return getSSLContext(sslConfiguration.getKeyStoreConfiguration(), sslConfiguration.getKeyConfiguration(),
getTrustManagers(sslConfiguration));
}
static SSLContext getSSLContext(KeyStoreConfiguration keyStoreConfiguration, KeyConfiguration keyConfiguration,
@Nullable TrustManager[] trustManagers) throws GeneralSecurityException, IOException {
KeyManager[] keyManagers = keyStoreConfiguration.isPresent()
? createKeyManagerFactory(keyStoreConfiguration, keyConfiguration).getKeyManagers() : null;
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
@@ -146,14 +150,6 @@ public class ClientHttpRequestFactoryFactory {
return sslContext;
}
@Nullable
static TrustManager[] getTrustManagers(SslConfiguration sslConfiguration)
throws GeneralSecurityException, IOException {
return sslConfiguration.getTrustStoreConfiguration().isPresent()
? createTrustManagerFactory(sslConfiguration.getTrustStoreConfiguration()).getTrustManagers() : null;
}
static KeyManagerFactory createKeyManagerFactory(KeyStoreConfiguration keyStoreConfiguration,
KeyConfiguration keyConfiguration) throws GeneralSecurityException, IOException {
@@ -186,6 +182,14 @@ public class ClientHttpRequestFactoryFactory {
return keyStore;
}
@Nullable
static TrustManager[] getTrustManagers(SslConfiguration sslConfiguration)
throws GeneralSecurityException, IOException {
return sslConfiguration.getTrustStoreConfiguration().isPresent()
? createTrustManagerFactory(sslConfiguration.getTrustStoreConfiguration()).getTrustManagers() : null;
}
private static String getKeyStoreType(KeyStoreConfiguration keyStoreConfiguration) {
if (StringUtils.hasText(keyStoreConfiguration.getStoreType())
@@ -263,13 +267,30 @@ public class ClientHttpRequestFactoryFactory {
}
/**
* {@link ClientHttpRequestFactory} for Apache Http Components.
* Utilities to create a {@link ClientHttpRequestFactory} for Apache Http Components.
*
* @author Mark Paluch
*/
static class HttpComponents {
public static class HttpComponents {
static ClientHttpRequestFactory usingHttpComponents(ClientOptions options, SslConfiguration sslConfiguration)
/**
* Create a {@link ClientHttpRequestFactory} using Apache Http Components.
* @param options must not be {@literal null}
* @param sslConfiguration must not be {@literal null}
* @return a new and configured {@link HttpComponentsClientHttpRequestFactory}
* instance.
* @throws GeneralSecurityException
* @throws IOException
*/
public static HttpComponentsClientHttpRequestFactory usingHttpComponents(ClientOptions options,
SslConfiguration sslConfiguration) throws GeneralSecurityException, IOException {
HttpClientBuilder httpClientBuilder = getHttpClientBuilder(options, sslConfiguration);
return new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build());
}
public static HttpClientBuilder getHttpClientBuilder(ClientOptions options, SslConfiguration sslConfiguration)
throws GeneralSecurityException, IOException {
HttpClientBuilder httpClientBuilder = HttpClients.custom();
@@ -279,7 +300,7 @@ public class ClientHttpRequestFactoryFactory {
if (hasSslConfiguration(sslConfiguration)) {
SSLContext sslContext = getSSLContext(sslConfiguration, getTrustManagers(sslConfiguration));
SSLContext sslContext = getSSLContext(sslConfiguration);
String[] enabledProtocols = null;
@@ -309,20 +330,36 @@ public class ClientHttpRequestFactoryFactory {
.setRedirectsEnabled(true).build();
httpClientBuilder.setDefaultRequestConfig(requestConfig);
return new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build());
return httpClientBuilder;
}
}
/**
* {@link ClientHttpRequestFactory} for the {@link okhttp3.OkHttpClient}.
* Utilities to create a {@link ClientHttpRequestFactory} for the
* {@link okhttp3.OkHttpClient}.
*
* @author Mark Paluch
*/
static class OkHttp3 {
public static class OkHttp3 {
static ClientHttpRequestFactory usingOkHttp3(ClientOptions options, SslConfiguration sslConfiguration)
/**
* Create a {@link ClientHttpRequestFactory} using {@link okhttp3.OkHttpClient}.
* @param options must not be {@literal null}
* @param sslConfiguration must not be {@literal null}
* @return a new and configured {@link OkHttp3ClientHttpRequestFactory} instance.
* @throws GeneralSecurityException
* @throws IOException
*/
public static OkHttp3ClientHttpRequestFactory usingOkHttp3(ClientOptions options,
SslConfiguration sslConfiguration) throws GeneralSecurityException, IOException {
Builder builder = getBuilder(options, sslConfiguration);
return new OkHttp3ClientHttpRequestFactory(builder.build());
}
public static Builder getBuilder(ClientOptions options, SslConfiguration sslConfiguration)
throws GeneralSecurityException, IOException {
Builder builder = new Builder();
@@ -333,13 +370,14 @@ public class ClientHttpRequestFactoryFactory {
TrustManager[] trustManagers = getTrustManagers(sslConfiguration);
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
if (trustManagers == null || trustManagers.length != 1
|| !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException(
"Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
SSLContext sslContext = getSSLContext(sslConfiguration, trustManagers);
SSLContext sslContext = getSSLContext(sslConfiguration.getKeyStoreConfiguration(),
sslConfiguration.getKeyConfiguration(), trustManagers);
ConnectionSpec.Builder sslConnectionSpecBuilder = new ConnectionSpec.Builder(sslConnectionSpec);
@@ -354,15 +392,14 @@ public class ClientHttpRequestFactoryFactory {
sslConnectionSpec = sslConnectionSpecBuilder.build();
builder.sslSocketFactory(sslContext.getSocketFactory(), trustManager);
builder.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagers[0]);
}
builder.connectionSpecs(Arrays.asList(sslConnectionSpec, ConnectionSpec.CLEARTEXT));
builder.connectTimeout(options.getConnectionTimeout().toMillis(), TimeUnit.MILLISECONDS)
.readTimeout(options.getReadTimeout().toMillis(), TimeUnit.MILLISECONDS);
return new OkHttp3ClientHttpRequestFactory(builder.build());
return builder;
}
}

View File

@@ -116,7 +116,7 @@ class ClientHttpConnectorFactoryIntegrationTests {
}
@Test
void jettyClientShouldWork() {
void jettyClientShouldWork() throws Exception {
ClientHttpConnector factory = JettyClient.usingJetty(new ClientOptions(), Settings.createSslConfiguration());
@@ -128,7 +128,7 @@ class ClientHttpConnectorFactoryIntegrationTests {
}
@Test
void jettyClientWithExplicitEnabledCipherSuitesShouldWork() {
void jettyClientWithExplicitEnabledCipherSuitesShouldWork() throws Exception {
List<String> enabledCipherSuites = new ArrayList<String>();
enabledCipherSuites.add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384");
@@ -145,7 +145,7 @@ class ClientHttpConnectorFactoryIntegrationTests {
}
@Test
void jettyClientWithExplicitEnabledProtocolsShouldWork() {
void jettyClientWithExplicitEnabledProtocolsShouldWork() throws Exception {
List<String> enabledProtocols = new ArrayList<String>();
enabledProtocols.add("TLSv1.2");