Handle ServerResponse WebFlux - java

I want to perform some logic after RouterFunction is complete, and use ServerResponse here.
How to implement it in appropriate way?
For example:
I tried to solve it with HandlerFilterFunction in that way -
public HandlerFilterFunction<ServerResponse, ServerResponse> addMetrics() {
return (request, next) -> next.handle(request)
.doOnNext(response -> meterService.addMetric(response));
}
But this does not work. .doOnNext method is not executed

Related

Webflux returning Mono and using info in Object in Mono to set header

Let's say my Webflux handler returns a Mono on a product creation
That's easy to do.
But now, I want to complete the response with a location in the header.
To do so, I need to get the created product ID.
In my example, I used a block() which fails the reactive idea of the handler.
public Mono<ServerResponse> handleRequest(ServerRequest serverRequest) {
...
Mono<Product> monoProduct = // Service call to get the Mono<Product>
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.location(URI.create(String.format("/api/products/%s",
monoProduct.block().getId()))))
.body(monoProduct), ProductResponse.class);
}
How can I perform such a task without breaking the reactive principles?
You don't really need to block. You need to build reactive flow combining different operators.
In your case it could look like
public Mono<ServerResponse> handleRequest(ServerRequest serverRequest) {
return getProduct() // Service call to get the Mono<Product>
.map(product -> mapToResponse(product)) // Product -> ProductResponse
.flatMap(response ->
ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.location(URI.create(String.format("/api/products/%s", response.getId())))
.body(BodyInserters.fromValue(response))
);
}

Spring Webflux: Calling an endpoint inside flatmap not in parallel

In the following code in Spring Webflux application, I am calling an endpoint "myfunction" which internally calls another endpoint. If the list contains 3 values, I will hit the "cancel" endpoint 3 times. Here is the question. I want to hit the endpoint one by one which means once I get response for 1st value in list then only I want to hit for second value and so on. I know it is reactive framework, still do we have any way to do without using delayElements.
#RestController
#RequestMapping("test")
#Slf4j
public class MyRestController {
private final WebClient webClient;
public MyRestController(WebClient webClient) {
this.webClient = webClient.mutate().baseUrl("http://localhost:7076/test/").build();
}
#GetMapping("/myfunction")
public void callTest() {
Flux.fromIterable(List.of("e1", "e2", "e3"))
//.delayElements(Duration.ofMillis(1000))
.flatMap(event -> {
log.info(event);
return sendCancelRequest(event);
}).subscribe(log::info);
}
public Mono<String> sendCancelRequest(String event) {
return webClient.get()
.uri(uriBuilder -> uriBuilder.path("cancel").queryParam("event", event).build())
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(String.class);
}
#GetMapping("/cancel")
public Mono<String> callMe(#RequestParam String event) {
//try{Thread.sleep(5000);}catch (Exception e){}
return Mono.just(event + " cancelled");
}
}
For example:
Once I get response for "e1" then only I wanna to call "e2" as sequence and response matters for subsequent values in the list. Please assist here guys!

Problem: Returning Flux of type ResponseEntity

I have the following fragment where I want to return a Flux from a ResponseEntity<Response>:
#GetMapping("/{id}")
public Mono<ResponseEntity<Response>> findByDocumentClient(#PathVariable("id") String document){
return Mono.just(new ResponseEntity<>(new Response(technomechanicalService.findByDocumentClient(document), HttpStatus.OK.value(), null),
HttpStatus.OK))
.onErrorResume(error -> {
return Mono.just(new ResponseEntity<>(new Response(null, HttpStatus.BAD_REQUEST.value(), error.getMessage()),
HttpStatus.BAD_REQUEST));
});
}
The Response object is as follows:
public class Response{
private Object body;
private Integer status;
private String descStatus;
public Response(Object body, Integer status, String descStatus) {
this.body = body;
this.status = status;
this.descStatus = descStatus;
}
}
When consuming the Get method from postman, the service responds to the following:
{
"body": {
"scanAvailable": true,
"prefetch": -1
},
"status": 200,
"descStatus": null
}
Why does it generate this response? Why is the list of objects not responding?
It's because you are trying to code imperatively (traditional java) and you are serializing a Mono and not the actually value returned from the database. You should be coding functionally as reactor/webflux uses this type of development.
A Mono<T> is a producer that produces elements when someone subscribes to it. The subscriber is the one that started the call, in this case the client/browser.
Thats why you need to return a Mono<ResponseEntity> becuase when the client subscribes it will emit a ResponseEntity
So lets Look at your code:
#GetMapping("/{id}")
public Mono<ResponseEntity<Response>> findByDocumentClient(#PathVariable("id") String document){
return Mono.just(new ResponseEntity<>(new Response(technomechanicalService.findByDocumentClient(document), HttpStatus.OK.value(), null),
HttpStatus.OK))
.onErrorResume(error -> {
return Mono.just(new ResponseEntity<>(new Response(null, HttpStatus.BAD_REQUEST.value(), error.getMessage()),
HttpStatus.BAD_REQUEST));
});
}
The first thing you do, is to put your response straight into a Mono using Mono#just. In webflux a Mono is something that can emit something and as soon as you put something in one you are also telling the server that it can freely change which thread that performs the execution. So we basically want to go into a Mono as quick as possible so we can leverage webflux thread agnostic abilities.
then this line:
technomechanicalService.findByDocumentClient(document)
returns a Mono<T> and you place that in your your Response body. So it tries to serialize that into json, while you think that it takes its internal Value and serializes that its actually serializing the Mono.
So lets rewrite your code *im leaving out the error handling for now since im writing this on mobile:
#GetMapping("/{id}")
public Mono<ServerResponse> findByDocumentClient(#PathVariable("id") String document){
// We place our path variable in a mono so we can leverage
// webflux thread agnostic abilities
return Mono.just(document)
// We access the value by flatMapping and do our call to
// the database which will return a Mono<T>
.flatMap(doc -> technomechanicalService.findByDocumentClient(doc)
// We flatmap again over the db response to a ServerResponse
// with the db value as the body
.flatMap(value -> ServerResponse.ok().body(value)));
}
All this is super basic reactor/webflux stuff. I assume this is your first time using webflux. And if so i highly recommend going through the Reactor getting started of how the basics work because otherwise you will have a very hard time with reactor, and later on understanding webflux.
Agree with #Toerktumlare's answer. Quite comprehensive.
#Juan David Báez Ramos based on your answer(better if it were a comment), seems like what you want is putting technomechanicalService.findByDocumentClient(document) result as body in a Response object.
If so you can use Flux API's collectList() operator.
Example code:
#GetMapping("/{id}")
public Mono<ResponseEntity<Response>> findByDocumentClient(#PathVariable("id") String document) {
return technomechanicalService.findByDocumentClient(document)
.collectList()
.map(
listOfDocuments -> {
return new ResponseEntity<>(
new Response(listOfDocuments, HttpStatus.OK.value(), null), HttpStatus.OK);
}
)
.onErrorResume(
error -> {
return Mono.just(new ResponseEntity<>(
new Response(null, HttpStatus.BAD_REQUEST.value(), error.getMessage()),
HttpStatus.BAD_REQUEST));
}
);
}

