Nested webflux routing always goes with the route on the top - java

Can anyone tell me in this example of routes everytime I type ex: /api/person/1, etc it all goes to /api/person? No matter which method I choose, it always goes with /api/person.
#Bean
public RouterFunction<ServerResponse> monoRouterFunction(PersonService personService) {
return RouterFunctions
.nest(path("/api/person"),
route(method(GET), personService::findAllPeople)
.andRoute(GET("/{id}"), personService::findOnePerson)
.andRoute(POST("/add"), personService::addPerson)
.andRoute(PUT("/update"), personService::updatePerson)
.andRoute(DELETE("/delete/{id}"), personService::deletePerson));
}

Below code is working fine. I have personally tried in my local. As #Brain told, just add GET("/") for findAllPeople() handler method.
#Bean
public RouterFunction<ServerResponse> monoRouterFunction(PersonService personService)
{
return RouterFunctions
.nest(path("/api/person"),
route(method(GET("/")), personService::findAllPeople)
.andRoute(GET("/{id}"), personService::findOnePerson)
.andRoute(POST("/add"), personService::addPerson)
.andRoute(PUT("/update"), personService::updatePerson)
.andRoute(DELETE("/delete/{id}"), personService::deletePerson));
}
Sample working application: https://github.com/karthikaiselvan/spring-reactive-mongo

Unlike the annotation model, WebFlux.fn is very explicit about routing: ordering and all predicates matter. But the good thing is it's easier to debug and you can set debug points in your predicates to understand why a request is being routed to a handler.
In this case, this RouterFunction could be described as:
If the path starts with "/api/person"
and the method is GET -> then personService::findAllPeople
and the method is POST and path matches "/api/person/{id}" -> then personService::findOnePerson
etc
Because the first match wins, a request like "GET /api/person/42" will match 1), since it starts with "/api/person" and it is a GET request.
If you want to change that, you can either change the order of your routes, or change your predicate for route(GET("/"), personService::findAllPeople).

even we can remove static imports, besides if we have different types of media types we can use the requestPredicate like the below code :
RouterFunction<ServerResponse> json = route()
.nest(accept(APPLICATION_JSON), builder -> builder
.GET("/{id}", personHandler::findOnePerson)
.GET("", personHandler::findAllPeople)).build();
RouterFunction<ServerResponse> html = route()
.nest(accept(TEXT_HTML), builder -> builder
.GET("/{id}", personHandler::renderPerson)
.GET("", personHandler::renderPersons)).build();
return route()
.path("api/person", () -> html.and(json)) // the default would be the first one(here is html)
.build();
you can find more details in my repository: https://github.com/minarashidi/webflux

Related

Use Webclient with custom HttpMessageReader to synchronously read responses

