I'm a bit lost when playing with Mutiny. I have a testfunction that Creates a String uni and when that is succesful I want that it return a 200 OK or when it fails(which it shouldn´t with this simple string creation) a 500 Internal Server Error for now.
I can get it to work by using the onItemOrOnFailure() of mutiny but I'm trying to split up the handling of the success and failure scenario. I see my sout of the onItem() but I still get to the onFailure() and get a 500 response in postman. Why do I go into the onFailure? What am I not understanding?
So I expect one or the other but I get into both.
#GET
#Path("/test")
#Produces(MediaType.APPLICATION_JSON)
public Uni<RestResponse<?>> test() {
return Uni.createFrom().item("Hello world")
.onItem().transform(str -> {
var resp = RestResponse.ok(str);
System.out.println("In onItem");
return resp;
})
.onFailure().recoverWithNull().replaceWith(() -> {
System.out.println("In onFailure");
return RestResponse.status(500);
});
}
I think I figured it out. Its not the fault of the onFailure, but its just that the replaceWith isnt part of the onFailure. Its just always called as the next part.
So on success it goes onItem -> transform -> replaceWith and onFailure it goes onFailure -> recoverWithNull -> replaceWith.
So If i understand correctly; when using a recoverWith... function you step out of the failure event and continue with the rest of the steps Is this a correct assumption?
Ah well what a bit of sleep can do:)
Related
I'm trying to reactively fetch data from external API using two methods from some Service class.
I'm new to reactive and Spring in general, so it could be a very obvious mistake but I just can't find it
These are the two methods:
public Mono<SomeClass> get(int value) {
return webClient.get()
.uri("/" + value)
.retrieve()
.onRawStatus(HttpStatus.CONFLICT::equals, clientResponse -> {
return Mono.error(new SomeException1("Some message", clientResponse.rawStatusCode()));
})
.onRawStatus(HttpStatus.NOT_FOUND::equals, clientResponse -> {
return requestGeneration(value)
.flatMap(res -> Mono.error(new SomeException1("Some message", clientResponse.rawStatusCode())));
})
.bodyToMono(SomeClass.class)
.retryWhen(Retry.backoff(5, Duration.ofSeconds(8))
.filter(throwable -> throwable instanceof SomeException1));
}
private Mono<Void> requestGeneration(int value) {
return webClient.post()
.uri("/" + value)
.retrieve()
.onRawStatus(HttpStatus.BAD_REQUEST::equals, clientResponse -> {
return Mono.error(new SomeException2("Wrong value", clientResponse.rawStatusCode()));
})
.bodyToMono(Void.class);
}
Baasically what I'm trying to achieve is:
first GET from http://api.examplepage.com/{value}
if that API returns HTTP404 it means I need to first call POST to the same URL, because the data is not yet generated
the second function does the POST call and returns Mono<Void> because it is just HTTP200 or HTTP400 on bad generation seed (i don't need to process the response)
first function (GET call) could also return HTTP429 which means the data is generating right now, so I need to call again after some time period (5-300 seconds) and check if data has been generated already
then after some time it results in HTTP200 with generated data which I want to map to SomeClass and then return mapped data in controller below
#PostMapping("/follow/{value}")
public Mono<ResponseEntity<?>> someFunction(#PathVariable int value) {
return Mono.just(ResponseEntity.ok(service.get(value)));
}
all the code I posted is very simplified to the issues I'm struggling with and doesn't contain some things I think are not important in this question
and now the actual question:
it doesn't actually make the call? i really don't know what is happening
program doesn't enter onRawStatus, even if i change it to onStatus 2xx or whatever other httpstatus and log inside i see nothing as if it doesn't even enter the chains
when i manually call with postman it seems like the program calls have never been made because the GET call returns 404 (the program didn't request to generate data)
the controller only returns "scanAvailable": true, when i expect it to return mapped SomeClass json
// edit
i changed the code to be a full chain as suggested and it didn't solve the problem. all the status are still unreachable (code inside any onStatus nor onRawStatus never executes)
I have three WebClients that look something like this:
WebClient
public Mono<MyObject> getResponseOne() {
return webClient.get()
.uri(URI)
.header("header", header)
.bodyValue(body)
.retrieve()
.bodyToMono(MyObject.class);
}
Then I have a controller which calls multiple WebClients:
Controller
#ResponseBody
#PostMapping("/get")
public Mono<MyObject> processResponse() {
MyObject obj = getResponseOne().toFuture().get();
system.out.println("Got first response");
String str = getResponseTwo().toFuture().get();
system.out.println("Got second response");
//process and send with third WebClient
MyObject newObj = getResponseThree(obj, str).toFuture().get();
//process response from third WebClient and send to fourth WebClient
//return statement
}
When I call /get, the console only prints "Got first response" then it just stops there and anything below doesn't seem to be executing. I'm using Postman to send the request, so it keeps on waiting without getting any response.
This may or may not be relevant, but I'm using blocking calls because I need the both responses to be processed before sending it to a third WebClient, the response from the third WebClient will also go through additional processing before being returned as the response of processResponse().
Solution
I used Mono.zip() like Alex suggested:
#ResponseBody
#PostMapping("/get")
public Mono<MyObject> processResponse() {
//TupleN depends on the amount of Monos you want to process
Mono<Tuple2<MyObject,String>> output = Mono.zip(getResponseOne(),getResponseTwo());
return output.map(result ->{
// getT1() & getT2() is automatically generated by the tuple
MyObject obj = result.getT1();
String str = result.getT2();
getResponseThree(obj, str);
//process and return
});
}
More about Mono.zip()
In reactive API nothing happens until you subscribe. Your method returns Mono and you need to construct a flow combining publishers. There are multiple ways to combine depending on the required logic.
For example, if you need the result of the predecessor you could use flatMap to resolve Mono sequentially
return getResponseOne()
.flatMap(res -> getResponseTwo(res))
.flatMap(res -> getResponseThree(res));
In case calls are independent you could use then
return getResponseOne()
.then(getResponseTwo())
.then(getResponseThree());
You could also execute in parallel using Mono.when(getResponseOne(), getResponseTwo(), getResponseThree()) or Mono.zip(getResponseOne(), getResponseTwo(), getResponseThree()).
There are many other operators but the key here is to construct the flow and return Mono or Flux.
Most of the times instead of adding comments in an ordinary JUnit assertion, we add a message to the assertion, to explain why this is assertion is where it is:
Person p1 = new Person("Bob");
Person p2 = new Person("Bob");
assertEquals(p1, p2, "Persons with the same name should be equal.");
Now, when it comes to end point testing in a Spring Boot web environment I end up with this:
// Bad request because body not posted
mockMvc.perform(post("/postregistration")).andExpect(status().isBadRequest());
// Body posted, it should return OK
mockMvc.perform(post("/postregistration").content(toJson(registrationDto))
.andExpect(status().isOk()));
Is there a way to get rid of the comments and add a message to this kind of assertion? So, when the test fails I will see the message.
You can provide a custom ResultMatcher:
mockMvc.perform(post("/postregistration")
.content(toJson(registrationDto))
.andExpect(result -> assertEquals("Body posted, it should return OK", HttpStatus.OK.value() , result.getResponse().getStatus())))
mockMvc.perform(post("/postregistration"))
.andExpect(result -> assertEquals("Bad request because body not posted", HttpStatus.BAD_REQUEST.value(), result.getResponse().getStatus()));
Explaination:
As of today the method .andExpect() accepts only one ResultMatcher. When you use .andExpect(status().isOk()) the class StatusResultMatchers will create a ResultMatcher in this way:
public class StatusResultMatchers {
//...
public ResultMatcher isOk() {
return matcher(HttpStatus.OK);
}
//...
private ResultMatcher matcher(HttpStatus status) {
return result -> assertEquals("Status", status.value(), result.getResponse().getStatus());
}
}
As you can see the message is hard-coded to "Status" and there is no other built in method to configure it. So even though providing a custom ResultMatcher is a bit verbose, at the moment might be the only feasible way using mockMvc.
I figured out that assertDoesNotThrow responds hence improves the situation (according to what I ask):
assertDoesNotThrow(() -> {
mockMvc.perform(post("/postregistration")).andExpect(status().isBadRequest());
}, "Bad Request expected since body not posted.");
The following method will return a list of Strings with status OK.
#Async
#RequestMapping(path = "/device-data/search/asyncCompletable", method = RequestMethod.GET)
public CompletableFuture<ResponseEntity<?>> getValueAsyncUsingCompletableFuture() {
logger.info("** start getValueAsyncUsingCompletableFuture **");
// Get a future of type String
CompletableFuture<List<String>> futureList = CompletableFuture.supplyAsync(() -> processRequest());
return futureList.thenApply(dataList -> new ResponseEntity<List<String>>(dataList, HttpStatus.OK));
}
I would like to make this method more robust by adding the following features:
1) Since fetching the data will take some time ( processRequest())) than in the meanwhile I would like to return status ACCEPTED so there will be some notification for the user.
2) In case the list is empty then I want to return status NO_CONTENT.
How can I add these enhancements in the same method?
Thank you
Even though this is an #Async function, you're waiting for processRequest() to complete and thenApply() function takes the results and set HttpStatus.NO_CONTENT to ResponseEntity despite the results status.
You return that already processed result list, just like a synchronized invoke. I would rather let the client to decide if the List is empty or not, after an immediate response.
#Async
#RequestMapping(path = "/device-data/search/asyncCompletable", method = RequestMethod.GET)
public CompletableFuture<ResponseEntity<List<String>>> getValueAsyncUsingCompletableFuture() {
return CompletableFuture.supplyAsync(() -> ResponseEntity.accepted().body(processRequest()));
}
In the client code, once the get response via RestTemplate of WebClient wait for the response or continue the rest of the tasks.
CompletableFuture<ResponseEntity<List<String>>> response = ... // The response object, should be CompletableFuture<ResponseEntity<List<String>>> type hence returns from the upstream endpoint.
System.out.println(response.get().getBody()); // 'get()' returns a instance of 'ReponseEntity<List<String>>', that's why 'getBody()' invokes.
Well this CompletableFuture.get() is a blocking function and it would wait the current thread until the response arrives. You can continue without blocking for the response until particular response is required in the code.
For example I have two network request:
Single<FirstResponse> firstRequest = firstCall()
Single<SecondResponse> secondRequest = secondCall()
Now, the Second should only be called when the first is successful, so I do something like:
firstRequest().flatmap( firstResponse -> secondRequest ).subscribe()
It works well when if both calls gets completed successfully but how about I dont want the first call to get called when it already returned success?
So what I want to achieve is that, when the firstRequest successfully completed and the second failed, I only want the first to be skipped and only call the second.
Currently the only thing I could think of doing is something like:
public firstResponse = null;
public Single<FirstResponse> getFirstRequest() {
if (firstResponse!=null && firstResponse.isSuccess()) {
return Single.just(firstResponse);
} else {
return firstRequest;
}
}
public void doRequest() {
getFirstRequest()
.doOnSuccess( firstResponse -> this.firstReponse = firstResponse )
.flatMap( firstResponse -> secondRequest)
.subscribe()
}
I wonder if there is a better way to do this.
Thanks in advance.