Merge branch 'master' into 2.0.x

This commit is contained in:
Spencer Gibb
2017-11-21 19:27:49 -05:00
34 changed files with 1030 additions and 144 deletions

View File

@@ -1,3 +1,3 @@
releaser:
maven:
buildCommand: ./scripts/build.sh
buildCommand: ./scripts/build.sh {{systemProps}}

View File

@@ -669,8 +669,9 @@ in order for the Hystrix Dashboard to make a successful connection to the stream
Looking at an individual instances Hystrix data is not very useful in terms of the overall health of the system. https://github.com/Netflix/Turbine[Turbine] is an application that aggregates all of the relevant `/hystrix.stream` endpoints into a combined `/turbine.stream` for use in the Hystrix Dashboard. Individual instances are located via Eureka. Running Turbine is as simple as annotating your main class with the `@EnableTurbine` annotation (e.g. using spring-cloud-starter-netflix-turbine to set up the classpath). All of the documented configuration properties from https://github.com/Netflix/Turbine/wiki/Configuration-(1.x)[the Turbine 1 wiki] apply. The only difference is that the `turbine.instanceUrlSuffix` does not need the port prepended as this is handled automatically unless `turbine.instanceInsertPort=false`.
NOTE: By default, Turbine looks for the `/hystrix.stream` endpoint on a registered instance by looking up its `homePageUrl` entry in Eureka, then appending `/hystrix.stream` to it. This means that if `spring-boot-actuator` is running on its own port (which is the default), the call to `/hystrix.stream` will fail.
To make turbine find the Hystrix stream at the correct port, you need to add `management.port` to the instances' metadata:
NOTE: By default, Turbine looks for the `/hystrix.stream` endpoint on a registered instance by looking up its `hostName` and `port` entries in Eureka, then appending `/hystrix.stream` to it.
If the instance's metadata contains `management.port`, it will be used instead of the `port` value for the `/hystrix.stream` endpoint.
By default, metadata entry `management.port` is equal to the `management.port` configuration property, it can be overridden though with following configuration:
----
eureka:
instance:
@@ -955,7 +956,33 @@ zuul:
threadPoolKeyPrefix: zuulgw
----
[[how-to-provdie-a-key-to-ribbon]]
=== How to Provide a Key to Ribbon's `IRule`
If you need to provide your own `IRule` implementation to handle a special routing requirement like a canary test,
you probably want to pass some information to the `choose` method of `IRule`.
.com.netflix.loadbalancer.IRule.java
----
public interface IRule{
public Server choose(Object key);
:
----
You can provide some information that will be used to choose a target server by your `IRule` implementation like
the following:
----
RequestContext.getCurrentContext()
.set(FilterConstants.LOAD_BALANCER_KEY, "canary-test");
----
If you put any object into the `RequestContext` with a key `FilterConstants.LOAD_BALANCER_KEY`, it will
be passed to the `choose` method of `IRule` implementation. Above code must be executed before `RibbonRoutingFilter`
is executed and Zuul's pre filter is the best place to do that. You can easily access HTTP headers and query parameters
via `RequestContext` in pre filter, so it can be used to determine `LOAD_BALANCER_KEY` that will be passed to Ribbon.
If you don't put any value with `LOAD_BALANCER_KEY` in `RequestContext`, null will be passed as a parameter of `choose`
method.
[[spring-cloud-feign]]
== Declarative REST Client: Feign
@@ -1247,7 +1274,7 @@ protected interface HystrixClient {
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
@Override
public HystrixClient create(Throwable cause) {
return new HystrixClientWithFallBackFactory() {
return new HystrixClient() {
@Override
public Hello iFailSometimes() {
return new Hello("fallback; reason was: " + cause.getMessage());
@@ -2086,6 +2113,17 @@ class MyFallbackProvider implements FallbackProvider {
}
----
=== Zuul Timeouts
If you want to configure the socket timeouts and read timeouts for requests proxied through
Zuul there are two options based on your configuration.
If Zuul is using service discovery than you need to configure these timeouts via Ribbon properties,
`ribbon.ReadTimeout` and `ribbon.SocketTimeout`.
If you have configured Zuul routes by specifying URLs than you will need to use
`zuul.host.connect-timeout-millis` and `zuul.host.socket-timeout-millis`.
[[zuul-redirect-location-rewrite]]
=== Rewriting `Location` header
@@ -2694,6 +2732,27 @@ https://github.com/spring-projects/spring-retry[Spring Retry] on your applicatio
When Spring Retry is present load balanced `RestTemplates`, Feign, and Zuul will automatically
retry any failed requests (assuming you configuration allows it to).
==== BackOff Policies
By default no backoff policy is used when retrying requests. If you would like to configure
a backoff policy you will need to create a bean of type `LoadBalancedBackOffPolicyFactory`
which will be used to create a `BackOffPolicy` for a given service.
[source,java,indent=0]
----
@Configuration
public class MyConfiguration {
@Bean
LoadBalancedBackOffPolicyFactory backOffPolciyFactory() {
return new LoadBalancedBackOffPolicyFactory() {
@Override
public BackOffPolicy createBackOffPolicy(String service) {
return new ExponentialBackOffPolicy();
}
};
}
}
----
==== Configuration
Anytime Ribbon is used with Spring Retry you can control the retry functionality by configuring

View File

@@ -1,4 +1,4 @@
#!/bin/bash
(cd spring-cloud-netflix-hystrix-contract && ../mvnw clean install)
./mvnw clean install
(cd spring-cloud-netflix-hystrix-contract && ../mvnw clean install -B -Pdocs ${@})
./mvnw clean install -B -Pdocs ${@}

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.netflix.feign.ribbon;
import java.util.Map;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
@@ -32,11 +33,13 @@ import com.netflix.loadbalancer.ILoadBalancer;
*
* @author Spencer Gibb
* @author Dave Syer
* @author Ryan Baxter
*/
public class CachingSpringLoadBalancerFactory {
private final SpringClientFactory factory;
private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
private final LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory;
private boolean enableRetry = false;
private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
@@ -44,19 +47,35 @@ public class CachingSpringLoadBalancerFactory {
public CachingSpringLoadBalancerFactory(SpringClientFactory factory) {
this.factory = factory;
this.loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(factory);
this.loadBalancedBackOffPolicyFactory = null;
}
@Deprecated
//TODO remove in 2.0.x
public CachingSpringLoadBalancerFactory(SpringClientFactory factory,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
this.factory = factory;
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.loadBalancedBackOffPolicyFactory = null;
}
@Deprecated
//TODO remove in 2.0.0x
public CachingSpringLoadBalancerFactory(SpringClientFactory factory,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, boolean enableRetry) {
this.factory = factory;
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.enableRetry = enableRetry;
this.loadBalancedBackOffPolicyFactory = null;
}
public CachingSpringLoadBalancerFactory(SpringClientFactory factory,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
this.factory = factory;
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
this.enableRetry = true;
}
public FeignLoadBalancer create(String clientName) {
@@ -67,7 +86,7 @@ public class CachingSpringLoadBalancerFactory {
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
loadBalancedRetryPolicyFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}

View File

@@ -21,6 +21,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.feign.FeignAutoConfiguration;
import org.springframework.cloud.netflix.feign.support.FeignHttpClientProperties;
@@ -65,8 +66,9 @@ public class FeignRibbonClientAutoConfiguration {
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
SpringClientFactory factory,
LoadBalancedRetryPolicyFactory retryPolicyFactory) {
return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, true);
LoadBalancedRetryPolicyFactory retryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, loadBalancedBackOffPolicyFactory);
}
@Bean

View File

@@ -23,6 +23,7 @@ import feign.Response;
import java.io.IOException;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
@@ -32,6 +33,8 @@ import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import com.netflix.client.DefaultLoadBalancerRetryHandler;
@@ -48,12 +51,26 @@ import com.netflix.loadbalancer.Server;
public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements ServiceInstanceChooser {
private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
private final LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory;
@Deprecated
//TODO remove in 2.0.x
public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
super(lb, clientConfig, serverIntrospector);
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
this.loadBalancedBackOffPolicyFactory = new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
}
public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
super(lb, clientConfig, serverIntrospector);
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory == null ?
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory() : loadBalancedBackOffPolicyFactory;
}
@Override
@@ -72,6 +89,8 @@ public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements Ser
}
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
RetryTemplate retryTemplate = new RetryTemplate();
BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
: new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this, this.getClientName()));
return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() {

View File

@@ -35,6 +35,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
@@ -102,6 +104,13 @@ public class RibbonAutoConfiguration {
return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
}
@Bean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnMissingBean
public LoadBalancedBackOffPolicyFactory loadBalancedBackoffPolicyFactory() {
return new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
}
@Bean
@ConditionalOnMissingBean
public PropertiesFactory propertiesFactory() {

View File

@@ -31,6 +31,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
@@ -151,9 +152,11 @@ public class HttpClientRibbonConfiguration {
public RetryableRibbonLoadBalancingHttpClient retryableRibbonLoadBalancingHttpClient(
IClientConfig config, ServerIntrospector serverIntrospector,
ILoadBalancer loadBalancer, RetryHandler retryHandler,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, CloseableHttpClient httpClient) {
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, CloseableHttpClient httpClient,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(
httpClient, config, serverIntrospector, loadBalancedRetryPolicyFactory);
httpClient, config, serverIntrospector, loadBalancedRetryPolicyFactory,
loadBalancedBackOffPolicyFactory);
client.setLoadBalancer(loadBalancer);
client.setRetryHandler(retryHandler);
Monitors.registerObject("Client_" + this.name, client);

View File

@@ -24,6 +24,7 @@ import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
@@ -35,6 +36,8 @@ import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.http.HttpRequest;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.util.UriComponentsBuilder;
@@ -51,7 +54,11 @@ import com.netflix.loadbalancer.Server;
public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingHttpClient
implements ServiceInstanceChooser {
private LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory =
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
@Deprecated
//TODO remove in 2.0.x
public RetryableRibbonLoadBalancingHttpClient(IClientConfig config,
ServerIntrospector serverIntrospector,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
@@ -59,6 +66,8 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
}
@Deprecated
//TODO remove in 2.0.x
public RetryableRibbonLoadBalancingHttpClient(CloseableHttpClient delegate,
IClientConfig config, ServerIntrospector serverIntrospector,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
@@ -66,6 +75,15 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
}
public RetryableRibbonLoadBalancingHttpClient(CloseableHttpClient delegate,
IClientConfig config, ServerIntrospector serverIntrospector,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
super(delegate, config, serverIntrospector);
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
}
@Override
public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request,
final IClientConfig configOverride) throws Exception {
@@ -137,6 +155,8 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
retryTemplate.setRetryPolicy(retryPolicy == null || !retryable
? new NeverRetryPolicy()
: new RetryPolicy(request, retryPolicy, this, this.getClientName()));
BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
return retryTemplate.execute(callback);
}

View File

@@ -34,6 +34,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
@@ -115,9 +116,10 @@ public class OkHttpRibbonConfiguration {
ILoadBalancer loadBalancer,
RetryHandler retryHandler,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
OkHttpClient delegate) {
OkHttpClient delegate,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
RetryableOkHttpLoadBalancingClient client = new RetryableOkHttpLoadBalancingClient(delegate, config,
serverIntrospector, loadBalancedRetryPolicyFactory);
serverIntrospector, loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory);
client.setLoadBalancer(loadBalancer);
client.setRetryHandler(retryHandler);
Monitors.registerObject("Client_" + this.name, client);

View File

@@ -22,6 +22,7 @@ import okhttp3.Response;
import java.net.URI;
import org.apache.commons.lang.BooleanUtils;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
@@ -33,6 +34,8 @@ import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.http.HttpRequest;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.util.UriComponentsBuilder;
@@ -48,17 +51,31 @@ import com.netflix.loadbalancer.Server;
public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClient implements ServiceInstanceChooser {
private LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory =
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
@Deprecated
//TODO remove in 2.0.x
public RetryableOkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
super(delegate, config, serverIntrospector);
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
}
public RetryableOkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
super(delegate, config, serverIntrospector);
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
}
private OkHttpRibbonResponse executeWithRetry(OkHttpRibbonRequest request, LoadBalancedRetryPolicy retryPolicy,
RetryCallback<OkHttpRibbonResponse, Exception> callback)
throws Exception {
RetryTemplate retryTemplate = new RetryTemplate();
BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
boolean retryable = request.getContext() == null ? true :
BooleanUtils.toBooleanDefaultIfNull(request.getContext().getRetryable(), true);
retryTemplate.setRetryPolicy(retryPolicy == null || !retryable ? new NeverRetryPolicy()

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.netflix.ribbon.support;
import com.netflix.loadbalancer.reactive.LoadBalancerCommand;
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
@@ -140,4 +141,11 @@ public abstract class AbstractLoadBalancingClient<S extends ContextAwareRequest,
}
return this.secure;
}
@Override
protected void customizeLoadBalancerCommandBuilder(S request, IClientConfig config, LoadBalancerCommand.Builder<T> builder) {
if (request.getLoadBalancerKey() != null) {
builder.withServerLocator(request.getLoadBalancerKey());
}
}
}

View File

@@ -43,6 +43,7 @@ public abstract class ContextAwareRequest extends ClientRequest implements HttpR
}
this.uri = context.uri();
this.isRetriable = context.getRetryable();
this.loadBalancerKey = context.getLoadBalancerKey();
}
public RibbonCommandContext getContext() {
@@ -73,7 +74,8 @@ public abstract class ContextAwareRequest extends ClientRequest implements HttpR
RibbonCommandContext commandContext = new RibbonCommandContext(this.context.getServiceId(),
this.context.getMethod(), uri.toString(), this.context.getRetryable(),
this.context.getHeaders(), this.context.getParams(), this.context.getRequestEntity(),
this.context.getRequestCustomizers(), this.context.getContentLength());
this.context.getRequestCustomizers(), this.context.getContentLength(),
this.context.getLoadBalancerKey());
return commandContext;
}
}