Problem
I have defined a CustomHttpMessageReader (which implements HttpMessageReader<CustomClass>), which is able to read a multipart response from a server and converts the received parts into an object of a specific class. The CustomHttpMessageReader uses internally the DefaultPartHttpMessageReader to actually read/parse the multipart responses.
The CustomHttpMessageReader accumulates the parts read by the DefaultReader and converts them into the desired class CustomClass.
I've created a CustomHttpMessageConverter that does the same thing for a RestTemplate, but I struggle to do the same for a WebClient.
I always get the following Exception:
block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83)
at reactor.core.publisher.Flux.blockFirst(Flux.java:2600)
at com.company.project.deserializer.multipart.CustomHttpMessageReader.readMultipartData(CustomHttpMessageReader.java:116)
at com.company.project.deserializer.multipart.CustomHttpMessageReader.readMono(CustomHttpMessageReader.java:101)
at org.springframework.web.reactive.function.BodyExtractors.lambda$readToMono$14(BodyExtractors.java:211)
at java.base/java.util.Optional.orElseGet(Optional.java:369)
...
Mind you, I'm not interested in running WebClient asynchronously. I'm only future proofing my application because RestTemplate is apparently only in maintenance mode and the folks at Pivotal/Spring suggest using WebClient instead.
What I Tried
As I understand, there are threads that are not allowed to be blocked, namely the netty-nio one in the exception. I tried removing netty from my dependencies, so that I can rely solely on Tomcat. That however doesn't seem to help, as I get another exception, explaining me, that no suitable HttpConnector exists (exception thrown by the WebClient.Builder)
No suitable default ClientHttpConnector found
java.lang.IllegalStateException: No suitable default ClientHttpConnector found
at org.springframework.web.reactive.function.client.DefaultWebClientBuilder.initConnector(DefaultWebClientBuilder.java:297)
at org.springframework.web.reactive.function.client.DefaultWebClientBuilder.build(DefaultWebClientBuilder.java:266)
at com.company.project.RestClientUsingWebClient.getWebclient(RestClientUsingWebClient.java:160)
I've tried my code executed in a unit test as well, as starting a whole Spring context. The result is unfortunately the same.
Setup
To provide a bit more details, the following are snippets from the Classes mentioned earlier. The classes are not shown fully in order to understand better what is going on. All necessary methods are implemented (like e.g. canRead() in the Reader).
CustomHttpMessageReader
I also included in the class the usage of CustomPart (in addition to CustomClass) just to show, that the content of the Part is also read i.e. blocked.
public class CustomHttpMessageReader implements HttpMessageReader<CustomClass> {
private final DefaultPartHttpMessageReader defaultPartHttpMessageReader = new DefaultPartHttpMessageReader();
#Override
public Flux<CustomClass> read(final ResolvableType elementType, final ReactiveHttpInputMessage message,
final Map<String, Object> hints) {
return Flux.merge(readMono(elementType, message, hints));
}
#Override
public Mono<CustomClass> readMono(final ResolvableType elementType, final ReactiveHttpInputMessage message,
final Map<String, Object> hints) {
final List<CustomPart> customParts = readMultipartData(message);
return convertToCustomClass(customParts);
}
private List<CustomPart> readMultipartData(final ReactiveHttpInputMessage message) {
final ResolvableType resolvableType = ResolvableType.forClass(byte[].class);
return Optional.ofNullable(
defaultPartHttpMessageReader.read(resolvableType, message, Map.of())
.buffer()
.blockFirst()) // <- EXCEPTION IS THROWN HERE!
.orElse(new ArrayList<>())
.stream()
.map(part -> {
final byte[] content = Optional.ofNullable(part.content().blockFirst()) //<- HERE IS ANOTHER BLOCK
.map(DataBuffer::asByteBuffer)
.map(ByteBuffer::array)
.orElse(new byte[]{});
// Here we cherry pick some header fields
return new CustomPart(content, someHeaderFields);
}).collect(Collectors.toList());
}
}
Usage of WebClient
class RestClientUsingWebClient {
/**
* The "Main" Method for our purposes
*/
public Optional<CustomClass> getResource(final String baseUrl, final String id) {
final WebClient webclient = getWebclient(baseUrl);
//curl -X GET "http://BASE_URL/id" -H "accept: multipart/form-data"
return webclient.get()
.uri(uriBuilder -> uriBuilder.path(id).build()).retrieve()
.toEntity(CustomClass.class)
.onErrorResume(NotFound.class, e -> Mono.empty())
.blockOptional() // <- HERE IS ANOTHER BLOCK
.map(ResponseEntity::getBody);
}
//This exists also as a Bean definition
private WebClient getWebclient(final String baseUrl) {
final ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
.codecs(codecs -> {
codecs.defaultCodecs().maxInMemorySize(16 * 1024 * 1024);
codecs.customCodecs().register(new CustomHttpMessageReader()); // <- Our custom reader
})
.build();
return WebClient.builder()
.baseUrl(baseUrl)
.exchangeStrategies(exchangeStrategies)
.build();
}
}
Usage of build.gradle
For the sake of completion, here is what I think is the relevant part of my build.gradle
plugins {
id 'org.springframework.boot' version '2.7.2'
id 'io.spring.dependency-management' version '1.0.13.RELEASE'
...
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-web' // <- This
implementation 'org.springframework.boot:spring-boot-starter-webflux'
// What I tried:
// implementation ('org.springframework.boot:spring-boot-starter-webflux'){
// exclude group: 'org.springframework.boot', module: 'spring-boot-starter-reactor-netty'
//}
...
}
if we look in the stacktrace that you provided we see these 3 lines
at reactor.core.publisher.Flux.blockFirst(Flux.java:2600)
at com.company.project.deserializer.multipart.CustomHttpMessageReader.readMultipartData(CustomHttpMessageReader.java:116)
at com.company.project.deserializer.multipart.CustomHttpMessageReader.readMono(CustomHttpMessageReader.java:101)
They should be read from bottom to top. So what do they tell us?
The bottom line tells us that the function readMono on the line 101 in the class CustomHttpMessageReader.javawas called first.
That function then called the function readMultipartData on line 116 in the class CustomHttpMessageReader(same class as above)
Then the function blockFirst was called on line 2600 in the class Flux.
Thats your blocking call.
So we can tell that there is a blocking call in the function readMultipartData.
So why cant we block in the function? well if we look in the API for the interface that function is overriding HttpMessageReader we can se that the function returns a Mono<T> which means that the function is an async function.
And if it is async and we block we might get very very bad performance.
This interface is used within the Spring WebClient which is a fully async client.
You can use it in a non-async application, but that means you can block outside of the WebClient but internally, it needs to operate completely async if you want it to be as efficient as possible.
So the bottom line is that you should not block in any function that returns a Mono or a Flux.

