Add LoadBalancerLifecycle support.
This commit is contained in:
@@ -26,6 +26,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -44,8 +45,10 @@ class DefaultFeignLoadBalancerConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(OnRetryNotEnabledCondition.class)
|
||||
public Client feignClient(LoadBalancerClient loadBalancerClient, LoadBalancerProperties properties) {
|
||||
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient, properties);
|
||||
public Client feignClient(LoadBalancerClient loadBalancerClient, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient, properties,
|
||||
loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -55,9 +58,10 @@ class DefaultFeignLoadBalancerConfiguration {
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public Client feignRetryClient(LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties) {
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
return new RetryableFeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,
|
||||
loadBalancedRetryFactory, properties);
|
||||
loadBalancedRetryFactory, properties, loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.cloud.openfeign.loadbalancer;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Set;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
@@ -27,11 +28,18 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultRequestContext;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.HttpRequestContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -41,6 +49,7 @@ import org.springframework.util.Assert;
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public class FeignBlockingLoadBalancerClient implements Client {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(FeignBlockingLoadBalancerClient.class);
|
||||
@@ -51,11 +60,14 @@ public class FeignBlockingLoadBalancerClient implements Client {
|
||||
|
||||
private final LoadBalancerProperties properties;
|
||||
|
||||
private final LoadBalancerClientFactory loadBalancerClientFactory;
|
||||
|
||||
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancerProperties properties) {
|
||||
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
this.delegate = delegate;
|
||||
this.loadBalancerClient = loadBalancerClient;
|
||||
this.properties = properties;
|
||||
this.loadBalancerClientFactory = loadBalancerClientFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,19 +78,47 @@ public class FeignBlockingLoadBalancerClient implements Client {
|
||||
String hint = getHint(serviceId);
|
||||
DefaultRequest<DefaultRequestContext> lbRequest = new DefaultRequest<>(
|
||||
new DefaultRequestContext(request, hint));
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
|
||||
.getSupportedLifecycleProcessors(
|
||||
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
|
||||
HttpRequestContext.class, ClientHttpResponse.class, ServiceInstance.class);
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
|
||||
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
|
||||
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
|
||||
instance);
|
||||
if (instance == null) {
|
||||
String message = "Load balancer does not contain an instance for the service " + serviceId;
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn(message);
|
||||
}
|
||||
supportedLifecycleProcessors.forEach(
|
||||
lifecycle -> lifecycle.onComplete(new CompletionContext<ClientHttpResponse, ServiceInstance>(
|
||||
CompletionContext.Status.DISCARD, lbResponse)));
|
||||
return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value())
|
||||
.body(message, StandardCharsets.UTF_8).build();
|
||||
}
|
||||
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
|
||||
Request newRequest = Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.body(),
|
||||
request.charset(), request.requestTemplate());
|
||||
return delegate.execute(newRequest, options);
|
||||
return executeWithLoadBalancerLifecycleProcessing(options, supportedLifecycleProcessors, lbResponse,
|
||||
newRequest);
|
||||
}
|
||||
|
||||
private Response executeWithLoadBalancerLifecycleProcessing(Request.Options options,
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors,
|
||||
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse, Request newRequest)
|
||||
throws IOException {
|
||||
try {
|
||||
Response response = delegate.execute(newRequest, options);
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
|
||||
.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS, lbResponse, response)));
|
||||
return response;
|
||||
}
|
||||
catch (Exception exception) {
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
|
||||
.onComplete(new CompletionContext<>(CompletionContext.Status.FAILED, exception, lbResponse)));
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
// Visible for Sleuth instrumentation
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -37,7 +38,7 @@ import org.springframework.context.annotation.Import;
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@ConditionalOnBean(LoadBalancerClient.class)
|
||||
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
|
||||
@AutoConfigureBefore(FeignAutoConfiguration.class)
|
||||
@EnableConfigurationProperties(FeignHttpClientProperties.class)
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.openfeign.clientconfig.HttpClientFeignConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
@@ -43,7 +44,7 @@ import org.springframework.context.annotation.Import;
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ApacheHttpClient.class)
|
||||
@ConditionalOnBean(LoadBalancerClient.class)
|
||||
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
|
||||
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
|
||||
@Import(HttpClientFeignConfiguration.class)
|
||||
@EnableConfigurationProperties(LoadBalancerProperties.class)
|
||||
@@ -53,9 +54,9 @@ class HttpClientFeignLoadBalancerConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(OnRetryNotEnabledCondition.class)
|
||||
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
|
||||
LoadBalancerProperties properties) {
|
||||
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -65,10 +66,11 @@ class HttpClientFeignLoadBalancerConfiguration {
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties) {
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
|
||||
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
|
||||
properties);
|
||||
properties, loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.openfeign.clientconfig.OkHttpFeignConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
@@ -43,7 +44,7 @@ import org.springframework.context.annotation.Import;
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(OkHttpClient.class)
|
||||
@ConditionalOnProperty("feign.okhttp.enabled")
|
||||
@ConditionalOnBean(LoadBalancerClient.class)
|
||||
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
|
||||
@Import(OkHttpFeignConfiguration.class)
|
||||
@EnableConfigurationProperties(LoadBalancerProperties.class)
|
||||
class OkHttpFeignLoadBalancerConfiguration {
|
||||
@@ -52,9 +53,9 @@ class OkHttpFeignLoadBalancerConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(OnRetryNotEnabledCondition.class)
|
||||
public Client feignClient(okhttp3.OkHttpClient okHttpClient, LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancerProperties properties) {
|
||||
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
OkHttpClient delegate = new OkHttpClient(okHttpClient);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -64,10 +65,11 @@ class OkHttpFeignLoadBalancerConfiguration {
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, okhttp3.OkHttpClient okHttpClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties) {
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
OkHttpClient delegate = new OkHttpClient(okHttpClient);
|
||||
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
|
||||
properties);
|
||||
properties, loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
@@ -31,19 +32,26 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.HttpRequestContext;
|
||||
import org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRecoveryCallback;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator;
|
||||
import org.springframework.cloud.client.loadbalancer.RetryableRequestContext;
|
||||
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.retry.RetryListener;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
@@ -58,6 +66,7 @@ import org.springframework.util.Assert;
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 2.2.6
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public class RetryableFeignBlockingLoadBalancerClient implements Client {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(FeignBlockingLoadBalancerClient.class);
|
||||
@@ -70,12 +79,16 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
|
||||
|
||||
private final LoadBalancerProperties properties;
|
||||
|
||||
private final LoadBalancerClientFactory loadBalancerClientFactory;
|
||||
|
||||
public RetryableFeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties) {
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
this.delegate = delegate;
|
||||
this.loadBalancerClient = loadBalancerClient;
|
||||
this.loadBalancedRetryFactory = loadBalancedRetryFactory;
|
||||
this.properties = properties;
|
||||
this.loadBalancerClientFactory = loadBalancerClientFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,6 +101,11 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
|
||||
RetryTemplate retryTemplate = buildRetryTemplate(serviceId, request, retryPolicy);
|
||||
return retryTemplate.execute(context -> {
|
||||
Request feignRequest = null;
|
||||
ServiceInstance retrievedServiceInstance = null;
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
|
||||
.getSupportedLifecycleProcessors(
|
||||
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
|
||||
HttpRequestContext.class, ClientHttpResponse.class, ServiceInstance.class);
|
||||
// On retries the policy will choose the server and set it in the context
|
||||
// and extract the server and update the request being made
|
||||
if (context instanceof LoadBalancedRetryContext) {
|
||||
@@ -102,30 +120,38 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
|
||||
String hint = getHint(serviceId);
|
||||
DefaultRequest<RetryableRequestContext> lbRequest = new DefaultRequest<>(
|
||||
new RetryableRequestContext(previousServiceInstance, request, hint));
|
||||
serviceInstance = loadBalancerClient.choose(serviceId, lbRequest);
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
|
||||
retrievedServiceInstance = loadBalancerClient.choose(serviceId, lbRequest);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(String.format("Selected service instance: %s", serviceInstance));
|
||||
LOG.debug(String.format("Selected service instance: %s", retrievedServiceInstance));
|
||||
}
|
||||
lbContext.setServiceInstance(serviceInstance);
|
||||
lbContext.setServiceInstance(retrievedServiceInstance);
|
||||
}
|
||||
if (serviceInstance != null) {
|
||||
|
||||
if (retrievedServiceInstance == null) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Service instance was not resolved, executing the original request");
|
||||
}
|
||||
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
|
||||
retrievedServiceInstance);
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
|
||||
new CompletionContext<ClientHttpResponse, ServiceInstance>(CompletionContext.Status.DISCARD,
|
||||
lbResponse)));
|
||||
feignRequest = request;
|
||||
}
|
||||
else {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(String.format("Using service instance from LoadBalancedRetryContext: %s",
|
||||
serviceInstance));
|
||||
retrievedServiceInstance));
|
||||
}
|
||||
String reconstructedUrl = loadBalancerClient.reconstructURI(serviceInstance, originalUri)
|
||||
String reconstructedUrl = loadBalancerClient.reconstructURI(retrievedServiceInstance, originalUri)
|
||||
.toString();
|
||||
feignRequest = Request.create(request.httpMethod(), reconstructedUrl, request.headers(),
|
||||
request.body(), request.charset(), request.requestTemplate());
|
||||
}
|
||||
}
|
||||
if (feignRequest == null) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Service instance was not resolved, executing the original request");
|
||||
}
|
||||
feignRequest = request;
|
||||
}
|
||||
Response response = delegate.execute(feignRequest, options);
|
||||
Response response = executeWithLoadBalancerLifecycleProcessing(options, feignRequest,
|
||||
retrievedServiceInstance, retrievedServiceInstance != null, supportedLifecycleProcessors);
|
||||
int responseStatus = response.status();
|
||||
if (retryPolicy != null && retryPolicy.retryableStatusCode(responseStatus)) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
@@ -143,6 +169,28 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
|
||||
});
|
||||
}
|
||||
|
||||
private Response executeWithLoadBalancerLifecycleProcessing(Request.Options options, Request feignRequest,
|
||||
ServiceInstance retrievedServiceInstance, boolean loadBalanced,
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors) throws IOException {
|
||||
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
|
||||
retrievedServiceInstance);
|
||||
try {
|
||||
Response response = delegate.execute(feignRequest, options);
|
||||
if (loadBalanced) {
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
|
||||
.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS, lbResponse, response)));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
catch (Exception exception) {
|
||||
if (loadBalanced) {
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
|
||||
.onComplete(new CompletionContext<>(CompletionContext.Status.FAILED, exception, lbResponse)));
|
||||
}
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
private RetryTemplate buildRetryTemplate(String serviceId, Request request, LoadBalancedRetryPolicy retryPolicy) {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
BackOffPolicy backOffPolicy = this.loadBalancedRetryFactory.createBackOffPolicy(serviceId);
|
||||
|
||||
@@ -23,6 +23,8 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
@@ -34,8 +36,12 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultRequestContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -66,8 +72,12 @@ class FeignBlockingLoadBalancerClientTests {
|
||||
|
||||
private BlockingLoadBalancerClient loadBalancerClient = mock(BlockingLoadBalancerClient.class);
|
||||
|
||||
private final LoadBalancerClientFactory loadBalancerClientFactory = mock(LoadBalancerClientFactory.class);
|
||||
|
||||
private final LoadBalancerProperties loadBalancerProperties = new LoadBalancerProperties();
|
||||
|
||||
private FeignBlockingLoadBalancerClient feignBlockingLoadBalancerClient = new FeignBlockingLoadBalancerClient(
|
||||
delegate, loadBalancerClient, new LoadBalancerProperties());
|
||||
delegate, loadBalancerClient, loadBalancerProperties, loadBalancerClientFactory);
|
||||
|
||||
@Test
|
||||
void shouldExtractServiceIdFromRequestUrl() throws IOException {
|
||||
@@ -121,6 +131,35 @@ class FeignBlockingLoadBalancerClientTests {
|
||||
assertThat(new String(actualRequest.body())).isEqualTo("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExecuteLoadBalancerLifecycleCallbacks() throws IOException {
|
||||
Request request = testRequest();
|
||||
Request.Options options = new Request.Options();
|
||||
String url = "http://127.0.0.1/path";
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("test-1", "test", "test-host", 8888, false);
|
||||
when(loadBalancerClient.choose(eq("test"), any())).thenReturn(serviceInstance);
|
||||
when(loadBalancerClient.reconstructURI(serviceInstance, URI.create("http://test/path")))
|
||||
.thenReturn(URI.create(url));
|
||||
String callbackTestHint = "callbackTestHint";
|
||||
loadBalancerProperties.getHint().put("test", callbackTestHint);
|
||||
Map<String, LoadBalancerLifecycle> loadBalancerLifecycleBeans = new HashMap<>();
|
||||
loadBalancerLifecycleBeans.put("loadBalancerLifecycle", new TestLoadBalancerLifecycle());
|
||||
loadBalancerLifecycleBeans.put("anotherLoadBalancerLifecycle", new AnotherLoadBalancerLifecycle());
|
||||
when(loadBalancerClientFactory.getInstances("test", LoadBalancerLifecycle.class))
|
||||
.thenReturn(loadBalancerLifecycleBeans);
|
||||
|
||||
Object actualResult = feignBlockingLoadBalancerClient.execute(request, options);
|
||||
|
||||
Collection<org.springframework.cloud.client.loadbalancer.Request<Object>> lifecycleLogRequests = ((TestLoadBalancerLifecycle) loadBalancerLifecycleBeans
|
||||
.get("loadBalancerLifecycle")).getStartLog().values();
|
||||
Collection<CompletionContext<Object, ServiceInstance>> anotherLifecycleLogRequests = ((AnotherLoadBalancerLifecycle) loadBalancerLifecycleBeans
|
||||
.get("anotherLoadBalancerLifecycle")).getCompleteLog().values();
|
||||
assertThat(lifecycleLogRequests)
|
||||
.extracting(lbRequest -> ((DefaultRequestContext) lbRequest.getContext()).getHint())
|
||||
.contains(callbackTestHint);
|
||||
assertThat(anotherLifecycleLogRequests).extracting(CompletionContext::getClientResponse).contains(actualResult);
|
||||
}
|
||||
|
||||
private Request testRequest() {
|
||||
return testRequest("test");
|
||||
}
|
||||
@@ -137,4 +176,43 @@ class FeignBlockingLoadBalancerClientTests {
|
||||
|
||||
}
|
||||
|
||||
protected static class TestLoadBalancerLifecycle implements LoadBalancerLifecycle<Object, Object, ServiceInstance> {
|
||||
|
||||
final ConcurrentHashMap<String, org.springframework.cloud.client.loadbalancer.Request<Object>> startLog = new ConcurrentHashMap<>();
|
||||
|
||||
final ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> completeLog = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onStart(org.springframework.cloud.client.loadbalancer.Request<Object> request) {
|
||||
startLog.put(getName() + UUID.randomUUID(), request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(CompletionContext<Object, ServiceInstance> completionContext) {
|
||||
completeLog.put(getName() + UUID.randomUUID(), completionContext);
|
||||
}
|
||||
|
||||
ConcurrentHashMap<String, org.springframework.cloud.client.loadbalancer.Request<Object>> getStartLog() {
|
||||
return startLog;
|
||||
}
|
||||
|
||||
ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> getCompleteLog() {
|
||||
return completeLog;
|
||||
}
|
||||
|
||||
protected String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class AnotherLoadBalancerLifecycle extends TestLoadBalancerLifecycle {
|
||||
|
||||
@Override
|
||||
protected String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
@@ -35,10 +37,14 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultRequestContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.blocking.retry.BlockingLoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
@@ -70,10 +76,12 @@ class RetryableFeignBlockingLoadBalancerClientTests {
|
||||
|
||||
private BlockingLoadBalancerClient loadBalancerClient = mock(BlockingLoadBalancerClient.class);
|
||||
|
||||
private final LoadBalancerClientFactory loadBalancerClientFactory = mock(LoadBalancerClientFactory.class);
|
||||
|
||||
private LoadBalancerProperties properties = new LoadBalancerProperties();
|
||||
|
||||
private RetryableFeignBlockingLoadBalancerClient feignBlockingLoadBalancerClient = new RetryableFeignBlockingLoadBalancerClient(
|
||||
delegate, loadBalancerClient, retryFactory, properties);
|
||||
delegate, loadBalancerClient, retryFactory, properties, loadBalancerClientFactory);
|
||||
|
||||
private ServiceInstance serviceInstance = new DefaultServiceInstance("test-a", "test", "testhost", 80, false);
|
||||
|
||||
@@ -165,6 +173,37 @@ class RetryableFeignBlockingLoadBalancerClientTests {
|
||||
assertThat(new String(actualRequest.body())).isEqualTo("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExecuteLoadBalancerLifecycleCallbacks() throws IOException {
|
||||
Request request = testRequest();
|
||||
Request.Options options = new Request.Options();
|
||||
String url = "http://127.0.0.1/path";
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("test-1", "test", "test-host", 8888, false);
|
||||
when(loadBalancerClient.choose(eq("test"), any())).thenReturn(serviceInstance);
|
||||
when(loadBalancerClient.reconstructURI(serviceInstance, URI.create("http://test/path")))
|
||||
.thenReturn(URI.create(url));
|
||||
Response response = testResponse(200);
|
||||
when(delegate.execute(any(), any())).thenReturn(response);
|
||||
String callbackTestHint = "callbackTestHint";
|
||||
properties.getHint().put("test", callbackTestHint);
|
||||
Map<String, LoadBalancerLifecycle> loadBalancerLifecycleBeans = new HashMap<>();
|
||||
loadBalancerLifecycleBeans.put("loadBalancerLifecycle", new TestLoadBalancerLifecycle());
|
||||
loadBalancerLifecycleBeans.put("anotherLoadBalancerLifecycle", new AnotherLoadBalancerLifecycle());
|
||||
when(loadBalancerClientFactory.getInstances("test", LoadBalancerLifecycle.class))
|
||||
.thenReturn(loadBalancerLifecycleBeans);
|
||||
|
||||
Object actualResult = feignBlockingLoadBalancerClient.execute(request, options);
|
||||
|
||||
Collection<org.springframework.cloud.client.loadbalancer.Request<Object>> lifecycleLogRequests = ((TestLoadBalancerLifecycle) loadBalancerLifecycleBeans
|
||||
.get("loadBalancerLifecycle")).getStartLog().values();
|
||||
Collection<CompletionContext<Object, ServiceInstance>> anotherLifecycleLogRequests = ((AnotherLoadBalancerLifecycle) loadBalancerLifecycleBeans
|
||||
.get("anotherLoadBalancerLifecycle")).getCompleteLog().values();
|
||||
assertThat(lifecycleLogRequests)
|
||||
.extracting(lbRequest -> ((DefaultRequestContext) lbRequest.getContext()).getHint())
|
||||
.contains(callbackTestHint);
|
||||
assertThat(anotherLifecycleLogRequests).extracting(CompletionContext::getClientResponse).contains(actualResult);
|
||||
}
|
||||
|
||||
private Request testRequest() {
|
||||
return testRequest("test");
|
||||
}
|
||||
@@ -181,4 +220,44 @@ class RetryableFeignBlockingLoadBalancerClientTests {
|
||||
|
||||
}
|
||||
|
||||
protected static class TestLoadBalancerLifecycle implements LoadBalancerLifecycle<Object, Object, ServiceInstance> {
|
||||
|
||||
final ConcurrentHashMap<String, org.springframework.cloud.client.loadbalancer.Request<Object>> startLog = new ConcurrentHashMap<>();
|
||||
|
||||
final ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> completeLog = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onStart(org.springframework.cloud.client.loadbalancer.Request<Object> request) {
|
||||
startLog.put(getName() + UUID.randomUUID(), request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(CompletionContext<Object, ServiceInstance> completionContext) {
|
||||
completeLog.put(getName() + UUID.randomUUID(), completionContext);
|
||||
}
|
||||
|
||||
ConcurrentHashMap<String, org.springframework.cloud.client.loadbalancer.Request<Object>> getStartLog() {
|
||||
return startLog;
|
||||
}
|
||||
|
||||
ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> getCompleteLog() {
|
||||
return completeLog;
|
||||
}
|
||||
|
||||
protected String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class AnotherLoadBalancerLifecycle
|
||||
extends FeignBlockingLoadBalancerClientTests.TestLoadBalancerLifecycle {
|
||||
|
||||
@Override
|
||||
protected String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user