View File

@@ -1,37 +0,0 @@
/*
*
* * Copyright 2013-2016 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
* *
* * http://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.ribbon.support;
import java.io.IOException;
/**
* Exception to be thrown when the status code is deemed to be retryable.
* @author Ryan Baxter
* @deprecated Use {@link org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException} instead
*/
//TODO Remove in Edgeware
@Deprecated
public class RetryableStatusCodeException extends IOException {
private static final String MESSAGE = "Service %s returned a status code of %d";
public RetryableStatusCodeException(String serviceId, int statusCode) {
super(String.format(MESSAGE, serviceId, statusCode));
}
}

View File

@@ -30,6 +30,7 @@ import java.util.Objects;
/**
* @author Spencer Gibb
* @author Yongsung Yoon
*/
public class RibbonCommandContext {
private final String serviceId;
@@ -41,6 +42,7 @@ public class RibbonCommandContext {
private final List<RibbonRequestCustomizer> requestCustomizers;
private InputStream requestEntity;
private Long contentLength;
private Object loadBalancerKey;
/**
* Kept for backwards compatibility with Spring Cloud Sleuth 1.x versions
@@ -50,7 +52,7 @@ public class RibbonCommandContext {
String uri, Boolean retryable, MultiValueMap<String, String> headers,
MultiValueMap<String, String> params, InputStream requestEntity) {
this(serviceId, method, uri, retryable, headers, params, requestEntity,
new ArrayList<RibbonRequestCustomizer>(), null);
new ArrayList<RibbonRequestCustomizer>(), null, null);
}
public RibbonCommandContext(String serviceId, String method, String uri,
@@ -58,13 +60,22 @@ public class RibbonCommandContext {
MultiValueMap<String, String> params, InputStream requestEntity,
List<RibbonRequestCustomizer> requestCustomizers) {
this(serviceId, method, uri, retryable, headers, params, requestEntity,
requestCustomizers, null);
requestCustomizers, null, null);
}
public RibbonCommandContext(String serviceId, String method, String uri,
Boolean retryable, MultiValueMap<String, String> headers,
MultiValueMap<String, String> params, InputStream requestEntity,
List<RibbonRequestCustomizer> requestCustomizers, Long contentLength) {
this(serviceId, method, uri, retryable, headers, params, requestEntity,
requestCustomizers, contentLength, null);
}
public RibbonCommandContext(String serviceId, String method, String uri,
Boolean retryable, MultiValueMap<String, String> headers,
MultiValueMap<String, String> params, InputStream requestEntity,
List<RibbonRequestCustomizer> requestCustomizers, Long contentLength,
Object loadBalancerKey) {
Assert.notNull(serviceId, "serviceId may not be null");
Assert.notNull(method, "method may not be null");
Assert.notNull(uri, "uri may not be null");
@@ -80,6 +91,7 @@ public class RibbonCommandContext {
this.requestEntity = requestEntity;
this.requestCustomizers = requestCustomizers;
this.contentLength = contentLength;
this.loadBalancerKey = loadBalancerKey;
}
public URI uri() {
@@ -153,6 +165,14 @@ public class RibbonCommandContext {
this.contentLength = contentLength;
}
public Object getLoadBalancerKey() {
return loadBalancerKey;
}
public void setLoadBalancerKey(Object loadBalancerKey) {
this.loadBalancerKey = loadBalancerKey;
}
@Override
public boolean equals(Object o) {
if (this == o)
@@ -167,13 +187,14 @@ public class RibbonCommandContext {
.equals(params, that.params) && Objects
.equals(requestEntity, that.requestEntity) && Objects
.equals(requestCustomizers, that.requestCustomizers) && Objects
.equals(contentLength, that.contentLength);
.equals(contentLength, that.contentLength) && Objects
.equals(loadBalancerKey, that.loadBalancerKey);
}
@Override
public int hashCode() {
return Objects.hash(serviceId, method, uri, retryable, headers, params,
requestEntity, requestCustomizers, contentLength);
requestEntity, requestCustomizers, contentLength, loadBalancerKey);
}
@Override
@@ -188,6 +209,7 @@ public class RibbonCommandContext {
sb.append(", requestEntity=").append(requestEntity);
sb.append(", requestCustomizers=").append(requestCustomizers);
sb.append(", contentLength=").append(contentLength);
sb.append(", loadBalancerKey=").append(loadBalancerKey);
sb.append('}');
return sb.toString();
}

View File

@@ -32,6 +32,7 @@ import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
@@ -42,6 +43,10 @@ import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerContext;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.http.HttpRequest;
import org.springframework.retry.RetryContext;
import org.springframework.retry.backoff.BackOffContext;
import org.springframework.retry.backoff.BackOffInterruptedException;
import org.springframework.retry.backoff.BackOffPolicy;
import com.netflix.client.RequestSpecificRetryHandler;
import com.netflix.client.config.CommonClientConfigKey;
@@ -79,6 +84,8 @@ public class RetryableFeignLoadBalancerTests {
@Mock
private IClientConfig config;
private ServerIntrospector inspector = new DefaultServerIntrospector();
private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory =
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
private Integer defaultConnectTimeout = 10000;
private Integer defaultReadTimeout = 10000;
@@ -117,7 +124,8 @@ public class RetryableFeignLoadBalancerTests {
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://foo"));
Response response = Response.builder().status(200).headers(new HashMap<String, Collection<String>>()).build();
doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class));
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory);
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory,
loadBalancedBackOffPolicyFactory);
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
assertEquals(200, ribbonResponse.toResponse().status());
verify(client, times(1)).execute(any(Request.class), any(Request.Options.class));
@@ -136,7 +144,7 @@ public class RetryableFeignLoadBalancerTests {
public LoadBalancedRetryPolicy create(String s, ServiceInstanceChooser serviceInstanceChooser) {
return null;
}
});
}, loadBalancedBackOffPolicyFactory);
try {
feignLb.execute(request, null);
} catch(Exception e) {
@@ -167,10 +175,13 @@ public class RetryableFeignLoadBalancerTests {
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://foo"));
Response response = Response.builder().status(200).headers(new HashMap<String, Collection<String>>()).build();
doThrow(new IOException("boom")).doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class));
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory);
MyBackOffPolicyFactory backOffPolicyFactory = new MyBackOffPolicyFactory();
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory,
backOffPolicyFactory);
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
assertEquals(200, ribbonResponse.toResponse().status());
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
assertEquals(1, backOffPolicyFactory.getCount());
}
@Test
@@ -195,10 +206,13 @@ public class RetryableFeignLoadBalancerTests {
Response response = Response.builder().status(200).headers(new HashMap<String, Collection<String>>()).build();
Response fourOFourResponse = Response.builder().status(404).headers(new HashMap<String, Collection<String>>()).build();
doReturn(fourOFourResponse).doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class));
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory);
MyBackOffPolicyFactory backOffPolicyFactory = new MyBackOffPolicyFactory();
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory,
backOffPolicyFactory);
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
assertEquals(200, ribbonResponse.toResponse().status());
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
assertEquals(1, backOffPolicyFactory.getCount());
}
@Test
@@ -214,7 +228,8 @@ public class RetryableFeignLoadBalancerTests {
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://foo"));
Response response = Response.builder().status(200).headers(new HashMap<String, Collection<String>>()).build();
doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class));
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory);
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory,
loadBalancedBackOffPolicyFactory);
RequestSpecificRetryHandler retryHandler = feignLb.getRequestSpecificRetryHandler(request, config);
assertEquals(1, retryHandler.getMaxRetriesOnNextServer());
assertEquals(1, retryHandler.getMaxRetriesOnSameServer());
@@ -265,11 +280,35 @@ public class RetryableFeignLoadBalancerTests {
public List<Server> getAllServers() {
return null;
}
}, config, inspector, loadBalancedRetryPolicyFactory);
}, config, inspector, loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory);
ServiceInstance serviceInstance = feignLb.choose("foo");
assertEquals("foo", serviceInstance.getHost());
assertEquals(80, serviceInstance.getPort());
}
class MyBackOffPolicyFactory implements LoadBalancedBackOffPolicyFactory, BackOffPolicy {
private int count = 0;
@Override
public BackOffContext start(RetryContext retryContext) {
return null;
}
@Override
public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
count++;
}
public int getCount() {
return count;
}
@Override
public BackOffPolicy createBackOffPolicy(String service) {
return this;
}
}
}

View File

@@ -31,6 +31,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.commons.httpclient.HttpClientConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
@@ -43,6 +44,10 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.retry.RetryContext;
import org.springframework.retry.backoff.BackOffContext;
import org.springframework.retry.backoff.BackOffInterruptedException;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.test.util.ReflectionTestUtils;
import com.netflix.client.DefaultLoadBalancerRetryHandler;
@@ -55,6 +60,7 @@ import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.BDDMockito.given;
@@ -74,6 +80,7 @@ import static org.mockito.Mockito.when;
public class RibbonLoadBalancingHttpClientTests {
private ILoadBalancer loadBalancer;
private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory = new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
@Before
public void setup() {
@@ -198,9 +205,10 @@ public class RibbonLoadBalancingHttpClientTests {
}
private RetryableRibbonLoadBalancingHttpClient setupClientForRetry(int retriesNextServer, int retriesSameServer,
boolean retryable, boolean retryOnAllOps,
String serviceName, String host, int port,
HttpClient delegate, ILoadBalancer lb, String statusCodes) throws Exception {
boolean retryable, boolean retryOnAllOps,
String serviceName, String host, int port,
CloseableHttpClient delegate, ILoadBalancer lb, String statusCodes,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) throws Exception {
ServerIntrospector introspector = mock(ServerIntrospector.class);
RetryHandler retryHandler = new DefaultLoadBalancerRetryHandler(retriesSameServer, retriesNextServer, retryable);
doReturn(new Server(host, port)).when(lb).chooseServer(eq(serviceName));
@@ -215,7 +223,8 @@ public class RibbonLoadBalancingHttpClientTests {
doReturn(context).when(clientFactory).getLoadBalancerContext(eq(serviceName));
doReturn(clientConfig).when(clientFactory).getClientConfig(eq(serviceName));
LoadBalancedRetryPolicyFactory factory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(clientConfig, introspector, factory);
RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(delegate, clientConfig,
introspector, factory, loadBalancedBackOffPolicyFactory);
client.setLoadBalancer(lb);
ReflectionTestUtils.setField(client, "delegate", delegate);
return client;
@@ -240,7 +249,7 @@ public class RibbonLoadBalancingHttpClientTests {
doThrow(new IOException("boom")).doReturn(response).when(delegate).execute(any(HttpUriRequest.class));
ILoadBalancer lb = mock(ILoadBalancer.class);
RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps,
serviceName, host, port, delegate, lb, "");
serviceName, host, port, delegate, lb, "", loadBalancedBackOffPolicyFactory);
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
doReturn(uri).when(request).getURI();
doReturn(method).when(request).getMethod();
@@ -272,8 +281,9 @@ public class RibbonLoadBalancingHttpClientTests {
doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response).
when(delegate).execute(any(HttpUriRequest.class));
ILoadBalancer lb = mock(ILoadBalancer.class);
MyBackOffPolicyFactory myBackOffPolicyFactory = new MyBackOffPolicyFactory();
RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps,
serviceName, host, port, delegate, lb, "");
serviceName, host, port, delegate, lb, "", myBackOffPolicyFactory);
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
doReturn(uri).when(request).getURI();
doReturn(method).when(request).getMethod();
@@ -284,6 +294,7 @@ public class RibbonLoadBalancingHttpClientTests {
RibbonApacheHttpResponse returnedResponse = client.execute(request, null);
verify(delegate, times(3)).execute(any(HttpUriRequest.class));
verify(lb, times(1)).chooseServer(eq(serviceName));
assertEquals(2, myBackOffPolicyFactory.getCount());
}
@Test
@@ -305,8 +316,9 @@ public class RibbonLoadBalancingHttpClientTests {
doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response).
when(delegate).execute(any(HttpUriRequest.class));
ILoadBalancer lb = mock(ILoadBalancer.class);
MyBackOffPolicyFactory myBackOffPolicyFactory = new MyBackOffPolicyFactory();
RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps,
serviceName, host, port, delegate, lb, "");
serviceName, host, port, delegate, lb, "", myBackOffPolicyFactory);
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
doReturn(method).when(request).getMethod();
doReturn(uri).when(request).getURI();
@@ -317,6 +329,7 @@ public class RibbonLoadBalancingHttpClientTests {
verify(response, times(0)).close();
verify(delegate, times(3)).execute(any(HttpUriRequest.class));
verify(lb, times(1)).chooseServer(eq(serviceName));
assertEquals(2, myBackOffPolicyFactory.getCount());
}
@Test
@@ -336,7 +349,7 @@ public class RibbonLoadBalancingHttpClientTests {
when(delegate).execute(any(HttpUriRequest.class));
ILoadBalancer lb = mock(ILoadBalancer.class);
RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps,
serviceName, host, port, delegate, lb, "");
serviceName, host, port, delegate, lb, "", loadBalancedBackOffPolicyFactory);
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
doReturn(method).when(request).getMethod();
doReturn(uri).when(request).getURI();
@@ -376,8 +389,9 @@ public class RibbonLoadBalancingHttpClientTests {
doReturn(fourOFourStatusLine).when(fourOFourResponse).getStatusLine();
doReturn(fourOFourResponse).doReturn(response).when(delegate).execute(any(HttpUriRequest.class));
ILoadBalancer lb = mock(ILoadBalancer.class);
MyBackOffPolicyFactory myBackOffPolicyFactory = new MyBackOffPolicyFactory();
RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps,
serviceName, host, port, delegate, lb, "404");
serviceName, host, port, delegate, lb, "404", myBackOffPolicyFactory);
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
doReturn(uri).when(request).getURI();
doReturn(method).when(request).getMethod();
@@ -389,6 +403,7 @@ public class RibbonLoadBalancingHttpClientTests {
verify(fourOFourResponse, times(1)).close();
verify(delegate, times(2)).execute(any(HttpUriRequest.class));
verify(lb, times(0)).chooseServer(eq(serviceName));
assertEquals(1, myBackOffPolicyFactory.getCount());
}
@Configuration
@@ -480,4 +495,28 @@ public class RibbonLoadBalancingHttpClientTests {
return requestConfigCaptor.getValue();
}
class MyBackOffPolicyFactory implements LoadBalancedBackOffPolicyFactory, BackOffPolicy {
private int count = 0;
@Override
public BackOffContext start(RetryContext retryContext) {
return null;
}
@Override
public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
count++;
}
public int getCount() {
return count;
}
@Override
public BackOffPolicy createBackOffPolicy(String service) {
return this;
}
}
}

View File

@@ -56,6 +56,7 @@ public class ContextAwareRequestTests {
doReturn(new URI("http://foo")).when(context).uri();
doReturn("foo").when(context).getServiceId();
doReturn(new LinkedMultiValueMap<>()).when(context).getParams();
doReturn("testLoadBalancerKey").when(context).getLoadBalancerKey();
request = new TestContextAwareRequest(context);
}
@@ -97,6 +98,18 @@ public class ContextAwareRequestTests {
assertEquals(headers, request.getHeaders());
}
@Test
public void getLoadBalancerKey() throws Exception {
assertEquals("testLoadBalancerKey", request.getLoadBalancerKey());
RibbonCommandContext defaultContext = mock(RibbonCommandContext.class);
doReturn(new LinkedMultiValueMap()).when(defaultContext).getHeaders();
doReturn(null).when(defaultContext).getLoadBalancerKey();
ContextAwareRequest defaultRequest = new TestContextAwareRequest(defaultContext);
assertNull(defaultRequest.getLoadBalancerKey());
}
static class TestContextAwareRequest extends ContextAwareRequest {
public TestContextAwareRequest(RibbonCommandContext context) {

View File

@@ -1,36 +0,0 @@
/*
*
* * Copyright 2013-2016 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
* *
* * http://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.ribbon.support;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* @author Ryan Baxter
*/
public class RetryableStatusCodeExceptionTests {
@Test
public void testMessage() {
RetryableStatusCodeException ex = new RetryableStatusCodeException("foo", 404);
assertEquals("Service foo returned a status code of 404", ex.getMessage());
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.netflix.ribbon.support;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import org.junit.Test;
import org.springframework.http.HttpMethod;
@@ -28,6 +29,7 @@ import com.google.common.collect.Lists;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import okhttp3.Request;
@@ -92,4 +94,18 @@ public class RibbonCommandContextTest {
new ByteArrayInputStream(TEST_CONTENT),
Lists.newArrayList(requestCustomizer));
}
@Test
public void testNullSafetyWithNullableParameters() throws Exception {
LinkedMultiValueMap headers = new LinkedMultiValueMap();
LinkedMultiValueMap params = new LinkedMultiValueMap();
RibbonCommandContext testContext = new RibbonCommandContext("serviceId",
HttpMethod.POST.toString(), "/my/route", true, headers, params,
new ByteArrayInputStream(TEST_CONTENT), Collections.<RibbonRequestCustomizer>emptyList(),
null, null);
assertNotEquals(0, testContext.hashCode());
assertNotNull(testContext.toString());
}
}

View File

@@ -18,7 +18,7 @@
<eureka.version>1.8.4</eureka.version>
<feign.version>9.5.1</feign.version>
<hystrix.version>1.5.12</hystrix.version>
<ribbon.version>2.2.2</ribbon.version>
<ribbon.version>2.2.4</ribbon.version>
<servo.version>0.10.1</servo.version>
<zuul.version>1.3.0</zuul.version>
<rxjava.version>1.2.0</rxjava.version>
@@ -301,6 +301,46 @@
<artifactId>feign-okhttp</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson-jaxb</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-java8</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jaxb</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jaxrs</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-ribbon</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-sax</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>

View File

@@ -12,7 +12,6 @@
* 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;
@@ -23,7 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -51,6 +50,9 @@ import org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfi
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration;
import org.springframework.cloud.netflix.eureka.metadata.DefaultManagementMetadataProvider;
import org.springframework.cloud.netflix.eureka.metadata.ManagementMetadata;
import org.springframework.cloud.netflix.eureka.metadata.ManagementMetadataProvider;
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration;
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration;
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry;
@@ -119,58 +121,76 @@ public class EurekaClientAutoConfiguration {
return client;
}
@Bean
@ConditionalOnMissingBean
public ManagementMetadataProvider serviceManagementMetadataProvider() {
return new DefaultManagementMetadataProvider();
}
private String getProperty(String property) {
return this.env.containsProperty(property) ? this.env.getProperty(property) : "";
}
@Bean
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils) {
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,
ManagementMetadataProvider managementMetadataProvider) {
String hostname = getProperty("eureka.instance.hostname");
boolean preferIpAddress = Boolean.parseBoolean(getProperty("eureka.instance.prefer-ip-address"));
boolean isSecurePortEnabled = Boolean.parseBoolean(getProperty("eureka.instance.secure-port-enabled"));
int nonSecurePort = Integer.valueOf(env.getProperty("server.port", env.getProperty("port", "8080")));
int managementPort = Integer.valueOf(env.getProperty("management.port", String.valueOf(nonSecurePort)));
String managementContextPath = env.getProperty("management.context-path", env.getProperty("server.servlet.context-path", "/"));
String serverContextPath = env.getProperty("server.context-path", "/");
int serverPort = Integer.valueOf(env.getProperty("server.port", env.getProperty("port", "8080")));
Integer managementPort = env.getProperty("management.server.port", Integer.class);// nullable. should be wrapped into optional
String managementContextPath = env.getProperty("management.server.context-path");// nullable. should be wrapped into optional
Integer jmxPort = env.getProperty("com.sun.management.jmxremote.port", Integer.class);//nullable
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
instance.setNonSecurePort(nonSecurePort);
instance.setNonSecurePort(serverPort);
instance.setInstanceId(getDefaultInstanceId(env));
instance.setPreferIpAddress(preferIpAddress);
if(isSecurePortEnabled) {
int securePort = Integer.valueOf(env.getProperty("server.port", env.getProperty("port", "8080")));
instance.setSecurePort(securePort);
instance.setSecurePort(serverPort);
}
if (managementPort != nonSecurePort && managementPort != 0) {
if (StringUtils.hasText(hostname)) {
instance.setHostname(hostname);
}
String statusPageUrlPath = getProperty("eureka.instance.status-page-url-path");
String healthCheckUrlPath = getProperty("eureka.instance.health-check-url-path");
if (!managementContextPath.endsWith("/")) {
managementContextPath = managementContextPath + "/";
}
if (StringUtils.hasText(statusPageUrlPath)) {
instance.setStatusPageUrlPath(statusPageUrlPath);
}
if (StringUtils.hasText(healthCheckUrlPath)) {
instance.setHealthCheckUrlPath(healthCheckUrlPath);
}
if (StringUtils.hasText(hostname)) {
instance.setHostname(hostname);
}
String statusPageUrlPath = getProperty("eureka.instance.status-page-url-path");
String healthCheckUrlPath = getProperty("eureka.instance.health-check-url-path");
String scheme = instance.getSecurePortEnabled() ? "https" : "http";
try {
URL base = new URL(scheme, instance.getHostname(), managementPort, managementContextPath);
instance.setStatusPageUrl(new URL(base, StringUtils.trimLeadingCharacter(instance.getStatusPageUrlPath(), '/')).toString());
instance.setHealthCheckUrl(new URL(base, StringUtils.trimLeadingCharacter(instance.getHealthCheckUrlPath(), '/')).toString());
} catch (MalformedURLException e) {
log.error("Unable to set status page or health check url.", e);
if (StringUtils.hasText(statusPageUrlPath)) {
instance.setStatusPageUrlPath(statusPageUrlPath);
}
if (StringUtils.hasText(healthCheckUrlPath)) {
instance.setHealthCheckUrlPath(healthCheckUrlPath);
}
ManagementMetadata metadata = managementMetadataProvider.get(instance, serverPort,
serverContextPath, managementContextPath, managementPort);
if(metadata != null) {
instance.setStatusPageUrl(metadata.getStatusPageUrl());
instance.setHealthCheckUrl(metadata.getHealthCheckUrl());
Map<String, String> metadataMap = instance.getMetadataMap();
if (metadataMap.get("management.port") == null) {
metadataMap.put("management.port", String.valueOf(metadata.getManagementPort()));
}
}
setupJmxPort(instance, jmxPort);
return instance;
}
private void setupJmxPort(EurekaInstanceConfigBean instance, Integer jmxPort) {
Map<String, String> metadataMap = instance.getMetadataMap();
if (metadataMap.get("jmx.port") == null && jmxPort != null) {
metadataMap.put("jmx.port", String.valueOf(jmxPort));
}
}
@Bean
public DiscoveryClient discoveryClient(EurekaInstanceConfig config, EurekaClient client) {
return new EurekaDiscoveryClient(config, client);

View File

@@ -0,0 +1,97 @@
package org.springframework.cloud.netflix.eureka.metadata;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
import org.springframework.util.StringUtils;
import java.net.MalformedURLException;
import java.net.URL;
public class DefaultManagementMetadataProvider implements ManagementMetadataProvider {
private static final int RANDOM_PORT = 0;
private static final Log log = LogFactory.getLog(DefaultManagementMetadataProvider.class);
@Override
public ManagementMetadata get(EurekaInstanceConfigBean instance, int serverPort,
String serverContextPath, String managementContextPath,
Integer managementPort) {
if (isRandom(managementPort)) {
return null;
}
if (managementPort == null && isRandom(serverPort)) {
return null;
}
String healthCheckUrl = getHealthCheckUrl(instance, serverPort, serverContextPath,
managementContextPath, managementPort);
String statusPageUrl = getStatusPageUrl(instance, serverPort, serverContextPath,
managementContextPath, managementPort);
return new ManagementMetadata(healthCheckUrl, statusPageUrl, managementPort == null ? serverPort : managementPort);
}
private boolean isRandom(Integer port) {
return port != null && port == RANDOM_PORT;
}
private String getHealthCheckUrl(EurekaInstanceConfigBean instance, int serverPort, String serverContextPath,
String managementContextPath, Integer managementPort) {
String healthCheckUrlPath = instance.getHealthCheckUrlPath();
String healthCheckUrl = getUrl(instance, serverPort, serverContextPath, managementContextPath,
managementPort, healthCheckUrlPath);
log.debug("Constructed eureka meta-data healthcheckUrl: " + healthCheckUrl);
return healthCheckUrl;
}
public String getStatusPageUrl(EurekaInstanceConfigBean instance, int serverPort, String serverContextPath,
String managementContextPath, Integer managementPort) {
String statusPageUrlPath = instance.getStatusPageUrlPath();
String statusPageUrl = getUrl(instance, serverPort, serverContextPath, managementContextPath,
managementPort, statusPageUrlPath);
log.debug("Constructed eureka meta-data statusPageUrl: " + statusPageUrl);
return statusPageUrl;
}
private String getUrl(EurekaInstanceConfigBean instance, int serverPort,
String serverContextPath, String managementContextPath,
Integer managementPort, String urlPath) {
managementContextPath = refineManagementContextPath(serverContextPath, managementContextPath, managementPort);
if (managementPort == null) {
managementPort = serverPort;
}
String scheme = instance.getSecurePortEnabled() ? "https" : "http";
return constructValidUrl(scheme, instance.getHostname(), managementPort, managementContextPath, urlPath);
}
private String refineManagementContextPath(String serverContextPath, String managementContextPath,
Integer managementPort) {
if(managementContextPath != null) {
return managementContextPath;
}
if(managementPort != null) {
return "/";
}
return serverContextPath;
}
private String constructValidUrl(String scheme, String hostname, int port,
String contextPath, String statusPath) {
try {
if (!contextPath.endsWith("/")) {
contextPath = contextPath + "/";
}
URL base = new URL(scheme, hostname, port, contextPath);
String refinedStatusPath = StringUtils.trimLeadingCharacter(statusPath, '/');
return new URL(base, refinedStatusPath).toString();
} catch (MalformedURLException e) {
String message = getErrorMessage(scheme, hostname, port, contextPath, statusPath);
throw new IllegalStateException(message, e);
}
}
private String getErrorMessage(String scheme, String hostname, int port, String contextPath, String statusPath) {
return String.format("Failed to construct url for scheme: %s, hostName: %s port: %s contextPath: %s statusPath: %s",
scheme, hostname, port, contextPath, statusPath);
}
}

View File

@@ -0,0 +1,53 @@
package org.springframework.cloud.netflix.eureka.metadata;
import java.util.Objects;
public class ManagementMetadata {
private final String healthCheckUrl;
private final String statusPageUrl;
private final Integer managementPort;
public ManagementMetadata(String healthCheckUrl, String statusPageUrl, Integer managementPort) {
this.healthCheckUrl = healthCheckUrl;
this.statusPageUrl = statusPageUrl;
this.managementPort = managementPort;
}
public String getHealthCheckUrl() {
return healthCheckUrl;
}
public String getStatusPageUrl() {
return statusPageUrl;
}
public Integer getManagementPort() {
return managementPort;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ManagementMetadata that = (ManagementMetadata) o;
return Objects.equals(healthCheckUrl, that.healthCheckUrl) &&
Objects.equals(statusPageUrl, that.statusPageUrl) &&
Objects.equals(managementPort, that.managementPort);
}
@Override
public int hashCode() {
return Objects.hash(healthCheckUrl, statusPageUrl, managementPort);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("ManagementMetadata{");
sb.append("healthCheckUrl='").append(healthCheckUrl).append('\'');
sb.append(", statusPageUrl='").append(statusPageUrl).append('\'');
sb.append(", managementPort=").append(managementPort);
sb.append('}');
return sb.toString();
}
}

View File

@@ -0,0 +1,10 @@
package org.springframework.cloud.netflix.eureka.metadata;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
public interface ManagementMetadataProvider {
ManagementMetadata get(EurekaInstanceConfigBean instance, int serverPort,
String serverContextPath, String managementContextPath, Integer managementPort);
}

View File

@@ -12,7 +12,6 @@
* 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;
@@ -81,6 +80,54 @@ public class EurekaClientAutoConfigurationTests {
this.context.refresh();
}
@Test
public void shouldSetManagementPortInMetadataMapIfEqualToServerPort() throws Exception {
addEnvironment(this.context, "server.port=8989");
setupContext(RefreshAutoConfiguration.class);
EurekaInstanceConfigBean instance = this.context
.getBean(EurekaInstanceConfigBean.class);
assertEquals("8989", instance.getMetadataMap().get("management.port"));
}
@Test
public void shouldNotSetManagementAndJmxPortsInMetadataMap() throws Exception {
addEnvironment(this.context, "server.port=8989", "management.port=0");
setupContext(RefreshAutoConfiguration.class);
EurekaInstanceConfigBean instance = this.context
.getBean(EurekaInstanceConfigBean.class);
assertEquals(null, instance.getMetadataMap().get("management.port"));
assertEquals(null, instance.getMetadataMap().get("jmx.port"));
}
@Test
public void shouldSetManagementAndJmxPortsInMetadataMap() throws Exception {
addEnvironment(this.context, "management.port=9999",
"com.sun.management.jmxremote.port=6789");
setupContext(RefreshAutoConfiguration.class);
EurekaInstanceConfigBean instance = this.context
.getBean(EurekaInstanceConfigBean.class);
assertEquals("9999", instance.getMetadataMap().get("management.port"));
assertEquals("6789", instance.getMetadataMap().get("jmx.port"));
}
@Test
public void shouldNotResetManagementAndJmxPortsInMetadataMap() throws Exception {
addEnvironment(this.context, "management.port=9999",
"eureka.instance.metadata-map.jmx.port=9898",
"eureka.instance.metadata-map.management.port=7878");
setupContext(RefreshAutoConfiguration.class);
EurekaInstanceConfigBean instance = this.context
.getBean(EurekaInstanceConfigBean.class);
assertEquals("7878", instance.getMetadataMap().get("management.port"));
assertEquals("9898", instance.getMetadataMap().get("jmx.port"));
}
@Test
public void nonSecurePortPeriods() {
testNonSecurePort("server.port");
@@ -153,6 +200,34 @@ public class EurekaClientAutoConfigurationTests {
instance.getHealthCheckUrl().contains("/myHealthCheck"));
}
@Test
public void statusPageUrl_and_healthCheckUrl_do_not_contain_server_context_path() throws Exception {
addEnvironment(this.context, "server.port=8989",
"management.port=9999", "server.contextPath=/service");
setupContext(RefreshAutoConfiguration.class);
EurekaInstanceConfigBean instance = this.context
.getBean(EurekaInstanceConfigBean.class);
assertTrue("Wrong status page: " + instance.getStatusPageUrl(),
instance.getStatusPageUrl().endsWith(":9999/info"));
assertTrue("Wrong health check: " + instance.getHealthCheckUrl(),
instance.getHealthCheckUrl().endsWith(":9999/health"));
}
@Test
public void statusPageUrl_and_healthCheckUrl_contain_management_context_path() throws Exception {
addEnvironment(this.context,
"server.port=8989", "management.contextPath=/management");
setupContext(RefreshAutoConfiguration.class);
EurekaInstanceConfigBean instance = this.context
.getBean(EurekaInstanceConfigBean.class);
assertTrue("Wrong status page: " + instance.getStatusPageUrl(),
instance.getStatusPageUrl().endsWith(":8989/management/info"));
assertTrue("Wrong health check: " + instance.getHealthCheckUrl(),
instance.getHealthCheckUrl().endsWith(":8989/management/health"));
}
@Test
public void statusPageUrlPathAndManagementPortAndContextPath() {
TestPropertyValues.of( "server.port=8989",

View File

@@ -0,0 +1,138 @@
package org.springframework.cloud.netflix.eureka.metadata;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class DefaultManagementMetadataProviderTest {
private static final EurekaInstanceConfigBean INSTANCE = mock(EurekaInstanceConfigBean.class);
private final ManagementMetadataProvider provider = new DefaultManagementMetadataProvider();
@Before
public void setUp() throws Exception {
when(INSTANCE.getHostname()).thenReturn("host");
when(INSTANCE.getHealthCheckUrlPath()).thenReturn("health");
when(INSTANCE.getStatusPageUrlPath()).thenReturn("info");
}
@Test
public void serverPortIsRandomAndManagementPortIsNull() throws Exception {
int serverPort = 0;
String serverContextPath = "/";
String managementContextPath = null;
Integer managementPort = null;
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
assertThat(actual).isNull();
}
@Test
public void managementPortIsRandom() throws Exception {
int serverPort = 0;
String serverContextPath = "/";
String managementContextPath = null;
Integer managementPort = 0;
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
assertThat(actual).isNull();
}
@Test
public void serverPort() throws Exception {
int serverPort = 7777;
String serverContextPath = "/";
String managementContextPath = null;
Integer managementPort = null;
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:7777/health");
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:7777/info");
assertThat(actual.getManagementPort()).isEqualTo(7777);
}
@Test
public void serverPortManagementPort() throws Exception {
int serverPort = 7777;
String serverContextPath = "/";
String managementContextPath = null;
Integer managementPort = 8888;
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:8888/health");
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:8888/info");
assertThat(actual.getManagementPort()).isEqualTo(8888);
}
@Test
public void serverPortManagementPortServerContextPath() throws Exception {
int serverPort = 7777;
String serverContextPath = "/Server";
String managementContextPath = null;
Integer managementPort = 8888;
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:8888/health");
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:8888/info");
assertThat(actual.getManagementPort()).isEqualTo(8888);
}
@Test
public void serverPortManagementPortServerContextPathManagementContextPath() throws Exception {
int serverPort = 7777;
String serverContextPath = "/Server";
String managementContextPath = "/Management";
Integer managementPort = 8888;
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:8888/Management/health");
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:8888/Management/info");
assertThat(actual.getManagementPort()).isEqualTo(8888);
}
@Test
public void serverPortServerContextPathManagementContextPath() throws Exception {
int serverPort = 7777;
String serverContextPath = "/Server";
String managementContextPath = "/Management";
Integer managementPort = null;
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:7777/Management/health");
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:7777/Management/info");
assertThat(actual.getManagementPort()).isEqualTo(7777);
}
@Test
public void serverPortServerContextPath() throws Exception {
int serverPort = 7777;
String serverContextPath = "/Server";
String managementContextPath = null;
Integer managementPort = null;
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:7777/Server/health");
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:7777/Server/info");
assertThat(actual.getManagementPort()).isEqualTo(7777);
}
@Test
public void serverPortManagementPortManagementContextPath() throws Exception {
int serverPort = 7777;
String serverContextPath = "/";
String managementContextPath = "/Management";
Integer managementPort = 8888;
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:8888/Management/health");
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:8888/Management/info");
assertThat(actual.getManagementPort()).isEqualTo(8888);
}
}

View File

View File

@@ -22,7 +22,7 @@ import org.springframework.context.annotation.Configuration;
/**
* Responsible for adding in a marker bean to trigger activation of
* {@link ZuulServerAutoConfiguration}
* {@link ZuulProxyAutoConfiguration}
*
* @author Biju Kunjummen
*/

View File

@@ -44,6 +44,7 @@ import static org.springframework.cloud.netflix.zuul.filters.support.FilterConst
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.RIBBON_ROUTING_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ROUTE_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVICE_ID_KEY;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.LOAD_BALANCER_KEY;
/**
* Route {@link ZuulFilter} that uses Ribbon, Hystrix and pluggable http clients to send requests.
@@ -135,6 +136,7 @@ public class RibbonRoutingFilter extends ZuulFilter {
String serviceId = (String) context.get(SERVICE_ID_KEY);
Boolean retryable = (Boolean) context.get(RETRYABLE_KEY);
Object loadBalancerKey = context.get(LOAD_BALANCER_KEY);
String uri = this.helper.buildZuulRequestURI(request);
@@ -144,7 +146,7 @@ public class RibbonRoutingFilter extends ZuulFilter {
long contentLength = useServlet31 ? request.getContentLengthLong(): request.getContentLength();
return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params,
requestEntity, this.requestCustomizers, contentLength);
requestEntity, this.requestCustomizers, contentLength, loadBalancerKey);
}
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {

View File

@@ -70,6 +70,11 @@ public class FilterConstants {
*/
public static final String SERVICE_ID_KEY = "serviceId";
/**
* Zuul {@link com.netflix.zuul.context.RequestContext} key for use in {@link org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter}
*/
public static final String LOAD_BALANCER_KEY = "loadBalancerKey";
// ORDER constants -----------------------------------
/**

View File

@@ -0,0 +1,178 @@
/*
* Copyright 2013-2017 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
*
* http://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.zuul.filters.route;
import com.netflix.loadbalancer.AvailabilityFilteringRule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.*;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import static org.junit.Assert.assertEquals;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.LOAD_BALANCER_KEY;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* @author Yongsung Yoon
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = CanaryTestZuulProxyApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
value = { "zuul.routes.simple.path: /simple/**" })
@DirtiesContext
public class RibbonRoutingFilterLoadBalancerKeyIntegrationTests {
@Autowired
private TestRestTemplate testRestTemplate;
@Before
public void setTestRequestContext() {
RequestContext context = new RequestContext();
RequestContext.testSetCurrentContext(context);
}
@After
public void clear() {
RequestContext.getCurrentContext().clear();
}
@Test
public void invokeWithUserDefinedCanaryHeader() {
HttpHeaders headers = new HttpHeaders();
headers.set("X-Canary-Test", "true");
ResponseEntity<String> result = testRestTemplate.exchange("/simple/hello", HttpMethod.GET,
new HttpEntity<>(headers), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("canary", result.getBody());
}
@Test
public void invokeWithoutUserDefinedCanaryHeader() {
HttpHeaders headers = new HttpHeaders();
ResponseEntity<String> result = testRestTemplate.exchange("/simple/hello", HttpMethod.GET,
new HttpEntity<>(headers), String.class);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode());
}
}
@Configuration
@EnableAutoConfiguration
@RestController
@EnableZuulProxy
@RibbonClient(name = "simple", configuration = CanaryTestRibbonClientConfiguration.class)
class CanaryTestZuulProxyApplication {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello() {
return "canary";
}
@Bean
public ZuulFilter testCanarySupportPreFilter() {
return new ZuulFilter() {
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
if (checkIfCanaryRequest(context)) {
context.set(LOAD_BALANCER_KEY, "canary"); // set loadBalancerKey for IRule
}
return null;
}
private boolean checkIfCanaryRequest(RequestContext context) {
HttpServletRequest request = context.getRequest();
String canaryHeader = request.getHeader("X-Canary-Test"); // user defined header
if ((canaryHeader != null) && (canaryHeader.equalsIgnoreCase("true"))) {
return true;
}
return false;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
};
}
}
@Configuration
class CanaryTestRibbonClientConfiguration {
@LocalServerPort
private int port;
private static Server testCanaryInstance;
@Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("normal-routing-notexist-localhost", this.port));
}
@Bean
public IRule canaryTestRule() {
if (testCanaryInstance == null) {
testCanaryInstance = new Server("localhost", port); // use test server as a canary instance
}
return new TestCanaryRule();
}
public static class TestCanaryRule extends AvailabilityFilteringRule {
@Override
public Server choose(Object key) {
if ((key != null) && (key.equals("canary"))) {
return testCanaryInstance; // choose test canary server instead of normal servers.
}
return super.choose(key); // normal routing
}
}
}

View File

@@ -19,22 +19,74 @@ package org.springframework.cloud.netflix.zuul.filters.route;
import java.util.Collections;
import com.netflix.zuul.context.RequestContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext;
import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.mock.web.MockHttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.LOAD_BALANCER_KEY;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVICE_ID_KEY;
/**
* @author Spencer Gibb
* @author Yongsung Yoon
*/
public class RibbonRoutingFilterTests {
private RequestContext requestContext;
private RibbonRoutingFilter filter;
@Before
public void setUp() throws Exception {
setUpRequestContext();
setupRibbonRoutingFilter();
}
@After
public void tearDown() throws Exception {
requestContext.unset();
}
@Test
public void useServlet31Works() {
RibbonCommandFactory factory = mock(RibbonCommandFactory.class);
RibbonRoutingFilter filter = new RibbonRoutingFilter(new ProxyRequestHelper(), factory,
Collections.<RibbonRequestCustomizer>emptyList());
assertThat(filter.isUseServlet31()).isTrue();
}
@Test
public void testLoadBalancerKeyToRibbonCommandContext() throws Exception {
final String testKey = "testLoadBalancerKey";
requestContext.set(LOAD_BALANCER_KEY, testKey);
RibbonCommandContext commandContext = filter.buildCommandContext(requestContext);
assertThat(commandContext.getLoadBalancerKey()).isEqualTo(testKey);
}
@Test
public void testNullLoadBalancerKeyToRibbonCommandContext() throws Exception {
requestContext.set(LOAD_BALANCER_KEY, null);
RibbonCommandContext commandContext = filter.buildCommandContext(requestContext);
assertThat(commandContext.getLoadBalancerKey()).isNull();
}
private void setUpRequestContext() {
requestContext = RequestContext.getCurrentContext();
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
mockRequest.setMethod("GET");
mockRequest.setRequestURI("/foo/bar");
requestContext.setRequest(mockRequest);
requestContext.setRequestQueryParams(Collections.EMPTY_MAP);
requestContext.set(SERVICE_ID_KEY, "testServiceId");
}
private void setupRibbonRoutingFilter() {
RibbonCommandFactory factory = mock(RibbonCommandFactory.class);
filter = new RibbonRoutingFilter(new ProxyRequestHelper(), factory, Collections.<RibbonRequestCustomizer>emptyList());
}
}