From 731c8bf0fde6b597299ec20ff937e81f8b677abf Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Tue, 14 Feb 2017 13:59:25 -0500 Subject: [PATCH] Add the ability to provide a default ZuulFallbackProvider. Fixes #1506 --- .../main/asciidoc/spring-cloud-netflix.adoc | 50 +++++++++ .../support/AbstractRibbonCommandFactory.java | 14 ++- .../HttpClientRibbonCommandFallbackTests.java | 2 +- .../OkHttpRibbonCommandFallbackTests.java | 2 +- .../RestClientRibbonCommandFallbackTests.java | 2 +- .../support/RibbonCommandFallbackTests.java | 105 +++++++++++++++++- 6 files changed, 168 insertions(+), 7 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 00b26bf5..55881390 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -1755,6 +1755,56 @@ zuul: customers: /customers/** ---- +If you would like to provide a default fallback for all routes than you can create a bean of +type `ZuulFallbackProvider` and have the `getRoute` method return `*` or `null`. + +[source,java] +---- +class MyFallbackProvider implements ZuulFallbackProvider { + @Override + public String getRoute() { + return "*"; + } + + @Override + public ClientHttpResponse fallbackResponse() { + return new ClientHttpResponse() { + @Override + public HttpStatus getStatusCode() throws IOException { + return HttpStatus.OK; + } + + @Override + public int getRawStatusCode() throws IOException { + return 200; + } + + @Override + public String getStatusText() throws IOException { + return "OK"; + } + + @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-developer-guide]] === Zuul Developer Guide diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/support/AbstractRibbonCommandFactory.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/support/AbstractRibbonCommandFactory.java index 11c97a21..cc8c4e03 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/support/AbstractRibbonCommandFactory.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/support/AbstractRibbonCommandFactory.java @@ -30,15 +30,25 @@ import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider public abstract class AbstractRibbonCommandFactory implements RibbonCommandFactory { private Map fallbackProviderCache; + private ZuulFallbackProvider defaultFallbackProvider = null; public AbstractRibbonCommandFactory(Set fallbackProviders){ this.fallbackProviderCache = new HashMap<>(); for(ZuulFallbackProvider provider : fallbackProviders) { - fallbackProviderCache.put(provider.getRoute(), provider); + String route = provider.getRoute(); + if("*".equals(route) || route == null) { + defaultFallbackProvider = provider; + } else { + fallbackProviderCache.put(route, provider); + } } } protected ZuulFallbackProvider getFallbackProvider(String route) { - return fallbackProviderCache.get(route); + ZuulFallbackProvider provider = fallbackProviderCache.get(route); + if(provider == null) { + provider = defaultFallbackProvider; + } + return provider; } } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommandFallbackTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommandFallbackTests.java index 43a94807..085e12ae 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommandFallbackTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommandFallbackTests.java @@ -33,7 +33,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen * @author Ryan Baxter */ @RunWith(SpringRunner.class) -@SpringBootTest(classes = HttpClientRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = RANDOM_PORT, +@SpringBootTest(classes = RibbonCommandFallbackTests.TestConfig.class, webEnvironment = RANDOM_PORT, properties = { "zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**", "ribbon.ReadTimeout: 1"}) diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandFallbackTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandFallbackTests.java index 864e5504..fe07b64e 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandFallbackTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandFallbackTests.java @@ -31,7 +31,7 @@ import com.netflix.zuul.context.RequestContext; * @author Ryan Baxter */ @RunWith(SpringJUnit4ClassRunner.class) -@SpringBootTest(classes = OkHttpRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = { +@SpringBootTest(classes = RibbonCommandFallbackTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = { "zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**", "ribbon.ReadTimeout: 1"}) @DirtiesContext diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/restclient/RestClientRibbonCommandFallbackTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/restclient/RestClientRibbonCommandFallbackTests.java index ca9784f3..8496dbe4 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/restclient/RestClientRibbonCommandFallbackTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/restclient/RestClientRibbonCommandFallbackTests.java @@ -31,7 +31,7 @@ import com.netflix.zuul.context.RequestContext; * @author Ryan Baxter */ @RunWith(SpringJUnit4ClassRunner.class) -@SpringBootTest(classes = RestClientRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = { +@SpringBootTest(classes = RibbonCommandFallbackTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = { "zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**", "ribbon.ReadTimeout: 1"}) @DirtiesContext diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/RibbonCommandFallbackTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/RibbonCommandFallbackTests.java index 205204f4..03c768e3 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/RibbonCommandFallbackTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/RibbonCommandFallbackTests.java @@ -18,13 +18,35 @@ package org.springframework.cloud.netflix.zuul.filters.route.support; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Set; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ErrorAttributes; import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.cloud.netflix.ribbon.RibbonClient; +import org.springframework.cloud.netflix.ribbon.RibbonClients; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; +import org.springframework.cloud.netflix.zuul.EnableZuulProxy; +import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; +import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider; +import org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommandFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.bind.annotation.RestController; import static org.junit.Assert.assertEquals; @@ -47,11 +69,90 @@ public abstract class RibbonCommandFallbackTests { } @Test - public void noFallback() { + public void defaultFallback() { String uri = "/another/twolevel/slow"; ResponseEntity result = new TestRestTemplate().exchange( "http://localhost:" + this.port + uri, HttpMethod.GET, new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("default fallback", result.getBody()); + } + + // Don't use @SpringBootApplication because we don't want to component scan + @Configuration + @EnableAutoConfiguration + @RestController + @EnableZuulProxy + @RibbonClients({ + @RibbonClient(name = "simple", configuration = ZuulProxyTestBase.SimpleRibbonClientConfiguration.class), + @RibbonClient(name = "another", configuration = ZuulProxyTestBase.AnotherRibbonClientConfiguration.class)}) + public static class TestConfig extends ZuulProxyTestBase.AbstractZuulProxyApplication { + + @Autowired(required = false) + private Set zuulFallbackProviders = Collections.emptySet(); + + + @Bean + public RibbonCommandFactory ribbonCommandFactory( + final SpringClientFactory clientFactory) { + return new HttpClientRibbonCommandFactory(clientFactory, new ZuulProperties(), + zuulFallbackProviders); + } + + @Bean + public ZuulProxyTestBase.MyErrorController myErrorController( + ErrorAttributes errorAttributes) { + return new ZuulProxyTestBase.MyErrorController(errorAttributes); + } + + @Bean + public ZuulFallbackProvider defaultFallbackProvider() { + return new DefaultFallbackProvider(); + } + } + + public static class DefaultFallbackProvider implements ZuulFallbackProvider { + + @Override + public String getRoute() { + return "*"; + } + + @Override + public ClientHttpResponse fallbackResponse() { + return new ClientHttpResponse() { + @Override + public HttpStatus getStatusCode() throws IOException { + return HttpStatus.OK; + } + + @Override + public int getRawStatusCode() throws IOException { + return 200; + } + + @Override + public String getStatusText() throws IOException { + return null; + } + + @Override + public void close() { + + } + + @Override + public InputStream getBody() throws IOException { + return new ByteArrayInputStream("default fallback".getBytes()); + } + + @Override + public HttpHeaders getHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.TEXT_HTML); + return headers; + } + }; + } } }