Spring Cloud Gateway中异常处理

最近我们的项目在考虑使用Gateway,考虑使用Spring Cloud Gateway,发现网关的异常处理和spring boot 单体应用异常处理还是有很大区别的。让我们来回顾一下异常。

关于异常是拿来干什么的,很多人老程序员认为就是拿来我们Debug的时候排错的,当然这一点确实是异常机制非常大的一个好处,但异常机制包含着更多的意义。

  • 关注业务实现。异常机制使得业务代码与异常处理代码可以分开,你可以将一些你调用数据库操作的代码写在一个方法里而只需要在方法上加上throw DB相关的异常。至于如何处理它,你可以在调用该方法的时候处理或者甚至选择不处理,而不是直接在该方法内部添加上if判断如果数据库操作错误该如何办,这样业务代码会非常混乱。
  • 统一异常处理。与上一点有所联系。我当前所在项目的实践是,自定义业务类异常,在Controller或Service中抛出,让后使用Spring提供的异常接口统一处理我们自己在内部抛出的异常。这样一个异常处理架构就非常明了。
  • 程序的健壮性。如果没有异常机制,那么来了个对空对象的某方法调用怎么办呢?直接让程序挂掉?这令人无法接受,当然,我们自己平时写的一些小的东西确实是这样,没有处理它,让后程序挂了。但在web框架中,可以利用异常处理机制捕获该异常并将错误信息传递给我们然后继续处理下个请求。所以异常对于健壮性是非常有帮助的。

异常处理(又称为错误处理)功能提供了处理程序运行时出现的任何意外或异常情况的方法。异常处理使用 try、catch 和 finally 关键字来尝试可能未成功的操作,处理失败,以及在事后清理资源。异常根据意义成三种:业务、系统、代码异常,不同的异常采用不同的处理方式。具体的什么样的异常怎么处理就不说了。

红线和绿线代表两条异常路径

1,红线代表:请求到Gateway发生异常,可能由于后端app在启动或者是没启动

2,绿线代表:请求到Gateway转发到后端app,后端app发生异常,然后Gateway转发后端异常到前端

红线肯定是走Gateway自定义异常

两个类的代码如下(参考:http://cxytiandi.com/blog/detail/20548):

public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

    private static Logger logger = LoggerFactory.getLogger(JsonExceptionHandler.class);
    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
                                ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

        /**
         * 获取异常属性
         */
        @Override
        protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
            int code = HttpStatus.INTERNAL_SERVER_ERROR.value();
            Throwable error = super.getError(request);
            if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
                code = HttpStatus.NOT_FOUND.value();
            }
            return response(code, this.buildMessage(request, error));
        }

        /**
         * 指定响应处理方法为JSON处理的方法
         * @param errorAttributes
         */
        @Override
        protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
            return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
        }


        /**
         * 根据code获取对应的HttpStatus
         * @param errorAttributes
         */
        @Override
        protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
            int statusCode = (int) errorAttributes.get("code");
            return HttpStatus.valueOf(statusCode);
        }

        /**
         * 构建异常信息
         * @param request
         * @param ex
         * @return
         */
        private String buildMessage(ServerRequest request, Throwable ex) {
            StringBuilder message = new StringBuilder("Failed to handle request [");
            message.append(request.methodName());
            message.append(" ");
            message.append(request.uri());
            message.append("]");
            if (ex != null) {
                message.append(": ");
                message.append(ex.getMessage());
            }
            return message.toString();
        }

        /**
         * 构建返回的JSON数据格式
         * @param status      状态码
         * @param errorMessage  异常信息
         * @return
         */
        public static Map<String, Object> response(int status, String errorMessage) {
            Map<String, Object> map = new HashMap<>();
            map.put("code", status);
            map.put("message", errorMessage);
            map.put("data", null);
            logger.error(map.toString());
            return map;
        }

    }
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ExceptionHandlerConfiguration {

    private final ServerProperties serverProperties;
    private final ApplicationContext applicationContext;
    private final ResourceProperties resourceProperties;
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;
    public ExceptionHandlerConfiguration(ServerProperties serverProperties,
                                         ResourceProperties resourceProperties,
                                         ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                         ServerCodecConfigurer serverCodecConfigurer,
                                         ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
                errorAttributes,
                this.resourceProperties,
                this.serverProperties.getError(),
                this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;

绿线代表Gateway转发异常

转发的异常,肯定是springboot单体中处理的,至于spring单体中的异常是怎么处理的呢?肯定是用@ControllerAdvice去做。

@ExceptionHandler(value = Exception.class)
@ResponseBody
public ScanAppResponse exceptionHandler(HttpServletRequest request, Exception e) {
    String ip = RequestUtil.getIpAddress(request);
    logger.info("调用者IP:" + ip);
    String errorMessage = String.format("Url:[%s]%n{%s}", request.getRequestURL().toString(), e.getMessage());
    logger.error(errorMessage, e);
    return ScanAppResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
}

到这里基本上可以了,大家不要试着去用Gateway去捕获后端异常,回到最初的起点,API 网关(API Gateway)主要负责服务请求路由、组合及协议转换,异常同样也是一样,Gateway只负责转发单体应用的异常,不要试图Gateway捕获后端服务异常,然后再输出给前端。感谢猿天地的一句惊醒梦中人!

本文分享自微信公众号 - 只喝牛奶的杀手(killerhub)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-02-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏白安全组

四十五种获取webshell的方法

1.到GoogLe,搜索一些关键字,edit.asp? 韩国肉鸡为多,多数为MSSQL数据库!

28620
来自专栏白安全组

批量入侵SQL注入网站

这里有个输入域名的地方(就是你想拿哪个站点的网址)我们点批量扫描注入点,导入前面扫描的站点,然后批量查询

36450
来自专栏开发杂记

Liquibase的简单使用

LiquiBase是一个用于数据库重构和迁移的开源工具,通过日志文件的形式记录数据库的变更,然后执行日志文件中的修改,将数据库更新或回滚到一致的状态。它的目标是...

20360
来自专栏白安全组

黑客术语

“时间戳”是个听起来有些玄乎但实际上相当通俗易懂的名词,我们查看系统中的文件属性,其中显示的创建、修改、访问时间就是该文件的时间戳。对于大多数一般用户而言,通过...

12020
来自专栏ThoughtWorks

浅谈CDC在微服务中的应用

CDC(Change Data Capture)是一种通过监测数据变更(变更包括新增、修改、删除等)而对变更的数据进行进一步处理的一种设计模式,通常应用在数据仓...

14930
来自专栏白安全组

MySQL手工暴库语句

本期给大家整理了一下手工的爆库语句,虽然没有sqlmap那么好,但是在特定的情况下还是很有用,大家可以收藏作为一个笔记使用。

13620
来自专栏白安全组

懒人福音——渗透测试单行化

One-Lin3r是一个简单的轻量级框架,它的设计灵感来源于Metasploit中的web-delivery模块。

8820
来自专栏happyJared

MySQL 字符集、校对规则及索引

字符集指的是一种从二进制编码到某类字符符号的映射。校对规则则是指某种字符集下的排序规则。

12330
来自专栏Java编程指南

玩转SpringBoot之定时任务详解

基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。

17440
来自专栏用户5892232的专栏

OCP-052考试题库汇总(26)-CUUG内部解答版

Which three of these must be accessible to keep a database open?

9340

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励