Spring Webflux ServerResponse is blank - java

I have been banging my head for this simple issue on this simple code :
#Bean
public RouterFunction<ServerResponse> refresh() {
return route(GET("/api/refresh"), (request) ->
request.principal().or(Mono.empty()).flatMap((it) -> {
System.out.println(it);
return ServerResponse.ok().body(Mono.just(new FreshStatus(true)), FreshStatus.class);
}));
}
record FreshStatus(boolean isFresh) {}
I have two issues:
When not authenticated, the code inside flatMap is not called, despite my .or clause
When authenticated, the flatMap code is called, but the server response is just blank.
In both cases response is 200 OK.
I have tried using SecurityContextHolder nothing changed.
New code is:
ServerResponse.ok().body(request.principal()
.map(it -> new FreshStatus(true))
.defaultIfEmpty(new FreshStatus(false)), FreshStatus.class));

If the principal() method returns an empty Mono, the flatMap() method will not be called and the response will be empty. Mono.empty() will return a Mono that completes without emitting any item.
You can try the defaultIfEmpty() method instead.
Not sure whether this is what you want, but something like:
return route(GET("/api/refresh"), (request) ->
request.principal().defaultIfEmpty(new FreshStatus(false))
.flatMap((it) -> {
System.out.println(it);
return ServerResponse.ok().body(Mono.just(it), FreshStatus.class);
}));
As an aside, Josh Bloch would claim that in most cases, you're better off using an enum instead of a boolean. I tend to agree. This could be relevant for your FreshStatus record potentially.

Related

How to chain vavr's Either nicely?

