Does mono.cache works for Webclient response? - java

I'm trying to cache an authentication token response which is returned by a webclient call.
public Mono<Token> getToken() {
return webclient.post().retrieve()
.bodyToMono(Token.class)
.cache(this::getTokenLiveDuration, t -> Duration.ZERO, () -> Duration.ZERO).log();
}
public Mono<MyResponse> execute() {
return getToken().flatMap(token -> {
webclient.post().header("Auth", token.getValue())
.retrieve()
.bodyToMono(MyResponse.class)
}
}
But if I run execute() two time in a same instance, they have different tokens, which means the cache did not work.
What's the correct way of using .cache or the correct way to cache webclient response?

That's because each time getToken is called a new Mono is created with its own cache.
One way to make caching effective is creating a field for the cached token Mono and use that Mono in the execute method.

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

How to get raw token from ReactiveSecurityContextHolder?

I have a method:
#GetMapping("/foo")
public void> foo(JwtAuthenticationToken token) throws ExecutionException, InterruptedException {
Object object = ReactiveSecurityContextHolder.getContext()
.map(securityContext -> securityContext.getAuthentication().getPrincipal())
.toFuture()
.get();
System.out.println(object);
JwtAuthenticationToken object which is method argument is succesfully autowired and not null but
result of
Object object = ReactiveSecurityContextHolder.getContext()
.map(securityContext -> securityContext.getAuthentication().getPrincipal())
.toFuture()
.get();
is null.
Could you please explain why ? Is there way to fix it ?
related topic: How to get jwt token value in spring webflux? (to exchange it with Minio STS token)
It is not necessary to use ReactiveSecurityContextHolder to get the Jwt instance. For example, if JwtAuthenticationToken is non-null, you can get the Jwt instance by doing:
public Mono<Void> foo(JwtAuthenticationToken token) throws ExecutionException, InterruptedException {
Jwt jwt = token.getToken();
// ...
}
Or, you can translate it with #AuthenticationPrincipal like so:
public Mono<Void> foo(#AuthenticationPrincipal Jwt jwt) throws ExecutionException, InterruptedException {
// ...
}
Further, it is not possible to use block() with ReactiveSecurityContextHolder in the way the OP describes. block() subscribes immediately, and there is nothing immediately available in the Reactor Context at this point.
The reactive stack works somewhat in the inverse to perhaps what you are thinking. While there is a filter in Spring Security called ReactorContextWebFilter that populates the Reactor Context with the SecurityContext, its work is deferred until the HTTP response is subscribed to, for example by the browser. block() at this point states (correctly) that the Reactor Context is empty. Instead, if you participate in the existing subscription (instead of calling block()), then you are also deferring your work in the same way and will be able to use ReactiveSecurityContextHolder.
EDIT: Having read the additional context about the OP's situation from How to get jwt token value in spring webflux? (to exchange it with Minio STS token), it's clear now that the OP is aware of these ways to get the instance of a Jwt, and does not want to use them. I'll leave the answer here for completeness anyway.
Not really sure how token is related to the result of the bucketExists and why do you convert it to the CompletableFuture but here is an example how you can get token from the context.
#GetMapping(value = "/someEndpoint")
public Mono<Boolean> foo() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getPrincipal())
.cast(Jwt.class)
.map(jwt -> useToken(jwt));
}
Here is a test using org.springframework.security:spring-security-test
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWebTestClient
class RouteConfigTest {
#Autowired
private WebTestClient client;
#Test
void test() {
this.client.mutateWith(mockJwt())
.get()
.uri("/someEndpoint")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk();
}
}

Exception in not mapped inside flatMap

I have a piece of code that calls an external service. And I wanted to map error from this service. I tried to do that this way:
public Mono<Void> patch(String userId, Payload payload) {
return Mono.just(payload)
.flatMap(it -> client.patch(userId, PatchRequest.buildRequest(payload, userId))
.onErrorMap(throwable -> GeneralActionException.ofFailedSetup()));
}
But when I mocked the client to return RuntimeException
Mockito.when(client.patch(any(), any())).thenThrow(new RuntimeException());
It turned out my test:
StepVerifier.create(sut.patch(userId, payload))
.verifyError(GeneralActionException.class);
failed, because the returned error was RuntimeException:
However when I change the code a little, just like that:
public Mono<Void> patch(String userId, Payload payload) {
return Mono.just(payload)
.flatMap(it -> client.patch(userId, PatchRequest.buildRequest(payload, userId)))
.onErrorMap(throwable -> GeneralActionException.ofFailedSetup());
}
It turned out the test succeeded. The question is why? Because I don't understand why it works differently in both cases and especially why in the first example when error mapping is inside flatMap it doesn't map it to GeneralException?
Ok, I solved it. I mocked client wrongly.
Instead of:
Mockito.when(client.patch(any(), any())).thenThrow(new RuntimeException());
it should be:
Mockito.when(client.patch(any(), any())).thenReturn(Mono.error(RuntimeException::new));
as the client returns Mono<Void>

How to make Java Rest API to return response after first half of processing, then continue the second half after return response?

I have an API endpoint that's using Spring Boot.
What this endpoint does is it calls two other API endpoints and processes their response.
The first half of the process calls one API endpoint, get the response and return this response with a 202 Accepted to the surface.
After the 202 has been returned, the background is undergoing the second half of the process. Which is taking the response from the first API calls and further processing it.
I tried with Executor or CompletableFuture but both of them stopped after its return 202 and will not run the second half or they wait until the second half to complete only return the 202.
Is this possible to achieve or am I looking into wrong design?
Here is some sample code:
#PostMapping("/user")
public ResponseEntity<?> processUser(#Valid #RequestBody UserRequestDto request,
#RequestHeader("Authorization") String token) throws Exception {
CompletableFuture<UserResponseDto> response = CompletableFuture.supplyAsync(() ->
userService.processUser(request, token));
userService.processUserSecond(response, token);
return new ResponseEntity<>(response, HttpStatus.ACCEPTED);
}
To make it clear: The REST endpoint consists of two calls - processUser and processUserSecond. You want to get the result of the processUser, return its result and fire the processUserSecond asynchronously without waiting for its result.
Remember, in that case, the first call must be synchronous since you wait for its result to be used in the subsequent call. The latter can be asynchronous.
#PostMapping("/user")
public ResponseEntity<?> processUser(#Valid #RequestBody UserRequestDto request,
#RequestHeader("Authorization") String token)
{
// synchronous, waiting for the response to be used
UserResponseDto response = userService.processUser(request, token);
// asynchronous, fired without waiting for the response
CompletableFuture.runAsync(() -> userService.processUserSecond(response, token));
// return the result of the first (an only) synchronous call
return new ResponseEntity<>(response, HttpStatus.ACCEPTED);
}

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