Servlet的HttpResponse对象,返回响应报文,一般是这么写的,通过输出流直接就可以将返回报文输出。
OutputStream out = response.getOutputStream();
out.write("输出的内容");
out.flush();
在filter中如果发生异常(例如请求参数不合法),抛出异常信息的时候,调用方收到的返回码和body都是Spring Cloud Gateway框架处理来处理的。这一节我们分析一下,gateway的异常返回报文是怎么返回的,并定义一个自己的异常返回报文格式。
一、先定义一个Filter,直接抛出异常
定义一个直接抛出异常的filter
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内容如下:
{
"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
代码如下:
@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方法,分析见注释
@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方法如下
@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里面看看
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse);
}
最终跟到下面的这个方法:renderErrorResponse,从下面的截图可以看到,error Map这个对象,正是报文体的格式
如果我们想自定义一个异常响应的返回报文,如下,应该怎么弄呢?
{
"returnCode": "ERROR",
"errorMsg": "参数异常",
"body": null
}
我们实际上可以继承DefaultErrorWebExceptionHandler,并且实现其renderErrorResponse方法就可以了。
可以看到DefaultErrorWebExceptionHandler,是通过下面的方式注入到容器的,如果我们也定义一个也注册到容器,那么就会覆盖原有的实现
整体流程图如下:
三、自定义异常处理器
1、定义一个产生异常的filter,模拟产生异常
@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、自定义异常处理器
/**
* 自定义异常处理器
*/
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、注册异常处理器
@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、请求效果