Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate WebClient#exchange() and provide safer alternatives #25751

Closed
rstoyanchev opened this issue Sep 10, 2020 · 12 comments
Closed

Deprecate WebClient#exchange() and provide safer alternatives #25751

rstoyanchev opened this issue Sep 10, 2020 · 12 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@rstoyanchev
Copy link
Contributor

rstoyanchev commented Sep 10, 2020

exchange() provides more flexibility than retrieve() but leaves the full handling of the response to the application and it remains difficult to use it correctly in order to avoid memory and connection leaks. retrieve() is safer because it wraps the handling of the response (and the framework can ensure correct release of resources) and since 5.2 with access to the response status and headers via ResponseEntity, retrieve() can cover all of common use cases.

The actual reasons for using exchange() are few. One example is the ability to suppress an error status and produce either a typed object from a successful response or a different object from an error response with the output consumed as Mono<Object>. However exchange() continues to be used much more widely than that.

It is time to provide a safer alternative to exchange() that retains the same flexibility but allows the WebClient to be involved in the handling of the response. For example:

<V> Mono<V> exchangeToMono(Function<ClientResponse, ? extends Mono<V>> responseHandler);

<V> Flux<V> exchangeToFlux(Function<ClientResponse, ? extends Flux<V>> responseHandler);

We can then deprecate exchange() and remove it in the next major version.

@rstoyanchev rstoyanchev added in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement labels Sep 10, 2020
@rstoyanchev rstoyanchev added this to the 5.3 RC2 milestone Sep 10, 2020
@rstoyanchev rstoyanchev self-assigned this Sep 10, 2020
rstoyanchev added a commit that referenced this issue Sep 25, 2020
The exchange() method is now deprecated because it is not safe for
general use but that doesn't apply to the WebTestClient because it
exposes a different higher level API for response handling that
ensures the response is consumed. Nevertheless WebTestClient cannot
call WebClient.exchange() any more.

To fix this WebTestClient no longer delegates to WebClient and thus
gains direct access to the underlying ExchangeFunction. This is
not a big deal because WebClient and WebTestClient do the same
only when it comes to gathering builder options and request input.

See gh-25751
rstoyanchev added a commit that referenced this issue Sep 25, 2020
artembilan added a commit to spring-projects/spring-integration that referenced this issue Sep 28, 2020
* Adapt to WebFlux deprecations: spring-projects/spring-framework#25751
* Back to SNAPSHOTs
* Fix new Sonar smells
@rstoyanchev
Copy link
Contributor Author

Re-opening in order to also consider if we need any updates related to Kotlin.

@rstoyanchev rstoyanchev reopened this Oct 5, 2020
@sdeleuze
Copy link
Contributor

sdeleuze commented Oct 5, 2020

I think we need exchangeToFlow and awaitExchange variants with a function parameter, I will provide that.

@mplain
Copy link

mplain commented Nov 2, 2020

Hello!
I'm not sure how to proceed with this change, could you please take a look at my question?
https://stackoverflow.com/questions/64650820

@rstoyanchev
Copy link
Contributor Author

@mplain as mentioned in the deprecation notice the preferred replacement is .retrieve(). Your use case is decoding the body to byte[] and preparing a ServerResponse. That should be a straight forward replacement.

@mplain
Copy link

mplain commented Nov 3, 2020

@rstoyanchev I'm not sure I should decode the body to byte[], I just need to forward the response from the other server to my client. Is there a better way to do it, without decoding the body at my proxy server?

@rstoyanchev
Copy link
Contributor Author

You can decode to Flux<DataBuffer> but I see now that we are missing an option under .retrieve() for a ResponseEntity with a Flux. So it should have been .retrieve().toEntityFlux(DataBuffer.class).map(entity -> ... ) but that's not possible right now. Can you create a new issue and describe your use case briefly?

@mplain
Copy link

mplain commented Nov 3, 2020

@rstoyanchev meanwhile is there any way to combine .exchangeToMono() and Flux<DataBuffer>?

.exchangeToMono { response ->
   response.bodyToFlux<DataBuffer>().let(DataBufferUtils::join).flatMap { body ->
      ServerResponse.ok().body(body)
   }
}

I assume joining DataBuffers would be pretty much the same as using .bodyToMono<ByteArray>() in the first place?
So currently the only way to write the response body in a memory-efficient manner as Flux<DataBuffer> is via the deprecated .exchange() method, correct?

@rstoyanchev
Copy link
Contributor Author

Correct.

@karthikairam
Copy link

karthikairam commented Feb 4, 2021

As part of the unit testing, how can I mock the exchangeToMono(function) ? where I somehow want to execute the actual code in the function for code coverage.
Earlier when it was just exchange(), I just completely mocked and returned the expected response. But, now with exchangeToMono(function) I am not able to find any elegant way - except making the function gets prepared and returned from a public method. Then write a separate unit test case for that method :(. Which I don't feel like a good idea.
Any suggestions would be much appreciated. Thanks!

@karthikairam
Copy link

karthikairam commented Feb 5, 2021

As part of the unit testing, how can I mock the exchangeToMono(function) ? where I somehow want to execute the actual code in the function for code coverage.
Earlier when it was just exchange(), I just completely mocked and returned the expected response. But, now with exchangeToMono(function) I am not able to find any elegant way - except making the function gets prepared and returned from a public method. Then write a separate unit test case for that method :(. Which I don't feel like a good idea.
Any suggestions would be much appreciated. Thanks!

I am able to achieve it using Mockito.doAnswer() option from mockito framework. Please ignore my above question. Thanks!

Working sample code for reference. Hope someone might find it helpful.

final ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class);

//set the required mocking behaviours here on the "mockedClientResponse" obj.

Mockito.doAnswer(invocationOnMock -> {
        return invocationOnMock
            .<Function<ClientResponse, Mono<SomeDTO>>>getArgument(0)
            .apply(mockedClientResponse);
    })
    .when(headerSpec)
    .exchangeToMono(ArgumentMatchers.<Function<ClientResponse, Mono<SomeDTO>>>any());

@rstoyanchev
Copy link
Contributor Author

I'm not sure mocks are a good fit for mocking server responses. You can use something like OkHttp MockWebServer or WireMock.

@karthikairam
Copy link

karthikairam commented Feb 9, 2021

I'm not sure mocks are a good fit for mocking server responses. You can use something like OkHttp MockWebServer or WireMock.

Thanks for your response. Yes we use WireMock for the Component Test. But for the Unit Testing, it will be bit heavy to have stub server. So we've used Mockito to cover that. And above code works just fine.
Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

4 participants