Skip to content

Commit

Permalink
Allow reuse of library-specific configuration code in `ClientHttpRequ…
Browse files Browse the repository at this point in the history
…estFactoryFactory` and `ClientHttpConnectorFactory`.

See gh-760
  • Loading branch information
mp911de committed Mar 7, 2023
1 parent 5f28579 commit c792c6d
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import reactor.netty.http.Http11SslContextSpec;
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.JettyClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
Expand Down Expand Up @@ -81,52 +82,42 @@ public static ClientHttpConnector create(ClientOptions options, SslConfiguration
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);
}

if (JETTY_PRESENT) {
return JettyClient.usingJetty(options, sslConfiguration);
}

throw new IllegalStateException("No supported Reactive Http Client library available (Reactor Netty, Jetty)");
}

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 (REACTOR_NETTY_PRESENT) {
return ReactorNetty.usingReactorNetty(options, sslConfiguration);
}

if (!sslConfiguration.getEnabledProtocols().isEmpty()) {
sslContextBuilder.protocols(sslConfiguration.getEnabledProtocols());
}

if (!sslConfiguration.getEnabledCipherSuites().isEmpty()) {
sslContextBuilder.ciphers(sslConfiguration.getEnabledCipherSuites());
if (JETTY_PRESENT) {
return JettyClient.usingJetty(options, sslConfiguration);
}
}
catch (GeneralSecurityException | IOException e) {
throw new IllegalStateException(e);
}

throw new IllegalStateException("No supported Reactive Http Client library available (Reactor Netty, Jetty)");
}

/**
* {@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)) {
Expand All @@ -140,24 +131,59 @@ static ClientHttpConnector usingReactorNetty(ClientOptions options, SslConfigura
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) {

static class JettyClient {
try {

static ClientHttpConnector usingJetty(ClientOptions options, SslConfiguration sslConfiguration) {
if (sslConfiguration.getTrustStoreConfiguration().isPresent()) {
sslContextBuilder
.trustManager(createTrustManagerFactory(sslConfiguration.getTrustStoreConfiguration()));
}

try {
return new JettyClientHttpConnector(configureClient(getHttpClient(sslConfiguration), options));
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);
}
}

private static org.eclipse.jetty.client.HttpClient configureClient(
}

/**
* Utility methods to create {@link ClientHttpRequestFactory} using the Jetty Client.
*
* @author Mark Paluch
*/
static class JettyClient {

/**
* 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));
}

public static org.eclipse.jetty.client.HttpClient configureClient(
org.eclipse.jetty.client.HttpClient httpClient, ClientOptions options) {

httpClient.setConnectTimeout(options.getConnectionTimeout().toMillis());
Expand All @@ -166,7 +192,7 @@ private static org.eclipse.jetty.client.HttpClient configureClient(
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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,18 @@ public static ClientHttpRequestFactory create(ClientOptions options, SslConfigur
return new SimpleClientHttpRequestFactory();
}

private static SSLContext getSSLContext(SslConfiguration sslConfiguration, TrustManager[] trustManagers)
private 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);
Expand All @@ -166,7 +170,7 @@ private static SSLContext getSSLContext(SslConfiguration sslConfiguration, Trust
}

@Nullable
private static TrustManager[] getTrustManagers(SslConfiguration sslConfiguration)
static TrustManager[] getTrustManagers(SslConfiguration sslConfiguration)
throws GeneralSecurityException, IOException {

return sslConfiguration.getTrustStoreConfiguration().isPresent()
Expand Down Expand Up @@ -282,13 +286,30 @@ static boolean hasSslConfiguration(SslConfiguration sslConfiguration) {
}

/**
* {@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 {

/**
* 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);

static ClientHttpRequestFactory usingHttpComponents(ClientOptions options, SslConfiguration sslConfiguration)
return new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build());
}

public static HttpClientBuilder getHttpClientBuilder(ClientOptions options, SslConfiguration sslConfiguration)
throws GeneralSecurityException, IOException {

HttpClientBuilder httpClientBuilder = HttpClients.custom();
Expand All @@ -298,7 +319,7 @@ static ClientHttpRequestFactory usingHttpComponents(ClientOptions options, SslCo

if (hasSslConfiguration(sslConfiguration)) {

SSLContext sslContext = getSSLContext(sslConfiguration, getTrustManagers(sslConfiguration));
SSLContext sslContext = getSSLContext(sslConfiguration);

String[] enabledProtocols = null;

Expand Down Expand Up @@ -330,19 +351,36 @@ static ClientHttpRequestFactory usingHttpComponents(ClientOptions options, SslCo
// Support redirects
httpClientBuilder.setRedirectStrategy(new LaxRedirectStrategy());

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();
Expand All @@ -353,13 +391,14 @@ static ClientHttpRequestFactory usingOkHttp3(ClientOptions options, SslConfigura

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);

Expand All @@ -374,15 +413,14 @@ static ClientHttpRequestFactory usingOkHttp3(ClientOptions options, SslConfigura

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;
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ void reactorNettyClientWithExplicitEnabledProtocolsShouldWork() {
}

@Test
void jettyClientShouldWork() {
void jettyClientShouldWork() throws Exception {

ClientHttpConnector factory = JettyClient.usingJetty(new ClientOptions(), Settings.createSslConfiguration());

Expand All @@ -98,7 +98,7 @@ void jettyClientShouldWork() {
}

@Test
void jettyClientWithExplicitEnabledCipherSuitesShouldWork() {
void jettyClientWithExplicitEnabledCipherSuitesShouldWork() throws Exception {

List<String> enabledCipherSuites = new ArrayList<String>();
enabledCipherSuites.add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384");
Expand All @@ -115,7 +115,7 @@ void jettyClientWithExplicitEnabledCipherSuitesShouldWork() {
}

@Test
void jettyClientWithExplicitEnabledProtocolsShouldWork() {
void jettyClientWithExplicitEnabledProtocolsShouldWork() throws Exception {

List<String> enabledProtocols = new ArrayList<String>();
enabledProtocols.add("TLSv1.2");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ void shouldDeleteMetadata() {
this.kvOperations.delete(SECRET_NAME);
VaultMetadataResponse metadataResponse = this.vaultKeyValueMetadataOperations.get(SECRET_NAME);
Versioned.Metadata version1 = metadataResponse.getVersions().get(0);
assertThat(version1.getDeletedAt()).isBefore(Instant.now());
assertThat(version1.getDeletedAt()).isBefore(Instant.now().plusSeconds(5));

this.vaultKeyValueMetadataOperations.delete(SECRET_NAME);

Expand Down

0 comments on commit c792c6d

Please sign in to comment.