Настройка Zuul Exception

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

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

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

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

ОБНОВЛЕНИЕ:

Итак, я расширил Zuul Filter. Я вижу, как он попадает в метод run после того, как была выполнена ошибка, как мне изменить ответ. Ниже приводится то, что я получил до сих пор. Я где-то читал о 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();
}

Ответ 1

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

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;
    }
}

Ответ 2

У меня была та же проблема и я смог решить ее проще:

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

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

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

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

Ответ 3

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

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

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

Эти шаги помогут вам решить проблему.

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

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

Как можно настроить реакцию на ошибку:

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

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

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

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

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

Ответ 4

Вот шаги, которые нужно сделать с помощью @ControllerAdvice:

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

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

Ответ 5

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;
    }
}

Ответ 6

The simplest solution is to follow first 4 steps.

 1. Set the 'error.path=</customErrorUrl>'
 2. Create your own CustomErrorController extends
    AbstractErrorController which will not allow the
    BasicErrorController to be called.
 3. Customize accorind to your need refer below method from
    BasicErrorController.

<pre><code> 
    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        return new ResponseEntity<>(body, status);
    }
</pre></code> 

 4. You can control whether you want exception / stack trace to be printed or not can do as mentioned below:
<pre><code>
server.error.includeException=false
server.error.includeStacktrace=ON_TRACE_PARAM
</pre></code>

================================================== ==

5. If you want all together different error response re-throw your custom exception from your CustomErrorController and implement the Advice class as mentioned below:

<pre><code>

 @Controller

@Открытый класс Slf4j CustomErrorController расширяет BasicErrorController {

public CustomErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties,
        List<ErrorViewResolver> errorViewResolvers) {

    super(errorAttributes, serverProperties.getError(), errorViewResolvers);
    log.info("Created");
}

@Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
    HttpStatus status = getStatus(request);
    throw new CustomErrorException(String.valueOf(status.value()), status.getReasonPhrase(), body);
}

}

@ControllerAdvice
public class GenericExceptionHandler {
// Exception handler annotation invokes a method when a specific exception
    // occurs. Here we have invoked Exception.class since we
    // don't have a specific exception scenario.
    @ExceptionHandler(CustomException.class)
    @ResponseBody
    public ErrorListWsDTO customExceptionHandle(
            final HttpServletRequest request,
            final HttpServletResponse response,
            final CustomException exception) {
            LOG.info("Exception Handler invoked");
            ErrorListWsDTO errorData = null;
            errorData = prepareResponse(response, exception);
            response.setStatus(Integer.parseInt(exception.getCode()));
            return errorData;
    }

    /**
     * Prepare error response for BAD Request
     *
     * @param response
     * @param exception
     * @return
     */
    private ErrorListWsDTO prepareResponse(final HttpServletResponse response,
            final AbstractException exception) {
            final ErrorListWsDTO errorListData = new ErrorListWsDTO();
            final List<ErrorWsDTO> errorList = new ArrayList<>();
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            final ErrorWsDTO errorData = prepareErrorData("500",
                    "FAILURE", exception.getCause().getMessage());
            errorList.add(errorData);
            errorListData.setErrors(errorList);
            return errorListData;
    }

    /**
     * This method is used to prepare error data
     *
     * @param code
     *            error code
     * @param status
     *            status can be success or failure
     * @param exceptionMsg
     *            message description
     * @return ErrorDTO
     */
    private ErrorWsDTO prepareErrorData(final String code, final String status,
            final String exceptionMsg) {

            final ErrorWsDTO errorDTO = new ErrorWsDTO();
            errorDTO.setReason(code);
            errorDTO.setType(status);
            errorDTO.setMessage(exceptionMsg);
            return errorDTO;
    }

}
</pre></code>