自定義 Zuul 異常
- java
- Zuul
一、概述
Zuul 是 Netflix 的基於 JVM 的路由器和服務器端負載均衡器。 Zuul 的規則引擎提供了編寫規則和過濾器的靈活性,以增強 Spring Cloud 微服務架構中的路由。
在本文中,我們將通過編寫自定義錯誤過濾器來探索如何在 Zuul 中自定義異常和錯誤響應,這些過濾器會在代碼執行期間發生錯誤時運行。
2. Zuul 異常
Zuul 中所有處理的異常都是ZuulExceptions
。現在,讓我們明確一點, ZuulException
不能被@ControllerAdvice
捕獲並通過@ExceptionHandling
註釋方法。這是因為**ZuulException
**是從錯誤過濾器中拋出的。因此,它會跳過後續的過濾器鏈,並且永遠不會到達錯誤控制器。下圖描述了 Zuul 中錯誤處理的層次結構:
當出現ZuulException
時,Zuul 顯示如下錯誤響應:
{
"timestamp": "2022-01-23T22:43:43.126+00:00",
"status": 500,
"error": "Internal Server Error"
}
在某些場景下,我們可能需要在ZuulException.
Zuul 過濾器來救援。在下一節中,我們將討論如何擴展 Zuul 的錯誤過濾器和自定義ZuulException.
3. 自定義 Zuul 異常
spring-cloud-starter-netflix-zuul
的入門包包括三種類型的過濾器:前置、後置和錯誤過濾器。在這裡,我們將深入了解錯誤過濾器並探索名為SendErrorFilter
的 Zuul 錯誤過濾器的自定義。
首先,我們將禁用自動配置的默認SendErrorFilter
。這使我們不必擔心執行順序,因為這是唯一的 Zuul 默認錯誤過濾器。讓我們在application.yml
中添加屬性來禁用它:
zuul:
SendErrorFilter:
post:
disable: true
現在,讓我們編寫一個名為CustomZuulErrorFilter
的自定義 Zuul 錯誤過濾器,如果底層服務不可用,它會拋出自定義異常:
public class CustomZuulErrorFilter extends ZuulFilter {
}
這個自定義過濾器需要擴展com.netflix.zuul.ZuulFilter
並覆蓋它的一些方法`.`
首先,我們必須重寫filterType()
方法並將類型作為“error”
返回。這是因為我們要為錯誤過濾器類型配置 Zuul 過濾器:
@Override
public String filterType() {
return "error";
}
之後,我們覆蓋filterOrder()
並返回-1,
因此過濾器是鏈中的第一個:
@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;
}
讓我們分解這個run()
方法以了解它在做什麼。首先,我們獲取RequestContext
的實例。接下來,我們驗證從RequestContext
獲得的throwable
是否是ZuulException
的實例。然後,我們檢查throwable
中嵌套異常的原因是否是ConnectException
的實例。最後,我們使用響應的自定義屬性設置了上下文。
請注意,在設置自定義響應之前,我們會從上下文中清除throwable
,以防止在後續過濾器中進行進一步的錯誤處理。
此外,我們還可以在run()
方法中設置一個自定義異常,該異常可以由後續過濾器處理:
if (throwable.getCause().getCause().getCause() instanceof ConnectException) {
ZuulException customException = new ZuulException("", 503, "Service Unavailable");
context.setThrowable(customException);
}
上面的代碼片段將記錄堆棧跟踪並繼續執行下一個過濾器。
此外,我們可以修改這個例子來處理ZuulFilter.
4. 測試自定義 Zuul 異常
在本節中,我們將在CustomZuulErrorFilter
中測試自定義 Zuul 異常。
假設有一個ConnectException
,上述示例在 Zuul API 的響應中的輸出將是:
{
"timestamp": "2022-01-23T23:10:25.584791Z",
"status": 503,
"error": "Service Unavailable"
}
此外,**我們始終error.path
/error
**在application.yml
文件中配置 error.path 屬性來更改默認的 Zuul 錯誤轉發路徑 /error。
現在,讓我們通過一些測試用例來驗證它:
@Test
public void whenSendRequestWithCustomErrorFilter_thenCustomError() {
Response response = RestAssured.get("http://localhost:8080/foos/1");
assertEquals(503, response.getStatusCode());
}
在上面的測試場景中, /foos/1
的路由被故意保留下來,導致java.lang.
ConnectException
。結果,我們的自定義過濾器將攔截並響應 503 狀態。
現在,讓我們在不註冊自定義錯誤過濾器的情況下進行測試:
@Test
public void whenSendRequestWithoutCustomErrorFilter_thenError() {
Response response = RestAssured.get("http://localhost:8080/foos/1");
assertEquals(500, response.getStatusCode());
}
在不註冊自定義錯誤過濾器的情況下執行上述測試用例會導致 Zuul 響應狀態為 500。
5. 結論
在本教程中,我們了解了錯誤處理的層次結構,並深入研究了在 Spring Zuul 應用程序中配置自定義 Zuul 錯誤過濾器。此錯誤過濾器提供了自定義響應正文和響應代碼的機會。