7

We're using org.springframework.web.reactive.function.client.WebClient with reactor.netty.http.client.HttpClient as part of Spring 5.1.9 to make requests using the exchange() method. The documentation for this method highlights the following:

... when using exchange(), it is the responsibility of the application to consume any response content regardless of the scenario (success, error, unexpected data, etc). Not doing so can cause a memory leak.

Our use of exchange() is rather basic, but the documentation for error scenarios is unclear to me and I want to be certain that we are correctly releasing resources for all outcomes. In essence, we have a blocking implementation which makes a request and returns the ResponseEntity regardless of the response code:

    try {
        ...
        ClientResponse resp = client.method(method).uri(uri).syncBody(body).exchange().block();
        ResponseEntity<String> entity =  resp.toEntity(String.class).block();
        return entity;
    } catch (Exception e) {
        // log error details, return internal server error
    }

If I understand the implementation, exchange() will always give us a response if the request was successfully dispatched, regardless of response code (e.g. 4xx, 5xx). In that scenario, we just need to invoke toEntity() to consume the response. My concern is for error scenarios (e.g. no response, low-level connection errors, etc). Will the above exception handling catch all other scenarios and will any of them have a response that needs to be consumed?

Note: ClientResponse.releaseBody() was only introduced in 5.2

user1491636
  • 2,355
  • 11
  • 44
  • 71

2 Answers2

10

The response have to be consumed when the request was made, but if you can't do the request probably an exception was be throwed before, and you will no have problems with response.

In the documentation says:

NOTE: When using a ClientResponse through the WebClient exchange() method, you have to make sure that the body is consumed or released by using one of the following methods:

  1. body(BodyExtractor)
  2. bodyToMono(Class) or bodyToMono(ParameterizedTypeReference)
  3. bodyToFlux(Class) or bodyToFlux(ParameterizedTypeReference)
  4. toEntity(Class) or toEntity(ParameterizedTypeReference)
  5. toEntityList(Class) or toEntityList(ParameterizedTypeReference)
  6. toBodilessEntity()
  7. releaseBody()

You can also use bodyToMono(Void.class) if no response content is expected. However keep in mind the connection will be closed, instead of being placed back in the pool, if any content does arrive. This is in contrast to releaseBody() which does consume the full body and releases any content received.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/reactive/function/client/ClientResponse.html

You can try to use .retrieve() instead .exchange() and handle errors as your preference.

  public Mono<String> someMethod() {

    return webClient.method(method)
      .uri(uri)
      .retrieve()
      .onStatus(
        (HttpStatus::isError), // or the code that you want
        (it -> handleError(it.statusCode().getReasonPhrase())) //handling error request
      )
      .bodyToMono(String.class);


  }

  private Mono<? extends Throwable> handleError(String message) {
    log.error(message);
    return Mono.error(Exception::new);
  }

In this example I used Exception but you can create some exception more specific and then use some exception handler to return the http status that you want. Is not recommended to use block, a better way is pass the stream forward.

Raul Lopes
  • 121
  • 4
0

create some exception classes

Autowired ObjectMapper

Create a method that returns Throwable

Create a custom class for Error.

return webClient
                .get()
                .uri(endpoint)
                .retrieve()
                .bodyToMono(Model.class)
                .onErrorMap(WebClientException.class, this::handleHttpClientException);



private Throwable handleHttpClientException(Throwable ex) {
            if (!(ex instanceof WebClientResponseException)) {
                LOG.warn("Got an unexpected error: {}, will rethrow it", ex.toString());
                return ex;
            }
    
            WebClientResponseException wcre = (WebClientResponseException)ex;
    
            switch (wcre.getStatusCode()) {
                case NOT_FOUND -> throw new NotFoundException(getErrorMessage(wcre));
                case BAD_REQUEST -> throw new BadRequestException(getErrorMessage(wcre));
                default -> {
                    LOG.warn("Got a unexpected HTTP error: {}, will rethrow it", wcre.getStatusCode());
                    LOG.warn("Error body: {}", wcre.getResponseBodyAsString());
                    return ex;
                }
            }
        }



private String getErrorMessage(WebClientResponseException ex) {
        try {
            return mapper.readValue(ex.getResponseBodyAsString(), HttpErrorInfo.class).getMessage();
        } catch (IOException ioe) {
            return ex.getMessage();
        }
    }
Habeeb Okunade
  • 251
  • 2
  • 9