-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support Spring RestClient as TransportClientFactory #4281
base: main
Are you sure you want to change the base?
Changes from all commits
0a88c0f
783d4c1
142f1af
a9c6260
760dae3
1084cbc
b2c98b6
a6c2690
a9f8a23
6fd4f58
0ea8039
ac24e37
4c7ce21
871451c
e239d9b
8abfc6d
8581ab0
ffcff5d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,8 +25,6 @@ | |
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.AllNestedConditions; | ||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; | ||
|
@@ -42,19 +40,23 @@ | |
import org.springframework.cloud.netflix.eureka.RestTemplateTimeoutProperties; | ||
import org.springframework.cloud.netflix.eureka.http.DefaultEurekaClientHttpRequestFactorySupplier; | ||
import org.springframework.cloud.netflix.eureka.http.EurekaClientHttpRequestFactorySupplier; | ||
import org.springframework.cloud.netflix.eureka.http.RestClientDiscoveryClientOptionalArgs; | ||
import org.springframework.cloud.netflix.eureka.http.RestClientTransportClientFactories; | ||
import org.springframework.cloud.netflix.eureka.http.RestTemplateDiscoveryClientOptionalArgs; | ||
import org.springframework.cloud.netflix.eureka.http.RestTemplateTransportClientFactories; | ||
import org.springframework.cloud.netflix.eureka.http.WebClientDiscoveryClientOptionalArgs; | ||
import org.springframework.cloud.netflix.eureka.http.WebClientTransportClientFactories; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Conditional; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.web.client.RestClient; | ||
import org.springframework.web.reactive.function.client.WebClient; | ||
|
||
/** | ||
* @author Daniel Lavoie | ||
* @author Armin Krezovic | ||
* @author Olga Maciaszek-Sharma | ||
* @author Wonchul Heo | ||
*/ | ||
@Configuration(proxyBeanMethods = false) | ||
@EnableConfigurationProperties(RestTemplateTimeoutProperties.class) | ||
|
@@ -70,14 +72,14 @@ public TlsProperties tlsProperties() { | |
|
||
@Bean | ||
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate") | ||
@Conditional(JerseyClientNotPresentOrNotEnabledCondition.class) | ||
@Conditional(RestTemplateEnabledCondition.class) | ||
@ConditionalOnMissingBean(value = { AbstractDiscoveryClientOptionalArgs.class }, search = SearchStrategy.CURRENT) | ||
@ConditionalOnProperty(prefix = "eureka.client", name = "webclient.enabled", matchIfMissing = true, | ||
heowc marked this conversation as resolved.
Show resolved
Hide resolved
heowc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
havingValue = "false") | ||
public RestTemplateDiscoveryClientOptionalArgs restTemplateDiscoveryClientOptionalArgs(TlsProperties tlsProperties, | ||
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier, | ||
ObjectProvider<RestTemplateBuilder> restTemplateBuilders) throws GeneralSecurityException, IOException { | ||
logger.info("Eureka HTTP Client uses RestTemplate."); | ||
if (logger.isInfoEnabled()) { | ||
logger.info("Eureka HTTP Client uses RestTemplate."); | ||
} | ||
RestTemplateDiscoveryClientOptionalArgs result = new RestTemplateDiscoveryClientOptionalArgs( | ||
eurekaClientHttpRequestFactorySupplier, restTemplateBuilders::getIfAvailable); | ||
setupTLS(result, tlsProperties); | ||
|
@@ -112,13 +114,15 @@ private static void setupTLS(AbstractDiscoveryClientOptionalArgs<?> args, TlsPro | |
} | ||
|
||
@Configuration(proxyBeanMethods = false) | ||
@Conditional(JerseyClientPresentAndEnabledCondition.class) | ||
@Conditional(RestTemplateEnabledCondition.class) | ||
@ConditionalOnBean(value = AbstractDiscoveryClientOptionalArgs.class, search = SearchStrategy.CURRENT) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We still want to keep the |
||
static class DiscoveryClientOptionalArgsTlsConfiguration { | ||
|
||
DiscoveryClientOptionalArgsTlsConfiguration(TlsProperties tlsProperties, | ||
AbstractDiscoveryClientOptionalArgs optionalArgs) throws GeneralSecurityException, IOException { | ||
logger.info("Eureka HTTP Client uses Jersey"); | ||
if (logger.isInfoEnabled()) { | ||
logger.info("Eureka HTTP Client uses Jersey"); | ||
} | ||
setupTLS(optionalArgs, tlsProperties); | ||
} | ||
|
||
|
@@ -129,16 +133,15 @@ static class DiscoveryClientOptionalArgsTlsConfiguration { | |
@ConditionalOnProperty(prefix = "eureka.client", name = "webclient.enabled", havingValue = "true") | ||
protected static class WebClientConfiguration { | ||
|
||
@Autowired | ||
private TlsProperties tlsProperties; | ||
|
||
@Bean | ||
@ConditionalOnMissingBean( | ||
value = { AbstractDiscoveryClientOptionalArgs.class, RestTemplateDiscoveryClientOptionalArgs.class }, | ||
search = SearchStrategy.CURRENT) | ||
public WebClientDiscoveryClientOptionalArgs webClientDiscoveryClientOptionalArgs( | ||
public WebClientDiscoveryClientOptionalArgs webClientDiscoveryClientOptionalArgs(TlsProperties tlsProperties, | ||
ObjectProvider<WebClient.Builder> builder) throws GeneralSecurityException, IOException { | ||
logger.info("Eureka HTTP Client uses WebClient."); | ||
if (logger.isInfoEnabled()) { | ||
logger.info("Eureka HTTP Client uses WebClient."); | ||
} | ||
WebClientDiscoveryClientOptionalArgs result = new WebClientDiscoveryClientOptionalArgs( | ||
builder::getIfAvailable); | ||
setupTLS(result, tlsProperties); | ||
|
@@ -168,19 +171,27 @@ public WebClientNotFoundConfiguration() { | |
|
||
} | ||
|
||
static class JerseyClientPresentAndEnabledCondition extends AllNestedConditions { | ||
static class RestTemplateEnabledCondition extends AnyNestedCondition { | ||
|
||
JerseyClientPresentAndEnabledCondition() { | ||
RestTemplateEnabledCondition() { | ||
super(ConfigurationPhase.REGISTER_BEAN); | ||
} | ||
|
||
@ConditionalOnClass(name = "org.glassfish.jersey.client.JerseyClient") | ||
static class OnJerseyClientPresent { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Jersey conditions are not related to the |
||
@ConditionalOnProperty(value = "eureka.client.jersey.enabled", matchIfMissing = true) | ||
static class OnJerseyClientEnabled { | ||
|
||
} | ||
|
||
@ConditionalOnProperty(value = "eureka.client.jersey.enabled", matchIfMissing = true) | ||
static class OnJerseyClientEnabled { | ||
@ConditionalOnProperty(prefix = "eureka.client", name = "webclient.enabled", matchIfMissing = true, | ||
havingValue = "false") | ||
static class OnWebClientDisabled { | ||
|
||
} | ||
|
||
@ConditionalOnProperty(prefix = "eureka.client", name = "restclient.enabled", matchIfMissing = true, | ||
havingValue = "false") | ||
static class OnRestClientDisabled { | ||
|
||
} | ||
|
||
|
@@ -204,4 +215,32 @@ static class OnJerseyClientDisabled { | |
|
||
} | ||
|
||
@ConditionalOnMissingClass("org.glassfish.jersey.client.JerseyClient") | ||
@ConditionalOnClass(name = "org.springframework.web.client.RestClient") | ||
@ConditionalOnProperty(prefix = "eureka.client", name = "restclient.enabled", havingValue = "true") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After some more consideration, I actually think we could switch to using |
||
protected static class RestClientConfiguration { | ||
|
||
@Bean | ||
@ConditionalOnMissingBean(value = { AbstractDiscoveryClientOptionalArgs.class }, | ||
search = SearchStrategy.CURRENT) | ||
public RestClientDiscoveryClientOptionalArgs restClientDiscoveryClientOptionalArgs(TlsProperties tlsProperties, | ||
ObjectProvider<RestClient.Builder> builder) throws GeneralSecurityException, IOException { | ||
if (logger.isInfoEnabled()) { | ||
logger.info("Eureka HTTP Client uses RestClient."); | ||
} | ||
RestClientDiscoveryClientOptionalArgs result = new RestClientDiscoveryClientOptionalArgs( | ||
builder::getIfAvailable); | ||
setupTLS(result, tlsProperties); | ||
return result; | ||
} | ||
|
||
@Bean | ||
@ConditionalOnMissingBean(value = TransportClientFactories.class, search = SearchStrategy.CURRENT) | ||
public RestClientTransportClientFactories restClientTransportClientFactories( | ||
ObjectProvider<RestClient.Builder> builder) { | ||
return new RestClientTransportClientFactories(builder::getIfAvailable); | ||
} | ||
|
||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ | |
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
import org.springframework.boot.autoconfigure.condition.SearchStrategy; | ||
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration; | ||
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; | ||
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; | ||
import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||
import org.springframework.boot.web.client.RestTemplateBuilder; | ||
|
@@ -37,6 +38,8 @@ | |
import org.springframework.cloud.netflix.eureka.RestTemplateTimeoutProperties; | ||
import org.springframework.cloud.netflix.eureka.http.DefaultEurekaClientHttpRequestFactorySupplier; | ||
import org.springframework.cloud.netflix.eureka.http.EurekaClientHttpRequestFactorySupplier; | ||
import org.springframework.cloud.netflix.eureka.http.RestClientEurekaHttpClient; | ||
import org.springframework.cloud.netflix.eureka.http.RestClientTransportClientFactory; | ||
import org.springframework.cloud.netflix.eureka.http.RestTemplateEurekaHttpClient; | ||
import org.springframework.cloud.netflix.eureka.http.RestTemplateTransportClientFactory; | ||
import org.springframework.cloud.netflix.eureka.http.WebClientEurekaHttpClient; | ||
|
@@ -46,6 +49,7 @@ | |
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.core.env.Environment; | ||
import org.springframework.lang.Nullable; | ||
import org.springframework.web.client.RestClient; | ||
import org.springframework.web.reactive.function.client.WebClient; | ||
|
||
/** | ||
|
@@ -54,6 +58,7 @@ | |
* | ||
* @author Dave Syer | ||
* @author Armin Krezovic | ||
* @author Wonchul Heo | ||
*/ | ||
@ConditionalOnClass(ConfigServicePropertySourceLocator.class) | ||
@Conditional(EurekaConfigServerBootstrapConfiguration.EurekaConfigServerBootstrapCondition.class) | ||
|
@@ -109,6 +114,22 @@ public WebClientEurekaHttpClient configDiscoveryWebClientEurekaHttpClient(Eureka | |
|
||
} | ||
|
||
@Configuration(proxyBeanMethods = false) | ||
@ConditionalOnClass(name = "org.springframework.web.client.RestClient") | ||
@ConditionalOnProperty(prefix = "eureka.client", name = "restclient.enabled", havingValue = "true") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After some more consideration, I actually think we could switch to using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added related conditions while changing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, the logic after your changes has been modified - please take a look at the review comments. |
||
@ImportAutoConfiguration(RestClientAutoConfiguration.class) | ||
protected static class RestClientConfiguration { | ||
|
||
@Bean | ||
@ConditionalOnMissingBean(EurekaHttpClient.class) | ||
public RestClientEurekaHttpClient configDiscoveryRestClientEurekaHttpClient(EurekaClientConfigBean config, | ||
ObjectProvider<RestClient.Builder> builder, Environment env) { | ||
return (RestClientEurekaHttpClient) new RestClientTransportClientFactory(builder::getIfAvailable) | ||
.newClient(HostnameBasedUrlRandomizer.randomEndpoint(config, env)); | ||
} | ||
|
||
} | ||
|
||
static class EurekaConfigServerBootstrapCondition extends AllNestedConditions { | ||
|
||
EurekaConfigServerBootstrapCondition() { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/* | ||
* Copyright 2017-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.netflix.eureka.http; | ||
|
||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
|
||
import com.fasterxml.jackson.databind.BeanDescription; | ||
import com.fasterxml.jackson.databind.DeserializationFeature; | ||
import com.fasterxml.jackson.databind.JsonSerializer; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.fasterxml.jackson.databind.PropertyNamingStrategies; | ||
import com.fasterxml.jackson.databind.SerializationConfig; | ||
import com.fasterxml.jackson.databind.SerializationFeature; | ||
import com.fasterxml.jackson.databind.module.SimpleModule; | ||
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; | ||
import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase; | ||
import com.netflix.appinfo.InstanceInfo; | ||
import com.netflix.discovery.converters.jackson.mixin.ApplicationsJsonMixIn; | ||
import com.netflix.discovery.converters.jackson.mixin.InstanceInfoJsonMixIn; | ||
import com.netflix.discovery.converters.jackson.serializer.InstanceInfoJsonBeanSerializer; | ||
import com.netflix.discovery.shared.Applications; | ||
import com.netflix.discovery.shared.transport.EurekaHttpClient; | ||
|
||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; | ||
import org.springframework.lang.Nullable; | ||
|
||
/** | ||
* Utility class for dealing with {@link EurekaHttpClient}. | ||
* | ||
* @author Daniel Lavoie | ||
* @author Wonchul Heo | ||
* @since 4.2.0 | ||
*/ | ||
final class EurekaHttpClientUtils { | ||
|
||
private EurekaHttpClientUtils() { | ||
throw new AssertionError("Must not instantiate constant utility class"); | ||
} | ||
|
||
/** | ||
* Provides the serialization configurations required by the Eureka Server. JSON | ||
* content exchanged with eureka requires a root node matching the entity being | ||
* serialized or deserialized. Achieved with | ||
* {@link SerializationFeature#WRAP_ROOT_VALUE} and | ||
* {@link DeserializationFeature#UNWRAP_ROOT_VALUE}. | ||
* {@link PropertyNamingStrategies.SnakeCaseStrategy} is applied to the underlying | ||
* {@link ObjectMapper}. | ||
* @return a {@link MappingJackson2HttpMessageConverter} object | ||
*/ | ||
static MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() { | ||
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); | ||
converter.setObjectMapper(objectMapper()); | ||
return converter; | ||
} | ||
|
||
/** | ||
* Provides the serialization configurations required by the Eureka Server. JSON | ||
* content exchanged with eureka requires a root node matching the entity being | ||
* serialized or deserialized. Achieved with | ||
* {@link SerializationFeature#WRAP_ROOT_VALUE} and | ||
* {@link DeserializationFeature#UNWRAP_ROOT_VALUE}. | ||
* {@link PropertyNamingStrategies.SnakeCaseStrategy} is applied to the underlying | ||
* {@link ObjectMapper}. | ||
* @return a {@link ObjectMapper} object | ||
*/ | ||
static ObjectMapper objectMapper() { | ||
final ObjectMapper objectMapper = new ObjectMapper(); | ||
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); | ||
|
||
final SimpleModule jsonModule = new SimpleModule(); | ||
jsonModule.setSerializerModifier(createJsonSerializerModifier()); | ||
objectMapper.registerModule(jsonModule); | ||
|
||
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true); | ||
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); | ||
objectMapper.addMixIn(Applications.class, ApplicationsJsonMixIn.class); | ||
objectMapper.addMixIn(InstanceInfo.class, InstanceInfoJsonMixIn.class); | ||
|
||
return objectMapper; | ||
} | ||
|
||
private static BeanSerializerModifier createJsonSerializerModifier() { | ||
return new BeanSerializerModifier() { | ||
@Override | ||
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, | ||
JsonSerializer<?> serializer) { | ||
if (beanDesc.getBeanClass().isAssignableFrom(InstanceInfo.class)) { | ||
return new InstanceInfoJsonBeanSerializer((BeanSerializerBase) serializer, false); | ||
} | ||
return serializer; | ||
} | ||
}; | ||
} | ||
|
||
@Nullable | ||
static UserInfo extractUserInfo(String serviceUrl) { | ||
heowc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
try { | ||
final URI serviceURI = new URI(serviceUrl); | ||
if (serviceURI.getUserInfo() != null) { | ||
final String[] credentials = serviceURI.getUserInfo().split(":"); | ||
if (credentials.length == 2) { | ||
return new UserInfo(credentials[0], credentials[1]); | ||
} | ||
} | ||
} | ||
catch (URISyntaxException ignore) { | ||
} | ||
return null; | ||
} | ||
|
||
record UserInfo(String username, String password) { | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still want to keep the
JerseyClientNotPresentOrNotEnabledCondition
here.