玩轉SpringCloud專題(二十二)-SpringCloud之服務網關過濾器

我們瞭解了 Spring Cloud Zuul 作爲網關所具備的最基本功能:路由(Router),下面我們將關注 Spring Cloud Zuul 的另一核心功能:過濾器(Filter)

zuul的核心是一系列的filters, 其作用可以類比Servlet框架的Filter,或者AOP,本文我們就來具體介紹下自定義的zuul過濾器

1.Filter的使用場景

場景非常多:

請求鑑權:一般放在pre類型,如果發現沒有訪問權限,直接就攔截了
異常處理:一般會在error類型和post類型過濾器中結合來處理。
服務調用時長統計:pre和post結合使用。

2.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:返回一個Boolean值,判斷該過濾器是否需要執行。返回true執行,返回false不執行。
  • run:過濾器的具體業務邏輯。
  • filterType:返回字符串,代表過濾器的類型。包含以下4種:
    • pre:請求在被路由之前執行
    • routing:在路由請求時調用
    • post:在routing和errror過濾器之後調用
    • error:處理請求時發生錯誤調用
  • filterOrder:通過返回的int值來定義過濾器的執行順序,數字越小優先級越高。

網關過濾器的自定義方法有四個,過濾器的類型有四個,分別如下:
在這裏插入圖片描述

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.<SimpleClassName>.<filterType>.disable=true

比如要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter就設置

zuul:
  SendResponseFilter:
    post:
      disable: true

4.自定義Filter

4.1.創建Filter

首先自定義一個 Filter,繼承 ZuulFilter 抽象類,在 run() 方法中添加具體業務邏輯,具體如下:

package com.bruceliu.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * @BelongsProject: springcloud0310
 * @BelongsPackage: com.bruceliu.filter
 * @Author: bruceliu
 * @QQ:1241488705
 * @CreateTime: 2020-03-12 12:18
 * @Description: 自定義網關過濾器
 */
@Component
public class LogFilter extends ZuulFilter{

    private Logger logger = LoggerFactory.getLogger(LogFilter.class);

    /**
     * 過濾方法
     */
    @Override
    public Object run() {
        // 獲取Request上下文
        RequestContext rc = RequestContext.getCurrentContext();
        HttpServletRequest request = rc.getRequest();
        logger.info("LogFilter .... 請求的路徑是{},請求提交的方式是{}", request.getRequestURL().toString(),request.getMethod());
        return null;
    }

    /**
     * 是否開啓過濾:默認false
     */
    @Override
    public boolean shouldFilter() {
        // TODO Auto-generated method stub
        return true;
    }

    /**
     * 多個過濾器中的執行順序,數值越小,優先級越高
     */
    @Override
    public int filterOrder() {
        // TODO Auto-generated method stub
        return 0;
    }

    /**
     * 過濾器的類型
     */
    @Override
    public String filterType() {
        // TODO Auto-generated method stub
        return "pre";
    }

}

在上面實現的過濾器代碼中,我們通過繼承ZuulFilter抽象類並重寫了下面的四個方法來實現自定義的過濾器。這四個方法分別定義了:

filterType():過濾器的類型,它決定過濾器在請求的哪個生命週期中執行。這裏定義爲pre,代表會在請求被路由之前執行。
filterOrder():過濾器的執行順序。當請求在一個階段中存在多個過濾器時,需要根據該方法返回的值來依次執行。通過數字指定,數字越大,優先級越低。
shouldFilter():判斷該過濾器是否需要被執行。這裏我們直接返回了true,因此該過濾器對所有請求都會生效。實際運用中我們可以利用該函數來指定過濾器的有效範圍。
run():過濾器的具體邏輯。

4.2.啓動網關測試

重新啓動service-api-gateway,併發起下面的請求,對上面定義的過濾器做一個驗證:
啓動一個provider服務後,啓動我們的網關服務,然後請求訪問,查詢控制檯輸出
在這裏插入圖片描述
注意控制檯:
在這裏插入圖片描述
日誌有輸出,說明自定義的網關過濾器執行了。

5.過濾器小案例

5.1.服務鑑權

自定義過濾器:

package com.bruceliu.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;

/**
 * @BelongsProject: springcloud0310
 * @BelongsPackage: com.bruceliu.filter
 * @Author: bruceliu
 * @QQ:1241488705
 * @CreateTime: 2020-03-12 12:32
 * @Description: TODO
 */
@Component
public class MyFilter extends ZuulFilter {

    private Logger logger = LoggerFactory.getLogger(MyFilter.class);

    /**
     * 過濾器的類型,它決定過濾器在請求的哪個生命週期中執行。 這裏定義爲pre,代表會在請求被路由之前執行。
     *
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * filter執行順序,通過數字指定。 數字越大,優先級越低。
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return -1;
    }

    /**
     * 判斷該過濾器是否需要被執行。這裏我們直接返回了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();
        System.out.println("=============================================");
        System.out.println("用戶的請地址是:"+req.getRequestURI());
        System.out.println("用戶的所有請求參數是:");
        Map<String, String[]> map = req.getParameterMap();
        Set<Map.Entry<String, String[]>> entries = map.entrySet();
        for (Map.Entry<String, String[]> entry : entries) {
            System.out.println(entry.getKey()+"---"+ Arrays.toString(entry.getValue()));
        }
        System.out.println("=============================================");
        // 3) 從請求中獲取token
        String token = req.getParameter("access-token");
        // 4) 判斷
        if(token == null || "".equals(token.trim())){
            // 沒有token,登錄校驗失敗,攔截
            ctx.setSendZuulResponse(false);
            //設置信息
            ctx.setResponseBody("No Rrights!");
            // 返回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 內容進行編輯等。

重新啓動service-api-gateway,併發起下面的請求,對上面定義的過濾器做一個驗證:
訪問 http://127.0.0.1:2222/springcloud-consumer/test 返回 userToken is null
在這裏插入圖片描述
訪問 http://127.0.0.1:2222/springcloud-consumer/test?access-token=bruce

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章