Adds fallbackUri to Hystrix filter
fixes gh-38
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user