Проблема:
Мое REST-приложение использует другое REST-приложение, поскольку оно является "хабом" для нескольких других приложений.
Когда запрос к этому "концентратору" создает HttpClientErrorException
в одном из других приложений REST, которыми он управляет, я хочу, чтобы StatusCode
(с сообщением) был представлен пользователю моего "концентратора", а не просто ошибка стандартного внутреннего сервера 500 ...
Как это можно сделать?
Я использую Spring-Boot 2.1.4 для моего REST-приложения "хаб", которое отправляет запросы другим REST-приложениям ...
Простой тестовый пример с пружинной загрузкой:
@Test
@SuppressWarnings("unchecked")
@DisplayName("should use original rest exception")
void shouldUseRestException() {
when(restTemplateMock.exchange(anyString(), any(), any(), any(ParameterizedTypeReference.class)))
.thenThrow(new HttpClientErrorException(HttpStatus.I_AM_A_TEAPOT, "holy crap!"));
assertThatExceptionOfType(RestClientException.class).isThrownBy(() -> testRestTemplate.exchange(
url("/1001"), HttpMethod.GET, null, listOfMyGenericTypes()
)).withMessage("Http client says: holy crap!");
}
Мой RestControllerAdvice
:
@RestControllerAdvice
public class HubControllerAdvice {
@ResponseBody
@ExceptionHandler
public ResponseEntity<Object> handleHttClientErrorException(HttpClientErrorException httpClientErrorException) {
throw new HttpClientErrorException(httpClientErrorException.getStatusCode(), "Http client says: " + httpClientErrorException.getMessage());
}
}
Ошибка JUnit:
org.opentest4j.AssertionFailedError:
Expecting message:
<"Http client says: holy crap!">
but was:
<"Error while extracting response for type [java.util.List<MyGenericType>] and content type [application/json;charset=UTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 1]">
У меня есть рабочее решение, но это изменит поведение службы, и я бы очень хотел этого избежать. Это фактически возвращает объект ответа с кодом состояния исключения в качестве заголовка в моем API ... хотя это работает (см. Ниже), требуется, чтобы пользователи моего концентратора изменили свои ожидания относительно того, как он работает:
Мой RestControllerAdvice
:
@RestControllerAdvice
public class BidragDokumentRestControllerAdvice {
@ResponseBody
@ExceptionHandler
public ResponseEntity<Object> handleHttClientErrorException(HttpClientErrorException httpClientErrorException) {
return ResponseEntity
.status(httpClientErrorException.getStatusCode())
.header(HttpHeaders.WARNING, "Http client says: " + httpClientErrorException.getMessage())
.build();
}
}
Мой теперь работающий тест JUnit:
@Test
@SuppressWarnings("unchecked")
@DisplayName("should return empty response with original exception warning")
void shouldReturnEmptyResponseWithOriginalExceptionWarning() {
when(restTemplateMock.exchange(anyString(), any(), any(), any(ParameterizedTypeReference.class)))
.thenThrow(new HttpClientErrorException(HttpStatus.I_AM_A_TEAPOT, "holy crap!"));
var errorResponser = testRestTemplate.exchange(
url("/1001"), HttpMethod.GET, null, listOfMyGenericTypes()
);
assertThat(Optional.of(errorResponse)).hasValueSatisfying(responseEntity -> assertAll(
() -> assertThat(responseEntity.getStatusCode()).as("status").isEqualTo(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED),
() -> assertThat(responseEntity.getBody()).as("body").isNull(),
() -> {
HttpHeaders httpHeaders = errorResponse.getHeaders();
assertAll(
() -> assertThat(httpHeaders.get(HttpHeaders.WARNING)).as("warning header").isNotNull(),
() -> assertThat(httpHeaders.get(HttpHeaders.WARNING)).as("header value").isEqualTo(List.of("Http client says: 509 holy crap!"))
);
}
));
}