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

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

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

源码解析

其基本运行方式如下:

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


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


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


6. 当 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 统一管理;


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


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

5. 实现方式在自定义处理器类 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
}

"This application has no explicit mapping"意味着在Spring Boot应用程序中,找不到指定路径的映射。具体来说,当你访问一个没有被显式地映射到指定的控制器方法或视图的URL时,就会出现这个错误消息。 要解决这个问题,你可以采取以下几种方法: 1. 检查你的代码,确保你已经正确地配置了URL映射。检查你的控制器类和方法的注解,确保它们与你想要映射的URL路径相匹配。 2. 确保你的视图文件存在并正确地被映射到了对应的URL路径。检查视图文件的位置和命名是否正确。 3. 如果你想要自定义错误处理的行为,可以创建一个专门的错误处理器类,并在其中定义处理错误的方法。然后将这个错误处理器类与错误处理的URL路径进行映射。 总之,当出现"This application has no explicit mapping"错误消息时,你需要检查你的代码和配置,确保URL路径正确地映射到了对应的控制器方法或视图。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [This application has no explicit mapping for /error, so you are seeing this as a fallback](https://blog.csdn.net/yangzhihua/article/details/131238533)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [word源码java-springboot2.0:springboot2.0、Mybatis-Plus、encache](https://download.csdn.net/download/weixin_38534683/19407985)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值