Ability to supply custom reactive ClientHttpConnector.
We now wrap ClientHttpConnector with a wrapper (ClientHttpConnectorWrapper) to allow supplying a custom ClientHttpConnector. Closes gh-564.
This commit is contained in:
@@ -40,12 +40,12 @@ import org.springframework.boot.context.config.ConfigDataLoaderContext;
|
||||
import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
|
||||
import org.springframework.boot.logging.DeferredLogFactory;
|
||||
import org.springframework.cloud.vault.config.VaultAutoConfiguration.TaskSchedulerWrapper;
|
||||
import org.springframework.cloud.vault.config.VaultReactiveAutoConfiguration.ClientHttpConnectorWrapper;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -170,7 +170,7 @@ public class VaultConfigDataLoader implements ConfigDataLoader<VaultConfigLocati
|
||||
private void registerImperativeInfrastructure(ConfigurableBootstrapContext bootstrap,
|
||||
VaultProperties vaultProperties) {
|
||||
|
||||
ImperativeInfrastructure infra = new ImperativeInfrastructure(bootstrap, vaultProperties, logFactory);
|
||||
ImperativeInfrastructure infra = new ImperativeInfrastructure(bootstrap, vaultProperties, this.logFactory);
|
||||
|
||||
infra.registerClientHttpRequestFactoryWrapper();
|
||||
infra.registerRestTemplateBuilder();
|
||||
@@ -200,8 +200,8 @@ public class VaultConfigDataLoader implements ConfigDataLoader<VaultConfigLocati
|
||||
VaultProperties vaultProperties) {
|
||||
|
||||
ReactiveInfrastructure reactiveInfrastructure = new ReactiveInfrastructure(bootstrap, vaultProperties,
|
||||
logFactory);
|
||||
reactiveInfrastructure.registerClientHttpConnector();
|
||||
this.logFactory);
|
||||
reactiveInfrastructure.registerClientHttpConnectorWrapper();
|
||||
reactiveInfrastructure.registerWebClientBuilder();
|
||||
reactiveInfrastructure.registerWebClientFactory();
|
||||
|
||||
@@ -522,22 +522,22 @@ public class VaultConfigDataLoader implements ConfigDataLoader<VaultConfigLocati
|
||||
this.logFactory = logFactory;
|
||||
}
|
||||
|
||||
void registerClientHttpConnector() {
|
||||
// not a bean
|
||||
this.bootstrap.registerIfAbsent(ClientHttpConnector.class,
|
||||
ctx -> this.configuration.createClientHttpConnector());
|
||||
void registerClientHttpConnectorWrapper() {
|
||||
registerIfAbsent(this.bootstrap, "clientHttpConnectorWrapper", ClientHttpConnectorWrapper.class,
|
||||
() -> new ClientHttpConnectorWrapper(this.configuration.createClientHttpConnector()));
|
||||
}
|
||||
|
||||
public void registerWebClientBuilder() {
|
||||
// not a bean
|
||||
this.bootstrap.registerIfAbsent(WebClientBuilder.class,
|
||||
ctx -> this.configuration.createWebClientBuilder(ctx.get(ClientHttpConnector.class),
|
||||
this.endpointProvider, Collections.emptyList()));
|
||||
ctx -> this.configuration.createWebClientBuilder(
|
||||
ctx.get(ClientHttpConnectorWrapper.class).getConnector(), this.endpointProvider,
|
||||
Collections.emptyList()));
|
||||
}
|
||||
|
||||
void registerWebClientFactory() {
|
||||
registerIfAbsent(this.bootstrap, "vaultWebClientFactory", WebClientFactory.class,
|
||||
ctx -> new DefaultWebClientFactory(ctx.get(ClientHttpConnector.class),
|
||||
ctx -> new DefaultWebClientFactory(ctx.get(ClientHttpConnectorWrapper.class).getConnector(),
|
||||
connector -> this.configuration.createWebClientBuilder(connector, this.endpointProvider,
|
||||
Collections.emptyList())));
|
||||
}
|
||||
|
||||
@@ -24,10 +24,12 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
@@ -35,6 +37,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
@@ -79,6 +82,9 @@ import static org.springframework.cloud.vault.config.VaultAutoConfiguration.Task
|
||||
@AutoConfigureBefore(VaultAutoConfiguration.class)
|
||||
public class VaultReactiveAutoConfiguration implements InitializingBean {
|
||||
|
||||
@Nullable
|
||||
private final ConfigurableApplicationContext applicationContext;
|
||||
|
||||
private final VaultProperties vaultProperties;
|
||||
|
||||
private final VaultReactiveConfiguration configuration;
|
||||
@@ -91,20 +97,26 @@ public class VaultReactiveAutoConfiguration implements InitializingBean {
|
||||
|
||||
private final List<WebClientCustomizer> customizers;
|
||||
|
||||
@Nullable
|
||||
private ClientHttpConnector clientHttpConnector;
|
||||
|
||||
/**
|
||||
* Used for Vault communication.
|
||||
* @deprecated since 3.0.2, use
|
||||
* {@link #VaultReactiveAutoConfiguration(ConfigurableApplicationContext, VaultProperties, ObjectProvider, ObjectProvider, ObjectProvider)}
|
||||
* instead.
|
||||
*/
|
||||
@Nullable
|
||||
private WebClientBuilder webClientBuilder;
|
||||
|
||||
@Deprecated
|
||||
public VaultReactiveAutoConfiguration(VaultProperties vaultProperties,
|
||||
ObjectProvider<ReactiveVaultEndpointProvider> reactiveEndpointProvider,
|
||||
ObjectProvider<VaultEndpointProvider> endpointProvider,
|
||||
ObjectProvider<List<WebClientCustomizer>> webClientCustomizers) {
|
||||
this(null, vaultProperties, reactiveEndpointProvider, endpointProvider, webClientCustomizers);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public VaultReactiveAutoConfiguration(@Nullable ConfigurableApplicationContext applicationContext,
|
||||
VaultProperties vaultProperties, ObjectProvider<ReactiveVaultEndpointProvider> reactiveEndpointProvider,
|
||||
ObjectProvider<VaultEndpointProvider> endpointProvider,
|
||||
ObjectProvider<List<WebClientCustomizer>> webClientCustomizers) {
|
||||
|
||||
this.applicationContext = applicationContext;
|
||||
this.vaultProperties = vaultProperties;
|
||||
this.configuration = new VaultReactiveConfiguration(vaultProperties);
|
||||
|
||||
@@ -122,14 +134,6 @@ public class VaultReactiveAutoConfiguration implements InitializingBean {
|
||||
AnnotationAwareOrderComparator.sort(this.customizers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
|
||||
this.clientHttpConnector = createConnector(this.vaultProperties);
|
||||
|
||||
this.webClientBuilder = webClientBuilder(this.clientHttpConnector);
|
||||
}
|
||||
|
||||
protected WebClientBuilder webClientBuilder(ClientHttpConnector connector) {
|
||||
|
||||
if (this.reactiveEndpointProvider != null) {
|
||||
@@ -145,6 +149,26 @@ public class VaultReactiveAutoConfiguration implements InitializingBean {
|
||||
"Cannot create WebClientBuilder as neither ReactiveEndpointProvider nor EndpointProvider configured");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ClientHttpConnectorWrapper} containing a
|
||||
* {@link ClientHttpConnector}. {@link ClientHttpConnector} is not exposed as root
|
||||
* bean because {@link ClientHttpConnector} is configured with {@link ClientOptions}
|
||||
* and {@link SslConfiguration} which are not necessarily applicable for the whole
|
||||
* application.
|
||||
* @return the {@link ClientHttpConnectorWrapper} to wrap a
|
||||
* {@link ClientHttpConnector} instance.
|
||||
* @since 3.0.2
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ClientHttpConnectorWrapper clientHttpConnectorWrapper() {
|
||||
return new ClientHttpConnectorWrapper(createConnector(this.vaultProperties));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ClientHttpConnector} configured with {@link ClientOptions} and
|
||||
* {@link SslConfiguration} which are not necessarily applicable for the whole
|
||||
@@ -165,9 +189,18 @@ public class VaultReactiveAutoConfiguration implements InitializingBean {
|
||||
@ConditionalOnMissingBean
|
||||
public WebClientFactory vaultWebClientFactory() {
|
||||
|
||||
Assert.state(this.clientHttpConnector != null, "ClientHttpConnector must not be null");
|
||||
ClientHttpConnector connector = getClientHttpConnector();
|
||||
|
||||
return new DefaultWebClientFactory(this.clientHttpConnector, this::webClientBuilder);
|
||||
return new DefaultWebClientFactory(connector, this::webClientBuilder);
|
||||
}
|
||||
|
||||
protected ClientHttpConnector getClientHttpConnector() {
|
||||
|
||||
if (this.applicationContext != null) {
|
||||
return this.applicationContext.getBean(ClientHttpConnectorWrapper.class).getConnector();
|
||||
}
|
||||
|
||||
return createConnector(this.vaultProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,13 +213,13 @@ public class VaultReactiveAutoConfiguration implements InitializingBean {
|
||||
@ConditionalOnMissingBean(ReactiveVaultOperations.class)
|
||||
public ReactiveVaultTemplate reactiveVaultTemplate(ObjectProvider<ReactiveSessionManager> sessionManager) {
|
||||
|
||||
Assert.state(this.webClientBuilder != null, "WebClientBuilder must not be null");
|
||||
WebClientBuilder webClientBuilder = webClientBuilder(getClientHttpConnector());
|
||||
|
||||
if (this.vaultProperties.getAuthentication() == VaultProperties.AuthenticationMethod.NONE) {
|
||||
return new ReactiveVaultTemplate(this.webClientBuilder);
|
||||
return new ReactiveVaultTemplate(webClientBuilder);
|
||||
}
|
||||
|
||||
return new ReactiveVaultTemplate(this.webClientBuilder, sessionManager.getObject());
|
||||
return new ReactiveVaultTemplate(webClientBuilder, sessionManager.getObject());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,4 +274,38 @@ public class VaultReactiveAutoConfiguration implements InitializingBean {
|
||||
() -> beanFactory.getBeanProvider(ClientAuthentication.class, false).getIfAvailable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link ClientHttpConnector} to not expose the bean globally.
|
||||
*
|
||||
* @since 3.0.2
|
||||
*/
|
||||
public static class ClientHttpConnectorWrapper implements InitializingBean, DisposableBean {
|
||||
|
||||
private final ClientHttpConnector connector;
|
||||
|
||||
public ClientHttpConnectorWrapper(ClientHttpConnector connector) {
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
if (this.connector instanceof DisposableBean) {
|
||||
((DisposableBean) this.connector).destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
|
||||
if (this.connector instanceof InitializingBean) {
|
||||
((InitializingBean) this.connector).afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
|
||||
public ClientHttpConnector getConnector() {
|
||||
return this.connector;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.vault.authentication.AuthenticationSteps;
|
||||
@@ -45,8 +46,12 @@ import org.springframework.vault.core.ReactiveVaultOperations;
|
||||
import org.springframework.vault.core.ReactiveVaultTemplate;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClientRequestException;
|
||||
|
||||
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.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link VaultReactiveAutoConfiguration}.
|
||||
@@ -184,6 +189,24 @@ public class VaultReactiveAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldConsiderCustomConnector() {
|
||||
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.cloud.vault.kv.enabled=false", "spring.cloud.vault.token=foo",
|
||||
"spring.cloud.vault.session.lifecycle.enabled=false")
|
||||
.withUserConfiguration(CustomConnector.class)
|
||||
.withBean("vaultTokenSupplier", VaultTokenSupplier.class,
|
||||
() -> () -> Mono.just(VaultToken.of("foo".toCharArray())))
|
||||
.withBean("taskSchedulerWrapper", VaultAutoConfiguration.TaskSchedulerWrapper.class,
|
||||
() -> new VaultAutoConfiguration.TaskSchedulerWrapper(new ThreadPoolTaskScheduler()))
|
||||
.run(context -> {
|
||||
|
||||
ReactiveVaultOperations operations = context.getBean(ReactiveVaultOperations.class);
|
||||
operations.delete("foo").as(StepVerifier::create).verifyError(WebClientRequestException.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class AuthenticationFactoryConfiguration {
|
||||
|
||||
@@ -234,4 +257,17 @@ public class VaultReactiveAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomConnector {
|
||||
|
||||
@Bean
|
||||
VaultReactiveAutoConfiguration.ClientHttpConnectorWrapper myWrapper() {
|
||||
ClientHttpConnector mock = mock(ClientHttpConnector.class);
|
||||
when(mock.connect(any(), any(), any())).thenReturn(Mono.error(new UnsupportedOperationException()));
|
||||
|
||||
return new VaultReactiveAutoConfiguration.ClientHttpConnectorWrapper(mock);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user