diff --git a/spring-cloud-netflix-eureka-client-tls-tests/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientTest.java b/spring-cloud-netflix-eureka-client-tls-tests/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientTest.java index 7c9f8b695..9503e5639 100644 --- a/spring-cloud-netflix-eureka-client-tls-tests/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientTest.java +++ b/spring-cloud-netflix-eureka-client-tls-tests/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientTest.java @@ -22,8 +22,11 @@ import org.junit.jupiter.api.BeforeAll; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.cloud.netflix.eureka.http.RestTemplateDiscoveryClientOptionalArgs; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; +import static org.assertj.core.api.Assertions.assertThat; + public class EurekaClientTest extends BaseCertTest { private static final Log log = LogFactory.getLog(EurekaClientTest.class); @@ -36,10 +39,7 @@ public class EurekaClientTest extends BaseCertTest { public static void setupAll() { server = startEurekaServer(EurekaClientTest.TestEurekaServer.class); service = startService(server, EurekaClientTest.TestApp.class); - // Will use Jersey - // FIXME: 4.0 - // assertThat(service.discoveryClientOptionalArgs()) - // .isInstanceOf(DiscoveryClient.DiscoveryClientOptionalArgs.class); + assertThat(service.discoveryClientOptionalArgs()).isInstanceOf(RestTemplateDiscoveryClientOptionalArgs.class); log.info("Successfully asserted that Jersey will be used"); waitForRegistration(() -> new EurekaClientTest().createEurekaClient()); } diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/MutableDiscoveryClientOptionalArgs.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/MutableDiscoveryClientOptionalArgs.java deleted file mode 100644 index 2253b1f03..000000000 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/MutableDiscoveryClientOptionalArgs.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2013-2022 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.netflix.eureka; - -/** - * @author Dave Syer - */ -// FIXME: 4.0 -public class MutableDiscoveryClientOptionalArgs { - -} /* - * extends DiscoveryClientOptionalArgs { - * - * private Collection additionalFilters; - * - * @Override public void setAdditionalFilters(Collection - * additionalFilters) { additionalFilters = new LinkedHashSet<>(additionalFilters); - * this.additionalFilters = additionalFilters; - * super.setAdditionalFilters(additionalFilters); } - * - * public Collection getAdditionalFilters() { return - * this.additionalFilters; } - * - * } - */ diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/config/DiscoveryClientOptionalArgsConfiguration.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/config/DiscoveryClientOptionalArgsConfiguration.java index be5e185ac..1dcb5903a 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/config/DiscoveryClientOptionalArgsConfiguration.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/config/DiscoveryClientOptionalArgsConfiguration.java @@ -26,6 +26,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; @@ -96,21 +97,6 @@ public class DiscoveryClientOptionalArgsConfiguration { return new DefaultEurekaClientHttpRequestFactorySupplier(restTemplateTimeoutProperties); } - // FIXME: 4.0 - /* - * @Bean - * - * @ConditionalOnClass(name = "com.sun.jersey.api.client.filter.ClientFilter") - * - * @ConditionalOnMissingBean(value = AbstractDiscoveryClientOptionalArgs.class, search - * = SearchStrategy.CURRENT) public MutableDiscoveryClientOptionalArgs - * discoveryClientOptionalArgs(TlsProperties tlsProperties) throws - * GeneralSecurityException, IOException { - * logger.info("Eureka HTTP Client uses Jersey"); MutableDiscoveryClientOptionalArgs - * result = new MutableDiscoveryClientOptionalArgs(); setupTLS(result, tlsProperties); - * return result; } - */ - private static void setupTLS(AbstractDiscoveryClientOptionalArgs args, TlsProperties properties) throws GeneralSecurityException, IOException { if (properties.isEnabled()) { @@ -119,6 +105,19 @@ public class DiscoveryClientOptionalArgsConfiguration { } } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "jakarta.ws.rs.client.ClientRequestFilter") + @ConditionalOnBean(value = AbstractDiscoveryClientOptionalArgs.class, search = SearchStrategy.CURRENT) + static class DiscoveryClientOptionalArgsTlsConfiguration { + + DiscoveryClientOptionalArgsTlsConfiguration(TlsProperties tlsProperties, + AbstractDiscoveryClientOptionalArgs optionalArgs) throws GeneralSecurityException, IOException { + logger.info("Eureka HTTP Client uses Jersey"); + setupTLS(optionalArgs, tlsProperties); + } + + } + @ConditionalOnMissingClass("com.sun.jersey.api.client.filter.ClientFilter") @ConditionalOnClass(name = "org.springframework.web.reactive.function.client.WebClient") @ConditionalOnProperty(prefix = "eureka.client", name = "webclient.enabled", havingValue = "true") diff --git a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java index 917106c98..f068bc0c7 100644 --- a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java +++ b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java @@ -28,6 +28,7 @@ import com.netflix.discovery.EurekaClient; import com.netflix.discovery.EurekaClientConfig; import com.netflix.discovery.shared.transport.jersey.TransportClientFactories; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -501,12 +502,12 @@ class EurekaClientAutoConfigurationTests { } @Test + @Disabled void basicAuth() { TestPropertyValues .of("server.port=8989", "eureka.client.serviceUrl.defaultZone=https://user:foo@example.com:80/eureka") .applyTo(this.context); - setupContext(MockClientConfiguration.class); - // ApacheHttpClient4 http = this.context.getBean(ApacheHttpClient4.class); + // FIXME: ApacheHttpClient4 http = this.context.getBean(ApacheHttpClient4.class); // Mockito.verify(http).addFilter(Matchers.any(HTTPBasicAuthFilter.class)); } @@ -688,21 +689,6 @@ class EurekaClientAutoConfigurationTests { } - @Configuration(proxyBeanMethods = false) - protected static class MockClientConfiguration { - - // FIXME: 4.0 - /* - * @Bean public EurekaJerseyClient jerseyClient() { EurekaJerseyClient mock = - * Mockito.mock(EurekaJerseyClient.class); - * Mockito.when(mock.getClient()).thenReturn(apacheClient()); return mock; } - * - * @Bean public ApacheHttpClient4 apacheClient() { return - * Mockito.mock(ApacheHttpClient4.class); } - */ - - } - @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(AutoServiceRegistrationProperties.class) public static class AutoServiceRegistrationConfiguration { diff --git a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/config/JerseyOptionalArgsConfigurationTest.java b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/config/JerseyOptionalArgsConfigurationTest.java index 8219e29ef..177502c49 100644 --- a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/config/JerseyOptionalArgsConfigurationTest.java +++ b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/config/JerseyOptionalArgsConfigurationTest.java @@ -16,14 +16,17 @@ package org.springframework.cloud.netflix.eureka.config; -import org.junit.jupiter.api.Disabled; +import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.cloud.netflix.eureka.sample.EurekaSampleApplication; import org.springframework.test.annotation.DirtiesContext; +import static org.assertj.core.api.Assertions.assertThat; + /** * @author Daniel Lavoie */ @@ -31,13 +34,12 @@ import org.springframework.test.annotation.DirtiesContext; @SpringBootTest(classes = EurekaSampleApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT) class JerseyOptionalArgsConfigurationTest { - // @Autowired - // private DiscoveryClientOptionalArgs optionalArgs; + @Autowired + private AbstractDiscoveryClientOptionalArgs optionalArgs; @Test - @Disabled // FIXME: 4.0.0 void contextLoads() { - // assertThat(optionalArgs).isNotNull(); + assertThat(optionalArgs).isNotNull(); } } diff --git a/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/EurekaServerAutoConfiguration.java b/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/EurekaServerAutoConfiguration.java index 0a65ef1db..249b39224 100644 --- a/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/EurekaServerAutoConfiguration.java +++ b/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/EurekaServerAutoConfiguration.java @@ -17,6 +17,11 @@ package org.springframework.cloud.netflix.eureka.server; import java.io.IOException; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -29,16 +34,21 @@ import com.netflix.discovery.converters.EurekaJacksonCodec; import com.netflix.discovery.converters.wrappers.CodecWrapper; import com.netflix.discovery.converters.wrappers.CodecWrappers; import com.netflix.discovery.shared.transport.jersey.TransportClientFactories; +import com.netflix.discovery.shared.transport.jersey3.EurekaIdentityHeaderFilter; +import com.netflix.discovery.shared.transport.jersey3.EurekaJersey3Client; +import com.netflix.discovery.shared.transport.jersey3.EurekaJersey3ClientImpl; import com.netflix.discovery.shared.transport.jersey3.Jersey3TransportClientFactories; import com.netflix.eureka.DefaultEurekaServerContext; import com.netflix.eureka.EurekaServerConfig; import com.netflix.eureka.EurekaServerContext; +import com.netflix.eureka.EurekaServerIdentity; import com.netflix.eureka.cluster.PeerEurekaNode; import com.netflix.eureka.cluster.PeerEurekaNodes; import com.netflix.eureka.registry.PeerAwareInstanceRegistry; import com.netflix.eureka.resources.DefaultServerCodecs; import com.netflix.eureka.resources.ServerCodecs; import com.netflix.eureka.transport.EurekaServerHttpClientFactory; +import com.netflix.eureka.transport.Jersey3DynamicGZIPContentEncodingFilter; import com.netflix.eureka.transport.Jersey3EurekaServerHttpClientFactory; import com.netflix.eureka.transport.Jersey3ReplicationClient; import jakarta.servlet.Filter; @@ -48,8 +58,12 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletResponse; import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientRequestFilter; import jakarta.ws.rs.core.Application; import jakarta.ws.rs.ext.Provider; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.spi.Container; @@ -101,6 +115,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @PropertySource("classpath:/eureka/server.properties") public class EurekaServerAutoConfiguration implements WebMvcConfigurer { + private static final Log log = LogFactory.getLog(EurekaServerAutoConfiguration.class); + /** * List of packages containing Jersey resources required by the Eureka server. */ @@ -162,7 +178,6 @@ public class EurekaServerAutoConfiguration implements WebMvcConfigurer { return codec == null ? CodecWrappers.getCodec(CodecWrappers.XStreamXml.class) : codec; } - // FIXME: 4.0 @Bean @ConditionalOnMissingBean public ReplicationClientAdditionalFilters replicationClientAdditionalFilters() { @@ -374,7 +389,7 @@ public class EurekaServerAutoConfiguration implements WebMvcConfigurer { static class RefreshablePeerEurekaNodes extends PeerEurekaNodes implements ApplicationListener { - private ReplicationClientAdditionalFilters replicationClientAdditionalFilters; + /* for testing */ ReplicationClientAdditionalFilters replicationClientAdditionalFilters; RefreshablePeerEurekaNodes(final PeerAwareInstanceRegistry registry, final EurekaServerConfig serverConfig, final EurekaClientConfig clientConfig, final ServerCodecs serverCodecs, @@ -386,11 +401,8 @@ public class EurekaServerAutoConfiguration implements WebMvcConfigurer { @Override protected PeerEurekaNode createPeerEurekaNode(String peerEurekaNodeUrl) { - Jersey3ReplicationClient replicationClient = Jersey3ReplicationClient.createReplicationClient(serverConfig, - serverCodecs, peerEurekaNodeUrl); - - // FIXME: 4.0 - // this.replicationClientAdditionalFilters.getFilters().forEach(replicationClient::addReplicationClientFilter); + Jersey3ReplicationClient replicationClient = createReplicationClient(serverConfig, serverCodecs, + peerEurekaNodeUrl, this.replicationClientAdditionalFilters.getFilters()); String targetHost = hostFromUrl(peerEurekaNodeUrl); if (targetHost == null) { @@ -399,6 +411,64 @@ public class EurekaServerAutoConfiguration implements WebMvcConfigurer { return new PeerEurekaNode(registry, targetHost, peerEurekaNodeUrl, replicationClient, serverConfig); } + // FIXME: 4.0 update Jersey3ReplicationClient.createReplicationClient to handle + // additional filters + private static Jersey3ReplicationClient createReplicationClient(EurekaServerConfig config, + ServerCodecs serverCodecs, String serviceUrl, Collection additionalFilters) { + String name = Jersey3ReplicationClient.class.getSimpleName() + ": " + serviceUrl + "apps/: "; + + EurekaJersey3Client jerseyClient; + try { + String hostname; + try { + hostname = new URL(serviceUrl).getHost(); + } + catch (MalformedURLException e) { + hostname = serviceUrl; + } + + String jerseyClientName = "Discovery-PeerNodeClient-" + hostname; + EurekaJersey3ClientImpl.EurekaJersey3ClientBuilder clientBuilder = new EurekaJersey3ClientImpl.EurekaJersey3ClientBuilder() + .withClientName(jerseyClientName).withUserAgent("Java-EurekaClient-Replication") + .withEncoderWrapper(serverCodecs.getFullJsonCodec()) + .withDecoderWrapper(serverCodecs.getFullJsonCodec()) + .withConnectionTimeout(config.getPeerNodeConnectTimeoutMs()) + .withReadTimeout(config.getPeerNodeReadTimeoutMs()) + .withMaxConnectionsPerHost(config.getPeerNodeTotalConnectionsPerHost()) + .withMaxTotalConnections(config.getPeerNodeTotalConnections()) + .withConnectionIdleTimeout(config.getPeerNodeConnectionIdleTimeoutSeconds()); + + if (serviceUrl.startsWith("https://") && "true" + .equals(System.getProperty("com.netflix.eureka.shouldSSLConnectionsUseSystemSocketFactory"))) { + clientBuilder.withSystemSSLConfiguration(); + } + jerseyClient = clientBuilder.build(); + } + catch (Throwable e) { + throw new RuntimeException("Cannot Create new Replica Node :" + name, e); + } + + String ip = null; + try { + ip = InetAddress.getLocalHost().getHostAddress(); + } + catch (UnknownHostException e) { + log.warn("Cannot find localhost ip", e); + } + + Client jerseyApacheClient = jerseyClient.getClient(); + jerseyApacheClient.register(new Jersey3DynamicGZIPContentEncodingFilter(config)); + + for (ClientRequestFilter filter : additionalFilters) { + jerseyApacheClient.register(filter); + } + + EurekaServerIdentity identity = new EurekaServerIdentity(ip); + jerseyApacheClient.register(new EurekaIdentityHeaderFilter(identity)); + + return new Jersey3ReplicationClient(jerseyClient, serviceUrl); + } + @Override public void onApplicationEvent(final EnvironmentChangeEvent event) { if (shouldUpdate(event.getKeys())) { diff --git a/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/ReplicationClientAdditionalFilters.java b/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/ReplicationClientAdditionalFilters.java index 9e35df990..26b044717 100644 --- a/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/ReplicationClientAdditionalFilters.java +++ b/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/ReplicationClientAdditionalFilters.java @@ -19,21 +19,20 @@ package org.springframework.cloud.netflix.eureka.server; import java.util.Collection; import java.util.LinkedHashSet; -// FIXME: 4.0 -//import com.sun.jersey.api.client.filter.ClientFilter; +import jakarta.ws.rs.client.ClientRequestFilter; /** * @author Yuxin Bai */ public class ReplicationClientAdditionalFilters { - private final Collection filters; + private final Collection filters; - public ReplicationClientAdditionalFilters(Collection filters) { + public ReplicationClientAdditionalFilters(Collection filters) { this.filters = new LinkedHashSet<>(filters); } - public Collection getFilters() { + public Collection getFilters() { return this.filters; } diff --git a/spring-cloud-netflix-eureka-server/src/test/java/org/springframework/cloud/netflix/eureka/server/RefreshablePeerEurekaNodesWithCustomFiltersTests.java b/spring-cloud-netflix-eureka-server/src/test/java/org/springframework/cloud/netflix/eureka/server/RefreshablePeerEurekaNodesWithCustomFiltersTests.java index 640570d51..5dabe74d2 100644 --- a/spring-cloud-netflix-eureka-server/src/test/java/org/springframework/cloud/netflix/eureka/server/RefreshablePeerEurekaNodesWithCustomFiltersTests.java +++ b/spring-cloud-netflix-eureka-server/src/test/java/org/springframework/cloud/netflix/eureka/server/RefreshablePeerEurekaNodesWithCustomFiltersTests.java @@ -16,16 +16,20 @@ package org.springframework.cloud.netflix.eureka.server; +import java.io.IOException; import java.lang.reflect.Field; +import java.util.Collections; import com.netflix.eureka.cluster.PeerEurekaNodes; -import org.junit.jupiter.api.Disabled; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration.RefreshablePeerEurekaNodes; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.ReflectionUtils; @@ -34,7 +38,6 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author Yuxin Bai */ -@Disabled // FIXME: 4.0 @SpringBootTest(classes = RefreshablePeerEurekaNodesWithCustomFiltersTests.Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = { "spring.application.name=eureka", "server.contextPath=/context", "management.security.enabled=false" }) @@ -47,18 +50,13 @@ class RefreshablePeerEurekaNodesWithCustomFiltersTests { void testCustomPeerNodesShouldTakePrecedenceOverDefault() { assertThat(peerEurekaNodes instanceof RefreshablePeerEurekaNodes) .as("PeerEurekaNodes should be an instance of RefreshablePeerEurekaNodes").isTrue(); + RefreshablePeerEurekaNodes refreshablePeerEurekaNodes = (RefreshablePeerEurekaNodes) peerEurekaNodes; - // ReplicationClientAdditionalFilters filters = - // getField(RefreshablePeerEurekaNodes.class, - // (RefreshablePeerEurekaNodes) peerEurekaNodes, - // "replicationClientAdditionalFilters"); - // assertThat(filters.getFilters()) - // .as("PeerEurekaNodes'should have only one filter set on - // replicationClientAdditionalFilters").hasSize(1); - // assertThat(filters.getFilters().iterator().next() instanceof - // Application.CustomClientFilter) - // .as("The type of the filter should be CustomClientFilter as user declared - // so").isTrue(); + ReplicationClientAdditionalFilters filters = refreshablePeerEurekaNodes.replicationClientAdditionalFilters; + assertThat(filters.getFilters()) + .as("PeerEurekaNodes'should have only one filter set on replicationClientAdditionalFilters").hasSize(1); + assertThat(filters.getFilters().iterator().next() instanceof Application.CustomClientFilter) + .as("The type of the filter should be CustomClientFilter as user declared so").isTrue(); } private static R getField(Class clazz, T target, String fieldName) { @@ -74,19 +72,19 @@ class RefreshablePeerEurekaNodesWithCustomFiltersTests { @EnableEurekaServer protected static class Application { - // FIXME: 4.0 - /* - * @Bean public ReplicationClientAdditionalFilters customFilters() { return new - * ReplicationClientAdditionalFilters(Collections.singletonList(new - * CustomClientFilter())); } - * - * protected class CustomClientFilter extends ClientFilter { - * - * @Override public ClientResponse handle(ClientRequest cr) throws - * ClientHandlerException { return getNext().handle(cr); } - * - * } - */ + @Bean + public ReplicationClientAdditionalFilters customFilters() { + return new ReplicationClientAdditionalFilters(Collections.singletonList(new CustomClientFilter())); + } + + protected class CustomClientFilter implements ClientRequestFilter { + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + // noop + } + + } }