From 4c53fd2bf67626bae6822a2d2acc07e48dbe028c Mon Sep 17 00:00:00 2001 From: Dominik Mostek Date: Wed, 6 Sep 2017 21:19:08 +0200 Subject: [PATCH] Fallback cause is propagated to fallback provider Cause can now be propagated to fallback provider in order to respond accordingly to a exception type. New interface is introduced in order not to break any existing falback providers. --- .../zuul/filters/route/FallbackProvider.java | 36 +++ .../filters/route/ZuulFallbackProvider.java | 2 + .../route/support/AbstractRibbonCommand.java | 24 +- ...onCommandCauseFallbackPropagationTest.java | 264 ++++++++++++++++++ 4 files changed, 324 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/FallbackProvider.java create mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/RibbonCommandCauseFallbackPropagationTest.java diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/FallbackProvider.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/FallbackProvider.java new file mode 100644 index 00000000..42e5ddfa --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/FallbackProvider.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.zuul.filters.route; + +import org.springframework.http.client.ClientHttpResponse; + +/** + * Extension of {@link ZuulFallbackProvider} which adds possibility to choose proper response + * based on the exception which caused the main method to fail. + * + * @author Dominik Mostek + */ +public interface FallbackProvider extends ZuulFallbackProvider { + + /** + * Provides a fallback response based on the cause of the failed execution. + * + * @param cause cause of the main method failure + * @return the fallback response + */ + ClientHttpResponse fallbackResponse(Throwable cause); +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/ZuulFallbackProvider.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/ZuulFallbackProvider.java index d5383b52..479655f0 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/ZuulFallbackProvider.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/ZuulFallbackProvider.java @@ -23,7 +23,9 @@ import org.springframework.http.client.ClientHttpResponse; /** * Provides fallback when a failure occurs on a route. * @author Ryan Baxter + * @deprecated Use {@link FallbackProvider} */ +@Deprecated public interface ZuulFallbackProvider { /** diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/support/AbstractRibbonCommand.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/support/AbstractRibbonCommand.java index 5431405e..eeacb4a7 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/support/AbstractRibbonCommand.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/support/AbstractRibbonCommand.java @@ -22,6 +22,7 @@ import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider; +import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; import org.springframework.http.client.ClientHttpResponse; import com.netflix.client.AbstractLoadBalancerAwareClient; import com.netflix.client.ClientRequest; @@ -68,7 +69,13 @@ public abstract class AbstractRibbonCommand, ClientRequest, HttpResponse> { + + public TestRibbonCommand(AbstractLoadBalancerAwareClient client, ZuulFallbackProvider fallbackProvider) { + this(client, new ZuulProperties(), fallbackProvider); + } + + public TestRibbonCommand(AbstractLoadBalancerAwareClient client, + ZuulProperties zuulProperties, + ZuulFallbackProvider fallbackProvider) { + super("testCommand", client, null, zuulProperties, fallbackProvider); + } + + public TestRibbonCommand(AbstractLoadBalancerAwareClient client, + ZuulFallbackProvider fallbackProvider, + int timeout) { + // different name is used because of properties caching + super(getSetter("testCommand2", new ZuulProperties()).andCommandPropertiesDefaults(defauts(timeout)), + client, null, fallbackProvider, null); + } + + private static HystrixCommandProperties.Setter defauts(final int timeout) { + return HystrixCommandProperties.Setter().withExecutionTimeoutEnabled(true) + .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD) + .withExecutionTimeoutInMilliseconds(timeout); + } + + @Override + protected ClientRequest createRequest() throws Exception { + return null; + } + } + + public static class TestClient extends AbstractLoadBalancerAwareClient { + + private final RuntimeException exception; + + public TestClient(RuntimeException exception) { + super(null); + this.exception = exception; + } + + @Override + public IResponse executeWithLoadBalancer(final ClientRequest request, final IClientConfig requestConfig) throws ClientException { + throw exception; + } + + @Override + public RequestSpecificRetryHandler getRequestSpecificRetryHandler(final ClientRequest clientRequest, final IClientConfig iClientConfig) { + return null; + } + + @Override + public IResponse execute(final ClientRequest clientRequest, final IClientConfig iClientConfig) throws Exception { + return null; + } + } + + public static class TestFallbackProvider implements FallbackProvider { + + private final ClientHttpResponse response; + private Throwable cause; + + public TestFallbackProvider(final ClientHttpResponse response) { + this.response = response; + } + + @Override + public ClientHttpResponse fallbackResponse(final Throwable cause) { + this.cause = cause; + return response; + } + + @Override + public String getRoute() { + return "test-route"; + } + + @Override + public ClientHttpResponse fallbackResponse() { + throw new UnsupportedOperationException("fallback without cause is not supported"); + } + + public Throwable getCause() { + return cause; + } + + public static TestFallbackProvider withResponse(final HttpStatus status) { + return new TestFallbackProvider(getClientHttpResponse(status)); + } + } + + public static class TestZuulFallbackProviderWithoutCause implements ZuulFallbackProvider { + + private final ClientHttpResponse response; + + public TestZuulFallbackProviderWithoutCause(final ClientHttpResponse response) { + this.response = response; + } + + public TestZuulFallbackProviderWithoutCause(final HttpStatus status) { + this(getClientHttpResponse(status)); + } + + @Override + public String getRoute() { + return "test-route"; + } + + @Override + public ClientHttpResponse fallbackResponse() { + return response; + } + } + + private static ClientHttpResponse getClientHttpResponse(final HttpStatus status) { + return new ClientHttpResponse() { + @Override + public HttpStatus getStatusCode() throws IOException { + return status; + } + + @Override + public int getRawStatusCode() throws IOException { + return getStatusCode().value(); + } + + @Override + public String getStatusText() throws IOException { + return getStatusCode().getReasonPhrase(); + } + + @Override + public void close() { + } + + @Override + public InputStream getBody() throws IOException { + return new ByteArrayInputStream("test".getBytes()); + } + + @Override + public HttpHeaders getHeaders() { + return new HttpHeaders(); + } + }; + } +}