spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.77k stars 38.16k forks source link

HttpRequest awareness for RestClientException #33814

Closed snejokeee closed 3 weeks ago

snejokeee commented 1 month ago

Hello everyone. This is my first contribution, so please don't blame me too much. So, my use case is to develop an internal library for other teams in my company to standardize the approach to exception handling.

In a spring-framework 5.*, we used WebClient as the default way to consume the internal microservices REST interfaces.

var response = webClientInjectedBean
    .get()
    .uri("http://internal-api.my-company.com:8080/api/external-service/v1/useful-method")
    .retrieve()
    .onStatus(HttpStatusCode::isError, ClientResponse::createException)
    .bodyToMono(new ParameterizedTypeReference<ExternalServiceResponseModel>() {})
    .block();

The purpose of my library is to provide a predefined @ControllerAdvice bean with @ExceptionHandler(WebClientRequestException.class) and @ExceptionHandler(WebClientResponseException.class) methods that handle exceptions and return @ResponseEntity<ErrorDescription> (ErrorDescription is the default template for all error responses used throughout our company). In those processing methods, i can get an instance of org.springframework.http.Http.HttpMethod and URI (for WebClientRequestException) and even an entire org.springframework.http.Http.HttpRequest (for WebClientResponseException) corresponding to the current exception. This is very useful just to build a message for response, like this:

@ExceptionHandler(WebClientResponseException.class)
public ResponseEntity<ErrorDescription> handle(final WebClientResponseException exception) {
    var request = exception.getRequest();
    assert request != null;
    var errorDescription = ErrorDescription.builder()
        .serviceName(applicationName)
        .userMessage(
            "An error occurred when calling " + request.getMethod() + " " + request.getURI() + ". " +
            "The response status code is " + exception.getStatusCode() + ". See the developerMessage for details.")
        .developerMessage(exception.getMessage())
        .build();
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
        .contentType(MediaType.APPLICATION_JSON)
        .body(errorDescription);

Now to the point. We are now trying to upgrade to spring-framewrok 6.* and use RestClient, and I need to refine the library to keep the same approach to error handling. But I'm stuck on a problem with getting the request information when processing a RestClientException. I asked this question on stackowerflow, but it seems that the standard StatusHandler implementation for RestClient avoids the request parameter and default error handling does not use HttpMethod and URI in case of RestClient (i dont know how it works with RestTemplate).

After a bit of research, I realized that I could make a custom interceptor or/and customizer for RestClient.Builder to create exceptions myself and put all the necessary information into it, but I think that the request information is very useful in context of error handling, and it can be out-of-the-box like with WebClient.

I am grateful to everyone who has read this long text. Thank you.

rstoyanchev commented 3 weeks ago

Thank you for this contribution. Unfortunately, as a project stewarded by Broadcom, we are unable to accept contributions from Russian sources due to Broadcom export restrictions at this time. Thanks for your continued use of Spring.