我們了解了 Spring Cloud Zuul 作為網關所具備的最基本功能:路由(Router),下面我們将關注 Spring Cloud Zuul 的另一核心功能:過濾器(Filter)
1.Filter的使用場景
場景非常多:
請求鑒權:一般放在pre類型,如果發現沒有通路權限,直接就攔截了
異常處理:一般會在error類型和post類型過濾器中結合來處理。
服務調用時長統計:pre和post結合使用。
3.ZuulFilter
ZuulFilter是過濾器的頂級父類。
在這裡我們看一下其中定義的4個最重要的方法:
public abstract ZuulFilter implements IZuulFilter{
abstract public String filterType();
abstract public int filterOrder();
boolean shouldFilter();// 來自IZuulFilter
Object run() throws ZuulException;// IZuulFilter
}
-
:傳回一個shouldFilter
值,判斷該過濾器是否需要執行。傳回true執行,傳回false不執行。Boolean
-
:過濾器的具體業務邏輯。run
-
:傳回字元串,代表過濾器的類型。包含以下4種:filterType
-
:請求在被路由之前執行pre
-
:在路由請求時調用routing
-
:在routing和errror過濾器之後調用post
-
:處理請求時發生錯誤調用error
-
:通過傳回的int值來定義過濾器的執行順序,數字越小優先級越高。filterOrder
3.Filter 的生命周期
這張是Zuul官網提供的請求生命周期圖,清晰的表現了一個請求在各個過濾器的執行順序。
Filter 的生命周期有 4 個,分别是 “PRE”、“ROUTING”、“POST” 和“ERROR”,整個生命周期可以用下圖來表示
Zuul 大部分功能都是通過過濾器來實作的,這些過濾器類型對應于請求的典型生命周期。
PRE:這種過濾器在請求被路由之前調用。我們可利用這種過濾器實作身份驗證、在叢集中選擇請求的微服務、記錄調試資訊等。
ROUTING:這種過濾器将請求路由到微服務。這種過濾器用于建構發送給微服務的請求,并使用 Apache HttpClient 或 Netfilx Ribbon 請求微服務。
POST:這種過濾器在路由到微服務以後執行。這種過濾器可用來為響應添加标準的 HTTP Header、收集統計資訊和名額、将響應從微服務發送給用戶端等。
ERROR:在其他階段發生錯誤時執行該過濾器。 除了預設的過濾器類型,Zuul 還允許我們建立自定義的過濾器類型。例如,我們可以定制一種 STATIC 類型的過濾器,直接在 Zuul 中生成響應,而不将請求轉發到後端的微服務。
- 正常流程:
- 請求到達首先會經過pre類型過濾器,而後到達routing類型,進行路由,請求就到達真正的服務提供者,執行請求,傳回結果後,會到達post過濾器。而後傳回響應。
- 異常流程:
- 整個過程中,pre或者routing過濾器出現異常,都會直接進入error過濾器,再error處理完畢後,會将請求交給POST過濾器,最後傳回給使用者。
- 如果是error過濾器自己出現異常,最終也會進入POST過濾器,而後傳回。
- 如果是POST過濾器出現異常,會跳轉到error過濾器,但是與pre和routing不同的時,請求不會再到達POST過濾器了。
所有内置過濾器清單:
Zuul中預設實作的Filter執行順序:
如何禁用指定的Filter
可以在 application.yml 中配置需要禁用的 filter,格式為
zuul...disable=true。
比如要禁用
org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter
就設定
zuul:
SendResponseFilter:
post:
disable: true
自定義Filter:
首先自定義一個 Filter,繼承 ZuulFilter 抽象類,在 run() 方法中添加具體業務邏輯,具體如下:
/**
* @author bruceliu
* @create 2019-10-19 12:12
* @description
*/
@Component
public class MyFilter extends ZuulFilter {
/**
* 過濾器的類型,它決定過濾器在請求的哪個生命周期中執行。 這裡定義為pre,代表會在請求被路由之前執行。
*
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* filter執行順序,通過數字指定。 數字越大,優先級越低。
*
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 判斷該過濾器是否需要被執行。這裡我們直接傳回了true,是以該過濾器對所有請求都會生效。 實際運用中我們可以利用該函數來指定過濾器的有效範圍。
*
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 過濾器的具體邏輯
*
* @return
*/
@Override
public Object run() {
// 登入校驗邏輯。
// 1)擷取Zuul提供的請求上下文對象
RequestContext ctx = RequestContext.getCurrentContext();
// 2) 從上下文中擷取request對象
HttpServletRequest req = ctx.getRequest();
// 3) 從請求中擷取token
String token = req.getParameter("access-token");
// 4) 判斷
if(token == null || "".equals(token.trim())){
// 沒有token,登入校驗失敗,攔截
ctx.setSendZuulResponse(false);
// 傳回401狀态碼。也可以考慮重定向到登入頁。
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
// 校驗通過,可以考慮把使用者資訊放入上下文,繼續向後執行
return null;
}
}
在上面實作的過濾器代碼中,我們通過繼承ZuulFilter抽象類并重寫了下面的四個方法來實作自定義的過濾器。這四個方法分别定義了:
filterType():過濾器的類型,它決定過濾器在請求的哪個生命周期中執行。這裡定義為pre,代表會在請求被路由之前執行。
filterOrder():過濾器的執行順序。當請求在一個階段中存在多個過濾器時,需要根據該方法傳回的值來依次執行。通過數字指定,數字越大,優先級越低。
shouldFilter():判斷該過濾器是否需要被執行。這裡我們直接傳回了true,是以該過濾器對所有請求都會生效。實際運用中我們可以利用該函數來指定過濾器的有效範圍。
run():過濾器的具體邏輯。這裡我們通過ctx.setSendZuulResponse(false)令 Zuul 過濾該請求,不對其進行路由,然後通過ctx.setResponseStatusCode(401)設定了其傳回的錯誤碼,當然我們也可以進一步優化我們的傳回,比如,通過ctx.setResponseBody(body)對傳回 body 内容進行編輯等。