Ribbon retry works & fix Content-Length error.
This change requires setting `ribbon.http.client.enabled=true`. This change addresses issue #648 Getting Retry Working In RestTemplate. There is also a fix in place that addresses the issue #357 RestTemplate Error. fixes gh-648
This commit is contained in:
committed by
Spencer Gibb
parent
a3ab3259e3
commit
05351b7809
@@ -79,9 +79,6 @@ public class RibbonAutoConfiguration {
|
||||
@Autowired
|
||||
private SpringClientFactory springClientFactory;
|
||||
|
||||
@Autowired
|
||||
private LoadBalancerClient loadBalancerClient;
|
||||
|
||||
@Bean
|
||||
public RestTemplateCustomizer restTemplateCustomizer(
|
||||
final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
|
||||
@@ -95,8 +92,7 @@ public class RibbonAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
|
||||
return new RibbonClientHttpRequestFactory(this.springClientFactory,
|
||||
this.loadBalancerClient);
|
||||
return new RibbonClientHttpRequestFactory(this.springClientFactory);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.apache.http.client.params.ClientPNames;
|
||||
import org.apache.http.client.params.CookiePolicy;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -48,6 +50,9 @@ import com.netflix.servo.monitor.Monitors;
|
||||
import com.sun.jersey.api.client.Client;
|
||||
import com.sun.jersey.client.apache4.ApacheHttpClient4;
|
||||
|
||||
import static com.netflix.client.config.CommonClientConfigKey.DeploymentContextBasedVipAddresses;
|
||||
import static org.springframework.cloud.netflix.ribbon.RibbonProperyUtils.setRibbonProperty;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@@ -155,6 +160,11 @@ public class RibbonClientConfiguration {
|
||||
return new DefaultServerIntrospector();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void preprocess() {
|
||||
setRibbonProperty(name, DeploymentContextBasedVipAddresses.key(), name);
|
||||
}
|
||||
|
||||
static class OverrideRestClient extends RestClient {
|
||||
|
||||
private ServerIntrospector serverIntrospector;
|
||||
|
||||
@@ -19,16 +19,12 @@ package org.springframework.cloud.netflix.ribbon;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.RibbonServer;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.client.http.HttpRequest;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
|
||||
/**
|
||||
@@ -38,12 +34,8 @@ public class RibbonClientHttpRequestFactory implements ClientHttpRequestFactory
|
||||
|
||||
private final SpringClientFactory clientFactory;
|
||||
|
||||
private LoadBalancerClient loadBalancer;
|
||||
|
||||
public RibbonClientHttpRequestFactory(SpringClientFactory clientFactory,
|
||||
LoadBalancerClient loadBalancer) {
|
||||
public RibbonClientHttpRequestFactory(SpringClientFactory clientFactory) {
|
||||
this.clientFactory = clientFactory;
|
||||
this.loadBalancer = loadBalancer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,28 +47,11 @@ public class RibbonClientHttpRequestFactory implements ClientHttpRequestFactory
|
||||
throw new IOException(
|
||||
"Invalid hostname in the URI [" + originalUri.toASCIIString() + "]");
|
||||
}
|
||||
ServiceInstance instance = this.loadBalancer.choose(serviceId);
|
||||
if (instance == null) {
|
||||
throw new IllegalStateException("No instances available for " + serviceId);
|
||||
}
|
||||
URI uri = this.loadBalancer.reconstructURI(instance, originalUri);
|
||||
|
||||
IClientConfig clientConfig = this.clientFactory
|
||||
.getClientConfig(instance.getServiceId());
|
||||
RestClient client = this.clientFactory.getClient(instance.getServiceId(),
|
||||
RestClient.class);
|
||||
IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId);
|
||||
RestClient client = this.clientFactory.getClient(serviceId, RestClient.class);
|
||||
HttpRequest.Verb verb = HttpRequest.Verb.valueOf(httpMethod.name());
|
||||
RibbonLoadBalancerContext context = this.clientFactory
|
||||
.getLoadBalancerContext(serviceId);
|
||||
|
||||
Server server = null;
|
||||
if (instance instanceof RibbonServer) {
|
||||
server = ((RibbonServer) instance).getServer();
|
||||
}
|
||||
|
||||
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
|
||||
|
||||
return new RibbonHttpRequest(uri, verb, client, clientConfig, statsRecorder);
|
||||
return new RibbonHttpRequest(originalUri, verb, client, clientConfig);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,21 +16,22 @@
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.client.http.HttpRequest;
|
||||
import com.netflix.client.http.HttpResponse;
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.AbstractClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.AbstractClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.client.http.HttpRequest;
|
||||
import com.netflix.client.http.HttpResponse;
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@@ -42,16 +43,14 @@ public class RibbonHttpRequest extends AbstractClientHttpRequest {
|
||||
private HttpRequest.Verb verb;
|
||||
private RestClient client;
|
||||
private IClientConfig config;
|
||||
private RibbonStatsRecorder statsRecorder;
|
||||
private ByteArrayOutputStream outputStream = null;
|
||||
|
||||
public RibbonHttpRequest(URI uri, HttpRequest.Verb verb, RestClient client,
|
||||
IClientConfig config, RibbonStatsRecorder statsRecorder) {
|
||||
IClientConfig config) {
|
||||
this.uri = uri;
|
||||
this.verb = verb;
|
||||
this.client = client;
|
||||
this.config = config;
|
||||
this.statsRecorder = statsRecorder;
|
||||
this.builder = HttpRequest.newBuilder().uri(uri).verb(verb);
|
||||
}
|
||||
|
||||
@@ -83,24 +82,27 @@ public class RibbonHttpRequest extends AbstractClientHttpRequest {
|
||||
builder.entity(outputStream.toByteArray());
|
||||
}
|
||||
HttpRequest request = builder.build();
|
||||
HttpResponse response = client.execute(request, config);
|
||||
statsRecorder.recordStats(response);
|
||||
HttpResponse response = client.executeWithLoadBalancer(request, config);
|
||||
return new RibbonHttpResponse(response);
|
||||
} catch (Exception e) {
|
||||
statsRecorder.recordStats(e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addHeaders(HttpHeaders headers) {
|
||||
for (String name : headers.keySet()) {
|
||||
// apache http RequestContent pukes if there is a body and
|
||||
// the dynamic headers are already present
|
||||
if (!isDynamic(name)) {
|
||||
List<String> values = headers.get(name);
|
||||
for (String value : values) {
|
||||
builder.header(name, value);
|
||||
}
|
||||
// apache http RequestContent pukes if there is a body and
|
||||
// the dynamic headers are already present
|
||||
if (isDynamic(name) && outputStream != null) {
|
||||
continue;
|
||||
}
|
||||
//Don't add content-length if the output stream is null. The RibbonClient does this for us.
|
||||
if (name.equals("Content-Length") && outputStream == null) {
|
||||
continue;
|
||||
}
|
||||
List<String> values = headers.get(name);
|
||||
for (String value : values) {
|
||||
builder.header(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import com.netflix.config.ConfigurationManager;
|
||||
import com.netflix.config.DynamicPropertyFactory;
|
||||
import com.netflix.config.DynamicStringProperty;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class RibbonProperyUtils {
|
||||
|
||||
public static final String VALUE_NOT_SET = "__not__set__";
|
||||
|
||||
public static final String DEFAULT_NAMESPACE = "ribbon";
|
||||
|
||||
public static void setRibbonProperty(String serviceId, String suffix, String value) {
|
||||
// how to set the namespace properly?
|
||||
String key = getRibbonKey(serviceId, suffix);
|
||||
DynamicStringProperty property = getProperty(key);
|
||||
if (property.get().equals(VALUE_NOT_SET)) {
|
||||
ConfigurationManager.getConfigInstance().setProperty(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getRibbonKey(String serviceId, String suffix) {
|
||||
return serviceId + "." + DEFAULT_NAMESPACE + "." + suffix;
|
||||
}
|
||||
|
||||
public static DynamicStringProperty getProperty(String key) {
|
||||
return DynamicPropertyFactory.getInstance().getStringProperty(key, VALUE_NOT_SET);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
package org.springframework.cloud.netflix.resttemplate;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.netflix.client.RetryHandler;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.AvailabilityFilteringRule;
|
||||
import com.netflix.loadbalancer.BaseLoadBalancer;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.IPing;
|
||||
import com.netflix.loadbalancer.IRule;
|
||||
import com.netflix.loadbalancer.LoadBalancerBuilder;
|
||||
import com.netflix.loadbalancer.LoadBalancerStats;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.ServerList;
|
||||
import com.netflix.loadbalancer.ServerStats;
|
||||
import com.netflix.niws.client.http.HttpClientLoadBalancerErrorHandler;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = RestTemplateRetryTest.Application.class)
|
||||
@WebAppConfiguration
|
||||
@IntegrationTest({
|
||||
"server.port=0",
|
||||
"spring.application.name=resttemplatetest",
|
||||
"logging.level.org.springframework.cloud.netflix.resttemplate=DEBUG",
|
||||
"badClients.ribbon.MaxAutoRetries=0", "badClients.ribbon.ReadTimeout=200",
|
||||
"badClients.ribbon.MaxAutoRetriesNextServer=10",
|
||||
"badClients.ribbon.OkToRetryOnAllOperations=true", "ribbon.http.client.enabled" })
|
||||
@DirtiesContext
|
||||
public class RestTemplateRetryTest {
|
||||
|
||||
final private static Log logger = LogFactory.getLog(RestTemplateRetryTest.class);
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port = 0;
|
||||
|
||||
@Autowired
|
||||
private RestTemplate testClient;
|
||||
|
||||
public RestTemplateRetryTest() {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@RibbonClient(name = "badClients", configuration = LocalBadClientConfiguration.class)
|
||||
public static class Application {
|
||||
|
||||
private AtomicInteger hits = new AtomicInteger(1);
|
||||
private AtomicInteger retryHits = new AtomicInteger(1);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/ping")
|
||||
public int ping() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/good")
|
||||
public int good() {
|
||||
int lValue = hits.getAndIncrement();
|
||||
return lValue;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/timeout")
|
||||
public int timeout() throws Exception {
|
||||
int lValue = retryHits.getAndIncrement();
|
||||
|
||||
// Force the good server to have 2 consecutive errors a couple of times.
|
||||
if (lValue == 2 || lValue == 3 || lValue == 5 || lValue == 6) {
|
||||
Thread.sleep(500);
|
||||
}
|
||||
return lValue;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/null")
|
||||
public int isNull() throws Exception {
|
||||
throw new NullPointerException("Null");
|
||||
}
|
||||
|
||||
@LoadBalanced
|
||||
@Bean
|
||||
RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
// Force Ribbon configuration by making one call.
|
||||
testClient.getForObject("http://badClients/ping", Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullPointer() throws Exception {
|
||||
|
||||
LoadBalancerStats stats = LocalBadClientConfiguration.balancer
|
||||
.getLoadBalancerStats();
|
||||
ServerStats badServer1Stats = stats
|
||||
.getSingleServerStat(LocalBadClientConfiguration.badServer);
|
||||
ServerStats badServer2Stats = stats
|
||||
.getSingleServerStat(LocalBadClientConfiguration.badServer2);
|
||||
ServerStats goodServerStats = stats
|
||||
.getSingleServerStat(LocalBadClientConfiguration.goodServer);
|
||||
|
||||
badServer1Stats.clearSuccessiveConnectionFailureCount();
|
||||
badServer2Stats.clearSuccessiveConnectionFailureCount();
|
||||
long targetConnectionCount = goodServerStats.getTotalRequestsCount() + 10;
|
||||
|
||||
// A null pointer should NOT trigger a circuit breaker.
|
||||
for (int index = 0; index < 10; index++) {
|
||||
try {
|
||||
testClient.getForObject("http://badClients/null", Integer.class);
|
||||
}
|
||||
catch (Exception exception) {
|
||||
}
|
||||
}
|
||||
logServerStats(LocalBadClientConfiguration.badServer);
|
||||
logServerStats(LocalBadClientConfiguration.badServer2);
|
||||
logServerStats(LocalBadClientConfiguration.goodServer);
|
||||
|
||||
Assert.isTrue(badServer1Stats.isCircuitBreakerTripped());
|
||||
Assert.isTrue(badServer2Stats.isCircuitBreakerTripped());
|
||||
Assert.isTrue(goodServerStats.getTotalRequestsCount() == targetConnectionCount);
|
||||
|
||||
// Wait for any timeout thread to finish.
|
||||
|
||||
}
|
||||
|
||||
private void logServerStats(Server server) {
|
||||
LoadBalancerStats stats = LocalBadClientConfiguration.balancer
|
||||
.getLoadBalancerStats();
|
||||
ServerStats serverStats = stats.getSingleServerStat(server);
|
||||
logger.debug("Server : " + server.toString() + " : Total Count == "
|
||||
+ serverStats.getTotalRequestsCount() + ", Failure Count == "
|
||||
+ serverStats.getFailureCount() + ", Successive Connection Failure == "
|
||||
+ serverStats.getSuccessiveConnectionFailureCount()
|
||||
+ ", Circuit Breaker ? == " + serverStats.isCircuitBreakerTripped());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestRetries() {
|
||||
|
||||
LoadBalancerStats stats = LocalBadClientConfiguration.balancer
|
||||
.getLoadBalancerStats();
|
||||
ServerStats badServer1Stats = stats
|
||||
.getSingleServerStat(LocalBadClientConfiguration.badServer);
|
||||
ServerStats badServer2Stats = stats
|
||||
.getSingleServerStat(LocalBadClientConfiguration.badServer2);
|
||||
ServerStats goodServerStats = stats
|
||||
.getSingleServerStat(LocalBadClientConfiguration.goodServer);
|
||||
|
||||
badServer1Stats.clearSuccessiveConnectionFailureCount();
|
||||
badServer2Stats.clearSuccessiveConnectionFailureCount();
|
||||
long targetConnectionCount = goodServerStats.getTotalRequestsCount() + 20;
|
||||
|
||||
int hits = 0;
|
||||
|
||||
for (int index = 0; index < 20; index++) {
|
||||
hits = testClient.getForObject("http://badClients/good", Integer.class);
|
||||
}
|
||||
|
||||
logServerStats(LocalBadClientConfiguration.badServer);
|
||||
logServerStats(LocalBadClientConfiguration.badServer2);
|
||||
logServerStats(LocalBadClientConfiguration.goodServer);
|
||||
|
||||
Assert.isTrue(badServer1Stats.isCircuitBreakerTripped());
|
||||
Assert.isTrue(badServer2Stats.isCircuitBreakerTripped());
|
||||
Assert.isTrue(goodServerStats.getTotalRequestsCount() == targetConnectionCount);
|
||||
Assert.isTrue(hits == 20);
|
||||
System.out.println("Retry Hits: " + hits);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestRetriesWithReadTimeout() throws Exception {
|
||||
|
||||
LoadBalancerStats stats = LocalBadClientConfiguration.balancer
|
||||
.getLoadBalancerStats();
|
||||
ServerStats badServer1Stats = stats
|
||||
.getSingleServerStat(LocalBadClientConfiguration.badServer);
|
||||
ServerStats badServer2Stats = stats
|
||||
.getSingleServerStat(LocalBadClientConfiguration.badServer2);
|
||||
ServerStats goodServerStats = stats
|
||||
.getSingleServerStat(LocalBadClientConfiguration.goodServer);
|
||||
|
||||
badServer1Stats.clearSuccessiveConnectionFailureCount();
|
||||
badServer2Stats.clearSuccessiveConnectionFailureCount();
|
||||
Assert.isTrue(!badServer1Stats.isCircuitBreakerTripped());
|
||||
Assert.isTrue(!badServer2Stats.isCircuitBreakerTripped());
|
||||
|
||||
int hits = 0;
|
||||
|
||||
for (int index = 0; index < 15; index++) {
|
||||
hits = testClient.getForObject("http://badClients/timeout", Integer.class);
|
||||
}
|
||||
logServerStats(LocalBadClientConfiguration.badServer);
|
||||
logServerStats(LocalBadClientConfiguration.badServer2);
|
||||
logServerStats(LocalBadClientConfiguration.goodServer);
|
||||
|
||||
Assert.isTrue(badServer1Stats.isCircuitBreakerTripped());
|
||||
Assert.isTrue(badServer2Stats.isCircuitBreakerTripped());
|
||||
Assert.isTrue(!goodServerStats.isCircuitBreakerTripped());
|
||||
|
||||
// 15 + 4 timeouts. See the endpoint for timeout conditions.
|
||||
Assert.isTrue(hits == 19);
|
||||
|
||||
// Wait for any timeout thread to finish.
|
||||
Thread.sleep(600);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Load balancer with fixed server list for "local" pointing to localhost
|
||||
// and some bogus servers are thrown in to test retry
|
||||
@Configuration
|
||||
class LocalBadClientConfiguration {
|
||||
|
||||
static BaseLoadBalancer balancer;
|
||||
static Server goodServer;
|
||||
static Server badServer;
|
||||
static Server badServer2;
|
||||
|
||||
public LocalBadClientConfiguration() {
|
||||
}
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port = 0;
|
||||
|
||||
@Bean
|
||||
public IRule loadBalancerRule() {
|
||||
// This is a good place to try different load balancing rules and how those rules
|
||||
// behave in failure
|
||||
// states: BestAvailableRule, WeightedResponseTimeRule, etc
|
||||
|
||||
// This rule just uses a round robin and will skip servers that are in circuit
|
||||
// breaker state.
|
||||
return new AvailabilityFilteringRule();
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
|
||||
ServerList<Server> serverList, IRule rule, IPing ping) {
|
||||
|
||||
goodServer = new Server("localhost", this.port);
|
||||
badServer = new Server("mybadhost", 10001);
|
||||
badServer2 = new Server("localhost", -1);
|
||||
|
||||
balancer = LoadBalancerBuilder
|
||||
.newBuilder()
|
||||
.withClientConfig(config)
|
||||
.withRule(rule)
|
||||
.withPing(ping)
|
||||
.buildFixedServerListLoadBalancer(
|
||||
Arrays.asList(badServer, badServer2, goodServer));
|
||||
return balancer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RetryHandler retryHandler() {
|
||||
return new OverrideRetryHandler();
|
||||
}
|
||||
|
||||
static class OverrideRetryHandler extends HttpClientLoadBalancerErrorHandler {
|
||||
public OverrideRetryHandler() {
|
||||
circuitRelated.add(UnknownHostException.class);
|
||||
retriable.add(UnknownHostException.class);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,13 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.eureka;
|
||||
|
||||
import static com.netflix.client.config.CommonClientConfigKey.DeploymentContextBasedVipAddresses;
|
||||
import static com.netflix.client.config.CommonClientConfigKey.EnableZoneAffinity;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@@ -35,14 +30,18 @@ import com.netflix.appinfo.EurekaInstanceConfig;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.config.ConfigurationManager;
|
||||
import com.netflix.config.DeploymentContext.ContextKey;
|
||||
import com.netflix.config.DynamicPropertyFactory;
|
||||
import com.netflix.config.DynamicStringProperty;
|
||||
import com.netflix.discovery.EurekaClientConfig;
|
||||
import com.netflix.loadbalancer.IPing;
|
||||
import com.netflix.loadbalancer.ServerList;
|
||||
import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList;
|
||||
import com.netflix.niws.loadbalancer.NIWSDiscoveryPing;
|
||||
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
import static com.netflix.client.config.CommonClientConfigKey.DeploymentContextBasedVipAddresses;
|
||||
import static com.netflix.client.config.CommonClientConfigKey.EnableZoneAffinity;
|
||||
import static org.springframework.cloud.netflix.ribbon.RibbonProperyUtils.setRibbonProperty;
|
||||
|
||||
/**
|
||||
* Preprocessor that configures defaults for eureka-discovered ribbon clients. Such as:
|
||||
* <code>@zone</code>, NIWSServerListClassName, DeploymentContextBasedVipAddresses,
|
||||
@@ -62,10 +61,6 @@ public class EurekaRibbonClientConfiguration {
|
||||
@Value("${ribbon.client.name}")
|
||||
private String serviceId = "client";
|
||||
|
||||
protected static final String VALUE_NOT_SET = "__not__set__";
|
||||
|
||||
protected static final String DEFAULT_NAMESPACE = "ribbon";
|
||||
|
||||
@Autowired(required = false)
|
||||
private EurekaClientConfig clientConfig;
|
||||
|
||||
@@ -132,25 +127,8 @@ public class EurekaRibbonClientConfiguration {
|
||||
}
|
||||
}
|
||||
}
|
||||
setProp(this.serviceId, DeploymentContextBasedVipAddresses.key(), this.serviceId);
|
||||
setProp(this.serviceId, EnableZoneAffinity.key(), "true");
|
||||
}
|
||||
|
||||
protected void setProp(String serviceId, String suffix, String value) {
|
||||
// how to set the namespace properly?
|
||||
String key = getKey(serviceId, suffix);
|
||||
DynamicStringProperty property = getProperty(key);
|
||||
if (property.get().equals(VALUE_NOT_SET)) {
|
||||
ConfigurationManager.getConfigInstance().setProperty(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
protected DynamicStringProperty getProperty(String key) {
|
||||
return DynamicPropertyFactory.getInstance().getStringProperty(key, VALUE_NOT_SET);
|
||||
}
|
||||
|
||||
protected String getKey(String serviceId, String suffix) {
|
||||
return serviceId + "." + DEFAULT_NAMESPACE + "." + suffix;
|
||||
setRibbonProperty(this.serviceId, DeploymentContextBasedVipAddresses.key(), this.serviceId);
|
||||
setRibbonProperty(this.serviceId, EnableZoneAffinity.key(), "true");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.cloud.netflix.ribbon.eureka.EurekaRibbonClientConfiguration.VALUE_NOT_SET;
|
||||
|
||||
import static org.springframework.cloud.netflix.ribbon.RibbonProperyUtils.*;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
@@ -82,12 +83,12 @@ public class EurekaRibbonClientConfigurationTests {
|
||||
String serviceId = "myService";
|
||||
String suffix = "mySuffix";
|
||||
String value = "myValue";
|
||||
DynamicStringProperty property = preprocessor.getProperty(preprocessor.getKey(
|
||||
DynamicStringProperty property = getProperty(getRibbonKey(
|
||||
serviceId, suffix));
|
||||
assertEquals("property doesn't have default value", VALUE_NOT_SET, property.get());
|
||||
preprocessor.setProp(serviceId, suffix, value);
|
||||
setRibbonProperty(serviceId, suffix, value);
|
||||
assertEquals("property has wrong value", value, property.get());
|
||||
preprocessor.setProp(serviceId, suffix, value);
|
||||
setRibbonProperty(serviceId, suffix, value);
|
||||
assertEquals("property has wrong value", value, property.get());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user