WebClient synchronous call does not return from within a filter stack trace - spring-webflux

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

Related

Chaining Reactive Asynchronus calls in spring

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.)

Check database for access in Spring Gateway Pre filter

I am using Spring Gateway, where I need to check further user access by Request path using DB call. My repository is like this.
public Mono<ActionMapping> getByUri(String url)
....
This is my current filter where I am using custom UsernamePasswordAuthenticationToken implementation.
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> exchange
.getPrincipal()
.filter(principal -> principal instanceof UserAuthenticationToken) // Custom implementation of UsernamePasswordAuthenticationToken
.cast(UserAuthenticationToken.class)
.map(userAuthenticationToken -> extractAuthoritiesAndSetThatToRequest(exchange, userAuthenticationToken))
.defaultIfEmpty(exchange)
.flatMap(chain::filter);
}
private ServerWebExchange extractAuthoritiesAndSetThatToRequest(ServerWebExchange exchange, UserAuthenticationToken authentication) {
var uriActionMapping = uriActionMappingRepository.findOneByUri(exchange.getRequest().getPath().toString()).block();
if ((uriActionMapping == null) || (authentication.getPermission().containsKey(uriActionMapping.getName()))) {
ServerHttpRequest request = exchange.getRequest()
.mutate()
.header("X-Auth", authentication.getName())
.build();
return exchange.mutate().request(request).build();
}
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.setComplete();
return exchange.mutate().response(response).build();
}
However, there are several problems here, first that it is blocking call. Also I am not sure I need to mutate exchange to return response like that. Is there anyway achieve this using filter in Spring Cloud Gateway.
Yes, it is a blocking call.
Firstly, Spring WebFlux is based on Reactor. In Reactor, most handling method will not recieve a null from Mono emit, e.g. map, flatMap. Sure, there are counterexamples, such as doOnSuccess, see also the javadoc of Mono.
So, we can just use handling methods to filter results instead of block. Those handling methods will return a empty Mono when recieve a null value.
Secondary, when it authorize failed, we should return a empty Mono instead of calling chain.filter. The chain.filter means "It's OK! Just do something after the Filter!". See also RequestRateLimiterGatewayFilterFactory, it also mutate the response.
So, we should set response to completed, and return a empty Mono if authorize failed.
Try this:
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> exchange
.getPrincipal()
.filter(principal -> principal instanceof UserAuthenticationToken) // Custom implementation of UsernamePasswordAuthenticationToken
.cast(UserAuthenticationToken.class)
.flatMap(userAuthenticationToken -> extractAuthoritiesAndSetThatToRequest(exchange, userAuthenticationToken))
.switchIfEmpty(Mono.defer(() -> exchange.getResponse().setComplete().then(Mono.empty())))
.flatMap(chain::filter);
}
// Maybe return empty Mono, e.g. findOneByUri not found, or Permissions does not containing
private Mono<ServerWebExchange> extractAuthoritiesAndSetThatToRequest(ServerWebExchange exchange, UserAuthenticationToken authentication) {
return uriActionMappingRepository.findOneByUri(exchange.getRequest().getPath().toString())
.filter(it -> authentication.getPermission().containsKey(it.getName()))
.map(it -> exchange.mutate()
.request(builder -> builder.header("X-Auth", authentication.getName()))
.build());
}
About mutate request, see also RewritePathGatewayFilterFactory.

How to make a MultiMock Http Callout Test for Salesforce?

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

Spring webflux : consume mono or flux from request

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

Is it possible to return a response from a Web API constructor?

I have a Web API ApiController base class and I would like to perform some validations in the constructor. This might include checking the current load on the server. If it's high, I'd like to return an appropriate HttpResponseMessage indicating the requestor should try again later.
Is something like this possible?
I Haven't tested it but that's not what the constructor is for. I don't think all plumbing is set at that time.
You could use global filters for this purpose. Here you have a sample that sets a global filter for authorization, you should use a similar logic but creating your own filter for your specific purposes.
A global filter would intercept all your requests and is executed before the controller actions so is a good place to perform your task.
Even though what you are doing sounds like it may be better to revise the approach. Note that you can throw HttpResponseException since the WebApi is Rest Service HttpResponseException is the recommended way to throw Exceptions back to the client.
var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent("No idea what happened "),
ReasonPhrase = "Something was not Not Found"
}
throw new HttpResponseException(resp);
As long as you're using .NET 4.5, then you'd be better off creating a custom MessageHandler. You'll need to extend DelegatingHandler in order to do that.
public class MyHandler : DelegatingHandler {
protected override async Task<HttpResponseMessage> SendAsync(
HttpMessageRequest request, CancellationToken cancellationToken) {
// Access the request object, and do your checking in here for things
// that might cause you to want to return a status before getting to your
// Action method.
// For example...
return request.CreateResponse(HttpStatusCode.Forbidden);
}
}
Then inside your WebApiConfig, just add the following code to use the new Handler:
config.MessageHandlers.Add(new MyHandler());
You can't throw HttpResponseException in constructor, that will always cause 500.
Easiest way is to override ExecuteAsync():
public override Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken) {
if (!myAuthLogicCheck()) {
// Return 401 not authorized
var msg = new HttpResponseMessage(HttpStatusCode.Unauthorized) { ReasonPhrase = "User not logged in" };
throw new HttpResponseException(msg);
}
return base.ExecuteAsync(controllerContext, cancellationToken);
}