Как получить HttpRequest в @ExceptionHandler с RestClientException?

Вопрос или проблема

Я разрабатываю внутреннюю общую библиотеку в своей компании, которая должна перехватывать все исключения и форматировать их в единый DTO (немного упрощенный).

{
    "serviceName": "my-service-name",
    "errorCode": "ExternalRequestException",
    "externalServiceUrl": "http://external-service.com:8080/api/method-name?param=1",
    "externalErrorCode": "InvalidArgumentException",
    "externalErrorMessage": "Недопустимый аргумент param ....",
    "internalErrors": []
}

У меня нет проблем с обработкой WebClientRequestException или WebClientResponseException в @ExceptionHandler, потому что они содержат requestUrl или целый HttpRequest для получения целевого url запроса. Поэтому я могу добавить org.springframework.web.reactive.function.client.Client.ClientResponse#createException в webClient следующим образом:

var response = webClient
            .get()
            .uri(“http://external-service.com:8080/api/method-name?param=1”)
            .retrieve()
            .onStatus(HttpStatusCode::isError, ClientResponse::createException)
            .bodyToMono(String.class)
            .block();

И я буду на 100% уверен, что WebClientRequestException или WebClientResponseException, выбрасываемые этим вызовом, будут содержать url запроса (http://external-service.com:8080/api/method-name?param=1 в этом примере).

Итак, основной вопрос заключается в том, как получить информацию о запросе, когда restClient выбрасывает исключение? Возможно, мне нужно разработать какой-то restClientInterceptor или что-то подобное, чтобы дополнить стандартные исключения специфической информацией о запросе.

Ответ или решение

Как получить HttpRequest в @ExceptionHandler с использованием RestClientException

В процессе разработки внутренней библиотеки для обработки исключений, важно эффективно управлять ситуациями, когда возникают внешние ошибки при работе с REST-клиентами. В данном контексте, вы столкнулись с необходимостью захватывать информацию о запросе, который привел к исключению RestClientException, чтобы иметь возможность корректно формировать DTO-ответ с детальной информацией об ошибке.

Проблема

При использовании Spring WebClient обработка ошибок через WebClientRequestException и WebClientResponseException предоставляет доступ к URL-адресу и другим деталям запроса. Однако, RestClientException, возникающий в некоторых случаях, может не включать все необходимые данные о запросе. Важно разработать подход, который позволит сохранять эти данные даже при возникновении исключения.

Решение: RestClientInterceptor

Одним из решений для решения этой проблемы является использование ClientHttpRequestInterceptor или создания собственного RestClientInterceptor. Это позволит вам перехватывать запросы и получать необходимые данные для обработки ошибок.

  1. Создание собственного Interceptor:
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class CustomRestClientInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(
            HttpRequest request, 
            byte[] body, 
            ClientHttpRequestExecution execution) throws IOException {

        // Ваша логика перед выполнением запроса
        return execution.execute(request, body);
    }
}
  1. Регистрация Interceptor:

Вам нужно зарегистрировать ваш Interceptor в конфигурации вашего RestTemplate, чтобы обеспечить его применение к каждому запросу.

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import java.util.Collections;

@Configuration
public class RestClientConfig {

    private final CustomRestClientInterceptor customRestClientInterceptor;

    public RestClientConfig(CustomRestClientInterceptor customRestClientInterceptor) {
        this.customRestClientInterceptor = customRestClientInterceptor;
    }

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder.additionalInterceptors(Collections.singletonList(customRestClientInterceptor)).build();
    }
}
  1. Обогащение исключений данными о запросе:

Теперь вы можете добавлять данные о запросе в исключения, чтобы обработать их в вашем @ExceptionHandler.

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(RestClientException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponse handleRestClientException(RestClientException ex) {
        String url = ""; // получить URL из контекста запроса или Interceptor
        // Формируйте ваш DTO с деталями об ошибке
        return new ErrorResponse("my-service-name", "ExternalRequestException", url, ex.getMessage());
    }
}

Заключение

Использование интерсепторов в Spring позволяет вам перехватывать и модифицировать HTTP-запросы, что, в свою очередь, дает возможность обогащать исключения дополнительной информацией о запросах. Это решение предоставляет вам возможность формировать структурированные ответы об ошибках и поддерживать целостность вашего API. Такой подход не только улучшает обработку ошибок, но также создает более информативный интерфейс для пользователей вашего сервиса.

Таким образом, используя данные из ваших RestClientInterceptor, вы сможете гарантировать, что даже при возникновении RestClientException у вас будет необходимая информация для генерации интуитивно понятных и удобных ответов для фронтенда.

Оцените материал
Добавить комментарий

Капча загружается...