diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 96e72881..30128a0c 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -1978,6 +1978,68 @@ class MyFallbackProvider implements ZuulFallbackProvider { } ---- +If you would like to choose the response based on the cause of the failure use `FallbackProvider` which will replace `ZuulFallbackProvder` in future versions. + +[source,java] +---- +class MyFallbackProvider implements FallbackProvider { + + @Override + public String getRoute() { + return "*"; + } + + @Override + public ClientHttpResponse fallbackResponse(final Throwable cause) { + if (cause instanceof HystrixTimeoutException) { + return response(HttpStatus.GATEWAY_TIMEOUT); + } else { + return fallbackResponse(); + } + } + + @Override + public ClientHttpResponse fallbackResponse() { + return response(HttpStatus.INTERNAL_SERVER_ERROR); + } + + private ClientHttpResponse response(final HttpStatus status) { + return new ClientHttpResponse() { + @Override + public HttpStatus getStatusCode() throws IOException { + return status; + } + + @Override + public int getRawStatusCode() throws IOException { + return status.value(); + } + + @Override + public String getStatusText() throws IOException { + return status.getReasonPhrase(); + } + + @Override + public void close() { + } + + @Override + public InputStream getBody() throws IOException { + return new ByteArrayInputStream("fallback".getBytes()); + } + + @Override + public HttpHeaders getHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + return headers; + } + }; + } +} +---- + [[zuul-redirect-location-rewrite]] === Rewriting `Location` header 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 8497284f..47d7e929 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 @@ -23,6 +23,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; @@ -69,7 +70,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(); + } + }; + } +}