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

У меня есть сценарий в Zuul, где служба, на которую направлен URL-адрес, тоже может быть недоступна. Таким образом, тело ответа генерируется с 500 HTTP Status и ZuulException в ответе тела JSON.

{
  "timestamp": 1459973637928,
  "status": 500,
  "error": "Internal Server Error",
  "exception": "com.netflix.zuul.exception.ZuulException",
  "message": "Forwarding error"
}

Все, что я хочу сделать, это настроить или удалить ответ JSON и, возможно, изменить код состояния HTTP.

Я попытался создать обработчик исключений с помощью @ControllerAdvice, но обработчик не захватил исключение.

ОБНОВЛЕНИЯ:

Итак, я расширил фильтр Zuul. Я вижу, как он попадает в метод запуска после того, как ошибка была выполнена, как мне тогда изменить ответ. Ниже то, что я получил до сих пор. Я где-то читал о SendErrorFilter, но как мне это реализовать и что он делает?

public class CustomFilter extends ZuulFilter {

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

    @Override
    public int filterOrder() {

        return 1;
    }

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

    @Override
    public Object run() {
        final RequestContext ctx = RequestContext.getCurrentContext();
        final HttpServletResponse response = ctx.getResponse();
        if (HttpStatus.INTERNAL_SERVER_ERROR.value() == ctx.getResponse().getStatus()) {
            try {
                response.sendError(404, "Error Error"); //trying to change the response will need to throw a JSON body.
            } catch (final IOException e) {
                e.printStackTrace();
            } ;
        }

        return null;
    }

Добавлен в класс с @EnableZuulProxy

@Bean
public CustomFilter customFilter() {
    return new CustomFilter();
}

person Grinish Nepal    schedule 06.04.2016    source источник
comment
Вы уже что-нибудь пробовали?   -  person Xtreme Biker    schedule 06.04.2016
comment
Я попытался добавить обработчик исключений, аннотировав класс с помощью @ControllerAdvice, но, похоже, это не сработало. Я думаю, что мне нужно что-то сделать с Zuul Filters, но не знаю, что должно произойти.   -  person Grinish Nepal    schedule 06.04.2016
comment
Хорошо, тогда было бы хорошо, если бы ваш вопрос был отредактирован, чтобы предпринять эти попытки, потому что, как вы можете заметить, есть какой-то отрицательный голос, который думал, что вы сами ничего не пробовали. Если вы улучшите вопрос, я дам вам свой +1, так как считаю это интересным предметом.   -  person Xtreme Biker    schedule 06.04.2016
comment
готово обновил вопрос.   -  person Grinish Nepal    schedule 06.04.2016
comment
Кодирование пользовательской реализации ErrorController также может помочь кому-то справиться с ошибкой пересылки: jmnarloch.wordpress.com/2015/09/16/   -  person Vladimir Salin    schedule 08.06.2017


Ответы (6)


Наконец-то мы получили это работающее [Закодировано одним из моих коллег]: -

public class CustomErrorFilter extends ZuulFilter {

    private static final Logger LOG = LoggerFactory.getLogger(CustomErrorFilter.class);
    @Override
    public String filterType() {
        return "post";
    }

    @Override
    public int filterOrder() {
        return -1; // Needs to run before SendErrorFilter which has filterOrder == 0
    }

    @Override
    public boolean shouldFilter() {
        // only forward to errorPath if it hasn't been forwarded to already
        return RequestContext.getCurrentContext().containsKey("error.status_code");
    }

    @Override
    public Object run() {
        try {
            RequestContext ctx = RequestContext.getCurrentContext();
            Object e = ctx.get("error.exception");

            if (e != null && e instanceof ZuulException) {
                ZuulException zuulException = (ZuulException)e;
                LOG.error("Zuul failure detected: " + zuulException.getMessage(), zuulException);

                // Remove error code to prevent further error handling in follow up filters
                ctx.remove("error.status_code");

                // Populate context with new response values
                ctx.setResponseBody(“Overriding Zuul Exception Body”);
                ctx.getResponse().setContentType("application/json");
                ctx.setResponseStatusCode(500); //Can set any error code as excepted
            }
        }
        catch (Exception ex) {
            LOG.error("Exception filtering in custom error filter", ex);
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        return null;
    }
}
person Grinish Nepal    schedule 03.10.2016
comment
Можете ли вы посоветовать, как можно перенаправить на стандартную страницу ошибок для любого исключения, возникшего на уровне zuul, поскольку я не хочу жестко кодировать ReponseBody. Спасибо @ grinish-nepal - person Joey Trang; 15.06.2017
comment
поэтому вы не добавили фильтр ошибок, а только фильтр сообщений. - person Bruce Lee; 03.09.2017
comment
Это фильтр сообщений, но он запускается перед sendErrorFilter, вы можете увидеть это в комментарии. - person Grinish Nepal; 17.10.2017
comment
последняя версия zuul не имеет error.exception или error.status_code. вместо этого используйте throwable. кстати, если вы хотите заменить ответ об ошибке, используйте тип фильтра erro вместо post - person sakit; 23.08.2020

Zuul RequestContext не содержит error.exception, как указано в этом ответе.
Обновите фильтр ошибок Zuul:

@Component
public class ErrorFilter extends ZuulFilter {
    private static final Logger LOG = LoggerFactory.getLogger(ErrorFilter.class);