Spring Webflux - "scanAvailable": true

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?
im not going to explain the difference between mapand flatMapsince 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));

Spring Integration subflow depending on .handle method

I am using spring integration to change a flow after particular retry is completed. My IntegrationFlow bean for errorResponse is as follows:
#Bean
public IntegrationFlow errorMailResponse(#Qualifier(ERROR_CHANNEL) PollableChannel errorChannel) {
return IntegrationFlows.from(errorChannel)
.handle(MessagingException.class, (payload, headers) -> handleMessageException(payload),
e -> e.poller(p -> p.fixedDelay(pollerInterval)))
.channel(NO_OUTPUT_CHANNEL)
.get();
}
If method handleMessageException returns an object I want the flow to continue to particular channel - MAIN_EVENTS_CHANNEL if handleMessageException returns null I want to continue to NO_OUTPUT_CHANNEL.
Is that possible to achieve with Spring integration? I tried to use subflow but I am not sure if it is the way. https://docs.spring.io/spring-integration/reference/html/dsl.html#java-dsl-subflows
#Bean
public IntegrationFlow errorMailResponse(#Qualifier(ERROR_CHANNEL) PollableChannel errorChannel) {
return IntegrationFlows.from(errorChannel)
.handle(MessagingException.class, (payload, headers) -> handleMailMessageException(payload),
e -> e.poller(p -> p.fixedDelay(pollerInterval)))
.publishSubscribeChannel(subscription -> subscription
.subscribe(subflow -> subflow
.<MailPojo>handle((payload, headers) -> {
// if if result handleMessageException == null
})
.channel(NO_OUTPUT_CHANNEL))
.subscribe(subflow -> subflow
.<MailPojo>handle((payload, headers) -> {
// if result handleMessageException !=null
})
.channel(MAIN_EVENTS_CHANNEL)))
.get();
}
Any help is appreciated.
First of all the null is not a payload. Therefore messaging does not support null in most cases. The integration flow just stops at the point where you return null: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#service-activator-namespace
The service activator is one of those components that is not required to produce a reply message. If your method returns null or has a void return type, the service activator exits after the method invocation, without any signals.
So, your assumption to make a logical decision is not correct with Spring Integration. You need to think about something what could be used as a signal for such a NO_OUTPUT_CHANNEL. You can create an artificial NullType and use a PayloadTypeRouter to determine when to go next according the payload type returned from your handleMailMessageException():
.<Object, Class<?>>route(Object::getClass, m -> m
.channelMapping(MailPojo.class, MAIN_EVENTS_CHANNEL)
.channelMapping(NullType.class, NO_OUTPUT_CHANNEL))
Another way is to use an Optional and check its content in the router function. Either way you need to use a router.

Give 404 response on DELETE in restful Spring Boot Reactive Controller

I am fairly new to Java and Spring Boot (coming from TypeScript) and
experimenting with a small restful CRUD Controller using the
reactive Spring Boot API.
There are many tutorials and examples out there but they all lack
proper response statuses, e.g. giving a 404 on DELETE when the
resource doesn't exist.
What I like to achieve is a DELETE handler which
returns "204 No Content" if the resource existed and was deleted successfully
returns "404 Not found" if the resource doesn't exist
A simple "I don't care about HTTP status" DELETE handler
looks like this:
#DeleteMapping("/{id}")
public Mono<Void> deletePet(#PathVariable String id) {
return petRepository.deleteById(id);
}
This always gives status 200, even when there is no Pet for this ID.
I tried to use petRepository.findById(id) and .defaultIfEmpty()
in several ways to catch the 404 case, but without luck. E.g. with
this implementation I am getting always 204:
#DeleteMapping("/{id}")
public Mono<ResponseEntity<Void>> deletePet(#PathVariable String id) {
return petRepository.findById(id)
.map(pet1 -> new ResponseEntity<Void>(HttpStatus.NO_CONTENT))
.defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND))
.flatMap(res -> {
return petRepository.deleteById(id)
.map(v -> new ResponseEntity<Void>(HttpStatus.NO_CONTENT));
});
}
I think I understand why this isn't working, because after the .defaultIfEmpty()
the Mono isn't empty anymore and the .flatMap will have something to work
on (the 404 response) so the deleteById() is executed. This returns an (obviously)
non empty Mono as well, so the status turns into NO_CONTENT again.
But all my (many) attempts to change this failed so I hope anyone has the right
solution for this problem.
Thanks! :)
When findById returns an empty Mono, the code below will not executed either map or flatMap and will only return the value from defaultIfEmpty
#DeleteMapping("/{id}")
public Mono<ResponseEntity<Void>> deletePet(#PathVariable String id) {
return petRepository.findById(id)
.map(pet1 -> new ResponseEntity<Void>(HttpStatus.NO_CONTENT))
.flatMap(res -> {
return petRepository.deleteById(id)
.map(v -> new ResponseEntity<Void>(HttpStatus.NO_CONTENT));
})
.defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND));
}
Also, your understanding as to why this happens in your code snippet is correct.
After some more research I found a solution:
#DeleteMapping("/{id}")
#ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<ResponseEntity<Void>> deletePet(#PathVariable String id) {
return petRepository.findById(id)
.defaultIfEmpty(new Pet())
.flatMap(pet -> {
if (null == pet.id) {
return Mono.just(new ResponseEntity<Void>(HttpStatus.NOT_FOUND));
}
else {
return petRepository.deleteById(id)
.map(v -> new ResponseEntity<Void>(HttpStatus.NO_CONTENT));
}
});
}
Now an empty Pet object is created when findById() gives an empty
result using defaultIfEmpty().
So the flatMap() gets either the Pet for the given ID or an empty
Pet. The latter is recognized by the fact that the id property is null
which is turned into a 404 response. In the other case the Pet is
deleted and 204 is returned. But note, that the .map() there isn't executed because of the empty deleteById() result. It's just necessary to satisfy the generic interface here. The 204 comes from the #ResponseStatus annotation.
So this is a possible solution - but it looks not very elegant to me (creating an empty Pet and having this no-op deleteById().map()).
If there is a better way to do this, please give your answer here.

