Adds fallbackUri to Hystrix filter

fixes gh-38
This commit is contained in:
Spencer Gibb
2018-01-08 16:34:54 -05:00
parent e117906db8
commit d634113acf
7 changed files with 138 additions and 68 deletions

View File

@@ -49,7 +49,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: after_route
uri: http://example.org
predicates:
@@ -68,7 +67,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: before_route
uri: http://example.org
predicates:
@@ -87,7 +85,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: between_route
uri: http://example.org
predicates:
@@ -106,7 +103,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: cookie_route
uri: http://example.org
predicates:
@@ -125,7 +121,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: header_route
uri: http://example.org
predicates:
@@ -144,7 +139,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: host_route
uri: http://example.org
predicates:
@@ -164,7 +158,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: method_route
uri: http://example.org
predicates:
@@ -183,7 +176,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: host_route
uri: http://example.org
predicates:
@@ -204,7 +196,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: query_route
uri: http://example.org
predicates:
@@ -220,7 +211,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: query_route
uri: http://example.org
predicates:
@@ -240,7 +230,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: remoteaddr_route
uri: http://example.org
predicates:
@@ -264,7 +253,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: add_request_header_route
uri: http://example.org
filters:
@@ -283,7 +271,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: add_request_parameter_route
uri: http://example.org
filters:
@@ -302,7 +289,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: add_request_header_route
uri: http://example.org
filters:
@@ -321,7 +307,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: hytstrix_route
uri: http://example.org
filters:
@@ -330,6 +315,28 @@ spring:
This wraps the remaining filters in a `HystrixCommand` with command name `myCommandName`.
The Hystrix filter takes an optional `fallbackUri` parameter. Currently, only `forward:` schemed URIs are supported. If the fallback is called, the request will be forwarded to the controller matched by the URI.
.application.yml
[source,yaml]
----
spring:
cloud:
gateway:
routes:
- id: hytstrix_route
uri: http://example.org
filters:
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallbackcontroller
This will forward to the `/fallbackcontroller` when the Hystrix fallback is called.
----
=== PrefixPath GatewayFilter Factory
The PrefixPath GatewayFilter Factory takes a single `prefix` parameter.
@@ -340,7 +347,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: prefixpath_route
uri: http://example.org
filters:
@@ -359,7 +365,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: preserve_host_route
uri: http://example.org
filters:
@@ -398,7 +403,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: requestratelimiter_route
uri: http://example.org
filters:
@@ -426,7 +430,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: prefixpath_route
uri: http://example.org
filters:
@@ -460,7 +463,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: removerequestheader_route
uri: http://example.org
filters:
@@ -479,7 +481,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: removeresponseheader_route
uri: http://example.org
filters:
@@ -498,7 +499,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: rewritepath_route
uri: http://example.org
predicates:
@@ -545,7 +545,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: setpath_route
uri: http://example.org
predicates:
@@ -566,7 +565,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: setresponseheader_route
uri: http://example.org
filters:
@@ -585,7 +583,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: setstatusstring_route
uri: http://example.org
filters:
@@ -653,7 +650,6 @@ spring:
cloud:
gateway:
routes:
# =====================================
- id: setstatus_route
uri: http://example.org
filters:

View File

