Merge branch 'master' into 2.0.x

This commit is contained in:
Spencer Gibb
2017-04-06 21:30:03 -06:00
49 changed files with 1592 additions and 169 deletions

View File

@@ -605,6 +605,14 @@ One of the main benefits of Hystrix is the set of metrics it gathers about each
.Hystrix Dashboard
image::Hystrix.png[]
== Hystrix Timeouts And Ribbon Clients
When using Hystrix commands that wrap Ribbon clients you want to make sure your Hystrix timeout
is configured to be longer than the configured Ribbon timeout, including any potential
retries that might be made. For example, if your Ribbon connection timeout is one second and
the Ribbon client might retry the request three times, than your Hystrix timeout should
be slightly more than three seconds.
[[netflix-hystrix-dashboard-starter]]
=== How to Include Hystrix Dashboard
@@ -1589,7 +1597,8 @@ If you are using `@EnableZuulProxy` with tha Spring Boot Actuator you
will enable (by default) an additional endpoint, available via HTTP as
`/routes`. A GET to this endpoint will return a list of the mapped
routes. A POST will force a refresh of the existing routes (e.g. in
case there have been changes in the service catalog).
case there have been changes in the service catalog). You can disable
this endpoint by setting `endpoints.routes.enabled` to `false`.
NOTE: the routes should respond automatically to changes in the
service catalog, but the POST to /routes is a way to force the change
@@ -1893,6 +1902,10 @@ Route filters:
* `SimpleHostRoutingFilter`: This filter sends requests to predetermined URLs via an Apache HttpClient. URLs are found in `RequestContext.getRouteHost()`.
==== Custom Zuul Filter examples
Most of the following "How to Write" examples below are included https://github.com/spring-cloud-samples/sample-zuul-filters[Sample Zuul Filters] project. There are also examples of manipulating the request or response body in that repository.
==== How to Write a Pre Filter
Pre filters are used to set up data in the `RequestContext` for use in filters downstream. The main use case is to set information required for route filters.
@@ -2448,6 +2461,22 @@ certain Ribbon properties. The properties you can use are
`client.ribbon.OkToRetryOnAllOperations`. See the https://github.com/Netflix/ribbon/wiki/Getting-Started#the-properties-file-sample-clientproperties[Ribbon documentation]
for a description of what there properties do.
In addition you may want to retry requests when certain status codes are returned in the
response. You can list the response codes you would like the Ribbon client to retry using the
property `clientName.ribbon.retryableStatusCodes`. For example
[source,yaml]
----
clientName:
ribbon:
retryableStatusCodes: 404,502
----
You can also create a bean of type `LoadBalancedRetryPolicy` and implement the `retryableStatusCode`
method to determine whether you want to retry a request given the status code.
==== Zuul
You can turn off Zuul's retry functionality by setting `zuul.retryable` to `false`. You

View File

@@ -248,7 +248,7 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
private String getUrl(Map<String, Object> attributes) {
String url = resolve((String) attributes.get("url"));
if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.endsWith("}"))) {
if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) {
if (!url.contains("://")) {
url = "http://" + url;
}

View File

@@ -29,6 +29,7 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFact
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.cloud.netflix.ribbon.support.RetryableStatusCodeException;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.policy.NeverRetryPolicy;
@@ -69,7 +70,7 @@ public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements Ser
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
: new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this, this.getClientName()));
@@ -89,6 +90,9 @@ public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements Ser
feignRequest = request.toRequest();
}
Response response = request.client().execute(feignRequest, options);
if(retryPolicy.retryableStatusCode(response.status())) {
throw new RetryableStatusCodeException(RetryableFeignLoadBalancer.this.getClientName(), response.status());
}
return new RibbonResponse(request.getUri(), response);
}
});

View File

