Вопрос или проблема
Я разрабатываю внутреннюю общую библиотеку в своей компании, которая должна перехватывать все исключения и форматировать их в единый 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
. Это позволит вам перехватывать запросы и получать необходимые данные для обработки ошибок.
- Создание собственного 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);
}
}
- Регистрация 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();
}
}
- Обогащение исключений данными о запросе:
Теперь вы можете добавлять данные о запросе в исключения, чтобы обработать их в вашем @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
у вас будет необходимая информация для генерации интуитивно понятных и удобных ответов для фронтенда.