I have a function returning an Either<MyError, String> (function2) , which result depends on another function returning another Either<MyError, SomethingElse> (function1)
Both functions rely on a Try block that could fail, and I want to compose those two first function to create a "handle" which will be the main function of my class.
There are basically 3 scenarios possible :
function1 fails : I want handle to return the error given by function1
function1 succeeds and function2 fails : function2 must return its own error then returned by handle
both functions work : handle must return the String
Here is my current code :
private Either<MyError, Path> getPath(Arg arg) { // function 1
return Try.of(() -> //some code that can fail)
.toEither().mapLeft(e -> new MyError("Error message for function1", e));
}
private Either<MyError, String> getContent(Path path) { // function 2
return Try.of(() -> //some code that can fail)
.toEither().mapLeft(e -> new MyError("Error message for function2", e));
}
public Either<MyError, String> handle(Arg arg) {
return Either.right(arg)
.map(this::getPath)
.map(this::getContent);
}
Everything works except the Handle function, I think that my problem might be related to the use of Either::map function, that might not be the thing for my case.
Any thought about this ?
Also, sorry if the answer seems obvious, i am quite new to functionnal programming and vavr.
The method that could help to make this work would be flatMap.
So if you use flatMap instead of map, the handle method will become something like:
public Either<MyError, String> handle(Arg arg) {
return Either.<MyError, Arg>right(arg)
.flatMap(this::getPath)
.flatMap(this::getContent);
}
The scenarios you mentioned are all covered with this flatMap method.
See the Either.flatMap documentation for the official docs about it.

Call Mono after first Mono is done and return result of first Mono

I want to perform two business operations, in a Webflux environment, in a way that the second operation happens only after the first one is succesfull. After the second one is done, I want to return the result of the first operation. The second operation calls a org.springframework.web.reactive.function.client.WebClient. This is what I have tried until now:
public Mono<ResponseEntity<Resource>> callOperations(){
return service.operation1()
.flatMap(resource -> {
service.operation2();
return resource;
})
.map(ResponseEntity::ok);
}
I also tried with then and subscribe but I can't get the webclient to perform the call and return the result of service.operation1. What must I do?
You need to construct a flow using reactive operators and let WebFlux subscribe to it. In your snippet there is no subscription to service.operation2()
public Mono<ResponseEntity<Resource>> callOperations(){
return service.operation1()
.flatMap(resource -> {
return service.operation2()
.thenReturn(resource);
})
...
}

Convert traditional loop of invoking weclient into non blocking way

I am new to reactive programming and I want to transform the following code into non blocking way.
For the sake of simplicity, I created a sample pseudo code based from my original code. Any help will be appreciated.
public Mono<Response> getResponse(List<Provider> providers) {
for (Provider provider : providers) {
Response response = provider.invokeHttpCall().block();
if(response.getMessage() == "Success") {
return Mono.just(response);
}
continue;
}
return Mono.empty();
}
provider.invokeHttpCall() method
#Override
public Mono<Response> invokeHttpCall(){
WebClient webClient = WebClient.create();
return webClient.post()
.uri("/provider").accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Response.class);
}
I tried several tactics to implement this, but still no luck. Either all providers are invoked or I need to block the webclient thread.
Flux.fromIterable(providers)
.concatMap(Provider::invokeHttpCall) // ensures providers are called sequentially
.filter(response -> response.getMessage().equals("Success"))
.next()
reactive is a kind of Stream. Please think it as a Stream and program it reactively.
I give you such followed code.
Firstly, use Flux.fromIterable() to create a flux stream from a List.
Next, use flatmap() and Lambda fuction to emit the invoke into another new thread.
use method filterWhen() and Lambda to get the "Success" response and just get the first "Success" elements. See filterwhen api Doc.
Finally, just use Mono.from() to wrap the Flux and then return the Mono type.
public Mono<Response> getResponse(List<Provider> providers) {
return Mono.from(Flux.fromIterable(providers)
.flatmap(provider ->
Mono.defer(() -> provider.invokeHttpCall())
.filterWhen(response -> response.getMessage() == "Success");
}
if you want to see result and println().
Just use .subsribe() method to excute it.
getResponse.subsribe(System.out::println);

how to solve the problem with put one data in mono to another mono in Spring Webflux?

I want to get a string data from another server by webclient object, and put it to another Mono object. But in a webclient, only readable that in .subscribe().
Because responseBody.subscribe() method is async, method test() will be return result object with empty message field before responseBody.subscribe() executed.
Of course, I knew that if I return responseBody object instead of result object, there is no problem. But I want to return not a responseBody object but result object with not empty field of message.
I want to return result when responseBody's subscribe() is completed.
How to change my code?
Please help me.
public Mono<ResultVO> test() {
Mono<ResultVO> result = Mono.just(new ResultVO());
WebClient client = webClientBuilder.baseUrl("http://XXXXXX").build();
Mono<String> responseBody = client.get().uri("/aaaa/bbbbb").retrieve().bodyToMono(String.class);
responseBody.subscribe( s -> {
result.subscribe(g -> g.setMessage(s));
});
return result;
}
...
#Data
public class ResultVO {
private long timestamp;
private String ip;
private String message;
...
}
I expect like this
{
"timestamp": 1566662695203,
"ip": "192.168.1.1",
"message": "c0db76f6-4eb5-4f84-be8d-018d53b453bb"
}
But result data is,
{
"timestamp": 1566662695203,
"ip": "192.168.1.1",
"message": ""
}
Putting this kind of logic into the subscribe method is not recommended, it can easily lead to 'callback hell' and eventually unmaintainable code. Also, I don't see the caller of the shared test method, but chances are that one of the Monos is subscribed twice, which also leads to quite confusing behaviour.
Instead, to combine Monos you can use zip, zipWith, flatMap and a couple of other operators.
One solution with zipWith method:
public Mono<ResultVO> test()
{
WebClient client = WebClient.builder().baseUrl("http://XXXXXX").build();
// dummy representation of another data source (db query, web service call...)
Mono<ResultVO> result = Mono.just(new ResultVO());
Mono<String> responseBody = client.get().uri("/aaaa/bbbbb").retrieve().bodyToMono(String.class);
return result.zipWith(responseBody,
(resultObj, body) -> new ResultVO(resultObj.getTimestamp(), resultObj.getIp(), body));
}
Couple of other notes:
If you are returning JSON through a REST endpoint of your reactive WebFlux application, then you never need to subscribe manually, Spring will do that for you
Avoid using mutable objects (the ones which you modify after creation with setters), instead create new object, this will make your code easier to reason about and less prone to concurrency issues
Useful read about available Reactor operators
First of all, you hardly ever subscribe in your own application.
Think of it this way. Your server is a publisher, that means that your server fetches data and then publishes it to whomever wants it.
The subscriber is usually the end client, that could be a react application, an angular application or any client.
I think you need to read up on the basics of how to use webflux and reactive programming.
This is how to do what you are asking for, with as minimal changes to your code, we map what we fetched to what we want returned.
public Mono<ResultVO> test() {
final WebClient client = webClientBuilder
.baseUrl("http://XXXXXX").build();
return client.get()
.uri("/aaaa/bbbbb")
.retrieve()
.bodyToMono(String.class)
.map(message -> {
final ResultVO resultVO = new ResultVO();
resultVO.setMessage(message);
return resultVO;
}
);
}

How to return a status code in a Lagom Service call

I want to implement a typical rest POST call in Lagom. The POST creates an object, and returns it, with a status code of 201.
However, the default return code is 200. It is possible to set a status code, as shown here (https://www.lagomframework.com/documentation/1.3.x/java/ServiceImplementation.html#Handling-headers).
However, I cannot figure out how to do it for a more complicated case. My create is asynchronious, and I return an object instead of a String.
This is the code I have:
#Override
public HeaderServiceCall<OrderRequest.CreateOrderRequest, Order> createOrder() {
UUID orderId = UUID.randomUUID();
ResponseHeader responseHeader = ResponseHeader.OK.withStatus(201);
return (requestHeader, request) -> {
CompletionStage<Order> stage = registry.refFor(OrderEntity.class, orderId.toString())
.ask(buildCreateOrder(orderId, request))
.thenApply(reply -> toApi(reply));
return CompletableFuture.completedFuture(Pair.create(responseHeader, stage.toCompletableFuture()));
};
}
However, the return value should be Pair<ResponseHeader, Order>, not Pair<ResponseHeader, CompletionStage<Order>> which I have now, so it does not compile.
I could of course extract the Order myself, by putting the completionStage into an CompletableFuture and getting that, but that would make the call synchronous and force me to deal with InterruptExceptions etc, which seems a bit complex for something that should be trivial.
What is the correct way to set a status code in Lagom?
You almost have it solved. Instead of creating a new completedFuture you could compose stage with a lambda that builds the final Pair like this:
return stage.thenApply( order -> Pair.create(responseHeader, order));
And putting all the pieces together:
registry.refFor(OrderEntity.class, orderId.toString())
.ask(buildCreateOrder(orderId, request))
.thenApply( reply -> toApi(reply));
.thenApply( order -> Pair.create(responseHeader, order));

Categories

Resources