@@ -327,8 +327,8 @@ public class GatewayAutoConfiguration {
@ConditionalOnClass({HystrixObservableCommand.class, RxReactiveStreams.class})
protected static class HystrixConfiguration {
@Bean
public HystrixGatewayFilterFactory hystrixGatewayFilterFactory() {
return new HystrixGatewayFilterFactory();
public HystrixGatewayFilterFactory hystrixGatewayFilterFactory(DispatcherHandler dispatcherHandler) {
return new HystrixGatewayFilterFactory(dispatcherHandler);
}
}

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.gateway.filter.factory;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
@@ -24,8 +25,11 @@ import java.util.function.Function;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.tuple.Tuple;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
@@ -34,6 +38,8 @@ import com.netflix.hystrix.HystrixObservableCommand.Setter;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import static com.netflix.hystrix.exception.HystrixRuntimeException.FailureType.TIMEOUT;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedQuery;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus;
import reactor.core.publisher.Mono;
@@ -46,30 +52,58 @@ import rx.Subscription;
*/
public class HystrixGatewayFilterFactory implements GatewayFilterFactory {
public static final String FALLBACK_URI = "fallbackUri";
private final DispatcherHandler dispatcherHandler;
public HystrixGatewayFilterFactory(DispatcherHandler dispatcherHandler) {
this.dispatcherHandler = dispatcherHandler;
}
@Override
public List<String> argNames() {
return Arrays.asList(NAME_KEY);
}
@Override
public boolean validateArgs() {
return false;
}
@Override
public GatewayFilter apply(Tuple args) {
//TODO: if no name is supplied, generate one from command id (useful for default filter)
final String commandName = args.getString(NAME_KEY);
return apply(commandName);
String commandName = args.getString(NAME_KEY);
if (args.hasFieldName(FALLBACK_URI)) {
URI fallbackUri = URI.create(args.getString(FALLBACK_URI));
if (!"forward".equals(fallbackUri.getScheme())) {
throw new IllegalArgumentException("Hystrix Filter currently only supports 'forward' URIs, found "+ fallbackUri);
}
return apply(commandName, fallbackUri);
}
return apply(commandName, null);
}
public GatewayFilter apply(String commandName) {
return apply(commandName, null);
}
public GatewayFilter apply(String commandName, URI fallbackUri) {
final HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey(getClass().getSimpleName());
final HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey(commandName);
final Setter setter = Setter.withGroupKey(groupKey)
.andCommandKey(commandKey);
return apply(setter);
return apply(setter, fallbackUri);
}
public GatewayFilter apply(Setter setter) {
return apply(setter, null);
}
public GatewayFilter apply(Setter setter, URI fallbackUri) {
return (exchange, chain) -> {
RouteHystrixCommand command = new RouteHystrixCommand(setter, exchange, chain);
RouteHystrixCommand command = new RouteHystrixCommand(setter, fallbackUri, exchange, chain);
return Mono.create(s -> {
Subscription sub = command.toObservable().subscribe(s::success, s::error, s::success);
@@ -89,18 +123,43 @@ public class HystrixGatewayFilterFactory implements GatewayFilterFactory {
//TODO: replace with HystrixMonoCommand that we write
private class RouteHystrixCommand extends HystrixObservableCommand<Void> {
private final URI fallbackUri;
private final ServerWebExchange exchange;
private final GatewayFilterChain chain;
RouteHystrixCommand(Setter setter, ServerWebExchange exchange, GatewayFilterChain chain) {
RouteHystrixCommand(Setter setter, URI fallbackUri, ServerWebExchange exchange, GatewayFilterChain chain) {
super(setter);
this.fallbackUri = fallbackUri;
this.exchange = exchange;
this.chain = chain;
}
@Override
protected Observable<Void> construct() {
return RxReactiveStreams.toObservable(this.chain.filter(this.exchange));
return RxReactiveStreams.toObservable(this.chain.filter(exchange));
}
@Override
protected Observable<Void> resumeWithFallback() {
if (this.fallbackUri == null) {
return super.resumeWithFallback();
}
//TODO: copied from RouteToRequestUrlFilter
URI uri = exchange.getRequest().getURI();
boolean encoded = containsEncodedQuery(uri);
URI requestUrl = UriComponentsBuilder.fromUri(uri)
.host(null)
.port(null)
.uri(this.fallbackUri)
.build(encoded)
.toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
ServerHttpRequest request = this.exchange.getRequest().mutate().uri(requestUrl).build();
ServerWebExchange mutated = exchange.mutate().request(request).build();
return RxReactiveStreams.toObservable(HystrixGatewayFilterFactory.this.dispatcherHandler.handle(mutated));
}
}
}

View File

@@ -103,6 +103,14 @@ public class GatewayFilterSpec extends UriSpec {
return filter(getBean(HystrixGatewayFilterFactory.class).apply(setter));
}
public GatewayFilterSpec hystrix(String commandName, URI fallbackUri) {
return filter(getBean(HystrixGatewayFilterFactory.class).apply(commandName, fallbackUri));
}
public GatewayFilterSpec hystrix(HystrixObservableCommand.Setter setter, URI fallbackUri) {
return filter(getBean(HystrixGatewayFilterFactory.class).apply(setter, fallbackUri));
}
public GatewayFilterSpec prefixPath(String prefix) {
return filter(getBean(PrefixPathGatewayFilterFactory.class).apply(prefix));
}

View File

@@ -17,6 +17,9 @@
package org.springframework.cloud.gateway.filter.factory;
import java.util.Collections;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.SpringBootConfiguration;
@@ -24,18 +27,14 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.cloud.gateway.test.TestUtils.assertStatus;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@@ -44,40 +43,39 @@ public class HystrixGatewayFilterFactoryTests extends BaseWebClientTests {
@Test
public void hystrixFilterWorks() {
Mono<ClientResponse> result = webClient.get()
.uri("/get")
testClient.get().uri("/get")
.header("Host", "www.hystrixsuccess.org")
.exchange();
StepVerifier.create(result)
.consumeNextWith(
response -> {
assertStatus(response, HttpStatus.OK);
HttpHeaders httpHeaders = response.headers().asHttpHeaders();
assertThat(httpHeaders.getFirst(ROUTE_ID_HEADER))
.isEqualTo("hystrix_success_test");
})
.expectComplete()
.verify(DURATION);
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals(ROUTE_ID_HEADER, "hystrix_success_test");
}
@Test
public void hystrixFilterTimesout() {
Mono<ClientResponse> result = webClient.get()
.uri("/delay/3")
testClient.get().uri("/delay/3")
.header("Host", "www.hystrixfailure.org")
.exchange();
.exchange()
.expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT);
}
StepVerifier.create(result)
.consumeNextWith(
response -> assertStatus(response, HttpStatus.GATEWAY_TIMEOUT))
.expectComplete()
.verify(DURATION);
@Test
public void hystrixFilterFallback() {
testClient.get().uri("/delay/3?a=b")
.header("Host", "www.hystrixfallback.org")
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"from\":\"fallbackcontroller\"}");
}
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(DefaultTestConfig.class)
public static class TestConfig { }
@RestController
public static class TestConfig {
@RequestMapping("/fallbackcontroller")
public Map<String, String> fallbackcontroller(@RequestParam("a") String a) {
return Collections.singletonMap("from", "fallbackcontroller");
}
}
}

View File

@@ -57,9 +57,7 @@ public class ForwardTests {
@Test
public void forwardWorks() {
this.client
.get()
.uri("/localcontroller")
this.client.get().uri("/localcontroller")
.header(HttpHeaders.HOST, "www.forward.org")
.exchange()
.expectStatus().isOk()

View File

@@ -60,7 +60,7 @@ spring:
# =====================================
- id: forward_test
uri: forward:///localcontroller
uri: forward:/localcontroller
predicates:
- Host=**.forward.org
@@ -72,6 +72,17 @@ spring:
filters:
- Hystrix=failcmd
# =====================================
- id: hystrix_fallback_test
uri: ${test.uri}
predicates:
- Host=**.hystrixfallback.org
filters:
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallbackcontroller
# =====================================
- id: hystrix_success_test
uri: ${test.uri}