I'm using a RouterFunction
to define endpoints in my Spring Boot application. My service returns a Mono<Object>
and I want to return the result of this when the endpoint is called. I also need to authenticate so I pass a UserPrinciple
object through.
Router
@Bean
RouterFunction<ServerResponse> router() {
return route()
.GET("/api/endpoint-name", this::getExample)
.build();
}
private Mono<ServerResponse> getExample(ServerRequest request) {
return ServerResponse.ok().body(fromPublisher(getUserPrincipal().map(service::getSomething), Object.class)).log();
}
private Mono<UserPrincipal> getUserPrincipal() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication())
.map(auth -> auth.getPrincipal())
.map(UserPrincipal.class::cast);
}
Service
public Mono<Object> getSomething(UserPrincipal userPrincipal) {
WebClient webClient = getWebClient(userPrincipal.getJwt());
return webClient.get()
.uri(uriBuilder -> uriBuilder.path("another/server/endpoint").build())
.retrieve()
.bodyToMono(Object.class);
}
The endpoint is returning this:
{
"scanAvailable": true
}
which suggests that I'm passing the Mono
into the body of the response instead of passing in the result. However I've used fromPublisher
which I thought would resolve this.
I can't find any examples where the service returns a Mono
and the route correctly returns the result of the Mono
.
How can I correctly pass a Mono/Flux
as the body of the response?
CodePudding user response:
im not going to explain the difference between map
and flatMap
since i have already written a quite comprehensive explanation here:
Do you have a test to show differences between the reactor map() and flatMap()?
The problem in the above code is the return of Object
. And input parameters of Object
into certain functions. The first function is pretty straight forward
Mono<UserPrincipal> = getUserPrincipal();
While the second one gets a bit more hairy:
Mono<Mono<Object> value = getUserPrincipal().map(service::getSomething);
So why are we getting A nested Mono?, well the get something returns a Mono<Object>
and the Map
return according the the api is Mono<R>
where R
is what we return from getSomething
.
We then stick it into the fromPublisher
which will unrap the first Mono
ending up trying to serialize the Mono<Object>
resulting in the strange response.
{
"scanAvailable": true
}
The answer here is pay more close attention to the type system. The body
function takes a Publisher
(Mono or Flux) so you don't need the fromPublisher
function.
And also changing map
to flatMap
since the return type from inside a flatMap
is a publisher
.
ServerResponse.ok()
.body(getUserPrincipal()
.flatMap(service::getSomething), Object.class));