I have a resource API that handles an object (Product for example).
I use PUT to update this object in the database.
And I want to return just en empty Mono to the user.
There is my code :
public Mono<ServerResponse> updateProduct(ServerRequest request){
Mono<Product> productReceived = request.bodyToMono(Product.class);
Mono<Product> result = productReceived.flatMap(item -> {
doSomeThing(item);
System.out.println("Called or not called!!");
return Mono.just(productService.product);
}).subscribe();
return ok()
.contentType(APPLICATION_JSON)
.body(Mono.empty(), Product.class);
}
The problem is my method doSomeThing() and the println are not called.
NB: I use subscribe but doesn't work.
Thanks.
I had a similar issue when I was new to Webflux. In short, you can't call subscribe on the request body and asynchronously return a response because the subscription might not have enough time to read the body. You can see a full explanation of a similar issue here.
To make your code work, you should couple the response with your logic stream. It should be something like the following:
public Mono<ServerResponse> updateProduct(ServerRequest request){
return request
.bodyToMono(Product.class)
.flatMap(item -> {
doSomeThing(item);
System.out.println("Called or not called!!");
return Mono.just(productService.product);
})
.then(ServerResponse.ok().build());
}
Related
I’m very new to the SpringReactor project.
Until now I've only used Mono from WebClient .bodyToMono() steps, and mostly block() those Mono's or .zip() multiple of them.
But this time I have a usecase where I need to asynchronously call methods in multiple service classes, and those multiple service classes are calling multiple backend api.
I understand Project Reactor doesn't provide asynchronous flow by default.
But we can make the publishing and/or subscribing on different thread and make code asynchronous
And that's what I am trying to do.
I tried to read the documentation here reactor reference but still not clear.
For the purpose of this question, I’m making up this imaginary scenario. that is a little closer to my use case.
Let's assume we need to get a search response from google for some texts searched under images.
Example Scenario
Let's have an endpoint in a Controller
This endpoint accepts the following object from request body
MultimediaSearchRequest{
Set<String> searchTexts; //many texts.
boolean isAddContent;
boolean isAddMetadata;
}
in the controller, I’ll break the above single request object into multiple objects of the below type.
MultimediaSingleSearchRequest{
String searchText;
boolean isAddContent;
boolean isAddMetadata;
}
This Controller talks to 3 Service classes.
Each of the service classes has a method searchSingleItem.
Each service class uses a few different backend Apis, but finally combines the results of those APIs responses into the same type of response class, let's call it MultimediaSearchResult.
class JpegSearchHandleService {
public MultimediaSearchResult searchSingleItem
(MultimediaSingleSearchRequest req){
return comboneAllImageData(
getNameApi(req),
getImageUrlApi(req),
getContentApi(req) //dont call if req.isAddContent false
)
}
}
class GifSearchHandleService {
public MultimediaSearchResult searchSingleItem
(MultimediaSingleSearchRequest req){
return comboneAllImageData(
getNameApi(req),
gitPartApi(req),
someRandomApi(req),
soemOtherRandomApi(req)
)
}
}
class VideoSearchHandleService {
public MultimediaSearchResult searchSingleItem
(MultimediaSingleSearchRequest req){
return comboneAllImageData(
getNameApi(req),
codecApi(req),
commentsApi(req),
anotherApi(req)
)
}
}
In the end, my controller returns the response as a List of MultimediaSearchResult
Class MultimediaSearchResponse{
List< MultimediaSearchResult> results;
}
If I want to use this all asynchronously using the project reactor. how to achieve it.
Like calling searchSingleItem method in each service for each searchText asynchronously.
Even within the services call each backend API asynchronously (I’m already using WebClient and converting response bodyToMono for backend API calls)
First, I will outline a solution for the upper "layer" of your scenario.
The code (a simple simulation of the scenario):
public class ChainingAsyncCallsInSpring {
public Mono<MultimediaSearchResponse> controllerEndpoint(MultimediaSearchRequest req) {
return Flux.fromIterable(req.getSearchTexts())
.map(searchText -> new MultimediaSingleSearchRequest(searchText, req.isAddContent(), req.isAddMetadata()))
.flatMap(multimediaSingleSearchRequest -> Flux.merge(
classOneSearchSingleItem(multimediaSingleSearchRequest),
classTwoSearchSingleItem(multimediaSingleSearchRequest),
classThreeSearchSingleItem(multimediaSingleSearchRequest)
))
.collectList()
.map(MultimediaSearchResponse::new);
}
private Mono<MultimediaSearchResult> classOneSearchSingleItem(MultimediaSingleSearchRequest req) {
return Mono.just(new MultimediaSearchResult("1"));
}
private Mono<MultimediaSearchResult> classTwoSearchSingleItem(MultimediaSingleSearchRequest req) {
return Mono.just(new MultimediaSearchResult("2"));
}
private Mono<MultimediaSearchResult> classThreeSearchSingleItem(MultimediaSingleSearchRequest req) {
return Mono.just(new MultimediaSearchResult("3"));
}
}
Now, some rationale.
In the controllerEndpoint() function, first we create a Flux that will emit every single searchText from the request. We map these to MultimediaSingleSearchRequest objects, so that the services can consume them with the additional metadata that was provided with the original request.
Then, Flux::flatMap the created MultimediaSingleSearchRequest objects into a merged Flux, which (as opposed to Flux::concat) ensures that all three publishers are subscribed to eagerly i.e. they don't wait for one another. It works best on this exact scenario, when several independent publishers need to be subscribed to at the same time and their order is not important.
After the flat map, at this point, we have a Flux<MultimediaSearchResult>.
We continue with Flux::collectList, thus collecting the emitted values from all publishers (we could also use Flux::reduceWith here).
As a result, we now have a Mono<List<MultimediaSearchResult>>, which can easily be mapped to a Mono<MultimediaSearchResponse>.
The results list of the MultimediaSearchResponse will have 3 items for each searchText in the original request.
Hope this was helpful!
Edit
Extending the answer with a point of view from the service classes as well. Assuming that each inner (optionally skipped) call returns a different type of result, this would be one way of going about it:
public class MultimediaSearchResult {
private Details details;
private ContentDetails content;
private MetadataDetails metadata;
}
public Mono<MultimediaSearchResult> classOneSearchSingleItem(MultimediaSingleSearchRequest req) {
return Mono.zip(getSomeDetails(req), getContentDetails(req), getMetadataDetails(req))
.map(tuple3 -> new MultimediaSearchResult(
tuple3.getT1(),
tuple3.getT2().orElse(null),
tuple3.getT3().orElse(null)
)
);
}
// Always wanted
private Mono<Details> getSomeDetails(MultimediaSingleSearchRequest req) {
return Mono.just(new Details("details")); // api call etc.
}
// Wanted if isAddContent is true
private Mono<Optional<ContentDetails>> getContentDetails(MultimediaSingleSearchRequest req) {
return req.isAddContent()
? Mono.just(Optional.of(new ContentDetails("content-details"))) // api call etc.
: Mono.just(Optional.empty());
}
// Wanted if isAddMetadata is true
private Mono<Optional<MetadataDetails>> getMetadataDetails(MultimediaSingleSearchRequest req) {
return req.isAddMetadata()
? Mono.just(Optional.of(new MetadataDetails("metadata-details"))) // api call etc.
: Mono.just(Optional.empty());
}
Optionals are used for the requests that might be skipped, since Mono::zip will fail if either of the zipped publishers emit an empty value.
If the results of each inner call extend the same base class or are the same wrapped return type, then the original answer applies as to how they can be combined (Flux::merge etc.)
In a Spring Gateway API I have a filter which calls a class to make a call to another API using WebClient. If I make the same call from say a controller the call returns. However when this webclient call is made from within the Filter stack it never returns. I am trying to make this call synchronously. I cannot use the block() method because Reactive classes error.
Here is the method in question:
public void doPost() {
ApiResponse<Void> response = webClientBuilder.build().post()
.uri("http://localhost:8080")
.retrieve()
.bodyToMono(new ParameterizedTypeReference<ApiResponse<Void>>() {})
.block();
}
I am very new to WebClient and need someone to tell me how I can synchronously make this call. I have tried another variation which is toFuture().get() instead of the last line but this also does not return.
It get the below error:
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-4
My mistake it is an authentication filter that this is being run from:
public class AuthServiceAuthenticationManager implements ReactiveAuthenticationManager {
private final MyClient myClient;
#Override
public Mono<Authentication> authenticate(Authentication authentication) {
//Below line does not return using my webclient
myClient.post();
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(), new ArrayList<GrantedAuthority>());
return Mono.just(token);
}
}
As I mentioned in comment, the reason is simple - you and blocking doPost is called from the reactive flow. WebClient is a non-blocking client and as you are using it from the ReactiveAuthenticationManager you could keep the whole flow reactive.
Solution:
Remove block() from the doPost and return Mono.
public Mono<ApiResponse<Void>> doPost() {
return webClientBuilder.build().post()
.uri("http://localhost:8080")
.retrieve()
.bodyToMono(new ParameterizedTypeReference<ApiResponse<Void>>() {})
}
Construct reactive flow in AuthServiceAuthenticationManager.
Logic of authenticate is not really clear but based on your example it could look like
public Mono<Authentication> authenticate(Authentication authentication) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(), new ArrayList<>());
return doPost()
.thenReturn(token);
}
I'd like to create a Spring WebClient that ignores a specific HTTP error. From the documentation of WebClient.retrieve():
By default, 4xx and 5xx responses result in a WebClientResponseException. To customize error handling, use ResponseSpec.onStatus(Predicate, Function) handlers.
I want all calls through a WebClient instance to ignore the specific HTTP error. That is why onStatus() is of no use to me (it has to be set per response).
The best I could come up with is this:
WebClient webClient = WebClient.builder().filter((request, next) -> {
Mono<ClientResponse> response = next.exchange(request);
response = response.onErrorResume(WebClientResponseException.class, ex -> {
return ex.getRawStatusCode() == 418 ? Mono.empty() : Mono.error(ex);
});
return response;
}).build();
URI uri = UriComponentsBuilder.fromUriString("https://httpstat.us/418").build().toUri();
webClient.get().uri(uri).retrieve().toBodilessEntity().block();
but it does throw the exception instead of ignoring it (the lambda passed to onErrorResume() is never called).
Edited: fixed the mistake pointed out by the first answer.
After extensive debugging of spring-webflux 5.3.4 and with the help of some ideas by Martin Tarjányi, I've come to this as the only possible "solution":
WebClient webClient = WebClient.builder().filter((request, next) -> {
return next.exchange(request).flatMap(res -> {
if (res.rawStatusCode() == HttpStatus.I_AM_A_TEAPOT.value()) {
res = res.mutate().rawStatusCode(299).build();
}
return Mono.just(res);
});
}).build();
URI uri = UriComponentsBuilder.fromUriString("https://httpstat.us/418").build().toUri();
String body = webClient.get().uri(uri).retrieve().toEntity(String.class).block().getBody();
The background: I am migrating some code from RestTemplate to WebClient. The old code looks like this:
RestTemplate restTemplate = ...;
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getRawStatusCode() == HttpStatus.I_AM_A_TEAPOT.value()) {
return;
}
super.handleError(response);
}
});
URI uri = UriComponentsBuilder.fromUriString("https://httpstat.us/418").build().toUri();
String body = restTemplate.getForEntity(uri, String.class).getBody();
I believe it is a straightforward and common case.
WebClient is not yet a 100% replacement for RestTemplate.
UPDATE: Turns out this answer doesn't address the core problem of filtering out a specific status code, just addresses a general coding pattern.
The reason onErrorResume lambda is not called is that response.onErrorResume creates a brand new Mono and your code does not use the result (i.e. it's not assigned to the response variable), so in the end a Mono without the onErrorResume operator is returned.
Using Project Reactor it's usually a good practice to avoid declaring local Mono and Flux variables and use a single chain instead. This helps to avoid similar subtle bugs.
WebClient webClient = WebClient.builder()
.filter((request, next) -> next.exchange(request)
.onErrorResume(WebClientResponseException.class, ex -> ex.getRawStatusCode() == 418 ? Mono.empty() : Mono.error(ex)))
.build();
If I have an Apex function that is named authorize() that just gets a username, password, and session token, and another function called getURL('id#', 'key'), that takes an id# for the record as a string and a key for the image to return as a string as parameters. getURL calls the authorize function inside it in order to get the credentials for its callout. The authorize is a post request, and the getURL is a get request.
I am trying to figure out how to test both of these callouts just so I can make sure that getURL is returning the proper JSON as a response. It doesn't even have to be the URL yet which is its intention eventually. But I just need to test it to make sure these callouts are working and that I am getting a response back for the 75% code coverage that it needs.
I made a multiRequestMock class that looks like this:
public class MultiRequestMock implements HttpCalloutMock {
Map<String, HttpCalloutMock> requests;
public MultiRequestMock(Map<String, HttpCalloutMock> requests) {
this.requests = requests;
}
public HTTPResponse respond(HTTPRequest req) {
HttpCalloutMock mock = requests.get(req.getEndpoint());
if (mock != null) {
return mock.respond(req);
} else {
throw new MyCustomException('HTTP callout not supported for test methods');
}
}
public void addRequestMock(String url, HttpCalloutMock mock) {
requests.put(url, mock);
}
}
I then began to write a calloutTest.cls file but wasn't sure how to use this mock class in order to test my original functions. Any clarity or assistance on this would be helpful Thank you.
I believe in your calloutTest class you use Test.setMock(HttpCalloutMock.class, new MultiRequestMock(mapOfRequests)); then call the getUrl and/or authorize methods and instead of the request really executing the response returned will be that which is specified in the response(HttpRequest) method you have implemented in the MultiRequestMock class. That is basically how I see it working, for more info and an example you can see this resource on testing callout classes. This will get you the code coverage you need but unfortunately cannot check you are getting the correct JSON response. For this, you may be able to use the dev console and Execute Anonymous?
You may want to look at simplifying your HttpCalloutMock Implementation and think about removing the map from the constructor as this class really only needs to return a simple response then your calloutTest class can be where you make sure the returned response is correct.
Hope this helps
I have spring boot controller
#PostMapping(path = ["/download"])
fun getFile(#RequestBody myObjectRq: myObjectRq, httpServletResponse: HttpServletResponse): CompletableFuture<HttpServletResponse> {
return GlobalScope.async {
val response = webService.getFile(myObjectRq)
response?.let {
httpServletResponse.setHeader("Content-Type", response.headers.get("Content-Type"))
httpServletResponse.setHeader("Content-Disposition", response.headers.get("Content-Disposition"))
httpServletResponse.writer.write(String(response.content.toByteArray()))
httpServletResponse.writer.flush()
httpServletResponse.status = response.status.value
}
httpServletResponse
}.asCompletableFuture()
}
in which I use service which in turn uses ktor client to send post request to external server which should respond sending csv file. csv file content depends on values I send in myObjectRq.
Service:
suspend fun getFile(myObjectRq: myObjectRq): HttpResponse {
val response = ktorClient.post<HttpResponse> {
accept(ContentType.Application.OctetStream)
url(externalWebServerUrl)
body = myObjectRq
contentType(ContentType.Application.Json)
}
log.info(String(response.content.toByteArray()))
response
}
Headers in response are properly set, also log.info(String(response.content.toByteArray())) in the method prints out the content of received file, but I can't set it as a body of HttpServletResponse. I keep getting org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation.
Also I get Inappropriate blocking method call for httpServletResponse.writer which kind of breaks async qualities of ktor client.
What do I do wrong? How should I solve it?
So, I think SpringBoot is confused with your return type. It is trying to find a way to serialize your return CompletableFuture<HttpServletResponse> into the body of the HTTP response but failing. I believe you can achieve the same result by changing your implementation as follows:
#PostMapping(path = ["/download"])
fun getFile(#RequestBody myObjectRq: myObjectRq, httpServletResponse: HttpServletResponse): CompletableFuture<Void> {
return GlobalScope.async {
val response = webService.getFile(myObjectRq)
response?.let {
httpServletResponse.setHeader("Content-Type", response.headers.get("Content-Type"))
httpServletResponse.setHeader("Content-Disposition", response.headers.get("Content-Disposition"))
httpServletResponse.writer.write(String(response.content.toByteArray()))
httpServletResponse.writer.flush()
httpServletResponse.status = response.status.value
}
null
}.asCompletableFuture()
}
I actually managed to solve this using CompletableFuture<ResponseEntity<ByteArray>> as return type and setting body of the response this way:
ResponseEntity.ok().body(response.content.toByteArray())
This also removed Inappropriate blocking method call warnings.