0


spring-cloud-gateway 网关自定义异常处理

最近想对 SpringCloudGateway 的网关请求转发响应异常信息进行统一的包装,比如:访问 404,需要返回自定义的 JSON 格式,替换原来的 springWeb 错误提示内容;

针对 springcloudgateway 进行了源码研究,了解到了 DefaultErrorWebExceptionHandler 在 gateway 中是如何运作的(其实是 springboot 里组件)。

源码解析

其基本运行方式如下:

  1. 通过 HttpHandlerAutoConfiguration 自动配置装载类创建 HttpHandler 实例 Bean,并将其装配到 spring-web 的核心生命周期与上下文内;
  2. 在此 httpHandler 方法的执行过程中,会调用 WebHttpHandlerBuilder.applicationContext (this.applicationContext).build ();

  1. 在 WebHttpHandlerBuilder 的 applicationContext () 方法中,会创建相应 WbFilters、Handlers 进行创建与属性赋值;
  2. 其中 applicationContext () 方法中会从 applicationContext 上下文中找到所有实现 WebExceptionHandler 接口的类。

  1. 在 WebHttpHandlerBuilder 的 build () 方法中会创建相应 WebHandler,赋值到 ExceptionHandlingWebHandler 的私有常量集合 exceptionHandlers 对象中,重要:此集合对象就是管理着网关转发异常处理器。并为实例对象 HttpWebHandlerAdapter 设置各种配置属性、上下文对象;

  1. 当 gateway 执行网关路由转发后,所有网关转发发生的异常会先调用 HttpWebHandlerAdapter 的 handle () 方法,该方法内又会调用 ExceptionHandlingWebHandler 类的 handle (),此方法会对 exceptionHandlers 异常集合,进行遍历执行用于组装或生成异常响应信息的各种响应结构,如:header、status、context;

exceptionHandlers 集合对象通常会有以下几种处理类(默认按以下顺序排列)

  • CheckpointInsertingHandler
  • DefaultErrorWebExceptionHandler(继承自 AbstractErrorWebExceptionHandler)
  • WebFluxResponseStatusExceptionHandler

以上几种处理器均实现了 WebExceptionHandler 接口

比如:我们常见的 404 错误信息

Whitelabel Error Page
This application has no configured error view, so you are seeing this as a fallback.
Mon Nov 08 20:53:11 CST 2021
[5877e728-1] There was an unexpected error (type=Not Found, status=404).

就是从 AbstractErrorWebExceptionHandler 异常处理器 renderDefaultErrorView () 方法中组装的内容输出的;

解决方案
增加自定义全局异常输出,我方案如下:

  1. 创建自定异常处理器,同亲实现 WebExceptionHandler 接口,用于在 WebHttpHandlerBuilder 的 applicationContext () 方法中将自定义异常处理器加载到 exceptionHandlers 集合中,实现 spring 统一管理;

  1. 因 exceptionHandlers 集合对象中已经进行了默认顺序添加 handler 异常处理类,自定义处理器类则加到了集合队列的尾部;默认 CheckpointInsertingHandler 排在第一索引位;

  1. 加入尾部会导致无法输出,因为在自定义处理器的前面 AbstractErrorWebExceptionHandler 处理器中会输出异常内容,从而阻断了自定义处理器的内容输出;
  2. 将自定义处理器类 CustomWebExceptionHandler 对象添加到 exceptionHandlers 集合对象最前例,默认进行异常处理器链路调用时,最先调用自定义处理器,从而向调用客户端输出自定义异常信息;以下是默认的 exceptionHandlers 集合对象;

  1. 实现方式在自定义处理器类 CustomWebExceptionHandler 上添加 @Order (-1) 注解标签,并设置排序值为 - 1,默认最小值优先级最大;以下添加自定义处理器类 CustomWebExceptionHandler 的 exceptionHandlers 集合对象;

实现代码
类:CustomWebExceptionHandler.java

import com.alibaba.fastjson.JSONObject;
import com.flying.fish.formwork.util.ApiResult;
import com.flying.fish.formwork.util.Constants;
import com.flying.fish.formwork.util.HttpResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

@Slf4j
@Order(-1)
@Component
public class CustomWebExceptionHandler implements WebExceptionHandler {

    private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS;
    //排除部份系统级的异常
    static {
        Set<String> exceptions = new HashSet<>();
        exceptions.add("AbortedException");
        exceptions.add("ClientAbortException");
        exceptions.add("EOFException");
        exceptions.add("EofException");
        DISCONNECTED_CLIENT_EXCEPTIONS = Collections.unmodifiableSet(exceptions);
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        if (exchange.getResponse().isCommitted() || isDisconnectedClientError(ex)) {
            return Mono.error(ex);
        }
        ServerHttpRequest request = exchange.getRequest();
        String rawQuery = request.getURI().getRawQuery();
        String query = StringUtils.hasText(rawQuery) ? "?" + rawQuery : "";
        String path = request.getPath() + query ;
        String message ;
        HttpStatus status = determineStatus(ex);
        if (status == null){
            status = HttpStatus.INTERNAL_SERVER_ERROR;
        }
        // 通过状态码自定义异常信息
        if (status.value() >= 400 && status.value() < 500){
            message = "路由服务不可达或禁止访问!";
        }else {
            message = "路由服务异常!";
        }
        message += " path:" + path;
        String jsonMsg = JSONObject.toJSONString(new ApiResult(Constants.FAILED, message, null));
        //工具类输出json字符串
        return HttpResponseUtils.write(exchange.getResponse(), status, jsonMsg);
    }

    @Nullable
    protected HttpStatus determineStatus(Throwable ex) {
        if (ex instanceof ResponseStatusException) {
            return ((ResponseStatusException) ex).getStatus();
        }
        return null;
    }

    private boolean isDisconnectedClientError(Throwable ex) {
        return DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName())
                || isDisconnectedClientErrorMessage(NestedExceptionUtils.getMostSpecificCause(ex).getMessage());
    }

    private boolean isDisconnectedClientErrorMessage(String message) {
        message = (message != null) ? message.toLowerCase() : "";
        return (message.contains("broken pipe") || message.contains("connection reset by peer"));
    }
}

通过上述方案,成功实现全局自定义异常捕获与消息自定义输出,如:404,输出为

{
"code": "0",
"msg": "路由服务不可达或禁止访问! path:/parent/userCenter/getUser2",
"timestamp": 1636376343775
}

标签: spring gateway java

本文转载自: https://blog.csdn.net/jilo88/article/details/125078951
版权归原作者 不会飞的小龙人 所有, 如有侵权,请联系我们删除。

“spring-cloud-gateway 网关自定义异常处理”的评论:

还没有评论