From d2168e90bbd928a2809f48f5895e526eeef99d26 Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Mon, 29 Apr 2024 08:57:28 -0400 Subject: [PATCH] Allow users to provide a customizer to customize the HttpClient behavior. (#2413) Fixes #2410 Co-authored-by: Ryan Baxter <524254+ryanjbaxter@users.noreply.github.com> --- .../EnvironmentRepositoryConfiguration.java | 6 ++-- .../ConfigurableHttpConnectionFactory.java | 10 +++++- .../HttpClient4BuilderCustomizer.java | 32 +++++++++++++++++++ ...ientConfigurableHttpConnectionFactory.java | 13 ++++---- ...tipleJGitEnvironmentRepositoryFactory.java | 16 +++++++++- .../server/support/HttpClient4Support.java | 12 ++++++- ...ConfigurableHttpConnectionFactoryTest.java | 21 ++++++++++++ 7 files changed, 99 insertions(+), 11 deletions(-) create mode 100644 spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/HttpClient4BuilderCustomizer.java diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java index 980c63be..bdfd8237 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java @@ -62,6 +62,7 @@ import org.springframework.cloud.config.server.environment.EnvironmentWatch; import org.springframework.cloud.config.server.environment.GoogleSecretManagerEnvironmentProperties; import org.springframework.cloud.config.server.environment.GoogleSecretManagerEnvironmentRepository; import org.springframework.cloud.config.server.environment.GoogleSecretManagerEnvironmentRepositoryFactory; +import org.springframework.cloud.config.server.environment.HttpClient4BuilderCustomizer; import org.springframework.cloud.config.server.environment.HttpClientConfigurableHttpConnectionFactory; import org.springframework.cloud.config.server.environment.HttpClientVaultRestTemplateFactory; import org.springframework.cloud.config.server.environment.HttpRequestConfigTokenProvider; @@ -184,11 +185,12 @@ public class EnvironmentRepositoryConfiguration { Optional jgitHttpConnectionFactory, Optional customTransportConfigCallback, Optional googleCloudSourceSupport, - GitCredentialsProviderFactory gitCredentialsProviderFactory) { + GitCredentialsProviderFactory gitCredentialsProviderFactory, + List customizers) { final TransportConfigCallbackFactory transportConfigCallbackFactory = new TransportConfigCallbackFactory( customTransportConfigCallback.orElse(null), googleCloudSourceSupport.orElse(null)); return new MultipleJGitEnvironmentRepositoryFactory(environment, server, jgitHttpConnectionFactory, - transportConfigCallbackFactory, gitCredentialsProviderFactory); + transportConfigCallbackFactory, gitCredentialsProviderFactory, customizers); } @Bean diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/ConfigurableHttpConnectionFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/ConfigurableHttpConnectionFactory.java index 66a3c732..5ad88a9d 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/ConfigurableHttpConnectionFactory.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/ConfigurableHttpConnectionFactory.java @@ -16,6 +16,9 @@ package org.springframework.cloud.config.server.environment; +import java.util.Collections; +import java.util.List; + import org.eclipse.jgit.transport.http.HttpConnectionFactory; /** @@ -23,6 +26,11 @@ import org.eclipse.jgit.transport.http.HttpConnectionFactory; */ public interface ConfigurableHttpConnectionFactory extends HttpConnectionFactory { - void addConfiguration(MultipleJGitEnvironmentProperties environmentProperties) throws Exception; + default void addConfiguration(MultipleJGitEnvironmentProperties environmentProperties) throws Exception { + addConfiguration(environmentProperties, Collections.EMPTY_LIST); + } + + void addConfiguration(MultipleJGitEnvironmentProperties environmentProperties, + List customizers) throws Exception; } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/HttpClient4BuilderCustomizer.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/HttpClient4BuilderCustomizer.java new file mode 100644 index 00000000..f8c46f98 --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/HttpClient4BuilderCustomizer.java @@ -0,0 +1,32 @@ +/* + * Copyright 2018-2024 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.cloud.config.server.environment; + +import org.apache.http.impl.client.HttpClientBuilder; + +/** + * @author Ryan Baxter + */ +public interface HttpClient4BuilderCustomizer { + + /** + * Allows for customization of the HttpClientBuilder before it is created. + * @param httpClientBuilder the HttpClientBuilder to customize + */ + void customize(HttpClientBuilder httpClientBuilder); + +} diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/HttpClientConfigurableHttpConnectionFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/HttpClientConfigurableHttpConnectionFactory.java index ab94b893..e3999444 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/HttpClientConfigurableHttpConnectionFactory.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/HttpClientConfigurableHttpConnectionFactory.java @@ -54,11 +54,11 @@ public class HttpClientConfigurableHttpConnectionFactory implements Configurable Map httpClientBuildersByUri = new LinkedHashMap<>(); @Override - public void addConfiguration(MultipleJGitEnvironmentProperties environmentProperties) - throws GeneralSecurityException { - addHttpClient(environmentProperties); + public void addConfiguration(MultipleJGitEnvironmentProperties environmentProperties, + List customizers) throws GeneralSecurityException { + addHttpClient(environmentProperties, customizers); for (JGitEnvironmentProperties repo : environmentProperties.getRepos().values()) { - addHttpClient(repo); + addHttpClient(repo, customizers); } } @@ -81,9 +81,10 @@ public class HttpClientConfigurableHttpConnectionFactory implements Configurable } } - private void addHttpClient(JGitEnvironmentProperties properties) throws GeneralSecurityException { + private void addHttpClient(JGitEnvironmentProperties properties, List customizers) + throws GeneralSecurityException { if (properties.getUri() != null && properties.getUri().startsWith("http")) { - this.httpClientBuildersByUri.put(properties.getUri(), HttpClient4Support.builder(properties)); + this.httpClientBuildersByUri.put(properties.getUri(), HttpClient4Support.builder(properties, customizers)); } } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/MultipleJGitEnvironmentRepositoryFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/MultipleJGitEnvironmentRepositoryFactory.java index 43484b26..157f0bfa 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/MultipleJGitEnvironmentRepositoryFactory.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/MultipleJGitEnvironmentRepositoryFactory.java @@ -16,6 +16,8 @@ package org.springframework.cloud.config.server.environment; +import java.util.Collections; +import java.util.List; import java.util.Optional; import io.micrometer.observation.ObservationRegistry; @@ -42,6 +44,8 @@ public class MultipleJGitEnvironmentRepositoryFactory private final GitCredentialsProviderFactory gitCredentialsProviderFactory; + private final List customizers; + @Deprecated public MultipleJGitEnvironmentRepositoryFactory(ConfigurableEnvironment environment, ConfigServerProperties server, TransportConfigCallbackFactory transportConfigCallbackFactory) { @@ -61,11 +65,21 @@ public class MultipleJGitEnvironmentRepositoryFactory Optional connectionFactory, TransportConfigCallbackFactory transportConfigCallbackFactory, GitCredentialsProviderFactory gitCredentialsProviderFactory) { + this(environment, server, connectionFactory, transportConfigCallbackFactory, gitCredentialsProviderFactory, + Collections.EMPTY_LIST); + } + + public MultipleJGitEnvironmentRepositoryFactory(ConfigurableEnvironment environment, ConfigServerProperties server, + Optional connectionFactory, + TransportConfigCallbackFactory transportConfigCallbackFactory, + GitCredentialsProviderFactory gitCredentialsProviderFactory, + List customizers) { this.environment = environment; this.server = server; this.connectionFactory = connectionFactory; this.transportConfigCallbackFactory = transportConfigCallbackFactory; this.gitCredentialsProviderFactory = gitCredentialsProviderFactory; + this.customizers = customizers; } @Override @@ -73,7 +87,7 @@ public class MultipleJGitEnvironmentRepositoryFactory throws Exception { if (this.connectionFactory.isPresent()) { HttpTransport.setConnectionFactory(this.connectionFactory.get()); - this.connectionFactory.get().addConfiguration(environmentProperties); + this.connectionFactory.get().addConfiguration(environmentProperties, this.customizers); } MultipleJGitEnvironmentRepository repository = new MultipleJGitEnvironmentRepository(this.environment, diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/HttpClient4Support.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/HttpClient4Support.java index 8b575eef..f0817154 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/HttpClient4Support.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/HttpClient4Support.java @@ -18,6 +18,8 @@ package org.springframework.cloud.config.server.support; import java.net.ProxySelector; import java.security.GeneralSecurityException; +import java.util.Collections; +import java.util.List; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; @@ -34,6 +36,7 @@ import org.apache.http.impl.conn.SystemDefaultRoutePlanner; import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContextBuilder; +import org.springframework.cloud.config.server.environment.HttpClient4BuilderCustomizer; import org.springframework.cloud.config.server.proxy.ProxyHostProperties; import org.springframework.util.CollectionUtils; @@ -48,6 +51,11 @@ public final class HttpClient4Support { public static HttpClientBuilder builder(HttpEnvironmentRepositoryProperties environmentProperties) throws GeneralSecurityException { + return builder(environmentProperties, Collections.EMPTY_LIST); + } + + public static HttpClientBuilder builder(HttpEnvironmentRepositoryProperties environmentProperties, + List customizers) throws GeneralSecurityException { SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); HttpClientBuilder httpClientBuilder = HttpClients.custom(); @@ -78,8 +86,10 @@ public final class HttpClient4Support { httpClientBuilder.disableRedirectHandling(); int timeout = environmentProperties.getTimeout() * 1000; - return httpClientBuilder.setSSLContext(sslContextBuilder.build()).setDefaultRequestConfig( + httpClientBuilder.setSSLContext(sslContextBuilder.build()).setDefaultRequestConfig( RequestConfig.custom().setSocketTimeout(timeout).setConnectTimeout(timeout).build()); + customizers.forEach(customizer -> customizer.customize(httpClientBuilder)); + return httpClientBuilder; } static class SchemeBasedRoutePlanner4 extends DefaultRoutePlanner { diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/HttpClientConfigurableHttpConnectionFactoryTest.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/HttpClientConfigurableHttpConnectionFactoryTest.java index 81acdaac..482ae566 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/HttpClientConfigurableHttpConnectionFactoryTest.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/HttpClientConfigurableHttpConnectionFactoryTest.java @@ -31,6 +31,10 @@ import org.junit.jupiter.api.Test; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; public class HttpClientConfigurableHttpConnectionFactoryTest { @@ -69,6 +73,23 @@ public class HttpClientConfigurableHttpConnectionFactoryTest { assertThat(actualHttpClientBuilder).isSameAs(expectedHttpClientBuilder); } + @Test + public void customizeHttpClient() throws Exception { + String url = "http://localhost/test.git"; + MultipleJGitEnvironmentProperties properties = new MultipleJGitEnvironmentProperties(); + properties.setUri(url); + HttpClient4BuilderCustomizer customizer = mock(HttpClient4BuilderCustomizer.class); + this.connectionFactory.addConfiguration(properties, List.of(customizer)); + + HttpConnection actualConnection = this.connectionFactory.create(new URL(url)); + + HttpClientBuilder expectedHttpClientBuilder = this.connectionFactory.httpClientBuildersByUri.values().stream() + .findFirst().get(); + HttpClientBuilder actualHttpClientBuilder = getActualHttpClientBuilder(actualConnection); + assertThat(actualHttpClientBuilder).isSameAs(expectedHttpClientBuilder); + verify(customizer, times(1)).customize(any(HttpClientBuilder.class)); + } + @Test public void longerUrl() throws Exception { MultipleJGitEnvironmentProperties properties = new MultipleJGitEnvironmentProperties();