Dependant webclient calls - Spring Reactive

I am trying to do two API calls, the second API call is dependent on the first API response. The following piece of code gives response for first weblient call.Here I am not getting the response from second API call. On log I could see that the request for the second webclient call is not even started with onSubscribe(). Can you please tell me what mistake am I doing.
#Autowired
Issue issue;
List issueList = new ArrayList<>();
public Mono<Response> getResponse(Request request) {
return webClient.post()
.uri("myURI")
.body(Mono.just(request),Request.class)
.retrieve()
.bodyToMono(Response.class)
.flatMap(resp->{
resp.getIssues().stream()
.forEach(issueTemp -> {
issue = issueTemp;
webClient.get()
.uri("mySecondURI" + issueTemp.getId())
.retrieve()
.bodyToMono(Issue.class)
.flatMap(issueTemp2-> {
issue.setSummary(issueTemp2.getSummary());
return Mono.just(issue);
}).log();
issueList.add(issue);
});
Response responseFinal = new Response();
responseFinal.setIssues(issueList);
return Mono.just(responseFinal);
}).log();
}
UPDATE 2:
I have changed my code to Functions and used Flux instead of stream iterations.What I am facing now is , all the iterations are get filtered out in doSecondCall method. Please refer my comment in doSecondCall method. Due to which the second call is not triggered. If i dont apply the filter, there are requests triggered like "issue/null" which also causes my service to go down.
public Mono<Response> getResponse(Request request) {
return webClient.post()
.uri("myURI")
.body(Mono.just(request),Request.class)
.retrieve()
.bodyToMono(Response.class)
.flatMap(r->
doSecondCall(r).flatMap(issueList->{
r.setIssues(issueList);
return Mono.just(r);
})
);
}
public Mono<Issue> doSecondCall(Response r) {
return Flux.fromIterable(r.getIssues())
.filter(rf->rf.getId()!=null) //everything gets filtered out
.flatMap(issue->getSummary(issue.getId()))
.collectList();
}
public Mono<Issue> getSummary(Response r) {
return webClient.get()
.uri("issue/"+id)
.retrieve()
.bodyToMono(Issue.class).log();
}
[ How does Reactive programming using WebFlux handles dependent external api calls ] #Thomas- Also ,Just found this thread. He basically says unless you block the first call, there is no way to declare the second one. Is that the case?
Why you are not triggering the second calls is because you are breaking the chain as i have mentioned in this answer (with examples).
Stop breaking the chain
// here...
.forEach(issueTemp -> {
issue = issueTemp; // and this is just silly? why?
webClient.get() // Here you are calling the webClient but ignoring the return value, so you are breaking the chain.
.uri("mySecondURI" + issueTemp.getId())
.retrieve()
.bodyToMono(Issue.class)
.flatMap(issueTemp2-> {
issue.setSummary(issueTemp2.getSummary());
return Mono.just(issue); // Return here but you are ignoring this return value
}).log();
issueList.add(issue);
});
You should use more functions to divide up your code. Make it a habit by writing a function and always start with the return statement. You code is very hard to read.
I think you should instead use a FLux instead of iterating a stream.
// something like the following i'm writing by free hand without IDE
// i have no idea what your logic looks like but you should get the point.
Flux.fromIterable(response.getIssues())
.flatMap(issue -> {
return getIssue(issue.getId())
.flatMap(response -> {
return issue.setSummary(reponse.getSummary());
});
}).collectList();

Categories