spring-cloud-gateway RewritePath GatewayFilter not working - spring-boot

I am developing an spring-cloud-gateway application. Where I am using a RewritePath GatewayFilter for processing some pathvariable. Following is my downstream api running on port 80.
#GetMapping("/appname/event/{eventId}")
public Mono<ResponseEntity> getEventTimeOutWithPathVariable(
#RequestHeader(name = "customerId") UUID customerId,
#PathVariable(name = "eventId") String eventId) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("customerId", customerId);
map.put("eventId", eventId);
return Mono.just(new ResponseEntity(map, HttpStatus.OK));
}
And in My gateway application the filter configs are given as:
- id: api_timeout_route
uri: http://localhost/appname/event/
predicates:
- Path=/withapitimeout/**
filters:
- Hystrix=apiTimeOut
- RewritePath=/withapitimeout/(?<segment>.*), /$\{segment}
But it is not working . what I am doing wrong? I am getting the following log.
Mapping [Exchange: GET http://localhost:8000/withapitimeout/306ac5d0-b6d8-4f78-bde8-c470478ed1b1]
to Route{id='api_timeout_route', uri=http://localhost:80/appname/event/
Mainly the path variable is not getting re-written. any help?

I'm not an expert but you can try something like this:
- id: api_timeout_route
uri: http://localhost
predicates:
- Path=/withapitimeout/**
filters:
- Hystrix=apiTimeOut
- RewritePath=/withapitimeout/(?<segment>.*), /appname/event/$\{segment}
Let me know ;)

Related

Spring Boot - request timeout 504

I have two Spring Boot REST application they talk with each other.
ProjectA, getTest rest service sometimes it takes a minute. It calls from projectB.
#PostMapping(value = "/test")
public ResponseEntity<Map<byte[], List<String>>> getTest(
#RequestBody ObjectDTO configDto) {
try {
HashMap<byte[], List<String>> testMap =
serviceImpl.test(configDto);
if (!testMap.isEmpty())
return new ResponseEntity<>(testMap, HttpStatus.CREATED);
else return new ResponseEntity<>(testMap, HttpStatus.NO_CONTENT);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
ProjectB which calls the API above.
#PostMapping(value = "/getTest")
#Async
public ResponseEntity<Map<byte[], List<String>>> getTest(
#RequestBody Config config) {
try {
Map<byte[], List<String>> val = serviceImpl.testConfig(config);
return new ResponseEntity<>(val, HttpStatus.CREATED);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
It works locally but when I run on prod it always returns after 6.2s:
upstream request timeout
I have already increased the timeout on the properties with the config below on Project B, but did not work.
server.tomcat.connection-timeout=120000
spring.mvc.async.request-timeout=120000
so the question is how to fix a 504 Gateway Timeout Error
Update:
Fixed by setting a timeout on ambassador .yaml file below.
apiVersion: getambassador.io/v2
kind: Mapping
metadata:
name: ambassador-header-mapping
namespace: x
spec:
prefix: /
timeout_ms: 60000
service: dummyservice:8080
ambassador_id: ambassador-x
headers:
X-Forwarded-Proto: https
Host: dummyhost
It looks like you have additional proxy server which have own timouts config.

Retaining the Request's Path During Spring Cloud Gateway Failover

Is there a way to externally configure Spring Cloud Gateway to failover to another data center? I'm thinking of something like this:
spring:
cloud:
gateway:
routes:
- id: test-service
uri: lb://test-service:8085/
predicates:
- Path=/test-service/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: fallback
fallbackUri: forward:/fallback
#fallbackUri: forward:/fallback/test-service
- id: fallback
uri: http://${fallback_data_center}
predicates:
- Path=/fallback/**
---
spring:
config:
activate:
on-profile: data_center_1
fallback_data_center: dc2.com
---
spring:
config:
activate:
on-profile: data_center_2
fallback_data_center: dc1.com
The problem I run into is that the CircuitBreaker filter's fallbackUri parameter only supports forward schemed URIs. However, the path part of the request URL is overridden with the path in the forward URL. So there does not appear to be a way to failover with the path from the original request such as if this configuration had received a request of http://dc1.com/test-service/some/path without creating a configuration for every possible path.
At the time of writing this answer there is still now official way of doing a failover to another host.
What we are trying to achieve in our team is to have routes with Retry and CircuitBreaker filters which can fallback to another host keeping the original request unmodified ( request payload, header, query params and most importantly the API context path ) and just replacing the host so we can fallback to another datacenter.
We archived this by using the default Gateway Retry and CircuitBreaker filters and developing a custom FallbackController which just replaces the host with a configured property and keeps the rest of the request unmodified including the request context path:
#RestController
#RequestMapping("/fallback")
#ConditionalOnProperty(value="gateway.fallback.enabled", havingValue = "true")
public class FallbackController {
private final GatewayFallbackConfig gatewayFallbackConfig;
private final WebClient webClient;
public FallbackController(GatewayFallbackConfig gatewayFallbackConfig) {
this.gatewayFallbackConfig = gatewayFallbackConfig;
this.webClient = WebClient.create();
}
#PostMapping
Mono<ResponseEntity<String>> postFallback(#RequestBody(required = false) String body,
ServerWebExchangeDecorator serverWebExchangeDecorator) {
return fallback(body, serverWebExchangeDecorator);
}
#GetMapping
Mono<ResponseEntity<String>> getFallback(#RequestBody(required = false) String body,
ServerWebExchangeDecorator serverWebExchangeDecorator) {
return fallback(body, serverWebExchangeDecorator);
}
#PatchMapping
Mono<ResponseEntity<String>> patchFallback(#RequestBody(required = false) String body,
ServerWebExchangeDecorator serverWebExchangeDecorator) {
return fallback(body, serverWebExchangeDecorator);
}
#DeleteMapping
Mono<ResponseEntity<String>> deleteFallback(#RequestBody(required = false) String body,
ServerWebExchangeDecorator serverWebExchangeDecorator) {
return fallback(body, serverWebExchangeDecorator);
}
private Mono<ResponseEntity<String>> fallback(String body, ServerWebExchangeDecorator serverWebExchangeDecorator) {
ServerHttpRequest originalRequest = serverWebExchangeDecorator.getDelegate().getRequest();
WebClient.RequestBodySpec request = webClient.method(originalRequest.getMethod())
.uri(buildFallbackURI(originalRequest));
Optional.ofNullable(body)
.ifPresent(request::bodyValue);
return request.exchangeToMono(response -> response.toEntity(String.class));
}
private URI buildFallbackURI(ServerHttpRequest originalRequest) {
return UriComponentsBuilder.fromHttpRequest(originalRequest)
.scheme(gatewayFallbackConfig.getScheme())
.host(gatewayFallbackConfig.getHost())
.port(gatewayFallbackConfig.getPort())
.build(ServerWebExchangeUtils.containsEncodedParts(originalRequest.getURI()))
.toUri();
}
With an additional property configuration holder:
#Getter
#Component
#RefreshScope
#ConditionalOnProperty(value="gateway.fallback.enabled", havingValue = "true")
public class GatewayFallbackConfig {
private final String scheme;
private final String host;
private final String port;
private final Set<String> excludedHeaders;
public GatewayFallbackConfig(
#Value("${gateway.fallback.scheme:https}") String scheme,
#Value("${gateway.fallback.host}") String host,
#Value("${gateway.fallback.port:#{null}}") String port,
#Value("${gateway.fallback.headers.exclude}") Set<String> excludedHeaders) {
this.scheme = scheme;
this.host = host;
this.port = port;
this.excludedHeaders = excludedHeaders;
}
And we are using it with a route configuration like that:
- id: example-route
uri: http://localhost:8080
predicates:
- Path=/foo/bar/**
filters:
- name: CircuitBreaker
args:
name: exampleCircuitBreaker
fallbackUri: forward:/fallback
statusCodes:
- INTERNAL_SERVER_ERROR
- BAD_GATEWAY
- SERVICE_UNAVAILABLE
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE,GATEWAY_TIMEOUT
series: SERVER_ERROR
methods: GET,POST,PUT,DELETE
exceptions: org.springframework.cloud.gateway.support.NotFoundException,javax.security.auth.login.LoginException
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
gateway:
fallback:
scheme: https
host: some.other.host.com
enabled: true

Did properties-based (from configuration server) override/replace java-based routes config?

I use server with defining of some routes in yml configuration, which stored in Consul Key/Value. When I'm trying to define route using Fluent API (Java based config), gateway doesn't work properly and doens't process this routes.
Example of server based config:
cloud:
gateway:
discovery:
locator:
enabled: false
routes:
- id: foo
predicates:
- Path=/foo/**
uri: lb:https://bar
And defining routes in Fluent style:
#Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p
.path("/testing_route")
.filters(f -> f.addRequestHeader("Hello", "World"))
.uri("http://httpbin.org:80"))
.build();
}
As result gateway return 404 status code for all requests to the /testing_route path, which mean this route is not working.
In case of my problem i want to modify request body using ModifyRequestBodyFilter which based on DSL configuration, that means - I need to use both ways to configure context.
In reality this code does nothing.
Can we combine RouteLocatorBuilder with property-based config in yml?
Spring Boot 2.2.5 RELEASE
Spring Cloud Hoxton.SR3
Answered in issue thread
https://github.com/spring-cloud/spring-cloud-gateway/issues/1953#issuecomment-705081934
TL;DR
Need to enable CachingRouteLocator
#Bean
#Primary
#ConditionalOnMissingBean(name = "cachedCompositeRouteLocator")
// TODO: property to disable composite?
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}

How to retry an external service when a call to an internal service fails using spring cloud gateway?

I'm implementing a service that mocks another service available on the web.
I'm trying to configure the spring cloud gateway to reroute requests to the public service when a call to my implementation fails.
I tried using the Hystrix filter the following way:
spring:
cloud:
gateway:
routes:
- id: foo
uri: http://my-internal-service/
filters:
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: https://the.public.service/
Unfortunately, like the documentation says:
Currently, only forward: schemed URIs are supported. If the fallback is called, the request will be forwarded to the controller matched by the URI.
Therefore, I can't use fallbackUri: https://....
Is there any plan to support this feature soon?
Otherwise, what are my options for this particular use case?
I ended up with a kind of hacky workaround that seems to work for my particular use case (i.e. a GET request):
Create my own fallback controller in the Gateway application
Configure the hystrix fallback to point to that controller
Use the WebClient to call my public service
This is what the end result looks like:
application.yml
spring:
cloud:
gateway:
default-filters:
- name: AddResponseHeader
args:
name: X-Data-Origin
value: My internal service
routes:
- id: foo
uri: http://my-internal-service/
filters:
- name: Hystrix
args:
name: local-service-fallback
fallbackUri: forward:/fallback/foo
FallbackController.java
#RestController
#RequestMapping(path = "/fallback")
public class FallbackController {
private static final String fallbackUri = "https://the.public.service";
WebClient webClient;
public FallbackController() {
webClient = WebClient.create(fallbackUri);
}
#GetMapping("/foo")
Mono<MyResponse> foo(ServerWebExchange failedExchange) {
failedExchange.getResponse().getHeaders().remove("X-Data-Origin");
failedExchange.getResponse().getHeaders().add("X-Data-Origin", "The public service");
// Now call the public service using the same GET request
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.uri(URI.create(fallbackUri))
.path("/path/to/service")
.queryParams(failedExchange.getRequest().getQueryParams())
.build();
return WebClient.create(uriComponents.toUriString())
.get()
.accept(MediaType.TEXT_XML)
.exchange()
.doOnSuccess(clientResponse -> {
// Copy the headers from the public service's response back to our exchange's response
failedExchange.getResponse().getHeaders()
.addAll(clientResponse.headers().asHttpHeaders());
})
.flatMap(clientResponse -> {
log.info("Data origin: {}",
failedExchange.getResponse().getHeaders().get("X-Data-Origin"));
return clientResponse.bodyToMono(MyResponse.class);
});
}
}
I had similar problem to solve.
I added new route for fallback and it worked.
.route(p -> p .path("/fallback/foo").uri("https://example.com"))

spring-cloud-gateway || need to configure global and application level and api level timeout

I am working in a spring-cloud-gateway project. Where I need to configure a Global Timeout/ application level timeout and api specific timeout. Following are my downstream apis:
#RestController
public class TestController {
// Should have global time out
#GetMapping("/global")
public Mono<ResponseEntity> testGlobalTimeOut(
#RequestHeader(name = "cId") UUID cId,
#RequestParam(name = "someNumber", required = false) Number someNumber) {
// Map<String, Object> map = populate Some Map Logic
return Mono.just(new ResponseEntity(map, HttpStatus.OK));
}
// Should have application level time out
#GetMapping("/appname/count")
public Mono<ResponseEntity> testApplicationTimeOut_1(
#RequestHeader(name = "cId") UUID cId,
#RequestParam(name = "someNumber", required = false) Number someNumber) {
// Map<String, Object> map = populate Some Map Logic
return Mono.just(new ResponseEntity(map, HttpStatus.OK));
}
// Should have application level time out
#GetMapping("/appname/posts")
public Mono<ResponseEntity> testApplicationTimeOut_2(
#RequestHeader(name = "cId") UUID cId,
#RequestParam(name = "someNumber", required = false) Number someNumber) {
// Map<String, Object> map = populate Some Map Logic
return Mono.just(new ResponseEntity(map, HttpStatus.OK));
}
// Should have api level time out
#GetMapping("/appname/posts/{postId}")
public Mono<ResponseEntity> getAPITimeOutWithPathVariable(
#RequestHeader(name = "cId") UUID cId,
#PathVariable(name = "postId") String postId) {
// Map<String, Object> map = populate Some Map Logic
return Mono.just(new ResponseEntity(map, HttpStatus.OK));
}
}
This apis are running as a downstream service. Now following are my route configurations for all these apis in my gateway-application:
# ============ Application Timeout =============
- id: application_timeout_route_1
uri: http://localhost/appname/count
predicates:
- Path=/withapplicationtimeout1**
filters:
- Hystrix=appTimeOut
- id: application_timeout_route_2
uri: http://localhost/appname/posts
predicates:
- Path=/withapplicationtimeout2**
filters:
- Hystrix=appTimeOut
# ============ API Level Timeout ===========
- id: api_timeout_route
uri: http://localhost
predicates:
- Path=/withapitimeout/**
filters:
- Hystrix=apiTimeOut
- RewritePath=/withapitimeout/(?<segment>.*), /appname/posts/$\{segment}
# Global Timeout Configuration
#hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 30000
# Application Level Timeout Configuration
hystrix.command.appTimeOut.execution.isolation.thread.timeoutInMilliseconds: 30000
# API Level Timeout Configuration
hystrix.command.apiTimeOut.execution.isolation.thread.timeoutInMilliseconds: 15000
Now the application level timeout and the api level timeout is working fine, But I am not getting any way to define the Global Timeout filter. Documentation for the same is yet not available:
https://github.com/spring-cloud/spring-cloud-gateway/blob/master/docs/src/main/asciidoc/spring-cloud-gateway.adoc#combined-global-filter-and-gatewayfilter-ordering
Any Idea how to do this?

Resources