    private static final String FILTER_TYPE = "error";
    private static final String THROWABLE_KEY = "throwable";
    private static final int FILTER_ORDER = -1;

    @Override
    public String filterType() {
        return FILTER_TYPE;
    }

    @Override
    public int filterOrder() {
        return FILTER_ORDER;
    }

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

    @Override
    public Object run() {
        final RequestContext context = RequestContext.getCurrentContext();
        final Object throwable = context.get(THROWABLE_KEY);

        if (throwable instanceof ZuulException) {
            final ZuulException zuulException = (ZuulException) throwable;
            LOG.error("Zuul failure detected: " + zuulException.getMessage());

            // remove error code to prevent further error handling in follow up filters
            context.remove(THROWABLE_KEY);

            // populate context with new response values
            context.setResponseBody("Overriding Zuul Exception Body");
            context.getResponse().setContentType("application/json");
            // can set any error code as excepted
            context.setResponseStatusCode(503);
        }
        return null;
    }
}
person xxxception    schedule 27.01.2019
comment
Почему это не отменяет мой текст ответа? Это просто пустое тело? - person D.Tomov; 16.04.2019
comment
@ D.Tomov, если вы хотите переопределить ответ, просто измените тип фильтра на error - person sakit; 23.08.2020

У меня была такая же проблема, и я решил ее проще

Просто поместите это в свой метод Filter run()

    if (<your condition>) {
        ZuulException zuulException = new ZuulException("User message", statusCode, "Error Details message");
        throw new ZuulRuntimeException(zuulException);
    }

и SendErrorFilter доставит пользователю сообщение с желаемым statusCode.

Это исключение в шаблоне исключения выглядит не очень хорошо, но здесь оно работает.

person Vasile Rotaru    schedule 04.07.2017
comment
Каковы преимущества использования ZuulRuntimeException перед простым использованием RuntimeException? - person IcedDante; 27.10.2017
comment
Таким образом вы можете добавить собственное сообщение и код состояния. С RuntimeException код состояния будет 500. Не уверен насчет сообщения - person Vasile Rotaru; 28.10.2017

Перенаправление часто выполняется фильтром, в этом случае запрос даже не доходит до контроллера. Это объяснило бы, почему ваш @ControllerAdvice не работает.

Если вы перейдете в контроллер, тогда @ControllerAdvice должен работать. Проверьте, создает ли Spring экземпляр класса, аннотированного @ControllerAdvice. Для этого установите точку останова в классе и посмотрите, сработала ли она.

Добавьте точку останова также в метод контроллера, где должна происходить пересылка. Может быть, вы случайно вызываете другой метод контроллера, чем проверяете?

Эти шаги должны помочь вам решить проблему.

В вашем классе, помеченном @ControllerAdvice, добавьте метод ExceptionHandler, аннотированный @ExceptionHandler (Exception.class), который должен улавливать каждое исключение.

РЕДАКТИРОВАТЬ: Вы можете попробовать добавить свой собственный фильтр, который преобразует ответ об ошибке, возвращаемый Zuulfilter. Там вы можете изменить ответ по своему усмотрению.

Здесь объясняется, как настроить реакцию на ошибку:

обработка исключений для фильтра весной

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

Если вы поместите его перед Zuulfilter, вам придется закодировать обработку ошибок после вызова doFilter ().

Если вы поместите его после Zuulfilter, вам придется закодировать обработку ошибок перед вызовом doFilter ().

Добавление точек останова в фильтр до и после doFilter () может помочь найти правильную позицию.

person Stefan Isele - prefabware.com    schedule 06.04.2016
comment
На самом деле я здесь ничего не пересылаю. В моем приложении загрузки sporing все, что у меня есть, это @EnableZullProxy из spring-cloud, чтобы я мог добавить свою маршрутизацию в конфигурацию. Так что там нет контроллера. Я надеялся, что controllerAdvice может сработать, но поскольку пересылка выполняется фильтром, мне нужно захватить эту часть и настроить ее, что я не мог понять, как. - person Grinish Nepal; 07.04.2016

Вот шаги, чтобы сделать это с помощью @ControllerAdvice:

