過濾器類型
Zuul 中的過濾器跟我們之前使用的 javax.servlet.Filter 不一樣,javax.servlet.Filter 只有一種類型,可以通過配置 urlPatterns 來攔截對應的請求。
而 Zuul 中的過濾器總共有 4 種類型,且每種類型都有對應的使用場景。
1)pre
可以在請求被路由之前調用。適用於身份認證的場景,認證通過後再繼續執行下面的流程。
2)route
在路由請求時被調用。適用於灰度發佈場景,在將要路由的時候可以做一些自定義的邏輯。
3)post
在 route 和 error 過濾器之後被調用。這種過濾器將請求路由到達具體的服務之後執行。適用於需要添加響應頭,記錄響應日誌等應用場景。
4)error
處理請求時發生錯誤時被調用。在執行過程中發送錯誤時會進入 error 過濾器,可以用來統一記錄錯誤信息。
請求生命週期
可以通過圖 1 看出整個過濾器的執行生命週期,
圖 1 過濾器生命週期
通過上面的圖可以清楚地知道整個執行的順序,請求發過來首先到 pre 過濾器,再到 routing 過濾器,最後到 post 過濾器,任何一個過濾器有異常都會進入 error 過濾器。
通過 com.netflix.zuul.http.ZuulServlet 也可以看出完整執行順序,ZuulServlet 類似 Spring-Mvc 的 DispatcherServlet,所有的 Request 都要經過 ZuulServlet 的處理。
ZuulServlet 源碼如下所示:
- @Override
- public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse)
- throws ServletException, IOException {
- try {
- init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
- RequestContext context = RequestContext.getCurrentContext();
- context.setZuulEngineRan();
- try {
- preRoute();
- } catch (ZuulException e) {
- error(e);
- postRoute();
- return;
- }
- try {
- route();
- } catch (ZuulException e) {
- error(e);
- postRoute();
- return;
- }
- try {
- postRoute();
- } catch (ZuulException e) {
- error(e);
- return;
- }
- } catch (Throwable e) {
- error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
- } finally {
- RequestContext.getCurrentContext().unset();
- }
- }
使用過濾器
我們創建一個 pre 過濾器,來實現 IP 黑名單的過濾操作,代碼如下所示。
- public class IpFilter extends ZuulFilter {
- // IP黑名單列表
- private List<String> blackIpList = Arrays.asList("127.0.0.1");
- public IpFilter() {
- super();
- }
- @Override
- public boolean shouldFilter() {
- return true
- }
- @Override
- public String filterType() {
- return "pre";
- }
- @Override
- public int filterOrder() {
- return 1;
- }
- @Override
- public Object run() {
- RequestContext ctx = RequestContext.getCurrentContext();
- String ip = IpUtils.getIpAddr(ctx.getRequest());
- // 在黑名單中禁用
- if (StringUtils.isNotBlank(ip) && blackIpList.contains(ip)) {
- ctx.setSendZuulResponse(false);
- ResponseData data = ResponseData.fail("非法請求 ", ResponseCode.NO_AUTH_CODE.getCode());
- ctx.setResponseBody(JsonUtils.toJson(data));
- ctx.getResponse().setContentType("application/json; charset=utf-8");
- return null;
- }
- return null;
- }
- }
由代碼可知,自定義過濾器需要繼承 ZuulFilter,並且需要實現下面幾個方法:
1)shouldFilter
是否執行該過濾器,true 爲執行,false 爲不執行,這個也可以利用配置中心來實現,達到動態的開啓和關閉過濾器。
2)filterType
過濾器類型,可選值有 pre、route、post、error。
3)filterOrder
過濾器的執行順序,數值越小,優先級越高。
4)run
執行自己的業務邏輯,本段代碼中是通過判斷請求的 IP 是否在黑名單中,決定是否進行攔截。blackIpList 字段是 IP 的黑名單,判斷條件成立之後,通過設置 ctx.setSendZuulResponse(false),告訴 Zuul 不需要將當前請求轉發到後端的服務了。通過 setResponseBody 返回數據給客戶端。