Перейти к основному содержимому

Настройка исключений Zuul

· 4 мин. чтения

1. Обзор

Zuul — это маршрутизатор на базе JVM и балансировщик нагрузки на стороне сервера от Netflix . Механизм правил Zuul обеспечивает гибкость при написании правил и фильтров для улучшения маршрутизации в архитектуре микросервисов Spring Cloud.

В этой статье мы рассмотрим, как настроить исключения и ответы на ошибки в Zuul, написав настраиваемые фильтры ошибок, которые запускаются при возникновении ошибки во время выполнения кода.

2. Исключения для зуулов

Все обрабатываемые исключения в Zuul — это ZuulExceptions . Теперь давайте проясним, что ZuulException не может быть перехвачено @ControllerAdvice и аннотировано методом @ExceptionHandling . Это связано с тем, что ZuulException выбрасывается из фильтра ошибок . Таким образом, он пропускает последующие цепочки фильтров и никогда не достигает контроллера ошибок. На следующем рисунке показана иерархия обработки ошибок в Zuul:

./4aea2cb01d4878e5df1bbff158e3a484.png

Zuul отображает следующий ответ об ошибке при наличии ZuulException :

{
"timestamp": "2022-01-23T22:43:43.126+00:00",
"status": 500,
"error": "Internal Server Error"
}

В некоторых сценариях нам может потребоваться настроить сообщение об ошибке или код состояния в ответе ZuulException. На помощь приходит фильтр Zuul. В следующем разделе мы обсудим, как расширить фильтр ошибок Zuul и настроить ZuulException.

3. Настройка исключений Zuul

Стартовый пакет для spring-cloud-starter-netflix-zuul включает три типа фильтров: предварительные , пост -фильтры и фильтры ошибок. Здесь мы углубимся в фильтры ошибок и изучим настройку фильтра ошибок Zuul, получившего название SendErrorFilter .

Во-первых, мы отключим фильтр SendErrorFilter по умолчанию , который настраивается автоматически. Это позволяет нам не беспокоиться о порядке выполнения, так как это единственный фильтр ошибок Zuul по умолчанию. Давайте добавим свойство в application.yml , чтобы отключить его:

zuul:
SendErrorFilter:
post:
disable: true

Теперь давайте напишем собственный фильтр ошибок Zuul под названием CustomZuulErrorFilter , который выдает пользовательское исключение, если базовая служба недоступна:

public class CustomZuulErrorFilter extends ZuulFilter {
}

Этот пользовательский фильтр должен расширять com.netflix.zuul.ZuulFilter и переопределять несколько его методов .

Во- первых, мы должны переопределить метод filterType() и вернуть тип как «ошибку» . Это потому, что мы хотим настроить фильтр Zuul для типа фильтра ошибок:

@Override
public String filterType() {
return "error";
}

После этого мы переопределяем filterOrder() и возвращаем -1, чтобы фильтр был первым в цепочке :

@Override
public int filterOrder() {
return -1;
}

Затем мы переопределяем метод shouldFilter() и безоговорочно возвращаем true , так как мы хотим связать этот фильтр во всех случаях:

@Override
public boolean shouldFilter() {
return true;
}

Наконец, давайте переопределим метод run () :

@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
Throwable throwable = context.getThrowable();

if (throwable instanceof ZuulException) {
ZuulException zuulException = (ZuulException) throwable;
if (throwable.getCause().getCause().getCause() instanceof ConnectException) {
context.remove("throwable");
context.setResponseBody(RESPONSE_BODY);
context.getResponse()
.setContentType("application/json");
context.setResponseStatusCode(503);
}
}
return null;
}

Давайте разберем этот метод run() , чтобы понять, что он делает. Во-первых, мы получаем экземпляр RequestContext . Затем мы проверяем, является ли throwable , полученный из RequestContext , экземпляром ZuulException . Затем мы проверяем, не является ли причиной вложенного исключения в throwable экземпляр ConnectException . Наконец, мы установили контекст с пользовательскими свойствами ответа.

Обратите внимание, что перед настройкой пользовательского ответа мы очищаем throwable от контекста, чтобы предотвратить дальнейшую обработку ошибок в последующих фильтрах .

Кроме того, мы также можем установить пользовательское исключение внутри нашего метода run() , которое может быть обработано последующими фильтрами:

if (throwable.getCause().getCause().getCause() instanceof ConnectException) {
ZuulException customException = new ZuulException("", 503, "Service Unavailable");
context.setThrowable(customException);
}

Приведенный выше фрагмент зарегистрирует трассировку стека и перейдет к следующим фильтрам.

Более того, мы можем изменить этот пример для обработки нескольких исключений внутри ZuulFilter.

4. Тестирование пользовательских исключений Zuul

В этом разделе мы протестируем пользовательские исключения Zuul в нашем CustomZuulErrorFilter .

Предполагая, что существует ConnectException , вывод приведенного выше примера в ответе Zuul API будет таким:

{
"timestamp": "2022-01-23T23:10:25.584791Z",
"status": 503,
"error": "Service Unavailable"
}

Кроме того, мы всегда можем изменить путь пересылки ошибок Zuul по умолчанию /error , настроив свойство error.path в файле application.yml .

Теперь давайте проверим это с помощью нескольких тестов:

@Test
public void whenSendRequestWithCustomErrorFilter_thenCustomError() {
Response response = RestAssured.get("http://localhost:8080/foos/1");
assertEquals(503, response.getStatusCode());
}

В приведенном выше тестовом сценарии маршрут для /foos/1 намеренно не используется, в результате чего java.lang. ConnectException . В результате наш пользовательский фильтр будет перехватывать и отвечать статусом 503.

Теперь давайте проверим это без регистрации пользовательского фильтра ошибок:

@Test
public void whenSendRequestWithoutCustomErrorFilter_thenError() {
Response response = RestAssured.get("http://localhost:8080/foos/1");
assertEquals(500, response.getStatusCode());
}

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

5. Вывод

В этом руководстве мы узнали об иерархии обработки ошибок и углубились в настройку пользовательского фильтра ошибок Zuul в приложении Spring Zuul. Этот фильтр ошибок предоставил возможность настроить тело ответа, а также код ответа. Как обычно, пример кода доступен на GitHub .