【问题标题】:Customizing Zuul Exception自定义 Zuul 异常
【发布时间】:2016-07-27 11:13:42
【问题描述】:

我在 Zuul 中有一个场景,其中路由 URL 的服务也可能关闭。因此,响应主体在 JSON 主体响应中被抛出 500 HTTP 状态和 ZuulException。

{
  "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();
}

【问题讨论】:

  • 你有没有尝试过?
  • 我尝试通过使用 @ControllerAdvice 注释类来添加异常处理程序,但这似乎不起作用。我想我需要用 Zuul 过滤器做一些事情,但不确定需要发生什么。
  • 好的,那么最好编辑您的问题以便进行此尝试,因为您会注意到有些反对者认为您自己没有尝试过任何事情。如果你改进了这个问题,我会给你我的 +1,因为我认为这是一个有趣的主题。
  • 完成更新问题。
  • 编写自定义 ErrorController 实现也可以帮助某人解决转发错误:jmnarloch.wordpress.com/2015/09/16/…

标签: java spring-boot spring-cloud microservices netflix-zuul


【解决方案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;
    }
}

【讨论】:

  • 您能否建议如何将 zuul 层发生的任何异常重定向到标准错误页面,因为我不想对 ReponseBody 进行硬编码。谢谢@grinish-nepal
  • 所以你没有添加 error 过滤器,而只添加了过滤器。
  • 它是一个后置过滤器,但它在 sendErrorFilter 之前运行,您可以在评论中看到。
  • zuul最新版本没有error.exceptionerror.status_code。而是使用throwable。顺便说一句,如果你想替换错误响应然后使用erro过滤器类型而不是post
【解决方案2】:

Zuul RequestContext 不包含error.exception 中提到的this answer
最新的 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;
    }
}

【讨论】:

  • 为什么这不会覆盖我的响应正文?它只是一个空的身体?
  • @D.Tomov 如果您想覆盖响应,只需将过滤器类型更改为 error
【解决方案3】:

我遇到了同样的问题,并且能够以更简单的方式解决它

只要把这个放到你的Filter run()方法中

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

SendErrorFilter 将向用户发送带有所需statusCode 的消息。

异常模式中的这个异常看起来不太好,但在这里可以工作。

【讨论】:

  • 使用 ZuulRuntimeException 比简单地使用 RuntimeException 有什么好处?
  • 这样您可以添加自定义消息和状态码。使用RuntimeException,状态码将为 500。不确定消息
【解决方案4】:

转发通常由过滤器完成,在这种情况下,请求甚至不会到达控制器。这可以解释为什么您的 @ControllerAdvice 不起作用。

如果您在控制器中转发,则 @ControllerAdvice 应该可以工作。 检查 spring 是否创建了带有 @ControllerAdvice 注释的类的实例。为此在类中放置一个断点并查看它是否被命中。

也在控制器方法中添加一个断点,转发应该发生。可能是您不小心调用了另一个控制器方法而不是您检查的?

这些步骤应该可以帮助您解决问题。

在用@ControllerAdvice 注释的类中添加一个用@ExceptionHandler(Exception.class) 注释的ExceptionHandler 方法,它应该捕获每个异常。

编辑: 您可以尝试添加您自己的过滤器来转换 Zuulfilter 返回的错误响应。您可以在此处随意更改响应。

这里解释了如何自定义错误响应:

exception handling for filter in spring

正确放置过滤器可能有点棘手。 不完全确定正确的位置,但您应该注意过滤器的顺序以及处理异常的位置。

如果将它放在 Zuulfilter 之前,则必须在调用 doFilter() 后编写错误处理代码。

如果将它放在 Zuulfilter 之后,则必须在调用 doFilter() 之前编写错误处理代码。

在 doFilter() 之前和之后的过滤器中添加断点可能有助于找到正确的位置。

【讨论】:

  • 其实我这里没有转发任何东西。在我的孢子引导应用程序中,我所拥有的只是来自 spring-cloud 的 @EnableZullProxy,这样我就可以将我的路由添加到配置中。所以那里没有控制器。我希望 controllerAdvice 可以工作,但由于转发是由过滤器完成的,我需要抓住那部分并对其进行自定义,我无法弄清楚如何。
【解决方案5】:

以下是使用@ControllerAdvice 执行此操作的步骤:

  1. 首先添加一个error类型的过滤器,让它在zuul本身的SendErrorFilter之前运行。
  2. 确保从RequestContext 中删除与异常关联的键,以防止SendErrorFilter 执行。
  3. 使用RequestDispatcher 将请求转发到ErrorController -- 解释如下。
  4. 添加一个@RestController 类并使其扩展AbstractErrorController,并再次重新抛出异常(在使用(键,异常)执行新错误过滤器的步骤中添加它,从RequestContext 中获取它您的控制器)。

现在将在您的 @ControllerAdvice 类中捕获异常。

【讨论】:

  • 这实际上有效,除了我将ErrorController 实现到ControllerAdvice 类并添加RestController 注释。这可能不酷,但它有效。
【解决方案6】:
    The simplest solution is to follow first 4 steps.


     1. Create your own CustomErrorController extends
        AbstractErrorController which will not allow the
        BasicErrorController to be called.
     2. Customize according 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
public class CustomErrorController extends 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>

【讨论】:

    【解决方案7】:

    这对我有用。 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
                    }
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-03
      • 2015-02-05
      • 2011-02-11
      • 2019-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多