Spring WebFlux throws 'producer' type is unknow when I return value in the response body - spring-boot

I'm using Spring Boot with Kotlin, and now trying to get status value from a GET restful service by passing a handler for a reactive service.
I can see that the handler I'm passing is in the request, but whenever I'm building the body, I get this exception:
java.lang.IllegalArgumentException: 'producer' type is unknown to ReactiveAdapterRegistry
at org.springframework.util.Assert.notNull(Assert.java:198) ~[spring-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException
Here is my code:
#Bean
fun getReceiptConversionStatus() = router {
accept(MediaType.APPLICATION_JSON).nest {
GET("/BsGetStatus/{handler}", ::handleGetStatusRequest)
}
}
private fun handleGetStatusRequest(serverRequest: ServerRequest): Mono<ServerResponse> = ServerResponse
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(GetStatusViewmodel(fromObject(serverRequest.pathVariable("handler"))), GetStatusViewmodel::class.java)
.switchIfEmpty(ServerResponse.notFound().build())
and that's my Viewmodel:
data class GetStatusViewmodel(
#JsonProperty("handler") val documentHandler: String
)

Flux and Monos are Producers. They produce stuff. You are not passing in a producer in the body thats why you get the error, it doesn't recognize the producer you are passing, because you are passing in a GetStatusViewmodel.
Your body needs to be of type Mono<GetStatusViewmodel>. You can either replace body with bodyValue (it will automatically wrap it for you) or you can wrap your GetStatusViewodel in a Mono using Mono#just before passing it into the body function.

For me, I was doing something like this:
webClient.post()
.uri("/some/endpoint")
.body(postRequestObj, PostRequest.class) // erroneous line
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(PostResponse.class)
.timeout(Duration.ofMillis(5000))
When looking at the springs docs for that function body(), this is what's explained:
Variant of body(Publisher, Class) that allows using any producer that can be resolved to Publisher via ReactiveAdapterRegistry.
Parameters:
producer - the producer to write to the request
elementClass - the type of elements produced
Returns:
this builder
So the first parameter can't just be any object, it has to be a producer. Changing my above code to wrap my object around in a Mono fixed this issue for me.
webClient.post()
.uri("/some/endpoint")
.body(Mono.just(postRequestObj), PostRequest.class)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(PostResponse.class)
.timeout(Duration.ofMillis(5000))
reference: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.RequestBodySpec.html

I actually solved it, and I will post it here just in case somebody would do the same mistake I did :( It was a typical mistake for those who work with Java, it was a wrong import.
I was using fromObject() method in my application "I updated the question to match my actual code". You can find this function in both of these imports, and I was using one of the overloaded body() functions to pass this wrong placed function:
//this is the wrong import I was using
import org.springframework.web.reactive.function.server.EntityResponse.fromObject
//this is the correct one for building the mono body
import org.springframework.web.reactive.function.BodyInserters.fromObject
By using the method from BodyInserters, you will be able to pass fromObject(T) to the body method and it will return the mono result.

The specified code resolved the issue
public Mono<ServerResponse> getName(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(birthday);
}

Related

Mockito - How can I test the behaviour based on HttpStatusCode?

I have this request with WebClient:
webClient
.get()
.uri(uri)
.accept(MediaType.APPLICATION_OCTET_STREAM)
.<Optional<ByteArrayResource>>exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.NOT_FOUND)) {
return Mono.just(Optional.empty());
}
return response.bodyToMono(ByteArrayResource.class).map(Optional::of);
})
.block();
How can I test the logic inside exchangeToMono()?
I'm using Mockito for testing this way:
given(headersSpecHeadOpMock.exchangeToMono()).willReturn(Mono.just(clientResponse))
But the problem here is that this way I'm not testing the HttpStatus.NOT_FOUND.
The problem I was having is that I was mocking the ClientResponse, but I would have to Mock a Function<ClientResponse, ? extends Mono<Optional<ByteArrayResource>>>.
The solution was to use an ArgumentCaptor to get the argument and then make the assert of his value like this:
ArgumentCaptor declaration:
ArgumentCaptor<Function<ClientResponse, ? extends Mono<Optional<ByteArrayResource>>>> captorLambda = ArgumentCaptor.forClass(Function.class);
Capture the argument:
given(headersSpecGetOpMock.<Optional<ByteArrayResource>>exchangeToMono(captorLambda.capture())).willReturn(Mono.just(Optional.empty()));
Assert the value returned by the client response:
assertThat(captorLambda.getValue().apply(clientResponse).block()).isEqualTo(Optional.empty());```
Consider rewriting your code as follows:
webClient
.get()
.uri(uri)
.accept(MediaType.APPLICATION_OCTET_STREAM)
.retrive()
.onStatus(status -> HttpStatus.NOT_FOUND == status, response -> Mono.just(Optional.empty()))
.bodyToMono(ByteArrayResource.class);
.map(Optional::of)
.block();
Now you can mock retrieve() method to test your conditions easily.
As a side note, please consider dropping block() call, this defeats the purpose of using reactive programming.
You can use okhttp to mock the server you are hitting as shown here: https://www.baeldung.com/spring-mocking-webclient
The only issue is that you have to use the .mutate() method on the webclient you are trying to test to edit the basurl to match that of the mock webserver. In my own implementation, I used getters and setters to alter the webclient baseurl and then set it back to the original at the end of the test.

Spring WebFlux - How to print response as String instead of object using WebClient

I have a Mono like below:
private void getNodeDetail() {
Mono<String> mono = webClient.get()
.uri("/alfresco/api/-default-/public/alfresco/versions/1/nodes/f37b52a8-de40-414b-b64d-a958137e89e2")
.retrieve().bodyToMono(String.class);
System.out.println(mono.subscribe());
System.out.println(mono.block());
}
Questions: The first sysout shows me reactor.core.publisher.LambdaSubscriber#77114efe while using block() it shows me what I need (json String). But I want to use Aysnc approach. So, given above, does it mean my target system (Alfresco in this case) DO NOT support async calls? If that is not the case, how can I print the response on the console in String format using subscribe(), just like block() ?
The subscribe() method returns a Disposable object:
public final Disposable subscribe()
The expected way to print the response on the console is to actually use the doOnNext operator, like this:
private void getNodeDetail() {
webClient.get()
.uri("/alfresco/api/-default-/public/alfresco/versions/1/nodes/f37b52a8-de40-414b-b64d-a958137e89e2")
.retrieve()
.bodyToMono(String.class)
.doOnNext(response -> System.out.println(response))
.subscribe();
}

Spring Reactive API response blank body while transforming Mono<Void> to Mono<Response>

I have a service call that returns Mono. Now while giving API response to user I want to send some response. I tried with flatMap as well as a map but it won't work. It gives me an empty body in response.
Here the code sample
//Service Call
public Mono<Void> updateUser(....) {
.......... return Mono<Void>...
}
//Rest Controller
#PostMapping(value = "/user/{id})
public Mono<Response> update(......) {
return service.updateUser(....)
.map(r -> new Response(200, "User updated successfully"));
}
When I hit the above API it gives me an empty body in response. Can anyone please help me to get body in API response?
Thank you
if you wish to ignore the return value from a Mono or Flux and just trigger something next in the chain you can use either the then operator, or the thenReturn operator.
The difference is that then takes a Mono while thenReturn takes a concrete value (witch will get wrapped in a Mono.
#PostMapping(value = "/user/{id})
public Mono<Response> update(......) {
return service.updateUser(....)
.thenReturn(new Response(200, "User updated successfully"));
}

How do I get the HTTP status code of a given URL via Spring?

I am working in a Spring #Component class and I am trying to get the HTTP status code of a particular URL for further processing. I have a function as follows:
fun getStatus() : String
{
val webClient = WebClient.create("https://stackoverflow.com")
val result = webClient.get()
.exchange().map { res -> res.rawStatusCode() }
println(result)
return "statusGotten"
}
However, rather than getting the Int value of the status code (e.g. 200, or 401), I am simply getting: "MonoMap".
I am new to both Spring and Web Programming in general, so I'm a little confused how to proceed from here. I'm aware that "result" is being returned as a "Mono", but I'm less clear about what a "Mono" is, or how I might transform it into something with more scrutable properties, as even looking at "result" in the debugger doesn't shed any light as to whether the HTTP request was actually sent or was successful:
Am I calling the webclient incorrectly? Or merely failing to parse the resultant data in a meaningful way? Any suggestions on how or where I might learn more about the underlying topics would be much appreciated as well.
If you need a blocking way to do this is easy just
#Test
public void myTest(){
WebClient client = WebClient.builder().baseUrl("https://stackoverflow.com/").build();
ClientResponse resp = client
.get()
.uri("questions/")
.exchange()
.block();
System.out.println("Status code response is: "+resp.statusCode());
}
But for this you can use directly the RestTemplate instead the webclient... the recomended way to do this is non blocking what means you should return a Mono with the status and consume outside your method like for example:
public Mono<HttpStatus> myMethod(){
WebClient client = WebClient.builder().baseUrl("https://stackoverflow.com/").build();
return client
.get()
.uri("questions/")
.exchange()
.map( clientResp -> clientResp.statusCode());
}
The way of consume this Mono depends of your code...

Spring WebFlux Post Issue

I am using WebClient to do an API post but it is not returning anything. I'm assuming that the thread is staying open and not completing since I can use a block to get what I want but I'm still pretty new to WebClient and asynchronous stuff so I'm not 100% sure.
Specifically I have this method:
public Mono<AppDto> applicationPost(AppDto dto){
return webClient.post()
.uri("/api/doStuff")
.contentType(MediaType.APPLICATION_JSON)
.body(MonoConverter.appDtoToMono(dto), String.class)
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(Map.class))
.map(MonoConverter::mapValueToAppDto);
}
Where MonoConverter does some conversion for mapping values so this should be irrelevant. The above returns a 202 Accepted but it does not return a value or hit my mapValueToAppDto method. The below however, does work:
public Mono<AppDto> applicationPost(AppDto dto){
Map map = webClient.post()
.uri("/api/doStuff")
.contentType(MediaType.APPLICATION_JSON)
.body(MonoConverter.appDtoToMono(dto), String.class)
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(Map.class))
.block();
return Mono.just(MonoConverter.mapValueToAppDto(map));
}
I'm assuming that this works since it uses block but then a get method I have that is in a similar fashion works:
public Mono<AppDto> applicationGetOne(String appId){
return webClient.get()
.uri("/api/getOne/{0}",appId)
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(Map.class))
.map(MonoConverter::mapValueToAppDto);
}
I would prefer to use the first snippet since it does not use block and it's simpler and in the same format as my other methods.
Does anyone have any idea why the first one isn't working or know how I could get it to work?
I found the reason why I was having this issue. It actual had to do with my controller
(D'oh!). For the post method, I have validation that binds errors so I was just returning a ResponseEntity without giving it a type. So I added a typing to the ResponseEntity and that fixed the issue.
e.g.:
#PostMapping(value="/post")
public ResponseEntity addThing(#Validated #RequestBody AppDto dto, BindingResult result){
...
}
And what fixed it:
#PostMapping(value="/post")
public ResponseEntity<Mono<?>> addThing(#Validated #RequestBody AppDto dto, BindingResult result){
...
}
I'm assuming that since at first the typing wasn't specified it wasn't using the thread the mono response was on and so I was never getting a response but by declaring the type, Spring knows to use the Mono thus allowing it to complete.

Resources