Commit 731c8bf0 by Ryan Baxter

Add the ability to provide a default ZuulFallbackProvider. Fixes #1506

parent d78da7b9
...@@ -1755,6 +1755,56 @@ zuul: ...@@ -1755,6 +1755,56 @@ zuul:
customers: /customers/** 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]]
=== Zuul Developer Guide === Zuul Developer Guide
......
...@@ -30,15 +30,25 @@ import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider ...@@ -30,15 +30,25 @@ import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider
public abstract class AbstractRibbonCommandFactory implements RibbonCommandFactory { public abstract class AbstractRibbonCommandFactory implements RibbonCommandFactory {
private Map<String, ZuulFallbackProvider> fallbackProviderCache; private Map<String, ZuulFallbackProvider> fallbackProviderCache;
private ZuulFallbackProvider defaultFallbackProvider = null;
public AbstractRibbonCommandFactory(Set<ZuulFallbackProvider> fallbackProviders){ public AbstractRibbonCommandFactory(Set<ZuulFallbackProvider> fallbackProviders){
this.fallbackProviderCache = new HashMap<>(); this.fallbackProviderCache = new HashMap<>();
for(ZuulFallbackProvider provider : fallbackProviders) { 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) { protected ZuulFallbackProvider getFallbackProvider(String route) {
return fallbackProviderCache.get(route); ZuulFallbackProvider provider = fallbackProviderCache.get(route);
if(provider == null) {
provider = defaultFallbackProvider;
}
return provider;
} }
} }
...@@ -33,7 +33,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen ...@@ -33,7 +33,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
* @author Ryan Baxter * @author Ryan Baxter
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(classes = HttpClientRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = RANDOM_PORT, @SpringBootTest(classes = RibbonCommandFallbackTests.TestConfig.class, webEnvironment = RANDOM_PORT,
properties = { properties = {
"zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**", "zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**",
"ribbon.ReadTimeout: 1"}) "ribbon.ReadTimeout: 1"})
......
...@@ -31,7 +31,7 @@ import com.netflix.zuul.context.RequestContext; ...@@ -31,7 +31,7 @@ import com.netflix.zuul.context.RequestContext;
* @author Ryan Baxter * @author Ryan Baxter
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @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/**", "zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**",
"ribbon.ReadTimeout: 1"}) "ribbon.ReadTimeout: 1"})
@DirtiesContext @DirtiesContext
......
...@@ -31,7 +31,7 @@ import com.netflix.zuul.context.RequestContext; ...@@ -31,7 +31,7 @@ import com.netflix.zuul.context.RequestContext;
* @author Ryan Baxter * @author Ryan Baxter
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @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/**", "zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**",
"ribbon.ReadTimeout: 1"}) "ribbon.ReadTimeout: 1"})
@DirtiesContext @DirtiesContext
......
...@@ -18,13 +18,35 @@ ...@@ -18,13 +18,35 @@
package org.springframework.cloud.netflix.zuul.filters.route.support; 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.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.context.embedded.LocalServerPort;
import org.springframework.boot.test.web.client.TestRestTemplate; 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.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.bind.annotation.RestController;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
...@@ -47,11 +69,90 @@ public abstract class RibbonCommandFallbackTests { ...@@ -47,11 +69,90 @@ public abstract class RibbonCommandFallbackTests {
} }
@Test @Test
public void noFallback() { public void defaultFallback() {
String uri = "/another/twolevel/slow"; String uri = "/another/twolevel/slow";
ResponseEntity<String> result = new TestRestTemplate().exchange( ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET, "http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class); 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<ZuulFallbackProvider> 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;
}
};
}
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment