吃透Spring Cloud gateway自定义错误处理Handler

这篇具有很好参考价值的文章主要介绍了吃透Spring Cloud gateway自定义错误处理Handler。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

我们来学习和了解下GatewayExceptionHandler有助于我们处理spring gateway和webFlux中的异常自定义处理。它继承自ErrorWebExceptionHandler,类关系图如下:

通过类上述关系图,我们可以看到,DefaultErrorWebExceptionHandler是ErrorWebExceptionHandler的实现类,如果我们不自定义类异常处理器,系统将自动装配DefaultErrorWebExceptionHandler。

我们就来庖丁解牛,一步步地来学习下这一组类和接口。

AbstractErrorWebExceptionHandler

AbstractErrorWebExceptionHandler实现了ErrorWebExceptionHandler接口,其中handle方法是其核心方法。我们来看下其实现代码:

public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
        // 判断response是否已经提交,并且调用isDisconnectedClientError方法判断是否是客户端断开连接。// 如果已经断开连接,已经无法将消息发送给客户端,直接Mono.error(throwable)抛出异常。if (!exchange.getResponse().isCommitted() && !this.isDisconnectedClientError(throwable)) {
            // exchange.getAttributes().putIfAbsent(ERROR_INTERNAL_ATTRIBUTE, error);this.errorAttributes.storeErrorInformation(throwable, exchange);
            // 创建request ServerRequestrequest= ServerRequest.create(exchange, this.messageReaders);
            returnthis.getRoutingFunction(this.errorAttributes).route(request).switchIfEmpty(Mono.error(throwable)).flatMap((handler) -> {
                return handler.handle(request);
            }).doOnNext((response) -> {
                this.logError(request, response, throwable);
            }).flatMap((response) -> {
                returnthis.write(exchange, response);
            });
        } else {
            return Mono.error(throwable);
        }
    }
复制代码

通过Handle方法的代码我们看出主要是实现了以下的事项:

  1. 判断response是否已经提交,并且调用isDisconnectedClientError方法判断是否是客户端断开连接。

  1. 如果已经断开连接,已经无法将消息发送给客户端,直接Mono.error(throwable)抛出异常。

  1. 将异常信息存储到exchange中。

  1. 创建request。

  1. 获取路由函数。

  1. 调用路由函数的handle方法。

  1. 然后执行日志记录。

  1. 最后将response写入到exchange中。

isDisconnectedClientError方法

privatebooleanisDisconnectedClientError(Throwable ex) {
        return DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName()) ||this.isDisconnectedClientErrorMessage(NestedExceptionUtils.getMostSpecificCause(ex).getMessage());
}
复制代码

DISCONNECTED_CLIENT_EXCEPTIONS是一个集合,里面存放了一些异常信息,如果是这些异常信息, 就认为是客户端断开连接了。 或者通过isDisconnectedClientErrorMessage方法判断是否是客户端断开连接的异常信息。

下面我们来看看isDisconnectedClientErrorMessage方法的实现。

isDisconnectedClientErrorMessage方法:

privatebooleanisDisconnectedClientErrorMessage(String message) {
        message = message != null ? message.toLowerCase() : "";
        return message.contains("broken pipe") || message.contains("connection reset by peer");
}
复制代码

上述代码的含义是:如果异常信息中包含“broken pipe”或者“connection reset by peer”,就认为是客户端断开连接了。

总结

综合起来,isDisconnectedClientError方法的含义是:如果DISCONNECTED_CLIENT_EXCEPTIONS集合中包含异常信息的类名或者异常信息中包含“broken pipe”或者“connection reset by peer”,就认为是客户端断开连接了。

NestedExceptionUtils

而isDisconnectedClientErrorMessage方法的参数message来自于NestedExceptionUtils.getMostSpecificCause(ex).getMessage()。我们进一步跟踪源码,可以看到NestedExceptionUtils.getMostSpecificCause(ex)方法的源码如下:

publicstatic Throwable getMostSpecificCause(Throwable ex) {
    Throwable cause;
    Throwableresult= ex;

    while(null != (cause = result.getCause()) && (result != cause)) {
        result = cause;
    }

    return result;
}
复制代码

上述代码的含义是:如果ex.getCause()不为空,并且ex.getCause()不等于ex,就将ex.getCause()赋值给result,然后继续执行while循环。直到ex.getCause()为空或者ex.getCause()等于ex,就返回result。

getRoutingFunction

我们看到获得路由调用的是getRoutingFunction方法,而这个方法是一个抽象方法,需要在具体的继承类中实现。

logError

protectedvoidlogError(ServerRequest request, ServerResponse response, Throwable throwable) {
        if (logger.isDebugEnabled()) {
            logger.debug(request.exchange().getLogPrefix() + this.formatError(throwable, request));
        }

        if (HttpStatus.resolve(response.rawStatusCode()) != null && response.statusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR)) {
            logger.error(LogMessage.of(() -> {
                return String.format("%s 500 Server Error for %s", request.exchange().getLogPrefix(), this.formatRequest(request));
            }), throwable);
        }

    }
复制代码

上述代码的含义:

  1. 如果是debug级别的日志,就以debug的方式打印异常信息。

  1. 如果response的状态码是500,就打印异常信息。

用到的formatError方法就是简单的讲错误格式化,代码不再一一分析。

write

在把内容写入的时候用到了私有方法write,我们看看write方法的代码如下:

private Mono<? extendsVoid> write(ServerWebExchange exchange, ServerResponse response) {
        exchange.getResponse().getHeaders().setContentType(response.headers().getContentType());
        return response.writeTo(exchange, newAbstractErrorWebExceptionHandler.ResponseContext());
    }
复制代码

上述代码的含义是:将response的contentType设置到exchange的response中,然后调用response的writeTo方法,将response写入到exchange中。ResponseContext是一个内部类,主要是携带了外部类中的viewResolvers和messageWriters属性。

其他的方法

afterPropertiesSet

publicvoidafterPropertiesSet()throws Exception {
        if (CollectionUtils.isEmpty(this.messageWriters)) {
            thrownewIllegalArgumentException("Property 'messageWriters' is required");
        }
}
复制代码

afterPropertiesSet主要是通过实现InitializingBean接口,实现afterPropertiesSet方法,判断messageWriters是否为空,如果为空就抛出异常。

renderDefaultErrorView

protected Mono<ServerResponse> renderDefaultErrorView(BodyBuilder responseBody, Map<String, Object> error) {
        StringBuilderbuilder=newStringBuilder();
        Datetimestamp= (Date)error.get("timestamp");
        Objectmessage= error.get("message");
        Objecttrace= error.get("trace");
        ObjectrequestId= error.get("requestId");
        builder.append("<html><body><h1>Whitelabel Error Page</h1>").append("<p>This application has no configured error view, so you are seeing this as a fallback.</p>").append("<div id='created'>").append(timestamp).append("</div>").append("<div>[").append(requestId).append("] There was an unexpected error (type=").append(this.htmlEscape(error.get("error"))).append(", status=").append(this.htmlEscape(error.get("status"))).append(").</div>");
        if (message != null) {
            builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
        }

        if (trace != null) {
            builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
        }

        builder.append("</body></html>");
        return responseBody.bodyValue(builder.toString());
    }
复制代码

renderDefaultErrorView方法主要是渲染默认的错误页面,如果没有自定义的错误页面,就会使用这个方法渲染默认的错误页面。

renderErrorView

protected Mono<ServerResponse> renderErrorView(String viewName, BodyBuilder responseBody, Map<String, Object> error) {
        if (this.isTemplateAvailable(viewName)) {
            return responseBody.render(viewName, error);
        } else {
            Resourceresource=this.resolveResource(viewName);
            return resource != null ? responseBody.body(BodyInserters.fromResource(resource)) : Mono.empty();
        }
    }
复制代码

上述代码的含义是:

  1. 如果viewName对应的模板存在,就使用模板渲染错误页面。

  1. 否则就使用静态资源渲染错误页面。在使用资源渲染的时候,调用resolveResource方法,代码如下:

private Resource resolveResource(String viewName) {
        // 获得所有的静态资源的路径
        String[] var2 = this.resources.getStaticLocations();
        intvar3= var2.length;
        // 遍历所有的静态资源for(intvar4=0; var4 < var3; ++var4) {
            Stringlocation= var2[var4];

            try {
                // 获得静态资源Resourceresource=this.applicationContext.getResource(location);
                // 获得静态资源的文件
                resource = resource.createRelative(viewName + ".html");
                if (resource.exists()) {
                    return resource;
                }
            } catch (Exception var7) {
            }
        }

        returnnull;
    }
复制代码

DefaultErrorWebExceptionHandler

DefaultErrorWebExceptionHandler是ErrorWebExceptionHandler的默认实现,继承了AbstractErrorWebExceptionHandler。通过对AbstractErrorWebExceptionHandler的分析,我们知道了大致实现原理,下面我们来看看DefaultErrorWebExceptionHandler的具体实现。

getRoutingFunction

在AbstractErrorWebExceptionHandler中,getRoutingFunction方法是一个抽象方法,需要子类实现。DefaultErrorWebExceptionHandler的getRoutingFunction方法的实现如下:

protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(this.acceptsTextHtml(), this::renderErrorView).andRoute(RequestPredicates.all(), this::renderErrorResponse);
}
复制代码

我们看到代码中调用了RouterFunctions.route和andRoute方法。RouterFunctions.route的作用是根据请求的accept头信息,判断是否是text/html类型,如果是就调用renderErrorView方法,否则就调用renderErrorResponse方法。

acceptsTextHtml

protected RequestPredicate acceptsTextHtml() {
        return (serverRequest) -> {
            try {
                // 获得请求头中的accept信息
                List<MediaType> acceptedMediaTypes = serverRequest.headers().accept();
                // 定义一个MediaType.ALL的MediaType对象MediaTypevar10001= MediaType.ALL;
                // 移除MediaType.ALL
                acceptedMediaTypes.removeIf(var10001::equalsTypeAndSubtype);
                // 根据类型和权重对MediaType进行排序
                MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
                // 获得acceptMediaTypes的stream对象Streamvar10000= acceptedMediaTypes.stream();
                // 获得MediaType.TEXT_HTML的MediaType对象
                var10001 = MediaType.TEXT_HTML;
                var10001.getClass();
                // 判断是否有MediaType.TEXT_HTMLreturn var10000.anyMatch(var10001::isCompatibleWith);
            } catch (InvalidMediaTypeException var2) {
                returnfalse;
            }
        };
    }
复制代码

acceptsTextHtml方法的作用是判断请求头中的accept信息是否是text/html类型。

renderErrorView

protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
    // 通过getErrorAttributes方法获得错误信息
        Map<String, Object> error = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML));
        // 获得错误状态码interrorStatus=this.getHttpStatus(error);
        // 获得错误页面的模板名称BodyBuilderresponseBody= ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8);
        // 获得错误页面的模板名称return Flux.just(this.getData(errorStatus).toArray(newString[0])).flatMap((viewName) -> {
            returnthis.renderErrorView(viewName, responseBody, error);
        }).switchIfEmpty(this.errorProperties.getWhitelabel().isEnabled() ? this.renderDefaultErrorView(responseBody, error) : Mono.error(this.getError(request))).next();
    }
复制代码

其中

  1. this.getData(errorStatus)通过错误的code获得错误页面的名称。

  1. this.renderErrorView(viewName, responseBody, error),通过错误页面的名称,错误信息,错误状态码,获得错误页面的响应体。

  1. this.renderDefaultErrorView(responseBody, error),通过错误信息,错误状态码,获得默认的错误页面的响应体。

renderErrorResponse方法

protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
    // 通过getErrorAttributes方法获得错误信息
        Map<String, Object> error = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
        // 获得错误状态码// 设置为json格式的响应体// 返回错误信息return ServerResponse.status(this.getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(error));
    }
复制代码

通过上述代码看到主要执行了以下流程:

  1. 通过getErrorAttributes方法获得错误信息

  1. 获得错误状态码

  1. 设置为json格式的响应体

  1. 返回错误信息

getErrorAttributeOptions

protected ErrorAttributeOptions getErrorAttributeOptions(ServerRequest request, MediaType mediaType) {
        ErrorAttributeOptionsoptions= ErrorAttributeOptions.defaults();
        if (this.errorProperties.isIncludeException()) {
            options = options.including(newInclude[]{Include.EXCEPTION});
        }

        if (this.isIncludeStackTrace(request, mediaType)) {
            options = options.including(newInclude[]{Include.STACK_TRACE});
        }

        if (this.isIncludeMessage(request, mediaType)) {
            options = options.including(newInclude[]{Include.MESSAGE});
        }

        if (this.isIncludeBindingErrors(request, mediaType)) {
            options = options.including(newInclude[]{Include.BINDING_ERRORS});
        }

        return options;
    }
复制代码

我们看到上述代码执行了下面的流程:

  1. 获得ErrorAttributeOptions对象

  1. 判断是否包含异常信息

  1. 判断是否包含堆栈信息

  1. 判断是否包含错误信息

  1. 判断是否包含绑定错误信息如果包含这些信息,就将对应的信息加入到ErrorAttributeOptions对象中。而这些判断主要是通过ErrorProperties对象中的配置来判断的。

自定义自己的异常处理类

当认为默认的DefaultErrorWebExceptionHandler不满足需求时,我们可以自定义自己的异常处理类。

继承DefaultErrorWebExceptionHandler

只需要继承DefaultErrorWebExceptionHandler类,然后重写getErrorAttributes方法即可。

import org.springframework.context.annotation.Configuration;

@Configuration@Order(-1)publicclassMyErrorWebExceptionHandlerextendsDefaultErrorWebExceptionHandler {
    publicMyErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    @Overrideprotected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(request, options);
        errorAttributes.put("ext", "自定义异常处理类");
        return errorAttributes;
    }
}
复制代码

继承AbstractErrorWebExceptionHandler

import org.springframework.context.annotation.Configuration;

@Configuration@Order(-1)publicclassMyErrorWebExceptionHandlerextendsAbstractErrorWebExceptionHandler {
    publicMyErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    @Overrideprotected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }

    @Overrideprotected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(request, options);
        errorAttributes.put("ext", "自定义异常处理类");
        return errorAttributes;
    }
}
复制代码

继承WebExceptionHandler

import org.springframework.context.annotation.Configuration;

@Configuration@Order(-1)publicclassMyErrorWebExceptionHandlerimplementsWebExceptionHandler {
    @Overridepublic Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponseresponse= exchange.getResponse();
        if (response.isCommitted()) {
            return Mono.error(ex);
        } else {
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
            return response.writeWith(Mono.just(response.bufferFactory().wrap("自定义异常处理类".getBytes())));
        }
    }
}
复制代码

总结

总体工作流程

经过对AbstractErrorWebExceptionHandler和DefaultErrorWebExceptionHandler中代码的阅读和跟踪。我们不难看出其工作机制。考虑到有返回API方式的json错误信息和错误页面方式的错误信息。所以ExceptionHandler通过request中的accept的http头进行判断,如果是json格式则以json的方式进行响应,否则则以错误页面的方式进行响应。而错误页面的方式,通过错误的code获得错误页面的名称,然后通过错误页面的名称,错误信息,错误状态码,获得错误页面的响应体。

得到的经验

  1. 异步编程和同步编程之间差异很大,特别是在对request和response进行操作的时候。

  1. 源代码充分考虑各种情况和细节,在编程中值得我们去做参考。

作者:油而不腻张大叔

链接:https://juejin.cn/post/7205111603464781882 文章来源地址https://www.toymoban.com/news/detail-654279.html

到了这里,关于吃透Spring Cloud gateway自定义错误处理Handler的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • spring cloud gateway自定义负载均衡, 优先转发到指定IP

    示例为优先将请求分配到指定IP, 如果指定IP没有服务, 则随机分配. GatewayLoadBalancerConfiguration.java 挂载配置类, 可以通过 @org.springframework.boot.autoconfigure.condition.ConditionalOnXXX 或者 @Profile 注解或者@ComponentScan过滤等方式来配置是否启用 在任意一个@Configuration注解的类上配置均可 L

    2024年02月07日
    浏览(41)
  • 第九章 : Spring cloud 网关中心 Gateway (内置断言与自定义断言)

    第九章 : Spring cloud 网关中心 Gateway (内置断言与自定义断言) 前言 本章知识点:Spring cloud Gateway 内置断言(Predicate)类型的 yaml 和 json 数据格式;自定义路由Predicate 断言的案例。 Gateway 内置 Predicate 格式配置 概述 本文针对 Spring Cloud Gateway 框架中断言(Predicate)类型在 Yaml

    2024年01月17日
    浏览(37)
  • Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway错误

      原因是因为spring cloud gateway 是建立在spring boot 2.x 和 spring webflux基础上的既:gateway 本身已经包含了spring mvc 的功能,正与提示的一样和spring boot 的web starter冲突了 找到gateway下面的spring-boot-starter-web依赖,然后 将其注释掉  

    2024年02月15日
    浏览(44)
  • spring cloud gateway中出现503 spring cloud gateway中出现503

    当搭建网关模块的时候出现503的错误的最大的可能就是没有设置负载均衡的依赖包  原先搭建的时候采用的是下面的方式进行设置的 上面的这种方式可以直接进行注册和发现,但是要求必须导入下面的依赖 希望简单的随笔能够帮助你!

    2024年02月11日
    浏览(52)
  • 【Spring Cloud 八】Spring Cloud Gateway网关

    【Spring Cloud一】微服务基本知识 【Spring Cloud 三】Eureka服务注册与服务发现 【Spring Cloud 四】Ribbon负载均衡 【Spring Cloud 五】OpenFeign服务调用 【Spring Cloud 六】Hystrix熔断 【Spring Cloud 七】Sleuth+Zipkin 链路追踪 在项目中是使用了Gateway做统一的请求的入口,以及统一的跨域处理以及

    2024年02月12日
    浏览(48)
  • Spring Cloud Gateway 超时、CORS配置 | Spring Cloud 17

    Spring Cloud Gateway 可以为所有路由配置 Http 超时(响应和连接) ,并为每个特定路由覆盖设置。 配置全局 http 超时: connect-timeout 必须 以毫秒为单位 指定。 response-timeout 必须指定为 java.time.Duration 使用示例:

    2024年02月14日
    浏览(55)
  • Spring Cloud Gateway

    目录 一、Spring Cloud Gateway 1、网关介绍 2、GateWay 3、GateWay项目搭建 4、GateWay配置路由的两种方式 4.1、YML配置 4.2、配置类 5、GateWay实现负载均衡 5.1、自动负载均衡 5.2、手动负载均衡 6、GateWay断言Predicate 7、GateWay的Filter         在 微服务架构 中,一个 系统会被拆分为很多

    2024年02月10日
    浏览(50)
  • 浅谈Spring Cloud Gateway

    网关:用户和微服务的桥梁 网关的核心是一组过滤器,按照先后顺序执行过滤操作。 Spring Cloud Gateway是基于webFlux框架实现,而webFlux框架底层则使用了高性能的Reactor模式通信框架的Netty Spring Cloud Gateway是Spring Cloud生态系统中的一个API网关,它基于Spring Framework 5,Spring Boot 2和P

    2024年02月11日
    浏览(45)
  • Spring Cloud Gateway学习

    传统的单体架构只有一个服务开放给客户端调用,但是在微服务架构体系中是将一个系统拆分成多个微服务,那么作为客户端如何去调用这些微服务呢?如果没有网关的存在,就只能在本地记录每个微服务的调用地址。 无网关的微服务架构存在的问题: 客户端多次请求不同

    2024年02月07日
    浏览(70)
  • 细说 Spring Cloud Gateway

    在微服务架构中,API 网关是一个非常重要的组件,它可以帮助我们实现服务的路由、负载均衡、认证授权等功能。Spring Cloud Gateway 是 Spring Cloud 官方推出的一个基于 Spring 5、Spring Boot 2 和 Project Reactor 的 API 网关实现。本文将介绍 Spring Cloud Gateway 的基本概念、核心组件以及如何

    2024年02月14日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包