Merge pull request #2255 from DominikMostek/fallback_cause
Fallback cause is propagated to fallback provider
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<LBC extends AbstractLoadBalancerAwar
|
||||
public AbstractRibbonCommand(String commandKey, LBC client,
|
||||
RibbonCommandContext context, ZuulProperties zuulProperties,
|
||||
ZuulFallbackProvider fallbackProvider, IClientConfig config) {
|
||||
super(getSetter(commandKey, zuulProperties));
|
||||
this(getSetter(commandKey, zuulProperties), client, context, fallbackProvider, config);
|
||||
}
|
||||
|
||||
protected AbstractRibbonCommand(Setter setter, LBC client,
|
||||
RibbonCommandContext context,
|
||||
ZuulFallbackProvider fallbackProvider, IClientConfig config) {
|
||||
super(setter);
|
||||
this.client = client;
|
||||
this.context = context;
|
||||
this.zuulFallbackProvider = fallbackProvider;
|
||||
@@ -126,11 +133,24 @@ public abstract class AbstractRibbonCommand<LBC extends AbstractLoadBalancerAwar
|
||||
@Override
|
||||
protected ClientHttpResponse getFallback() {
|
||||
if(zuulFallbackProvider != null) {
|
||||
return zuulFallbackProvider.fallbackResponse();
|
||||
return getFallbackResponse();
|
||||
}
|
||||
return super.getFallback();
|
||||
}
|
||||
|
||||
protected ClientHttpResponse getFallbackResponse() {
|
||||
if (zuulFallbackProvider instanceof FallbackProvider) {
|
||||
Throwable cause = getFailedExecutionException();
|
||||
cause = cause == null ? getExecutionException() : cause;
|
||||
if (cause == null) {
|
||||
zuulFallbackProvider.fallbackResponse();
|
||||
} else {
|
||||
return ((FallbackProvider) zuulFallbackProvider).fallbackResponse(cause);
|
||||
}
|
||||
}
|
||||
return zuulFallbackProvider.fallbackResponse();
|
||||
}
|
||||
|
||||
public LBC getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* 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.support;
|
||||
|
||||
import com.netflix.client.AbstractLoadBalancerAwareClient;
|
||||
import com.netflix.client.ClientException;
|
||||
import com.netflix.client.ClientRequest;
|
||||
import com.netflix.client.IResponse;
|
||||
import com.netflix.client.RequestSpecificRetryHandler;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.client.http.HttpResponse;
|
||||
import com.netflix.hystrix.HystrixCommandProperties;
|
||||
import com.netflix.hystrix.exception.HystrixTimeoutException;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Dominik Mostek
|
||||
*/
|
||||
public class RibbonCommandCauseFallbackPropagationTest {
|
||||
|
||||
@Test
|
||||
public void providerIsCalledInCaseOfException() throws Exception {
|
||||
TestZuulFallbackProviderWithoutCause provider = new TestZuulFallbackProviderWithoutCause(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
RuntimeException exception = new RuntimeException("Failed!");
|
||||
TestRibbonCommand testCommand = new TestRibbonCommand(new TestClient(exception), provider);
|
||||
|
||||
ClientHttpResponse response = testCommand.execute();
|
||||
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void causeIsProvidedForNewInterface() throws Exception {
|
||||
TestFallbackProvider provider = TestFallbackProvider.withResponse(HttpStatus.NOT_FOUND);
|
||||
RuntimeException exception = new RuntimeException("Failed!");
|
||||
TestRibbonCommand testCommand = new TestRibbonCommand(new TestClient(exception), provider);
|
||||
|
||||
ClientHttpResponse response = testCommand.execute();
|
||||
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||
Throwable cause = provider.getCause();
|
||||
assertThat(cause.getClass()).isEqualTo(exception.getClass());
|
||||
assertThat(cause.getMessage()).isEqualTo(exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executionExceptionIsUsedInsteadWhenFailedExceptionIsNull() throws Exception {
|
||||
TestFallbackProvider provider = TestFallbackProvider.withResponse(HttpStatus.BAD_GATEWAY);
|
||||
final RuntimeException exception = new RuntimeException("Failed!");
|
||||
TestRibbonCommand testCommand = new TestRibbonCommand(new TestClient(exception), provider) {
|
||||
@Override
|
||||
public Throwable getFailedExecutionException() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable getExecutionException() {
|
||||
return exception;
|
||||
}
|
||||
};
|
||||
|
||||
ClientHttpResponse response = testCommand.execute();
|
||||
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_GATEWAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timeoutExceptionIsPropagated() throws Exception {
|
||||
TestFallbackProvider provider = TestFallbackProvider.withResponse(HttpStatus.CONFLICT);
|
||||
RuntimeException exception = new RuntimeException("Failed!");
|
||||
TestRibbonCommand testCommand = new TestRibbonCommand(new TestClient(exception), provider, 1) {
|
||||
@Override
|
||||
protected ClientRequest createRequest() throws Exception {
|
||||
Thread.sleep(5);
|
||||
return super.createRequest();
|
||||
}
|
||||
};
|
||||
|
||||
ClientHttpResponse response = testCommand.execute();
|
||||
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
|
||||
assertThat(provider.getCause()).isNotNull();
|
||||
assertThat(provider.getCause().getClass()).isEqualTo(HystrixTimeoutException.class);
|
||||
}
|
||||
|
||||
public static class TestRibbonCommand
|
||||
extends AbstractRibbonCommand<AbstractLoadBalancerAwareClient<ClientRequest, HttpResponse>, ClientRequest, HttpResponse> {
|
||||
|
||||
public TestRibbonCommand(AbstractLoadBalancerAwareClient<ClientRequest, HttpResponse> client, ZuulFallbackProvider fallbackProvider) {
|
||||
this(client, new ZuulProperties(), fallbackProvider);
|
||||
}
|
||||
|
||||
public TestRibbonCommand(AbstractLoadBalancerAwareClient<ClientRequest, HttpResponse> client,
|
||||
ZuulProperties zuulProperties,
|
||||
ZuulFallbackProvider fallbackProvider) {
|
||||
super("testCommand", client, null, zuulProperties, fallbackProvider);
|
||||
}
|
||||
|
||||
public TestRibbonCommand(AbstractLoadBalancerAwareClient<ClientRequest, HttpResponse> 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user