@@ -90,12 +90,14 @@ public class RibbonAutoConfiguration {
@Bean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnMissingBean
public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory(SpringClientFactory clientFactory) {
return new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
}
@Bean
@ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnMissingBean
public LoadBalancedRetryPolicyFactory neverRetryPolicyFactory() {
return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
}

View File

@@ -0,0 +1,123 @@
/*
*
* * 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;
import java.util.ArrayList;
import java.util.List;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.util.StringUtils;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.IClientConfig;
import com.netflix.client.config.IClientConfigKey;
/**
* {@link LoadBalancedRetryPolicy} for Ribbon clients.
* @author Ryan Baxter
*/
public class RibbonLoadBalancedRetryPolicy implements LoadBalancedRetryPolicy {
public static final IClientConfigKey<String> RETRYABLE_STATUS_CODES = new CommonClientConfigKey<String>("retryableStatusCodes") {};
private int sameServerCount = 0;
private int nextServerCount = 0;
private String serviceId;
private RibbonLoadBalancerContext lbContext;
private ServiceInstanceChooser loadBalanceChooser;
List<Integer> retryableStatusCodes = new ArrayList<>();
public RibbonLoadBalancedRetryPolicy(String serviceId, RibbonLoadBalancerContext context, ServiceInstanceChooser loadBalanceChooser) {
this.serviceId = serviceId;
this.lbContext = context;
this.loadBalanceChooser = loadBalanceChooser;
}
public RibbonLoadBalancedRetryPolicy(String serviceId, RibbonLoadBalancerContext context, ServiceInstanceChooser loadBalanceChooser,
IClientConfig clientConfig) {
this.serviceId = serviceId;
this.lbContext = context;
this.loadBalanceChooser = loadBalanceChooser;
String retryableStatusCodesProp = clientConfig.getPropertyAsString(RETRYABLE_STATUS_CODES, "");
String[] retryableStatusCodesArray = retryableStatusCodesProp.split(",");
for(String code : retryableStatusCodesArray) {
if(!StringUtils.isEmpty(code)) {
try {
retryableStatusCodes.add(Integer.valueOf(code));
} catch (NumberFormatException e) {
//TODO log
}
}
}
}
public boolean canRetry(LoadBalancedRetryContext context) {
HttpMethod method = context.getRequest().getMethod();
return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations();
}
@Override
public boolean canRetrySameServer(LoadBalancedRetryContext context) {
return sameServerCount < lbContext.getRetryHandler().getMaxRetriesOnSameServer() && canRetry(context);
}
@Override
public boolean canRetryNextServer(LoadBalancedRetryContext context) {
//this will be called after a failure occurs and we increment the counter
//so we check that the count is less than or equals to too make sure
//we try the next server the right number of times
return nextServerCount <= lbContext.getRetryHandler().getMaxRetriesOnNextServer() && canRetry(context);
}
@Override
public void close(LoadBalancedRetryContext context) {
}
@Override
public void registerThrowable(LoadBalancedRetryContext context, Throwable throwable) {
//Check if we need to ask the load balancer for a new server.
//Do this before we increment the counters because the first call to this method
//is not a retry it is just an initial failure.
if(!canRetrySameServer(context) && canRetryNextServer(context)) {
context.setServiceInstance(loadBalanceChooser.choose(serviceId));
}
//This method is called regardless of whether we are retrying or making the first request.
//Since we do not count the initial request in the retry count we don't reset the counter
//until we actually equal the same server count limit. This will allow us to make the initial
//request plus the right number of retries.
if(sameServerCount >= lbContext.getRetryHandler().getMaxRetriesOnSameServer() && canRetry(context)) {
//reset same server since we are moving to a new server
sameServerCount = 0;
nextServerCount++;
if(!canRetryNextServer(context)) {
context.setExhaustedOnly();
}
} else {
sameServerCount++;
}
}
@Override
public boolean retryableStatusCode(int statusCode) {
return retryableStatusCodes.contains(statusCode);
}
}

View File

@@ -15,12 +15,9 @@
*/
package org.springframework.cloud.netflix.ribbon;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.http.HttpMethod;
/**
* @author Ryan Baxter
@@ -34,60 +31,10 @@ public class RibbonLoadBalancedRetryPolicyFactory implements LoadBalancedRetryPo
}
@Override
public LoadBalancedRetryPolicy create(final String serviceId, final ServiceInstanceChooser loadBalanceChooser) {
final RibbonLoadBalancerContext lbContext = this.clientFactory
public LoadBalancedRetryPolicy create(String serviceId, ServiceInstanceChooser loadBalanceChooser) {
RibbonLoadBalancerContext lbContext = this.clientFactory
.getLoadBalancerContext(serviceId);
return new LoadBalancedRetryPolicy() {
private int sameServerCount = 0;
private int nextServerCount = 0;
private ServiceInstance lastServiceInstance = null;
public boolean canRetry(LoadBalancedRetryContext context) {
HttpMethod method = context.getRequest().getMethod();
return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations();
}
@Override
public boolean canRetrySameServer(LoadBalancedRetryContext context) {
return sameServerCount < lbContext.getRetryHandler().getMaxRetriesOnSameServer() && canRetry(context);
}
@Override
public boolean canRetryNextServer(LoadBalancedRetryContext context) {
//this will be called after a failure occurs and we increment the counter
//so we check that the count is less than or equals to too make sure
//we try the next server the right number of times
return nextServerCount <= lbContext.getRetryHandler().getMaxRetriesOnNextServer() && canRetry(context);
}
@Override
public void close(LoadBalancedRetryContext context) {
}
@Override
public void registerThrowable(LoadBalancedRetryContext context, Throwable throwable) {
//Check if we need to ask the load balancer for a new server.
//Do this before we increment the counters because the first call to this method
//is not a retry it is just an initial failure.
if(!canRetrySameServer(context) && canRetryNextServer(context)) {
context.setServiceInstance(loadBalanceChooser.choose(serviceId));
}
//This method is called regardless of whether we are retrying or making the first request.
//Since we do not count the initial request in the retry count we don't reset the counter
//until we actually equal the same server count limit. This will allow us to make the initial
//request plus the right number of retries.
if(sameServerCount >= lbContext.getRetryHandler().getMaxRetriesOnSameServer() && canRetry(context)) {
//reset same server since we are moving to a new server
sameServerCount = 0;
nextServerCount++;
if(!canRetryNextServer(context)) {
context.setExhaustedOnly();
}
} else {
sameServerCount++;
}
}
};
return new RibbonLoadBalancedRetryPolicy(serviceId, lbContext, loadBalanceChooser, clientFactory.getClientConfig(serviceId));
}
}

View File

@@ -29,6 +29,7 @@ import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.cloud.netflix.feign.ribbon.FeignRetryPolicy;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.cloud.netflix.ribbon.support.RetryableStatusCodeException;
import org.springframework.http.HttpRequest;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
@@ -65,7 +66,8 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
CommonClientConfigKey.FollowRedirects, this.followRedirects));
final RequestConfig requestConfig = builder.build();
return this.executeWithRetry(request, new RetryCallback() {
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
RetryCallback retryCallback = new RetryCallback() {
@Override
public RibbonApacheHttpResponse doWithRetry(RetryContext context) throws Exception {
//on retries the policy will choose the server and set it in the context
@@ -88,13 +90,17 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
}
HttpUriRequest httpUriRequest = newRequest.toRequest(requestConfig);
final HttpResponse httpResponse = RetryableRibbonLoadBalancingHttpClient.this.delegate.execute(httpUriRequest);
if(retryPolicy.retryableStatusCode(httpResponse.getStatusLine().getStatusCode())) {
throw new RetryableStatusCodeException(RetryableRibbonLoadBalancingHttpClient.this.clientName,
httpResponse.getStatusLine().getStatusCode());
}
return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
}
});
};
return this.executeWithRetry(request, retryPolicy, retryCallback);
}
private RibbonApacheHttpResponse executeWithRetry(RibbonApacheHttpRequest request, RetryCallback<RibbonApacheHttpResponse, IOException> callback) throws Exception {
LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
private RibbonApacheHttpResponse executeWithRetry(RibbonApacheHttpRequest request, LoadBalancedRetryPolicy retryPolicy, RetryCallback<RibbonApacheHttpResponse, IOException> callback) throws Exception {
RetryTemplate retryTemplate = new RetryTemplate();
boolean retryable = request.getContext() == null ? true :
BooleanUtils.toBooleanDefaultIfNull(request.getContext().getRetryable(), true);

View File

@@ -63,10 +63,11 @@ public class RibbonApacheHttpRequest extends ContextAwareRequest implements Clon
entity.setContent(this.context.getRequestEntity());
// if the entity contentLength isn't set, transfer-encoding will be set
// to chunked in org.apache.http.protocol.RequestContent. See gh-1042
if (this.context.getContentLength() != null) {
entity.setContentLength(this.context.getContentLength());
} else if ("GET".equals(this.context.getMethod())) {
Long contentLength = this.context.getContentLength();
if ("GET".equals(this.context.getMethod()) && (contentLength == null || contentLength < 0)) {
entity.setContentLength(0);
} else if (contentLength != null) {
entity.setContentLength(contentLength);
}
builder.setEntity(entity);
}

View File

@@ -19,7 +19,6 @@ import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
import java.net.URI;
import org.apache.commons.lang.BooleanUtils;
import org.springframework.cloud.client.ServiceInstance;
@@ -30,6 +29,7 @@ import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.cloud.netflix.feign.ribbon.FeignRetryPolicy;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.cloud.netflix.ribbon.support.RetryableStatusCodeException;
import org.springframework.http.HttpRequest;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
@@ -55,10 +55,9 @@ public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClien
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
}
private OkHttpRibbonResponse executeWithRetry(OkHttpRibbonRequest request,
RetryCallback<OkHttpRibbonResponse, IOException> callback)
private OkHttpRibbonResponse executeWithRetry(OkHttpRibbonRequest request, LoadBalancedRetryPolicy retryPolicy,
RetryCallback<OkHttpRibbonResponse, Exception> callback)
throws Exception {
LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
RetryTemplate retryTemplate = new RetryTemplate();
boolean retryable = request.getContext() == null ? true :
BooleanUtils.toBooleanDefaultIfNull(request.getContext().getRetryable(), true);
@@ -70,7 +69,8 @@ public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClien
@Override
public OkHttpRibbonResponse execute(final OkHttpRibbonRequest ribbonRequest,
final IClientConfig configOverride) throws Exception {
return this.executeWithRetry(ribbonRequest, new RetryCallback() {
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
RetryCallback<OkHttpRibbonResponse, Exception> retryCallback = new RetryCallback<OkHttpRibbonResponse, Exception>() {
@Override
public OkHttpRibbonResponse doWithRetry(RetryContext context) throws Exception {
//on retries the policy will choose the server and set it in the context
@@ -95,9 +95,13 @@ public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClien
final Request request = newRequest.toRequest();
Response response = httpClient.newCall(request).execute();
if(retryPolicy.retryableStatusCode(response.code())) {
throw new RetryableStatusCodeException(RetryableOkHttpLoadBalancingClient.this.clientName, response.code());
}
return new OkHttpRibbonResponse(response, newRequest.getUri());
}
});
};
return this.executeWithRetry(ribbonRequest, retryPolicy, retryCallback);
}
@Override

View File

@@ -0,0 +1,34 @@
/*
*
* * 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
*/
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

@@ -20,74 +20,43 @@ import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Endpoint to display and reset the zuul proxy routes
* Endpoint to display the zuul proxy routes
*
* @author Spencer Gibb
* @author Dave Syer
* @author Ryan Baxter
*/
@ManagedResource(description = "Can be used to list and reset the reverse proxy routes")
public class RoutesEndpoint implements MvcEndpoint, ApplicationEventPublisherAware {
@ManagedResource(description = "Can be used to list the reverse proxy routes")
@ConfigurationProperties(prefix = "endpoints.routes")
public class RoutesEndpoint extends AbstractEndpoint<Map<String, String>> {
private static final String ID = "routes";
private RouteLocator routes;
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Autowired
public RoutesEndpoint(RouteLocator routes) {
super(ID, true);
this.routes = routes;
}
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
@ManagedOperation
public Map<String, String> reset() {
this.publisher.publishEvent(new RoutesRefreshedEvent(this.routes));
return getRoutes();
}
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
@ManagedAttribute
public Map<String, String> getRoutes() {
public Map<String, String> invoke() {
Map<String, String> map = new LinkedHashMap<>();
for (Route route : this.routes.getRoutes()) {
map.put(route.getFullPath(), route.getLocation());
}
return map;
}
@Override
public String getPath() {
return "/routes";
}
@Override
public boolean isSensitive() {
return true;
}
@Override
public Class<? extends Endpoint<?>> getEndpointType() {
return null;
}
}

View File

@@ -0,0 +1,58 @@
/*
*
* * 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.zuul;
import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Endpoint used to reset the reverse proxy routes
* @author Ryan Baxter
*/
@ManagedResource(description = "Can be used to reset the reverse proxy routes")
public class RoutesMvcEndpoint extends EndpointMvcAdapter implements ApplicationEventPublisherAware {
private RouteLocator routes;
private ApplicationEventPublisher publisher;
public RoutesMvcEndpoint(RoutesEndpoint endpoint, RouteLocator routes) {
super(endpoint);
this.routes = routes;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
@ManagedOperation
public Object reset() {
this.publisher.publishEvent(new RoutesRefreshedEvent(this.routes));
return super.invoke();
}
}

View File

@@ -20,6 +20,8 @@ import java.util.Collection;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -43,6 +45,9 @@ import org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter;
import org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter;
import org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter;
import org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter;
import org.springframework.cloud.netflix.zuul.metrics.DefaultCounterFactory;
import org.springframework.cloud.netflix.zuul.metrics.EmptyCounterFactory;
import org.springframework.cloud.netflix.zuul.metrics.EmptyTracerFactory;
import org.springframework.cloud.netflix.zuul.web.ZuulController;
import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
import org.springframework.context.ApplicationEvent;
@@ -53,8 +58,12 @@ import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.context.event.ContextRefreshedEvent;
import com.netflix.zuul.FilterLoader;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.filters.FilterRegistry;
import com.netflix.zuul.http.ZuulServlet;
import com.netflix.zuul.monitoring.CounterFactory;
import com.netflix.zuul.monitoring.TracerFactory;
/**
* @author Spencer Gibb
@@ -178,8 +187,39 @@ public class ZuulConfiguration {
private Map<String, ZuulFilter> filters;
@Bean
public ZuulFilterInitializer zuulFilterInitializer() {
return new ZuulFilterInitializer(this.filters);
public ZuulFilterInitializer zuulFilterInitializer(
CounterFactory counterFactory, TracerFactory tracerFactory) {
FilterLoader filterLoader = FilterLoader.getInstance();
FilterRegistry filterRegistry = FilterRegistry.instance();
return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
}
}
@Configuration
@ConditionalOnClass(CounterService.class)
protected static class ZuulCounterFactoryConfiguration {
@Bean
@ConditionalOnBean(CounterService.class)
public CounterFactory counterFactory(CounterService counterService) {
return new DefaultCounterFactory(counterService);
}
}
@Configuration
protected static class ZuulMetricsConfiguration {
@Bean
@ConditionalOnMissingBean(CounterFactory.class)
public CounterFactory counterFactory() {
return new EmptyCounterFactory();
}
@ConditionalOnMissingBean(TracerFactory.class)
@Bean
public TracerFactory tracerFactory() {
return new EmptyTracerFactory();
}
}

View File

@@ -22,13 +22,15 @@ import java.util.Map;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.util.ReflectionUtils;
import com.netflix.zuul.FilterLoader;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.filters.FilterRegistry;
import com.netflix.zuul.monitoring.MonitoringHelper;
import com.netflix.zuul.monitoring.CounterFactory;
import com.netflix.zuul.monitoring.TracerFactory;
import lombok.extern.apachecommons.CommonsLog;
/**
* @author Spencer Gibb
@@ -38,10 +40,22 @@ import com.netflix.zuul.monitoring.MonitoringHelper;
@CommonsLog
public class ZuulFilterInitializer implements ServletContextListener {
private Map<String, ZuulFilter> filters;
private final Map<String, ZuulFilter> filters;
private final CounterFactory counterFactory;
private final TracerFactory tracerFactory;
private final FilterLoader filterLoader;
private final FilterRegistry filterRegistry;
public ZuulFilterInitializer(Map<String, ZuulFilter> filters) {
public ZuulFilterInitializer(Map<String, ZuulFilter> filters,
CounterFactory counterFactory,
TracerFactory tracerFactory,
FilterLoader filterLoader,
FilterRegistry filterRegistry) {
this.filters = filters;
this.counterFactory = counterFactory;
this.tracerFactory = tracerFactory;
this.filterLoader = filterLoader;
this.filterRegistry = filterRegistry;
}
@Override
@@ -49,32 +63,31 @@ public class ZuulFilterInitializer implements ServletContextListener {
log.info("Starting filter initializer context listener");
// FIXME: mocks monitoring infrastructure as we don't need it for this simple app
MonitoringHelper.initMocks();
FilterRegistry registry = FilterRegistry.instance();
TracerFactory.initialize(tracerFactory);
CounterFactory.initialize(counterFactory);
for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
registry.put(entry.getKey(), entry.getValue());
filterRegistry.put(entry.getKey(), entry.getValue());
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("Stopping filter initializer context listener");
FilterRegistry registry = FilterRegistry.instance();
for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
registry.remove(entry.getKey());
filterRegistry.remove(entry.getKey());
}
clearLoaderCache();
TracerFactory.initialize(null);
CounterFactory.initialize(null);
}
private void clearLoaderCache() {
FilterLoader instance = FilterLoader.getInstance();
Field field = ReflectionUtils.findField(FilterLoader.class, "hashFiltersByType");
ReflectionUtils.makeAccessible(field);
@SuppressWarnings("rawtypes")
Map cache = (Map) ReflectionUtils.getField(field, instance);
Map cache = (Map) ReflectionUtils.getField(field, filterLoader);
cache.clear();
}

View File

@@ -139,6 +139,11 @@ public class ZuulProxyConfiguration extends ZuulConfiguration {
return new RoutesEndpoint(routeLocator);
}
@Bean
public RoutesMvcEndpoint zuulMvcEndpoint(RouteLocator routeLocator, RoutesEndpoint endpoint) {
return new RoutesMvcEndpoint(endpoint, routeLocator);
}
@Bean
public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
TraceProxyRequestHelper helper = new TraceProxyRequestHelper();

View File

@@ -199,7 +199,7 @@ public class SimpleRouteLocator implements RouteLocator, Ordered {
// do nothing
}
log.debug("adjustedPath=" + path);
log.debug("adjustedPath=" + adjustedPath);
return adjustedPath;
}

View File

@@ -21,36 +21,24 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
/**
* @author Spencer Gibb
*/
@Data
@RequiredArgsConstructor
@AllArgsConstructor
public class RibbonCommandContext {
@NonNull
private final String serviceId;
@NonNull
private final String method;
@NonNull
private final String uri;
private final Boolean retryable;
@NonNull
private final MultiValueMap<String, String> headers;
@NonNull
private final MultiValueMap<String, String> params;
private final InputStream requestEntity;
@NonNull
private final List<RibbonRequestCustomizer> requestCustomizers;
private Long contentLength;
@@ -65,6 +53,34 @@ public class RibbonCommandContext {
new ArrayList<RibbonRequestCustomizer>(), null);
}
public RibbonCommandContext(String serviceId, String method, String uri,
Boolean retryable, MultiValueMap<String, String> headers,
MultiValueMap<String, String> params, InputStream requestEntity,
List<RibbonRequestCustomizer> requestCustomizers) {
this(serviceId, method, uri, retryable, headers, params, requestEntity, requestCustomizers, 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) {
Assert.notNull(serviceId, "serviceId may not be null");
Assert.notNull(method, "method may not be null");
Assert.notNull(uri, "uri may not be null");
Assert.notNull(headers, "headers may not be null");
Assert.notNull(params, "params may not be null");
Assert.notNull(requestCustomizers, "requestCustomizers may not be null");
this.serviceId = serviceId;
this.method = method;
this.uri = uri;
this.retryable = retryable;
this.headers = headers;
this.params = params;
this.requestEntity = requestEntity;
this.requestCustomizers = requestCustomizers;
this.contentLength = contentLength;
}
public URI uri() {
try {
return new URI(this.uri);
@@ -83,4 +99,81 @@ public class RibbonCommandContext {
public String getVerb() {
return this.method;
}
public String getServiceId() {
return serviceId;
}
public String getMethod() {
return method;
}
public String getUri() {
return uri;
}
public Boolean getRetryable() {
return retryable;
}
public MultiValueMap<String, String> getHeaders() {
return headers;
}
public MultiValueMap<String, String> getParams() {
return params;
}
public InputStream getRequestEntity() {
return requestEntity;
}
public List<RibbonRequestCustomizer> getRequestCustomizers() {
return requestCustomizers;
}
public Long getContentLength() {
return contentLength;
}
public void setContentLength(Long contentLength) {
this.contentLength = contentLength;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RibbonCommandContext that = (RibbonCommandContext) o;
return Objects.equals(serviceId, that.serviceId) &&
Objects.equals(method, that.method) &&
Objects.equals(uri, that.uri) &&
Objects.equals(retryable, that.retryable) &&
Objects.equals(headers, that.headers) &&
Objects.equals(params, that.params) &&
Objects.equals(requestEntity, that.requestEntity) &&
Objects.equals(requestCustomizers, that.requestCustomizers) &&
Objects.equals(contentLength, that.contentLength);
}
@Override
public int hashCode() {
return Objects.hash(serviceId, method, uri, retryable, headers, params, requestEntity, requestCustomizers, contentLength);
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("RibbonCommandContext{");
sb.append("serviceId='").append(serviceId).append('\'');
sb.append(", method='").append(method).append('\'');
sb.append(", uri='").append(uri).append('\'');
sb.append(", retryable=").append(retryable);
sb.append(", headers=").append(headers);
sb.append(", params=").append(params);
sb.append(", requestEntity=").append(requestEntity);
sb.append(", requestCustomizers=").append(requestCustomizers);
sb.append(", contentLength=").append(contentLength);
sb.append('}');
return sb.toString();
}
}

View File

@@ -124,7 +124,7 @@ public class RibbonRoutingFilter extends ZuulFilter {
.buildZuulRequestQueryParams(request);
String verb = getVerb(request);
InputStream requestEntity = getRequestBody(request);
if (request.getContentLength() < 0) {
if (request.getContentLength() < 0 && !verb.equalsIgnoreCase("GET")) {
context.setChunkedRequestBody();
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2013-2015 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.metrics;
import org.springframework.boot.actuate.metrics.CounterService;
import com.netflix.zuul.monitoring.CounterFactory;
/**
* A counter based monitoring factory that uses {@link CounterService} to increment counters.
*
* @author Anastasiia Smirnova
*/
public class DefaultCounterFactory extends CounterFactory {
private final CounterService counterService;
public DefaultCounterFactory(CounterService counterService) {
this.counterService = counterService;
}
@Override
public void increment(String name) {
counterService.increment(name);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2013-2015 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.metrics;
import com.netflix.zuul.monitoring.CounterFactory;
/**
* A counter based monitoring factory that does nothing.
*
* @author Anastasiia Smirnova
*/
public class EmptyCounterFactory extends CounterFactory {
@Override
public void increment(String name) {
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2013-2015 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.metrics;
import com.netflix.zuul.monitoring.Tracer;
import com.netflix.zuul.monitoring.TracerFactory;
/**
* A time based monitoring factory that does nothing.
*
* @author Anastasiia Smirnova
*/
public class EmptyTracerFactory extends TracerFactory {
private final EmptyTracer emptyTracer = new EmptyTracer();
@Override
public Tracer startMicroTracer(String name) {
return emptyTracer;
}
private static final class EmptyTracer implements Tracer {
@Override
public void setName(String name) {
}
@Override
public void stopAndLog() {
}
}
}

View File

@@ -88,13 +88,13 @@ public class FeignHttpClientUrlTests {
Hello getHello();
}
@FeignClient(name = "beanappurl", url = "#{SERVER_URL}")
@FeignClient(name = "beanappurl", url = "#{SERVER_URL}path")
protected interface BeanUrlClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
}
@FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}")
@FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path")
protected interface BeanUrlClientNoProtocol {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
@@ -113,6 +113,11 @@ public class FeignHttpClientUrlTests {
return new Hello("hello world 1");
}
@RequestMapping(method = RequestMethod.GET, value = "/path/hello")
public Hello getHelloWithPath() {
return getHello();
}
@Bean(name="SERVER_URL")
public String serverUrl() {
return "http://localhost:" + port + "/";

View File

@@ -138,9 +138,9 @@ class LocalRibbonClientConfiguration {
@Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("___mybadhost__", 10001),
new Server("___mybadhost2__", 10002),
new Server("___mybadhost3__", 10003), new Server("localhost", this.port));
return new StaticServerList<>(new Server("mybadhost", 80),
new Server("mybadhost2", 10002),
new Server("mybadhost3", 10003), new Server("localhost", this.port));
}
}

View File

@@ -37,6 +37,7 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicy;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerContext;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
@@ -44,6 +45,7 @@ import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.http.HttpRequest;
import com.netflix.client.RequestSpecificRetryHandler;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
@@ -60,6 +62,7 @@ import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
@@ -98,6 +101,14 @@ public class RetryableFeignLoadBalancerTest {
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
IClientConfig config = mock(IClientConfig.class);
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt());
doReturn(true).when(config).get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
doReturn(defaultConnectTimeout).when(config).get(eq(CommonClientConfigKey.ConnectTimeout));
doReturn(defaultReadTimeout).when(config).get(eq(CommonClientConfigKey.ReadTimeout));
doReturn("404,502,foo, ,").when(config).getPropertyAsString(eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES),eq(""));
doReturn(config).when(clientFactory).getClientConfig(eq("default"));
RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
HttpRequest springRequest = mock(HttpRequest.class);
Request feignRequest = Request.create("GET", "http://foo", new HashMap<String, Collection<String>>(),
@@ -139,6 +150,14 @@ public class RetryableFeignLoadBalancerTest {
public void executeRetry() throws Exception {
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
IClientConfig config = mock(IClientConfig.class);
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt());
doReturn(true).when(config).get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
doReturn(defaultConnectTimeout).when(config).get(eq(CommonClientConfigKey.ConnectTimeout));
doReturn(defaultReadTimeout).when(config).get(eq(CommonClientConfigKey.ReadTimeout));
doReturn("").when(config).getPropertyAsString(eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES),eq(""));
doReturn(config).when(clientFactory).getClientConfig(eq("default"));
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
HttpRequest springRequest = mock(HttpRequest.class);
@@ -154,6 +173,34 @@ public class RetryableFeignLoadBalancerTest {
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
}
@Test
public void executeRetryOnStatusCode() throws Exception {
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
IClientConfig config = mock(IClientConfig.class);
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt());
doReturn(true).when(config).get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
doReturn(defaultConnectTimeout).when(config).get(eq(CommonClientConfigKey.ConnectTimeout));
doReturn(defaultReadTimeout).when(config).get(eq(CommonClientConfigKey.ReadTimeout));
doReturn("404").when(config).getPropertyAsString(eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES),eq(""));
doReturn(config).when(clientFactory).getClientConfig(eq("default"));
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
HttpRequest springRequest = mock(HttpRequest.class);
Request feignRequest = Request.create("GET", "http://foo", new HashMap<String, Collection<String>>(),
new byte[]{}, StandardCharsets.UTF_8);
Client client = mock(Client.class);
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();
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);
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
assertEquals(200, ribbonResponse.toResponse().status());
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
}
@Test
public void getRequestSpecificRetryHandler() throws Exception {
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);

View File

@@ -86,6 +86,7 @@ public class RibbonLoadBalancedRetryPolicyFactoryTest {
doReturn(nextServer).when(config).getPropertyAsInteger(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt());
doReturn(retryOnAllOps).when(config).get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), anyBoolean());
doReturn(retryOnAllOps).when(config).getPropertyAsBoolean(eq(CommonClientConfigKey.OkToRetryOnAllOperations), anyBoolean());
doReturn("").when(config).getPropertyAsString(eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES),eq(""));
doReturn(server.getServiceId()).when(config).getClientName();
doReturn(config).when(clientFactory).getClientConfig(eq(server.getServiceId()));
clientFactory.getLoadBalancerContext(server.getServiceId()).setRetryHandler(new DefaultLoadBalancerRetryHandler(config));
@@ -97,6 +98,7 @@ public class RibbonLoadBalancedRetryPolicyFactoryTest {
LoadBalancedRetryContext context = new LoadBalancedRetryContext(null, request);
assertThat(policy.canRetryNextServer(context), is(true));
assertThat(policy.canRetrySameServer(context), is(false));
assertThat(policy.retryableStatusCode(400), is(false));
}
@Test
@@ -112,6 +114,7 @@ public class RibbonLoadBalancedRetryPolicyFactoryTest {
doReturn(nextServer).when(config).getPropertyAsInteger(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt());
doReturn(retryOnAllOps).when(config).get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), anyBoolean());
doReturn(retryOnAllOps).when(config).getPropertyAsBoolean(eq(CommonClientConfigKey.OkToRetryOnAllOperations), anyBoolean());
doReturn("").when(config).getPropertyAsString(eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES),eq(""));
doReturn(server.getServiceId()).when(config).getClientName();
doReturn(config).when(clientFactory).getClientConfig(eq(server.getServiceId()));
clientFactory.getLoadBalancerContext(server.getServiceId()).setRetryHandler(new DefaultLoadBalancerRetryHandler(config));
@@ -123,6 +126,7 @@ public class RibbonLoadBalancedRetryPolicyFactoryTest {
LoadBalancedRetryContext context = new LoadBalancedRetryContext(null, request);
assertThat(policy.canRetryNextServer(context), is(false));
assertThat(policy.canRetrySameServer(context), is(false));
assertThat(policy.retryableStatusCode(400), is(false));
}
@Test
@@ -138,6 +142,7 @@ public class RibbonLoadBalancedRetryPolicyFactoryTest {
doReturn(nextServer).when(config).getPropertyAsInteger(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt());
doReturn(retryOnAllOps).when(config).get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), anyBoolean());
doReturn(retryOnAllOps).when(config).getPropertyAsBoolean(eq(CommonClientConfigKey.OkToRetryOnAllOperations), anyBoolean());
doReturn("").when(config).getPropertyAsString(eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES),eq(""));
doReturn(server.getServiceId()).when(config).getClientName();
doReturn(config).when(clientFactory).getClientConfig(eq(server.getServiceId()));
clientFactory.getLoadBalancerContext(server.getServiceId()).initWithNiwsConfig(config);
@@ -149,6 +154,7 @@ public class RibbonLoadBalancedRetryPolicyFactoryTest {
LoadBalancedRetryContext context = new LoadBalancedRetryContext(null, request);
assertThat(policy.canRetryNextServer(context), is(true));
assertThat(policy.canRetrySameServer(context), is(true));
assertThat(policy.retryableStatusCode(400), is(false));
}
@Test
@@ -161,6 +167,7 @@ public class RibbonLoadBalancedRetryPolicyFactoryTest {
doReturn(nextServer).when(config).get(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt());
doReturn(false).when(config).get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
doReturn(config).when(clientFactory).getClientConfig(eq(server.getServiceId()));
doReturn("").when(config).getPropertyAsString(eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES),eq(""));
clientFactory.getLoadBalancerContext(server.getServiceId()).setRetryHandler(new DefaultLoadBalancerRetryHandler(config));
RibbonLoadBalancerClient client = getRibbonLoadBalancerClient(server);
RibbonLoadBalancedRetryPolicyFactory factory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
@@ -189,9 +196,32 @@ public class RibbonLoadBalancedRetryPolicyFactoryTest {
}
}
assertThat(context.isExhaustedOnly(), is(true));
assertThat(policy.retryableStatusCode(400), is(false));
verify(context, times(4)).setServiceInstance(any(ServiceInstance.class));
}
@Test
public void testRetryableStatusCodest() throws Exception {
int sameServer = 3;
int nextServer = 3;
RibbonServer server = getRibbonServer();
IClientConfig config = mock(IClientConfig.class);
doReturn(sameServer).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
doReturn(nextServer).when(config).get(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt());
doReturn(false).when(config).get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
doReturn(config).when(clientFactory).getClientConfig(eq(server.getServiceId()));
doReturn("404,502,foo, ,").when(config).getPropertyAsString(eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES),eq(""));
clientFactory.getLoadBalancerContext(server.getServiceId()).setRetryHandler(new DefaultLoadBalancerRetryHandler(config));
RibbonLoadBalancerClient client = getRibbonLoadBalancerClient(server);
RibbonLoadBalancedRetryPolicyFactory factory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
LoadBalancedRetryPolicy policy = factory.create(server.getServiceId(), client);
HttpRequest request = mock(HttpRequest.class);
doReturn(HttpMethod.GET).when(request).getMethod();
assertThat(policy.retryableStatusCode(400), is(false));
assertThat(policy.retryableStatusCode(404), is(true));
assertThat(policy.retryableStatusCode(502), is(true));
}
protected RibbonLoadBalancerClient getRibbonLoadBalancerClient(
RibbonServer ribbonServer) {
given(this.loadBalancer.getName()).willReturn(ribbonServer.getServiceId());

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.netflix.ribbon.apache;
import java.io.IOException;
import java.net.URI;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpUriRequest;
@@ -29,6 +30,7 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicy;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerContext;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
@@ -185,7 +187,7 @@ public class RibbonLoadBalancingHttpClientTests {
private RetryableRibbonLoadBalancingHttpClient setupClientForRetry(int retriesNextServer, int retriesSameServer,
boolean retryable, boolean retryOnAllOps,
String serviceName, String host, int port,
HttpClient delegate, ILoadBalancer lb) throws Exception {
HttpClient delegate, ILoadBalancer lb, String statusCodes) throws Exception {
ServerIntrospector introspector = mock(ServerIntrospector.class);
RetryHandler retryHandler = new DefaultLoadBalancerRetryHandler(retriesSameServer, retriesNextServer, retryable);
doReturn(new Server(host, port)).when(lb).chooseServer(eq(serviceName));
@@ -193,10 +195,12 @@ public class RibbonLoadBalancingHttpClientTests {
clientConfig.set(CommonClientConfigKey.OkToRetryOnAllOperations, retryOnAllOps);
clientConfig.set(CommonClientConfigKey.MaxAutoRetriesNextServer, retriesNextServer);
clientConfig.set(CommonClientConfigKey.MaxAutoRetries, retriesSameServer);
clientConfig.set(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES, statusCodes);
clientConfig.setClientName(serviceName);
RibbonLoadBalancerContext context = new RibbonLoadBalancerContext(lb, clientConfig, retryHandler);
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
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);
client.setLoadBalancer(lb);
@@ -217,10 +221,13 @@ public class RibbonLoadBalancingHttpClientTests {
URI uri = new URI("http://" + host + ":" + port);
HttpClient delegate = mock(HttpClient.class);
final HttpResponse response = mock(HttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
doReturn(200).when(statusLine).getStatusCode();
doReturn(statusLine).when(response).getStatusLine();
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, "");
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
doReturn(uri).when(request).getURI();
doReturn(method).when(request).getMethod();
@@ -246,11 +253,14 @@ public class RibbonLoadBalancingHttpClientTests {
URI uri = new URI("http://" + host + ":" + port);
HttpClient delegate = mock(HttpClient.class);
final HttpResponse response = mock(HttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
doReturn(200).when(statusLine).getStatusCode();
doReturn(statusLine).when(response).getStatusLine();
doThrow(new IOException("boom")).doThrow(new IOException("boom again")).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, "");
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
doReturn(uri).when(request).getURI();
doReturn(method).when(request).getMethod();
@@ -276,11 +286,14 @@ public class RibbonLoadBalancingHttpClientTests {
URI uri = new URI("http://" + host + ":" + port);
HttpClient delegate = mock(HttpClient.class);
final HttpResponse response = mock(HttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
doReturn(200).when(statusLine).getStatusCode();
doReturn(statusLine).when(response).getStatusLine();
doThrow(new IOException("boom")).doThrow(new IOException("boom again")).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, "");
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
doReturn(method).when(request).getMethod();
doReturn(uri).when(request).getURI();
@@ -309,7 +322,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, "");
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
doReturn(method).when(request).getMethod();
doReturn(uri).when(request).getURI();
@@ -326,6 +339,42 @@ public class RibbonLoadBalancingHttpClientTests {
}
}
@Test
public void testRetryOnStatusCode() throws Exception {
int retriesNextServer = 0;
int retriesSameServer = 1;
boolean retryable = true;
boolean retryOnAllOps = false;
String serviceName = "foo";
String host = serviceName;
int port = 80;
HttpMethod method = HttpMethod.GET;
URI uri = new URI("http://" + host + ":" + port);
HttpClient delegate = mock(HttpClient.class);
final HttpResponse response = mock(HttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
doReturn(200).when(statusLine).getStatusCode();
doReturn(statusLine).when(response).getStatusLine();
final HttpResponse fourOFourResponse = mock(HttpResponse.class);
StatusLine fourOFourStatusLine = mock(StatusLine.class);
doReturn(404).when(fourOFourStatusLine).getStatusCode();
doReturn(fourOFourStatusLine).when(fourOFourResponse).getStatusLine();
doReturn(fourOFourResponse).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, "404");
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
doReturn(uri).when(request).getURI();
doReturn(method).when(request).getMethod();
doReturn(request).when(request).withNewUri(any(URI.class));
HttpUriRequest uriRequest = mock(HttpUriRequest.class);
doReturn(uri).when(uriRequest).getURI();
doReturn(uriRequest).when(request).toRequest(any(RequestConfig.class));
RibbonApacheHttpResponse returnedResponse = client.execute(request, null);
verify(delegate, times(2)).execute(any(HttpUriRequest.class));
verify(lb, times(0)).chooseServer(eq(serviceName));
}
@Configuration
protected static class UseDefaults {
@@ -395,8 +444,12 @@ public class RibbonLoadBalancingHttpClientTests {
ReflectionTestUtils.setField(client, "delegate", delegate);
ReflectionTestUtils.setField(client, "lb", loadBalancer);
HttpResponse httpResponse = mock(HttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
doReturn(200).when(statusLine).getStatusCode();
doReturn(statusLine).when(httpResponse).getStatusLine();
given(delegate.execute(any(HttpUriRequest.class))).willReturn(
mock(HttpResponse.class));
httpResponse);
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
doReturn(uri).when(request).getURI();
doReturn(request).when(request).withNewUri(any(URI.class));

View File

@@ -0,0 +1,36 @@
/*
*
* * 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 RetryableStatusCodeExceptionTest {
@Test
public void testMessage() {
RetryableStatusCodeException ex = new RetryableStatusCodeException("foo", 404);
assertEquals("Service foo returned a status code of 404", ex.getMessage());
}
}

View File

@@ -61,7 +61,7 @@ public class ContextPathZuulProxyApplicationTests {
private DiscoveryClientRouteLocator routes;
@Autowired
private RoutesEndpoint endpoint;
private RoutesMvcEndpoint endpoint;
@Before
public void setTestRequestContext() {

View File

@@ -0,0 +1,85 @@
/*
*
* * 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.zuul;
import java.util.Map;
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.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RestController;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Ryan Baxter
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
value = {"zuul.routes.sslservice.url=https://localhost:8443", "management.security.enabled=false"})
@DirtiesContext
public class RoutesEndpointIntegrationTests {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private SimpleZuulProxyApplication.RoutesRefreshListener refreshListener;
@Test
public void getRoutesTest() {
Map<String, String> routes = restTemplate.getForObject("/admin/routes", Map.class);
assertEquals("https://localhost:8443", routes.get("/sslservice/**"));
}
@Test
public void postRoutesTest() {
Map<String, String> routes = restTemplate.postForObject("/admin/routes", null, Map.class);
assertEquals("https://localhost:8443", routes.get("/sslservice/**"));
assertTrue(refreshListener.wasCalled());
}
@Configuration
@EnableAutoConfiguration
@RestController
@EnableZuulProxy
static class SimpleZuulProxyApplication {
@Component
static class RoutesRefreshListener implements ApplicationListener<RoutesRefreshedEvent> {
private boolean called = false;
@Override
public void onApplicationEvent(RoutesRefreshedEvent routesRefreshedEvent) {
called = true;
}
public boolean wasCalled() {
return called;
}
}
}
}

View File

@@ -0,0 +1,86 @@
/*
*
* * 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.zuul;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Ryan Baxter
*/
public class RoutesEndpointTests {
private RouteLocator locator;
@Before
public void setUp() {
this.locator = new RouteLocator() {
@Override
public Collection<String> getIgnoredPaths() {
return null;
}
@Override
public List<Route> getRoutes() {
List<Route> routes = new ArrayList<>();
routes.add(new Route("foo", "foopath", "foolocation", null, true, Collections.EMPTY_SET));
routes.add(new Route("bar", "barpath", "barlocation", null, true, Collections.EMPTY_SET));
return routes;
}
@Override
public Route getMatchingRoute(String path) {
return null;
}
};
}
@Test
public void testInvoke() {
RoutesEndpoint endpoint = new RoutesEndpoint(locator);
Map<String, String> result = new HashMap<String, String>();
for(Route r : locator.getRoutes()) {
result.put(r.getFullPath(), r.getLocation());
}
assertEquals(result , endpoint.invoke());
}
@Test
public void testId() {
RoutesEndpoint endpoint = new RoutesEndpoint(locator);
assertEquals("routes", endpoint.getId());
}
@Test
public void testIsSensitive() {
RoutesEndpoint endpoint = new RoutesEndpoint(locator);
assertTrue(endpoint.isSensitive());
}
}

View File

@@ -0,0 +1,91 @@
/*
*
* * 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.zuul;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.context.ApplicationEventPublisher;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Ryan Baxter
*/
@SpringBootTest
@RunWith(MockitoJUnitRunner.class)
public class RoutesMvcEndpointTests {
private RouteLocator locator;
private RoutesEndpoint endpoint;
@Mock
private ApplicationEventPublisher publisher;
@Before
public void setUp() {
this.locator = new RouteLocator() {
@Override
public Collection<String> getIgnoredPaths() {
return null;
}
@Override
public List<Route> getRoutes() {
List<Route> routes = new ArrayList<>();
routes.add(new Route("foo", "foopath", "foolocation", null, true, Collections.EMPTY_SET));
routes.add(new Route("bar", "barpath", "barlocation", null, true, Collections.EMPTY_SET));
return routes;
}
@Override
public Route getMatchingRoute(String path) {
return null;
}
};
endpoint = spy(new RoutesEndpoint(locator));
}
@Test
public void reset() throws Exception {
RoutesMvcEndpoint mvcEndpoint = new RoutesMvcEndpoint(endpoint, locator);
mvcEndpoint.setApplicationEventPublisher(publisher);
Map<String, String> result = new HashMap<String, String>();
for(Route r : locator.getRoutes()) {
result.put(r.getFullPath(), r.getLocation());
}
assertEquals(result , mvcEndpoint.reset());
verify(endpoint, times(1)).invoke();
verify(publisher, times(1)).publishEvent(isA(RoutesRefreshedEvent.class));
}
}

View File

@@ -66,7 +66,7 @@ public class ServletPathZuulProxyApplicationTests {
private DiscoveryClientRouteLocator routes;
@Autowired
private RoutesEndpoint endpoint;
private RoutesMvcEndpoint endpoint;
@Before
public void setTestRequestContext() {

View File

@@ -66,7 +66,7 @@ public class SimpleZuulProxyApplicationTests {
private DiscoveryClientRouteLocator routes;
@Autowired
private RoutesEndpoint endpoint;
private RoutesMvcEndpoint endpoint;
@Before
public void setTestRequestContext() {

View File

@@ -0,0 +1,102 @@
/*
* 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;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContextEvent;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ReflectionUtils;
import com.netflix.zuul.FilterLoader;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.filters.FilterRegistry;
import com.netflix.zuul.monitoring.CounterFactory;
import com.netflix.zuul.monitoring.TracerFactory;
import org.junit.Test;
import java.lang.reflect.Constructor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
public class ZuulFilterInitializerTests {
private static final ServletContextEvent DUMMY_SERVLET_CONTEXT_EVENT = mock(
ServletContextEvent.class);
private Map<String, ZuulFilter> filters = getFilters();
private CounterFactory counterFactory = mock(CounterFactory.class);
private TracerFactory tracerFactory = mock(TracerFactory.class);
private FilterLoader filterLoader = new FilterLoader();
private FilterRegistry filterRegistry = getFilterRegistry();
private final ZuulFilterInitializer initializer = new ZuulFilterInitializer(filters,
counterFactory, tracerFactory, filterLoader, filterRegistry);
@Test
public void shouldSetupOnContextInitializedEvent() throws Exception {
initializer.contextInitialized(DUMMY_SERVLET_CONTEXT_EVENT);
assertEquals(tracerFactory, TracerFactory.instance());
assertEquals(counterFactory, CounterFactory.instance());
assertThat(filterRegistry.getAllFilters())
.containsAll(filters.values());
}
@Test
public void shouldCleanupOnContextDestroyed() throws Exception {
initializer.contextDestroyed(DUMMY_SERVLET_CONTEXT_EVENT);
assertEquals(null, ReflectionTestUtils.getField(TracerFactory.class, "INSTANCE"));
assertEquals(null,
ReflectionTestUtils.getField(CounterFactory.class, "INSTANCE"));
assertTrue(FilterRegistry.instance().getAllFilters().isEmpty());
assertTrue(getHashFiltersByType().isEmpty());
}
private Map getHashFiltersByType() {
Field field = ReflectionUtils.findField(FilterLoader.class, "hashFiltersByType");
ReflectionUtils.makeAccessible(field);
return (Map) ReflectionUtils.getField(field, FilterLoader.getInstance());
}
private Map<String, ZuulFilter> getFilters() {
Map<String, ZuulFilter> filters = new HashMap<>();
filters.put("key1", mock(ZuulFilter.class));
filters.put("key2", mock(ZuulFilter.class));
return filters;
}
private FilterRegistry getFilterRegistry() {
try {
Constructor<FilterRegistry> constructor = FilterRegistry.class
.getDeclaredConstructor(new Class[0]);
constructor.setAccessible(true);
return constructor.newInstance(new Object[0]);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,122 @@
/*
* 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;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.LocalServerPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.netflix.zuul.context.RequestContext;
import static org.junit.Assert.assertEquals;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ZuulProxyApplicationTests.ZuulProxyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"zuul.routes.simple:/simple/**", "logging.level.org.apache.http: DEBUG" })
@DirtiesContext
public class ZuulProxyApplicationTests {
@LocalServerPort
private int port;
@Before
public void setTestRequestcontext() {
RequestContext context = new RequestContext();
RequestContext.testSetCurrentContext(context);
}
@Test
public void getHasCorrectTransferEncoding() {
ResponseEntity<String> result = new TestRestTemplate().getForEntity(
"http://localhost:" + this.port + "/simple/transferencoding", String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("missing", result.getBody());
}
@Test
public void postHasCorrectTransferEncoding() {
ResponseEntity<String> result = new TestRestTemplate().postForEntity(
"http://localhost:" + this.port + "/simple/transferencoding", new HttpEntity<>("hello"),
String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("missing", result.getBody());
}
// Don't use @SpringBootApplication because we don't want to component scan
@Configuration
@EnableAutoConfiguration
@RestController
@EnableZuulProxy
@RibbonClient(name = "simple", configuration = TestRibbonClientConfiguration.class)
static class ZuulProxyApplication {
@RequestMapping(value = "/transferencoding", method = RequestMethod.GET)
public String get(@RequestHeader(name = "Transfer-Encoding", required = false) String transferEncoding) {
if (transferEncoding == null) {
return "missing";
}
return transferEncoding;
}
@RequestMapping(value = "/transferencoding", method = RequestMethod.POST)
public String post(@RequestHeader(name = "Transfer-Encoding", required = false) String transferEncoding,
@RequestBody String hello) {
if (transferEncoding == null) {
return "missing";
}
return transferEncoding;
}
}
// Load balancer with fixed server list for "simple" pointing to localhost
@Configuration
static class TestRibbonClientConfiguration {
@LocalServerPort
private int port;
@Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", this.port));
}
}
}

View File

@@ -23,7 +23,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.RoutesEndpoint;
import org.springframework.cloud.netflix.zuul.RoutesMvcEndpoint;
import org.springframework.cloud.netflix.zuul.ZuulProxyConfiguration;
import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter;
@@ -59,7 +59,7 @@ public class CustomHostRoutingFilterTests {
private DiscoveryClientRouteLocator routes;
@Autowired
private RoutesEndpoint endpoint;
private RoutesMvcEndpoint endpoint;
@Before
public void setTestRequestcontext() {

View File

@@ -22,7 +22,7 @@ import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.RoutesEndpoint;
import org.springframework.cloud.netflix.zuul.RoutesMvcEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpEntity;
@@ -59,7 +59,7 @@ public class PatternServiceRouteMapperIntegrationTests {
private DiscoveryClientRouteLocator routes;
@Autowired
private RoutesEndpoint endpoint;
private RoutesMvcEndpoint endpoint;
@Before
public void setTestRequestcontext() {

View File

@@ -47,7 +47,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
"disableretry.ribbon.MaxAutoRetriesNextServer: 1",
"zuul.routes.globalretrydisabled: /globalretrydisabled/**",
"globalretrydisabled.ribbon.MaxAutoRetries: 1",
"globalretrydisabled.ribbon.MaxAutoRetriesNextServer: 1"
"globalretrydisabled.ribbon.MaxAutoRetriesNextServer: 1",
"retryable.ribbon.retryableStatusCodes: 404,403"
})
@DirtiesContext
public class HttpClientRibbonRetryIntegrationTests extends RibbonRetryIntegrationTestBase {

View File

@@ -36,6 +36,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
"zuul.routes.retryable: /retryable/**",
"zuul.routes.retryable.retryable: true",
"retryable.ribbon.OkToRetryOnAllOperations: true",
"retryable.ribbon.retryableStatusCodes: 404",
"retryable.ribbon.MaxAutoRetries: 1",
"retryable.ribbon.MaxAutoRetriesNextServer: 1",
"zuul.routes.getretryable: /getretryable/**",

View File

@@ -25,8 +25,15 @@ import org.junit.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicy;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerContext;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
@@ -37,6 +44,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.netflix.loadbalancer.Server;
@@ -74,6 +82,15 @@ public abstract class RibbonRetryIntegrationTestBase {
assertEquals(HttpStatus.OK, result.getStatusCode());
}
@Test
public void retryableFourOFour() {
String uri = "/retryable/404everyothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
}
@Test
public void postRetryOK() {
String uri = "/retryable/posteveryothererror";
@@ -161,6 +178,17 @@ public abstract class RibbonRetryIntegrationTestBase {
return timeout();
}
@RequestMapping("/404everyothererror")
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<String> fourOFourError() {
boolean shouldError = error;
error = !error;
if(shouldError) {
return new ResponseEntity<String>("not found", HttpStatus.NOT_FOUND);
}
return new ResponseEntity<String>("no error", HttpStatus.OK);
}
}
@Configuration
@@ -173,6 +201,46 @@ public abstract class RibbonRetryIntegrationTestBase {
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", this.port));
}
}
@Configuration
public static class FourOFourRetryableRibbonConfiguration extends RibbonClientConfiguration {
@Bean
public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory(SpringClientFactory factory) {
return new MyRibbonRetryPolicyFactory(factory);
}
public static class MyRibbonRetryPolicyFactory extends RibbonLoadBalancedRetryPolicyFactory {
private SpringClientFactory factory;
public MyRibbonRetryPolicyFactory(SpringClientFactory clientFactory) {
super(clientFactory);
this.factory = clientFactory;
}
@Override
public LoadBalancedRetryPolicy create(String serviceId, ServiceInstanceChooser loadBalanceChooser) {
RibbonLoadBalancerContext lbContext = this.factory
.getLoadBalancerContext(serviceId);
return new MyLoadBalancedRetryPolicy(serviceId, lbContext, loadBalanceChooser);
}
class MyLoadBalancedRetryPolicy extends RibbonLoadBalancedRetryPolicy {
public MyLoadBalancedRetryPolicy(String serviceId, RibbonLoadBalancerContext context, ServiceInstanceChooser loadBalanceChooser) {
super(serviceId, context, loadBalanceChooser);
}
@Override
public boolean retryableStatusCode( int statusCode) {
if(statusCode == HttpStatus.NOT_FOUND.value()) {
return true;
}
return super.retryableStatusCode(statusCode);
}
}
}
}
}

View File

@@ -37,7 +37,7 @@ import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorContro
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorAttributes;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.cloud.netflix.zuul.RoutesEndpoint;
import org.springframework.cloud.netflix.zuul.RoutesMvcEndpoint;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator;
@@ -88,7 +88,7 @@ public abstract class ZuulProxyTestBase {
protected DiscoveryClientRouteLocator routes;
@Autowired
protected RoutesEndpoint endpoint;
protected RoutesMvcEndpoint endpoint;
@Autowired
protected RibbonCommandFactory<?> ribbonCommandFactory;

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2013-2015 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.metrics;
import org.springframework.boot.actuate.metrics.CounterService;
import com.netflix.zuul.monitoring.CounterFactory;
import org.junit.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class DefaultCounterFactoryTests {
private static final String NAME = "my-super-metric-name";
private final CounterService counterService = mock(CounterService.class);
private final CounterFactory factory = new DefaultCounterFactory(counterService);
@Test
public void shouldIncrement() throws Exception {
factory.increment(NAME);
verify(counterService).increment(NAME);
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2013-2015 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.metrics;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.cloud.ClassPathExclusions;
import org.springframework.cloud.netflix.zuul.ZuulConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.zuul.monitoring.CounterFactory;
import com.netflix.zuul.monitoring.TracerFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@ClassPathExclusions({ "spring-boot-starter-actuator-*.jar",
"spring-boot-actuator-*.jar" })
public class ZuulEmptyMetricsApplicationTests {
private AnnotationConfigApplicationContext context;
@Before
public void setUp() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(ZuulEmptyMetricsApplicationTestsConfiguration.class,
ZuulConfiguration.class);
context.refresh();
this.context = context;
}
@After
public void tearDown() throws Exception {
if (this.context != null) {
this.context.close();
}
}
@Test
public void shouldSetupDefaultCounterFactoryIfCounterServiceIsPresent()
throws Exception {
CounterFactory factory = this.context.getBean(CounterFactory.class);
assertEquals(EmptyCounterFactory.class, factory.getClass());
}
@Test
public void shouldSetupEmptyTracerFactory() throws Exception {
TracerFactory factory = this.context.getBean(TracerFactory.class);
assertEquals(EmptyTracerFactory.class, factory.getClass());
}
@Configuration
static class ZuulEmptyMetricsApplicationTestsConfiguration {
@Bean
ServerProperties serverProperties() {
return new ServerProperties();
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2013-2015 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.metrics;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.netflix.zuul.EnableZuulServer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import com.netflix.zuul.exception.ZuulException;
import com.netflix.zuul.monitoring.CounterFactory;
import com.netflix.zuul.monitoring.TracerFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {
ZuulMetricsApplicationTests.ZuulMetricsApplicationTestsConfiguration.class,
ZuulMetricsApplicationTests.ZuulConfig.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext
public class ZuulMetricsApplicationTests {
private static final Map<String, Long> counters = new HashMap<>();
@Autowired
CounterFactory counterFactory;
@Autowired
TracerFactory tracerFactory;
@Test
public void shouldSetupDefaultCounterFactoryIfCounterServiceIsPresent()
throws Exception {
assertEquals(DefaultCounterFactory.class, counterFactory.getClass());
}
@Test
public void shouldSetupEmptyTracerFactory() throws Exception {
assertEquals(EmptyTracerFactory.class, tracerFactory.getClass());
}
@Test
public void shouldIncrementCounters() throws Exception {
new ZuulException("any", 500, "cause");
new ZuulException("any", 500, "cause");
assertEquals((long) counters.get("ZUUL::EXCEPTION:cause:500"), 2L);
new ZuulException("any", 404, "cause2");
new ZuulException("any", 404, "cause2");
new ZuulException("any", 404, "cause2");
assertEquals((long) counters.get("ZUUL::EXCEPTION:cause2:404"), 3L);
}
// Don't use @SpringBootApplication because we don't want to component scan
@Configuration
@EnableAutoConfiguration
@EnableZuulServer
static class ZuulConfig {
}
@Configuration
static class ZuulMetricsApplicationTestsConfiguration {
@Bean
public ServerProperties serverProperties() {
return new ServerProperties();
}
@Bean
public CounterService counterService() {
return new CounterService() {
// not thread safe, but we are ok with it in tests
@Override
public void increment(String metricName) {
Long counter = counters.get(metricName);
if (counter == null) {
counter = 0L;
}
counters.put(metricName, ++counter);
}
@Override
public void decrement(String metricName) {
}
@Override
public void reset(String metricName) {
}
};
}
}
}

View File

@@ -27,6 +27,7 @@ import java.util.Map;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.util.Assert;
import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.appinfo.InstanceInfo;
@@ -102,7 +103,8 @@ public class EurekaDiscoveryClient implements DiscoveryClient {
public static class EurekaServiceInstance implements ServiceInstance {
private InstanceInfo instance;
EurekaServiceInstance(InstanceInfo instance) {
public EurekaServiceInstance(InstanceInfo instance) {
Assert.notNull(instance, "Service instance required");
this.instance = instance;
}

View File

@@ -126,6 +126,10 @@
color: red;
}
.badRequest {
color: #00CC99;
}
.dependencies .rejected {
color: purple;
}

View File

@@ -17,6 +17,7 @@
<div class="cell borderRight">
<a href="javascript://" title="Successful Request Count" class="line tooltip success"><%= addCommas(rollingCountSuccess) %></a>
<a href="javascript://" title="Short-circuited Request Count" class="line tooltip shortCircuited"><%= addCommas(rollingCountShortCircuited) %></a>
<a href="javascript://" title="Bad Request Count" class="line tooltip badRequest"><%= addCommas(rollingCountBadRequests) %></a>
<br>
</div>
</div>

View File

@@ -93,6 +93,10 @@ h3.sectionHeader {
color: brown;
}
.badRequest {
color: #00CC99;
}
@media screen and (max-width: 1100px) {
.container {
padding-left: 5px;

View File

@@ -54,7 +54,7 @@
<a href="javascript://" onclick="hystrixMonitor.sortByLatency995();">99.5</a>
</div>
<div class="menu_legend">
<span class="success">Success</span> | <span class="shortCircuited">Short-Circuited</span> | <span class="timeout">Timeout</span> | <span class="rejected">Rejected</span> | <span class="failure">Failure</span> | <span class="errorPercentage">Error %</span>
<span class="success">Success</span> | <span class="shortCircuited">Short-Circuited</span> | <span class="badRequest"> Bad Request</span> | <span class="timeout">Timeout</span> | <span class="rejected">Rejected</span> | <span class="failure">Failure</span> | <span class="errorPercentage">Error %</span>
</div>
</div>
</div>