【深入解析spring cloud gateway】07 自定义异常返回报文

Servlet的HttpResponse对象,返回响应报文,一般是这么写的,通过输出流直接就可以将返回报文输出。

java 复制代码
OutputStream out = response.getOutputStream();
out.write("输出的内容");
out.flush();

在filter中如果发生异常(例如请求参数不合法),抛出异常信息的时候,调用方收到的返回码和body都是Spring Cloud Gateway框架处理来处理的。这一节我们分析一下,gateway的异常返回报文是怎么返回的,并定义一个自己的异常返回报文格式。

一、先定义一个Filter,直接抛出异常

定义一个直接抛出异常的filter

java 复制代码
public class ExceptionFilter implements GlobalFilter, Ordered {

    @Override    
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        throw new IllegalArgumentException("参数不合法");    }

    @Override    
    public int getOrder() {
        return 0;    
    }
}

异常抛出如下图

json内容如下:

json 复制代码
{
    "timestamp": "2023-08-28T03:55:02.380+00:00",
    "path": "/hello-service/hello",
    "status": 500,
    "error": "Internal Server Error",
    "requestId": "0204dca5-1"
}

二、源码分析

上节我们分析了核心流程。在整个核心流程中,我们并没有关注有异常的情况。

入口HttpWebHandlerAdapter调用的delegate实际上就是:DefaultErrorWebExceptionHandler

代码如下:

java 复制代码
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
    //省略部分代码
    return getDelegate().handle(exchange)
            .doOnSuccess(aVoid -> logResponse(exchange))
            .onErrorResume(ex -> handleUnresolvedError(exchange, ex))
            .then(Mono.defer(response::setComplete));
}

进入DefaultErrorWebExceptionHandler的handle方法,分析见注释

java 复制代码
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
    Mono<Void> completion;
    try {
 //正常的处理流程
        completion = super.handle(exchange);
    }
    catch (Throwable ex) {
        completion = Mono.error(ex);
    }
//产生异常的情况,由异常处理器来进行处理
    for (WebExceptionHandler handler : this.exceptionHandlers) {
        completion = completion.onErrorResume(ex -> handler.handle(exchange, ex));
    }
    return completion;
}

如果产生异常的情况,由异常处理器来进行处理,这个异常处理器是一个列表。

而异常处理器最核心的就是这个:DefaultErrorWebExceptionHandler

其handle方法如下

java 复制代码
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
    if (exchange.getResponse().isCommitted() || isDisconnectedClientError(throwable)) {
        return Mono.error(throwable);
    }
    this.errorAttributes.storeErrorInformation(throwable, exchange);
    ServerRequest request = ServerRequest.create(exchange, this.messageReaders);
    return getRoutingFunction(this.errorAttributes).route(request)
        .switchIfEmpty(Mono.error(throwable))
        .flatMap((handler) -> handler.handle(request))
        .doOnNext((response) -> logError(request, response, throwable))
        .flatMap((response) -> write(exchange, response));
}

跟到getRoutingFunction里面看看

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

最终跟到下面的这个方法:renderErrorResponse,从下面的截图可以看到,error Map这个对象,正是报文体的格式

如果我们想自定义一个异常响应的返回报文,如下,应该怎么弄呢?

json 复制代码
{
    "returnCode": "ERROR",
    "errorMsg": "参数异常",
    "body": null
}

我们实际上可以继承DefaultErrorWebExceptionHandler,并且实现其renderErrorResponse方法就可以了。

可以看到DefaultErrorWebExceptionHandler,是通过下面的方式注入到容器的,如果我们也定义一个也注册到容器,那么就会覆盖原有的实现

整体流程图如下:

三、自定义异常处理器

1、定义一个产生异常的filter,模拟产生异常

java 复制代码
@Slf4j
public class ExceptionFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("filter产生了异常");
        throw new IllegalArgumentException("参数不合法");
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

2、自定义异常处理器

java 复制代码
/**
 * 自定义异常处理器
 */
public class CustomErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {

    public CustomErrorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties.Resources resources, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resources, errorProperties, applicationContext);
    }

    @Override
    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
        // 最终是用responseBodyMap来生成响应body的
        Map<String, Object> responseBodyMap = new HashMap<>();

        // 这里和父类的做法一样,取得DefaultErrorAttributes整理出来的所有异常信息
        Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));

        // 原始的异常信息可以用getError方法取得
        Throwable throwable = getError(request);

        responseBodyMap.put("returnCode", "my error code");
        responseBodyMap.put("errorMsg", throwable.getMessage());
        responseBodyMap.put("body", null);
        return ServerResponse
                // http返回码
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                // 类型和以前一样
                .contentType(MediaType.APPLICATION_JSON)
                // 响应body的内容
                .body(BodyInserters.fromValue(responseBodyMap));
    }
}

3、注册异常处理器

java 复制代码
@Configuration(proxyBeanMethods = false)
public class ExceptionHandlerConfig {
    private final ServerProperties serverProperties;

    public ExceptionHandlerConfig(ServerProperties serverProperties) {
        this.serverProperties = serverProperties;
    }

    @Bean
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes,
                                                             WebProperties webProperties, ObjectProvider<ViewResolver> viewResolvers,
                                                             ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) {
        CustomErrorWebExceptionHandler exceptionHandler = new CustomErrorWebExceptionHandler(errorAttributes,
                webProperties.getResources(), this.serverProperties.getError(), applicationContext);
        exceptionHandler.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));
        exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}

4、请求效果

相关推荐
脱脱克克7 小时前
云端 OpenClaw 远程执行本地进程原理机制详解:Gateway、approvals 与 system.run 到底谁在判定、谁在执行?
linux·gateway·openclaw
indexsunny1 天前
互联网大厂Java面试实战:从Spring Boot到微服务架构的深度解析
java·spring boot·spring cloud·kafka·prometheus·security·microservices
隔壁小邓1 天前
SpringCloud微服务拆分原则
spring cloud·微服务·架构
七夜zippoe1 天前
OpenClaw Gateway 服务:启动、停止、监控
微服务·架构·gateway·监控·openclaw
arvin_xiaoting1 天前
OpenClaw学习总结_I_核心架构系列_Gateway架构详解
学习·架构·llm·gateway·ai-agent·飞书机器人·openclaw
柯儿的天空1 天前
【OpenClaw 全面解析:从零到精通】第007篇:流量枢纽——OpenClaw Gateway 网关深度解析
人工智能·gpt·ai作画·gateway·aigc·ai编程·ai写作
Zhao_yani1 天前
微服务核心组件:Gateway
java·微服务·gateway
gallonyin1 天前
【企业级龙虾】LLM Gateway 工程化落地:配置中心、429故障转移与统计持久化实战
gateway·openclaw
深念Y1 天前
Nginx和Spring Cloud Gateway
运维·服务器·网络·网关·nginx·spring cloud·微服务
道清茗2 天前
【Kubernetes知识点问答题】Kustomize、CRD 与 Gateway API
容器·kubernetes·gateway