After this fix spring-projects/spring-framework@21d0696 ReactorHttpClientResponse has introduced a state manager to check on cancellation state.

I wonder if that fix needs to happen upstream in reactor or reactor netty yet but now you will throw the IllegalStateException with "The client response body has been released already due to cancellation." if you do an equivalent of:

ClientResponse r = webclient.post()
               .uri("/bla")
               .exchange()
               .take(1)
               .block()

Comment From: rstoyanchev

Thanks @smaldini. I'm not sure I follow. The exchange() method returns a Mono and that doesn't have take(int). Could you clarify the scenario you have in mind?

Comment From: spring-projects-issues

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Comment From: smaldini

The scenario is actually an integration with another RS library that wait for the first onNext and cancel, which is the equivalent of the code above. The issue is in exchange() and is related to the state check/update with doOnSubscribe/doOnCancel .

Comment From: rstoyanchev

Okay thanks for clarifying. I was able to reproduce as follows:

Mono<ClientResponse> responseMono = this.webClient.post()
    .uri("/pojo/capitalize")
    .accept(MediaType.APPLICATION_JSON)
    .contentType(MediaType.APPLICATION_JSON)
    .bodyValue(...)
    .exchange();

Pojo pojo = Flux.from(responseMono)
    .take(1)
    .blockLast()
    .bodyToMono(...)
    .block();

If this comes through a different RS library, it implies that the result from exchange() is wrapped as a Publisher and used through that library thereafter? However from there you still need to call bodyToMono or bodyToFlux on ClientResponse and that would require wrapping those too. This is okay although not something we've explicitly designed for. The alternative would be to use Flux and Mono to extract the result and then wrap it with any other library.

That said I think the exchange() method is partly to blame for exposing the response in this way. Note that the plan is to deprecate and replace it, so once #25751 is resolved I think that will supersede this issue because the above would no longer be possible and you would have something like this instead:

Pojo pojo = this.webClient.post()
    .uri("/pojo/capitalize")
    .accept(MediaType.APPLICATION_JSON)
    .contentType(MediaType.APPLICATION_JSON)
    .bodyValue(...)
    .exchangeToMono(response -> response.bodyToMono(Pojo.class))
    .block();

Comment From: rstoyanchev

@smaldini #25751 is now resolved. If you are able give it a try with 5.3 snapshots.

Comment From: smaldini

Thanks a lot for looking at it, it feels like this is aligned with what we do in Reactor Netty HttpClient now (controlling the lifecycle with a bounded function).

Comment From: rstoyanchev

Yes indeed.