  1. Сначала добавьте фильтр типа error и позвольте ему запускаться перед SendErrorFilter в самом zuul.
  2. Обязательно удалите ключ, связанный с исключением, из RequestContext, чтобы предотвратить выполнение SendErrorFilter.
  3. Используйте RequestDispatcher для пересылки запроса на ErrorController - поясняется ниже.
  4. Добавьте класс @RestController и сделайте так, чтобы он расширял AbstractErrorController, и повторно выбросите исключение (добавьте его на этапе выполнения вашего нового фильтра ошибок с помощью (ключ, исключение), получите его из RequestContext в вашем контроллере).

Теперь исключение будет обнаружено в вашем классе @ControllerAdvice.

person Karim Masoud    schedule 30.08.2017
comment
это действительно работает, за исключением того, что я реализовал ErrorController в классе ControllerAdvice и добавил аннотацию RestController. Это может быть не круто, но это работает. - person Bruce Lee; 04.09.2017

Это то, что у меня сработало. RestExceptionResponse - это класс, который используется в @ControllerAdvice, поэтому у нас есть идентичный ответ исключения в случае внутренних ZuulExceptions.

@Component
@Log4j
public class CustomZuulErrorFilter extends ZuulFilter {

    private static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";

    @Override
    public String filterType() {
        return ERROR_TYPE;
    }

    @Override
    public int filterOrder() {
        return SEND_ERROR_FILTER_ORDER - 1; // Needs to run before SendErrorFilter which has filterOrder == 0
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        Throwable ex = ctx.getThrowable();
        return ex instanceof ZuulException && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
    }

    @Override
    public Object run() {
        try {
            RequestContext ctx = RequestContext.getCurrentContext();
            ZuulException ex = (ZuulException) ctx.getThrowable();

            // log this as error
            log.error(StackTracer.toString(ex));

            String requestUri = ctx.containsKey(REQUEST_URI_KEY) ? ctx.get(REQUEST_URI_KEY).toString() : "/";
            RestExceptionResponse exceptionResponse = new RestExceptionResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex, requestUri);

            // Populate context with new response values
            ctx.setResponseStatusCode(500);
            this.writeResponseBody(ctx.getResponse(), exceptionResponse);

            ctx.set(SEND_ERROR_FILTER_RAN, true);
        }
        catch (Exception ex) {
            log.error(StackTracer.toString(ex));
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        return null;
    }


    private void writeResponseBody(HttpServletResponse response, Object body) throws IOException {
        response.setContentType("application/json");
        try (PrintWriter writer = response.getWriter()) {
            writer.println(new JSonSerializer().toJson(body));
        }
    }
}

Результат выглядит так:

{
    "timestamp": "2020-08-10 16:18:16.820",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/service",
    "exception": {
        "message": "Filter threw Exception",
        "exceptionClass": "com.netflix.zuul.exception.ZuulException",
        "superClasses": [
            "com.netflix.zuul.exception.ZuulException",
            "java.lang.Exception",
            "java.lang.Throwable",
            "java.lang.Object"
        ],
        "stackTrace": null,
        "cause": {
            "message": "com.netflix.zuul.exception.ZuulException: Forwarding error",
            "exceptionClass": "org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException",
            "superClasses": [
                "org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException",
                "java.lang.RuntimeException",
                "java.lang.Exception",
                "java.lang.Throwable",
                "java.lang.Object"
            ],
            "stackTrace": null,
            "cause": {
                "message": "Forwarding error",
                "exceptionClass": "com.netflix.zuul.exception.ZuulException",
                "superClasses": [
                    "com.netflix.zuul.exception.ZuulException",
                    "java.lang.Exception",
                    "java.lang.Throwable",
                    "java.lang.Object"
                ],
                "stackTrace": null,
                "cause": {
                    "message": "Load balancer does not have available server for client: template-scalable-service",
                    "exceptionClass": "com.netflix.client.ClientException",
                    "superClasses": [
                        "com.netflix.client.ClientException",
                        "java.lang.Exception",
                        "java.lang.Throwable",
                        "java.lang.Object"
                    ],
                    "stackTrace": null,
                    "cause": null
                }
            }
        }
    }
}
person Peter Nagy    schedule 10.08.2020