自定义 Zuul Exception
1、概览
Zuul 是 Netflix 推出的基于 JVM 的网关和服务器端负载均衡器。Zuul 的规则引擎提供了灵活性,可以编写规则和过滤器(Filter)来增强在 Spring Cloud 微服务架构中的路由功能。。
本文将会带你了解如何通过自定义 Error Filter 来自定义 Zuul 中的异常和 Error 响应。当代码执行过程中发生错误时,这些自定义 Error Filter 将被执行。
2、Zuul 异常
Zuul 中处理的所有异常都是 ZuulException
。
ZuulException
不能通过 @ControllerAdvice
捕获,也不能通过 @ExceptionHandling
对方法进行注解。这是因为 ZuulException
是由 Error Filter 抛出的。因此,它会跳过后续的过滤链,永远不会到达 Error Controller。下图描述了 Zuul 中错误处理的层次结构:
出现 ZuulException
时,Zuul 会显示以下错误响应:
{
"timestamp": "2022-01-23T22:43:43.126+00:00",
"status": 500,
"error": "Internal Server Error"
}
在某些情况下,可能需要自定义 ZuulException
响应中的错误信息或状态码。这时,Zuul Filter 就能派上用场了。
3、自定义 Zuul 异常
spring-cloud-starter-netflix-zuul
Starter 包括三种 Filter:Pre(前置)、Post(后置)和 Error(错误)。
本文重点深入了解 Error Filter。
首先,禁用自动配置的默认 SendErrorFilter
。这样就不必担心执行顺序,因为这是唯一的 Zuul 默认 Error Filter。
在 application.yml
中添加属性来禁用它:
zuul:
SendErrorFilter:
post:
disable: true
现在,编写一个名为 CustomZuulErrorFilter
的自定义 Zuul Error Filter,在底层服务不可用时抛出一个自定义异常:
public class CustomZuulErrorFilter extends ZuulFilter {
}
此自定义 Filter 需要继承 com.netflix.zuul.ZuulFilter
,并覆写其中的一些方法。
首先,必须覆写 filterType()
方法,并将类型返回为 “error”。这是因为我们配置的是一个 Error 类型的 Filter:
@Override
public String filterType() {
return "error";
}
之后,覆写 filterOrder()
并返回 -1
,这样 Filter 就是链中的第一个:
@Override
public int filterOrder() {
return -1;
}
接着,覆写 shouldFilter()
方法,并无条件返回 true
,因为我们希望在所有情况下都进入该过滤器:
@Override
public boolean shouldFilter() {
return true;
}
最后,覆写 run()
方法:
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
Throwable throwable = context.getThrowable();
if (throwable instanceof ZuulException) {
ZuulException zuulException = (ZuulException) throwable;
if (throwable.getCause().getCause().getCause() instanceof ConnectException) {
context.remove("throwable");
context.setResponseBody(RESPONSE_BODY);
context.getResponse()
.setContentType("application/json");
context.setResponseStatusCode(503);
}
}
return null;
}
如上。首先,获取 RequestContext
的实例。接着,验证从 RequestContext
中获取的 throwable
是否是 ZuulException
的实例。然后,检查 throwable
中嵌套异常的原因是否是 ConnectException
的实例。最后,用响应的自定义属性设置 context。
注意,在设置自定义响应之前,会清除上下文中的 throwable
,以防止后续 filter 进一步处理错误。
此外,还可以在 run()
方法中设置一个自定义异常,由后续 filter 处理:
if (throwable.getCause().getCause().getCause() instanceof ConnectException) {
ZuulException customException = new ZuulException("", 503, "Service Unavailable");
context.setThrowable(customException);
}
上述代码段将记录堆栈信息,并进入下一个 filter。
也以修改此示例,在 ZuulFilter
内处理多个异常。
4、测试自定义 Zuul 异常
测试 CustomZuulErrorFilter
中的自定义 Zuul 异常。
假设出现了 ConnectException
,在 Zuul API 的响应中,上述示例的输出将是:
{
"timestamp": "2022-01-23T23:10:25.584791Z",
"status": 503,
"error": "Service Unavailable"
}
此外,还可以通过配置 application.yml
文件中的 error.path
属性来更改默认的 Zuul error 转发路径 /error
。
测试:
@Test
public void whenSendRequestWithCustomErrorFilter_thenCustomError() {
Response response = RestAssured.get("http://localhost:8080/foos/1");
assertEquals(503, response.getStatusCode());
}
在上述测试场景中,/foos/1
路由被故意关闭,导致 java.lang.ConnectException
异常。因此,自定义 Filter 将拦截并响应 503 状态码。
现在,在不注册自定义 Error Filter 的情况下进行测试:
@Test
public void whenSendRequestWithoutCustomErrorFilter_thenError() {
Response response = RestAssured.get("http://localhost:8080/foos/1");
assertEquals(500, response.getStatusCode());
}
在未注册自定义 Error Filter 的情况下执行上述测试用例,结果 Zuul 响应状态码为 500。
5、总结
本文介绍了 Zuul 异常处理的层级结构,并深入了解了如何在 Spring Zuul 应用中配置自定义的 Zuul Error Filter,以提供自定义响应体和状态码。
参考:https://www.baeldung.com/zuul-customize-exception