How to make reactive #WebFilter work properly with #RequestBody?

I am trying to make a reactive #WebFilter that executes stuff before and after the actual server exchange (i. e. the controller code handling the request):
public static class Foobar implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return Mono.empty()
.doOnEach(s -> /* BEFORE */))
.then(chain.filter(exchange) /* CONTROLLER */)
.and(Mono.empty().doOnEach(s -> /* AFTER */))
.and(Mono.empty().doFinally(s -> /* FINALLY */));
}
}
Everything works as expected for simple GET requests that return a Mono:
#RestController
#RequestMapping
public static class Foo {
#GetMapping
#PostMapping(value = "foo")
public Mono<String> foo(ServerWebExchange exchange) {
return Mono.just("FOOBAR").map(e -> "OK");
}
}
But something really unexpected happens when the controller receives a parameter annotated as #RequestBody. Say, for example a POST request that takes a Mono<String> from the client:
#RestController
#RequestMapping
public static class Bar {
#PostMapping(value = "bar")
public Mono<String> bar(ServerWebExchange exchange, #RequestBody Mono<String> text) {
return text.map(s -> "OK");
}
}
In this case, all steps in my filter are executed before the controller gets to complete the request. This means that the web exchange is committed independently of the filter and therefore I cannot do anything right after the response is sent back to the client.
So I'm wondering:
Is this some kind of Spring bug?
Am I doing something wrong?
Or is this simply the expected behavior?
I've created a small Gist containing a test case that reproduces the problem:
https://gist.github.com/guillermocalvo/740b4fcab471ebc6fe69227fee6d79d5
Edit after Brian's comment:
I still think this might be a bug because Mono.then doesn't seem to have any effect at all:
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.doOnSubscribe(s -> logger.info("onSubscribe response committed:" +
exchange.getResponse().isCommitted()))
.then().doOnEach(s -> logger.info("then doOnEach response committed:" +
exchange.getResponse().isCommitted()))
.doFinally(s -> logger.info("doFinally response committed:" +
exchange.getResponse().isCommitted()));
}
Additionally, if I put stuff in doOnEach is not executed either:
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.doOnSubscribe(s -> logger.info("FILTER-BEFORE-CHAIN/commited=" +
response.isCommitted()))
.doOnEach(s -> logger.info("FILTER-AFTER-CHAIN/commited=" +
response.isCommitted()))
.doFinally(s -> logger.info("FILTER-FINALLY/commited=" +
response.isCommitted()));
}
I don't think this is a bug in Spring (nor in Reactor in this case), but rather a wrong choice of operators to achieve what you're trying to do.
Mono.doOnEach is executed for each signal (next element, completion, error, cancel); this will be executed several times per request in our case.
Mono.and joins the termination signals - so it waits for both Mono to be done and then completes. But both Monos are not executed sequentially, they're subscribed at the same time. Mono.just completes right away, independently of what happens with the filter chain.
In your case, you don't need to have something more complex than adding one operator when the processing starts (doOnSubscribe happens when the mapping has been done and we're starting to execute the handler) and another one when we're done (doFinally happens when it's done, with completion, error or cancellation).
#Component
public class MyFilter implements WebFilter {
Logger logger = LoggerFactory.getLogger(getClass());
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.doOnSubscribe(s -> logger.info("onSubscribe response committed:" +
exchange.getResponse().isCommitted()))
.doFinally(s -> logger.info("doFinally response committed:" +
exchange.getResponse().isCommitted()));
}
}
Note that this approach works as long as you're not performing blocking operations in the DoOnXYZ methods, as they're side effects methods. Doing so will create significant performance problems in your application (and reactor might even reject that operation with an exception). So logging, adding an element to a map are fine - but publishing something to an event queue, for example, is not. In this case, you should use instead chain operations with Mono.then().
Edit
I don't think there is a bug with Mono.then() - doOnEach only works with signals sent downstream (next, error, complete). In this case, you should only get the complete signal. If you'd like to get the context in all cases, you can take a look at Mono.deferWithContext.

Why does this simple Webflux Controller calls the Webclient retrieve method twice?

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")
}
}

Categories

Resources