I need to get information from the response body of certain responses as a json string.
I have seen the following example of modifying it:
#Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rewrite_response_upper", r -> r.host("*.rewriteresponseupper.org")
.filters(f -> f.prefixPath("/httpbin")
.modifyResponseBody(String.class, String.class,
(exchange, s) -> Mono.just(s.toUpperCase()))).uri(uri)
.build();
}
GatewayFilter Factories
But how would I just log it as a string?
Can I do it in a custom filter?
Related
I have created below webclient and using it inside of my service to make HTTP third party calls.
#Configuration
public class WebclientConfig {
#Bean
public WebClient webClient() {
// custom client connector with connection pool disabled is being used as by default the connection pooling is done and connection reset happens after some idle time.
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.newConnection()))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
and in my service, I am calling the third party service like below.
private Flux<BusinessObject> getBusinessObjects(String serviceURL) {
return this.webClient.get()
.uri(serviceURL)
.retrieve()
.bodyToFlux(BusinessObject.class) //code below this, do I have to copy for each webclient request to configure the retry, even if the values are same
.retryWhen(Retry.backoff(3, Duration.of(2, ChronoUnit.SECONDS))
.doBeforeRetry((value) -> log.info("Retrying request " + value))
.filter(error -> error instanceof WebClientRequestException)
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) ->
new RuntimeException(retrySignal.failure().getMessage())));
}
My question is as in comment in above code.
I have multiple webclient calls, but I want to configure the retry backoff configuration at a single place. How can I do that? so that my code should look like below
private Flux<BusinessObject> getBusinessObjects(String serviceURL) {
return this.webClient.get()
.uri(serviceURL)
.retrieve()
.bodyToFlux(BusinessObject.class)
.somCommonRetryCodeWrappingTheRetryLogic();
}
You can use transform operator for this purpose:
private Flux<BusinessObject> getBusinessObjects(String serviceURL) {
return this.webClient.get()
.uri(serviceURL)
.retrieve()
.bodyToFlux(BusinessObject.class)
.transform(originalFlux -> wrapWithRetry(originalFlux));
}
private <T> Flux<T> wrapWithRetry(Flux<T> originalFlux) {
return originalFlux
.retryWhen(Retry.backoff(3, Duration.of(2, ChronoUnit.SECONDS))
.doBeforeRetry((value) -> log.info("Retrying request " + value))
.filter(error -> error instanceof WebClientRequestException)
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) ->
new RuntimeException(retrySignal.failure().getMessage())));
}
Only drawback is that if you have Mono use cases as well then you need to implement it twice.
If that is still too much copy-paste, you can consider defining an ExchangeFilterFunction to apply retry for every WebClient call automatically. Something like this:
WebClient.builder()
// ...
.filter((request, next) -> next.exchange(request).retry()) // add full retry config here
.build();
I'm able to return ResponseEntity using toEntity() method like below:
#GetMapping("/uri")
public Mono<ResponseEntity<Data[]>> methodName() {
return webClient
.get()
.uri("http://localhost:8088/externalService")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(Data[].class);
}
But I want to process response headers before returning.
The above code converts WebClient response to ResponseEntity and returns immediately but I want to store it in a ResponseEntity variable, process it, and then return the ResponseEntity back.
I referred this -> Spring WebClient Documentation
WHen I tried to store it in a varibale, I get this error -> "block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3"
You can simply use the Reactor's map operator to modify the headers:
return webClient
.get()
.uri("http://localhost:8088/externalService")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(Data[].class)
.map(responseEntity -> responseEntity.getHeaders().add("header", "header-value");
Alternatively, you can use .handle operator in order to provide response processing:
.handle((responseEntity, sink) -> {
if(!isValid(responseEntity)){
sink.error(new InvalidResponseException());
} else if (isOk(responseEntity))
sink.next(responseEntity);
}
else {
//just ignore element
}
})
Spring Starter Web dependency was missing in my pom.xml. Found it and added it back.
Now able to get WebClient response in ResponseEntity format.
I am using Spring Cloud Gateway, I want to route incoming request to ServiceA or ServiceB based on a value in the JWT token. So basically I want to extract the Token and get a userId from it and based on that information route to either service-a and service-b.
So the incoming request is something like "/v1/customers" and I need to prefix it with either /service-a/v1/customers to that it will be route to urlServiceA.
Here is the code of my Routes.
#Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
String urlServiceA = "a";
String urlServiceB = "b";
return builder.routes()
.route(r -> r.path("/service-a/v1/**")
.filters(f -> f.filter(authentication))
.uri(urlServiceA))
.route(r -> r.path("/service-b/v1/**")
.filters(f -> f.filter(authentication))
.uri(urlServiceB))
.build();
}
How can I do it ? Please help.
Thanks
I am unable to forward my request to a URI with contextpath.
For example I am requesting my Spring cloud gateway server with
http://localhost:1010/mygateway/server/date
And I expect it to forward to
http://myapiserver.com/xyzapi/v1/server/date
But It gives me 404
#Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("api-server-date", r -> r.host("**")
.and()
.path("/mygateway/server/date")
.filters(f -> f.stripPrefix(1))
.uri("https://myapiserver.com/xyzapi/v1"))
.build();
}
It works when I change my configuration to this
#Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("api-server-date", r -> r.host("**")
.and()
.path("/mygateway/xyzapi/v1/server/date")
.filters(f -> f.stripPrefix(1))
.uri("https://myapiserver.com"))
.build();
}
I don't know what configuration I am providing wrong. It should work.
All I want is to use
https://myapiserver.com/xyzapi/v1
As my URI. Meaning all Incoming traffic to my spring cloud gateway server should forward to this URL after appending the incoming url
Kindly suggest what is wrong ? Or is it something not possible with Spring Cloud Gateway ?
I think what you want (looking at online samples) is:
#Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
String requestHandlerUrl = "http://myapiserver.com/xyzapi/v1/server/date";
return builder.routes()
.route("api-server-date", r -> r.host("**")
.and()
.path("/mygateway/server/date")
.filters(f -> f.stripPrefix(1))
.uri(requestHandlerUrl)
.build();
}
... so that requests made to whatever host your app is deployed on are forwarded/passed to requestHandlerUrl as the destination.
It looks as if /mygateway/server/date is the "shortcut" or alias for the URL you actually want to handle the request.
Some examples here: https://cloud.spring.io/spring-cloud-gateway/multi/multi__configuration.html#_fluent_java_routes_api
I have a very simple Webflux controller that just do a GET request to another service endpoint and returns a simple JSON list. The problem is the remote endpoint is always called twice.
This issue doesn't happen if I used Mono as the return type of the controller instead of Flux!
// This calls "/remote/endpoint" twice!
#GetMapping("/blabla")
fun controller() : Flux<JsonNode> {
return webClient.get()
.uri("/remote/endpoint")
.retrieve()
.bodyToMono(JsonNode::class.java)
.flatMapIterable { body ->
body.get("data")
}
}
// This calls "/remote/endpoint" once.
#GetMapping("/blabla")
fun controller() : Mono<JsonNode> {
return webClient.get()
.uri("/remote/endpoint")
.retrieve()
.bodyToMono(JsonNode::class.java)
.map { body ->
body.get("data")
}
}