Inside a REST controller, I need to call a REST to obtain a value to be used as a URI variable to a second REST call.
#PostMapping
public void abbina(#RequestBody DocumentsUploadRequest documentsUploadRequest) {
Mono<GetResult> result = WebClient
.create(url)
.get()
.....
.retrieve()
.bodyToMono(GetResult.class)
;
WebClient.post()
.uri(...)
.path("/{partita}")
.build(result.block().getValue()))
.....
.bodyToMono(PostResult.class)
....
}
The problem is that inside a WebFlux REST isn't possibile call block on a mono/flux.
The code throw
java.lang.IllegalStateException block()/blockFirst()/blockLast() are
blocking, which is not supported in thread reactor-http
I tried to change
.build(result.block().getValue()))
with
.build(result.share().block().getValue()))
But now the problem is that result.share().block() hangs indefinitely.
First of all, you should never block within a reactive pipeline. You should subscribe instead. In that particular case, the Spring Webflux framework will subscribe for you as long as you provide your publisher. To achieve this, the controller method has to return your Mono publisher like this:
#PostMapping
public Mono<Void> abbina(#RequestBody Attribute documentsUploadRequest) {
}
Here, the Mono<Void> defines that your publisher will complete without any value.
Then you have to build a reactive pipeline without blocking.
The result of the first HTTP call is a Mono of GetResult:
private Mono<GetResult> getResult() {
return WebClient
.get()
//...
.retrieve()
.bodyToMono(GetResult.class);
}
Similarly, the second HTTP call returns a Mono of PostResult:
private Mono<PostResult> postResult(String firstResult) {
return WebClient
.post()
//...
.retrieve()
.bodyToMono(PostResult.class);
}
Finally, combine these two publisher in order to build your pipeline using the flatmap operator:
#PostMapping
public Mono<Void> abbina(#RequestBody Attribute documentsUploadRequest) {
return getResult()
.flatMap(result -> postResult(result.getValue()))
.then();
}
I'd recommend taking a look at the following guide: Building a Reactive RESTful Web Service
Related
If we use reactive approach in Spring we can return Flux/Mono type in methods. So in controller it's good to wrap response ResponseEntity and return it and in case of endpoints which return one object we can write in reactive next code:
#GetMapping(value = "/to-do/{toDoId}", produces = {
MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE})
public Mono<ResponseEntity<ToDo>> getToDo(#Valid #PathVariable Long toDoId) {
return repository.findById(toDoId)
.map(ResponseEntity::ok);
}
But what if we want to return Flux? After some experiments I found this solution:
#GetMapping(value = "/to-do", produces = {
MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE})
public ResponseEntity<Flux<ToDo>> getToDos() {
return ResponseEntity.ok().body(
repository.findAll()
);
}
But if I understand correct here in such format, response like ResponseEntity<Flux<ToDo>> will be blocking? So it's better to do something like <Flux<ResponseEntity<ToDo>> ? If it's so, how to get it? I should subscribe?
It seems you don't really use the ResponseEntity to customize the reponse status so, why not just return Mono or Flux?
#GetMapping(value = "/to-do/{toDoId}", produces = {
MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE})
public Mono<ToDo> getToDo(#Valid #PathVariable Long toDoId) {
return repository.findById(toDoId);
}
#GetMapping(value = "/to-do", produces = {
MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE})
public Flux<ToDo> getToDos() {
return repository.findAll();
}
In any case, according to the documentation it's perfectly fine to return ResponseEntity<Mono> and ResponseEntity<Flux>, both will provide an asynchronous, non-blocking response:
ResponseEntity<Mono> or ResponseEntity<Flux> make the response
status and headers known immediately while the body is provided
asynchronously at a later point. Use Mono if the body consists of 0..1
values or Flux if it can produce multiple values.
There are multiple ways you can return value using ResponseEntity from your reactive controller:
Publisher can be Mono or Flux
ResponseEntity<Publisher<T>> - this makes the response status and headers known immediately while the body is provided asynchronously at some later point of time.
Publisher<ResponseEntity<T>> - this provides all three: response
status, headers, and body, asynchronously at some later point of time. It allows
the headers and response status to vary depending on the outcome of
asynchronous handling.
Also it is possible to use the following construct, less common, though:
Mono<ResponseEntity<Publisher<T>>> - provide the response status and headers asynchronously first, and later response body, also asynchronously
So far, I've not seen any solutions that is working for me. I've tried this and this.
Here is my custom filter:
#Component
public class TestFilter implements GlobalFilter, Ordered {
#Autowired
private ModifyResponseBodyGatewayFilterFactory modifyFilter;
#Autowired
private rewriteBody bodyRewrite;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).map(ex -> {
GatewayFilter delegate = modifyFilter.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, bodyRewrite));
delegate.filter(exchange, chain);
return ex;
});
}
#Override
public int getOrder() {
return -1;
}
#Component
public class rewriteBody implements RewriteFunction<byte[], byte[]> {
#Override
public Publisher<byte[]> apply(ServerWebExchange exchange, byte[] body) {
byte[] newBody = "New response".getBytes();
return Mono.just(newBody);
}
}
}
The ModifyResponseBodyGatewayFilterFactory works if I implement it in a pre-filter, but how do I modify it in the post-filter.
To answer the question about modifying the response in post-filter. Firstly, need to understand the pre and post filters developed in Spring Cloud Gateway.
There is no specific separation for pre and post filters in Spring Cloud Gateway by any interface or any other component. It is simply 'How logic has been written for the same filter'.
If any logic written before chain.filter(exchange) method call are executed before running another 'filter in chain' or 'calling target service endpoint'. Since, the logic/code running before making call to another filter or target endpoint, it is called pre-filter and used for pre-processing like adding additional headers, security assertions, rate limiting and so on.
If any logic written after chain.filter(exchange) method call are executed after the processing completed in chain.filter(exchange) method, means the 'target service endpoint' has been completed and then the logic/lines written after chain.filter(exchange) is being executed. Therefore, it is called post-filter.
Since, it is just way of writing and placement of code decides whether it is for pre/post both can be written in single filter.
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return Mono.just(exchange)
.map(it -> {
it.getRequest().getHeaders().add("x-pre-header", "value");
return it;
})
.doOnNext(it -> {
logger.debug("Pre-Processing/PreFilter");
})
.map(it -> chain.filter(it))
.then()
.map(it -> {
exchange.getResponse().getHeaders().add("x-post-header", "value");
return it;
})
.doOnNext(it -> {
logger.debug("Post-Processing/PostFilter");
});
}
Additionally, sequence of execution of filters are controlled by ordering of filters.
Now, when question comes for modifying response body, it is very clear that response will be provided only when 'target service endpoint' called which requires chain.filter(exchange).
Here there is a twist, called 'response commit'. When response is already committed, cannot change in response body and as soon as chain.filter(exchange) is called, it will take micro/mili-seconds to write response to client and commit the response. Means, if any code written after then() method makes changes in response body it will throw exception 'response already committed'.
To avoid it, response body always modified while making chain.filter(exchange) call. Example, consider code written in ModifyResponseBodyGatewayFilterFactory's method filter(...) as:
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange.mutate()
.response(new ModifiedServerHttpResponse(exchange, config)).build());
}
Here, exchange is mutated and response body is set at the same moment. It will modify the response and invoke other filters in chain, if there is no any remaining filters in chain then it serve the response to client.
So conceptually, response body modification happens as post activity only it the filter comes later in chain. Needs like other filter should not be executed once response body modified / some specific filter needs to be executed after response body modification need to be managed by Filter's ordering.
Trying to extract the body of a POST request sent using Postman to my Spring application.
I've tried doing
ServerRequest.bodyToMono(String.class).toProducer().peek() but that returns null.
ServerRequest.bodyToMono(String.class).block() isn't supported anymore.
Also tried doing it this way:
Mono<String> bodyData = request.bodyToMono(String.class);
System.out.println("\nsubscribing to the Mono.....");
bodyData.subscribeOn(Schedulers.newParallel("requestBody")).subscribe(value -> {
log.debug(String.format("%n value consumed: %s" ,value));
});
But I can't seem to get anything to show up in the logs.
If you are looking for an example of a reactive rest endpoint that just stores the body in some cache the following example should achieve this
public class Example implements HandlerFunction<ServerResponse> {
private final CacheService cache;
#Override
public Mono<ServerResponse> handle(ServerRequest request) {
return request.bodyToMono(String.class)
.doOnNext(body -> cache.put(body))
.flatMap(body -> ServerResponse.noContent().build());
}
}
I have a Spring boot application with Spring WebFlux. I want to call a API Rest in a non blocking way and after that, inside subscribe method, call another API Rest.
The first call executes correct and the subscribe method too. My problem is that inside the subscribe, how can I request another API Rest?
#Autowired
WebClient.Builder webClientBuilder;
Mono<UserRating> monoUserRating = webClientBuilder.build().get().uri("http://ratings-data-service:8083/ratingsdata/user/" + userId) .retrieve().bodyToMono(UserRating.class);
monoUserRating.subscribe(CatalogResource::handleResponseUserRating);
private static List<CatalogItem> handleResponseUserRating(UserRating userRating) {
System.out.println("How to call another API???");
//this doesn't work since is not static
Movie movie = webClientBuilder.build().get().uri("http://movie-info-service:8082/movies/"+ rating.getMovieId())
.retrieve().bodyToMono(Movie.class).block();
}
Use Mono.flatMap to bind two async operations together, link to doc.
#Autowired
WebClient.Builder webClientBuilder;
public void main() {
Mono<Movie> movieMono = getUserRating(userId)
.flatMap(userRating -> handlerUserRating(userRating));
movieMono.subscribe(movie -> handleMovie(movie));
}
private Mono<UserRating> getUserRating(String userId) {
return webClientBuilder.build()
.get()
.uri("http://ratings-data-service:8083/ratingsdata/user/" + userId)
.retrieve()
.bodyToMono(UserRating.class);
}
private Mono<Movie> handleUserRating(UserRating rating) {
return webClientBuilder.build()
.get()
.uri("http://movie-info-service:8082/movies/"+ rating.getMovieId())
.retrieve()
.bodyToMono(Movie.class);
}
I have the following integration configuration in my web app:
#Bean
IntegrationFlow giraFlow() {
return IntegrationFlows.from(
MessageChannels.direct("gira.input"))
.split()
.transform(transformer)
.handle(parserService)
.channel(routerChannel())
.get();
}
#Bean
MessageChannel routerChannel() {
return MessageChannels.queue("routerChannel", 10)
.get();
}
#Bean
IntegrationFlow routerChannelFlow() {
return IntegrationFlows.from(
routerChannel())
.route(p -> p.getKind().name(),
m -> m.suffix("Channel")
.channelMapping(TaskKind.CREATE.name(), "create")
.channelMapping(TaskKind.RELOAD.name(), "reload")
.get();
}
and a gateway:
#MessagingGateway
public interface GW {
#Gateway(requestChannel = "gira.input")
Task gira(Collection<Request> messages);
}
and a parserService
#Service
#Slf4j
public class ParserService {
public Task handle(IssueTrackerTask task) {
log.info("Parser service handling task {}", task);
return task;
}
}
I call the gateway method from Spring MVC controller and I want it to return me a Task object that the parserService returns in it's body method. The important thing is that I want controller to be blocked until it gets the value from the parserService. After it gets this value, I want my integration flow to proceed asynchronously with the routerChannelFlow, so that web controller method would return as fast as possible, and all heavy operations in the routerChannelFlow would be done without blocking controller.
Here's a part of the controller that has this gateway method call:
...
Task gira = gw.gira(messages);
log.info("Result {}", gira);
...
The problem is that log.info is never reached and gira() gateway is blocked forever.
How can I achieve my desired behavior?
P.S. The parserService is actually not needed in my app, this is just what I thought would help me to define a return value for my gateway, but it actually did not help:(
UPDATE
So here's what I got after Gary Russell's comment:
#Bean
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
pool.setCorePoolSize(10);
pool.setMaxPoolSize(10);
pool.setWaitForTasksToCompleteOnShutdown(true);
return pool;
}
#Bean
MessageChannel routerChannel() {
return MessageChannels
.publishSubscribe("routerChannel", executor())
.get();
}
#Bean
IntegrationFlow routerChannelFlow() {
return IntegrationFlows
.from(routerChannel())
.publishSubscribeChannel(s -> s
.subscribe(f -> f
.bridge(null))
.subscribe(process()))
.get();
}
#Bean
IntegrationFlow process() {
return f ->
f.<IssueTrackerTask, String>route(p -> p.getKind().name(),
m -> m.suffix("Channel")
.channelMapping(TaskKind.CREATE.name(), "create")
.channelMapping(TaskKind.RELOAD.name(), "reload")
}
And when I try to use this pipeline, I get the following error Dispatcher has no subscribers for channel 'application:development:9000.dummy'. It's definitely a misconfiguration issue, but I cannot figure out what am I doing wrong.
UPDATE
Changed channel("dummy") to bridge(null).
What is downstream of the create and reload channels?
What do you do with the Task result in the controller (aside from logging it)?
If you don't need a result, change the gateway return to void and add an executor channel downstream of the gateway.
If you want the Task object returned, you need the routerChannel to be a publish/subscribe channel with an executor and two subscribers - a bridge to nowhere (input channel and no output channel) which will return the Task to the gateway, and the router, which will route the Task on a separate thread; the downstream flows from the router must not return a result (the gateway will have long-before stopped waiting for a result).
Instead of adding an executor to routerChannel, you could make the two router channels executor channels instead.
Your last solution is almost there, you just don't need to use .channel("dummy"), but just .bridge(null) in that first subflow subscriber for the .publishSubscribeChannel().
What you need there is just to send message back to the gateway's replyChannel, which is TemporaryReplyChannel in message headers.
The BridgeHandler is the best way do "nothing" but just send message to the appropriate replyChannel from headers.
Also consider to add executor to the channel after .split() if your .transform() and .hanlde() logic is enough heavy.