Spring Cloud 系列之 Netflix Zuul 服務網關(二)

本篇文章爲系列文章,未讀第一集的同學請猛戳這裏:Spring Cloud 系列之 Netflix Zuul 服務網關(一)

本篇文章講解 Zuul 網關過濾器實現統一鑑權以及網關過濾器異常統一處理。


網關過濾器

點擊鏈接觀看:網關過濾器視頻(獲取更多請關注公衆號「哈嘍沃德先生」)

Zuul 包含了對請求的路由和過濾兩個核心功能,其中路由功能負責將外部請求轉發到具體的微服務實例上,是實現外部訪問統一入口的基礎;而過濾器功能則負責對請求的處理過程進行干預,是實現請求校驗,服務聚合等功能的基礎。然而實際上,路由功能在真正運行時,它的路由映射和請求轉發都是由幾個不同的過濾器完成的。

路由映射主要通過 pre 類型的過濾器完成,它將請求路徑與配置的路由規則進行匹配,以找到需要轉發的目標地址;而請求轉發的部分則是由 routing 類型的過濾器來完成,對 pre 類型過濾器獲得的路由地址進行轉發。所以說,過濾器可以說是 Zuul 實現 API 網關功能最核心的部件,每一個進入 Zuul 的 http 請求都會經過一系列的過濾器處理鏈得到請求響應並返回給客戶端。

關鍵名詞

  • 類型:定義路由流程中應用過濾器的階段。共 pre、routing、post、error 4 個類型。
  • 執行順序:在同類型中,定義過濾器執行的順序。比如多個 pre 類型的執行順序。
  • 條件:執行過濾器所需的條件。true 開啓,false 關閉。
  • 動作:如果符合條件,將執行的動作。具體操作。

過濾器類型

  • pre:請求被路由到源服務器之前執行的過濾器
    • 身份認證
    • 選路由
    • 請求日誌
  • routing:處理將請求發送到源服務器的過濾器
  • post:響應從源服務器返回時執行的過濾器
    • 對響應增加 HTTP 頭
    • 收集統計和度量指標
    • 將響應以流的方式發送回客戶端
  • error:上述階段中出現錯誤時執行的過濾器

入門案例

創建過濾器

Spring Cloud Netflix Zuul 中實現過濾器必須包含 4 個基本特徵:過濾器類型,執行順序,執行條件,動作(具體操作)。這些步驟都是 ZuulFilter 接口中定義的 4 個抽象方法:

package com.example.filter;

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

import javax.servlet.http.HttpServletRequest;

/**
 * 網關過濾器
 */
@Component
public class CustomFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(CustomFilter.class);

    /**
     * 過濾器類型
     *      pre
     *      routing
     *      post
     *      error
     *
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 執行順序
     *      數值越小,優先級越高
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 執行條件
     *      true 開啓
     *      false 關閉
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 動作(具體操作)
     *      具體邏輯
     *
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        // 獲取請求上下文
        RequestContext rc = RequestContext.getCurrentContext();
        HttpServletRequest request = rc.getRequest();
        logger.info("CustomFilter...method={}, url={}",
                request.getMethod(),
                request.getRequestURL().toString());
        return null;
    }

}
  • filterType:該函數需要返回一個字符串代表過濾器的類型,而這個類型就是在 http 請求過程中定義的各個階段。在 Zuul 中默認定義了 4 個不同的生命週期過程類型,具體如下:
    • pre:請求被路由之前調用
    • routing: 路由請求時被調用
    • post:routing 和 error 過濾器之後被調用
    • error:處理請求時發生錯誤時被調用
  • filterOrder:通過 int 值來定義過濾器的執行順序,數值越小優先級越高。
  • shouldFilter:返回一個 boolean 值來判斷該過濾器是否要執行。
  • run:過濾器的具體邏輯。在該函數中,我們可以實現自定義的過濾邏輯,來確定是否要攔截當前的請求,不對其進行後續路由,或是在請求路由返回結果之後,對處理結果做一些加工等。

訪問

訪問:http://localhost:9000/product-service/product/1 控制檯輸出如下:

CustomFilter...method=GET, url=http://localhost:9000/product-service/product/1

統一鑑權

接下來我們在網關過濾器中通過 token 判斷用戶是否登錄,完成一個統一鑑權案例。

創建過濾器

package com.example.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
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.io.IOException;
import java.io.PrintWriter;

/**
 * 權限驗證過濾器
 */
@Component
public class AccessFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(AccessFilter.class);

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        // 獲取請求上下文
        RequestContext rc = RequestContext.getCurrentContext();
        HttpServletRequest request = rc.getRequest();
        // 獲取表單中的 token
        String token = request.getParameter("token");
        // 業務邏輯處理
        if (null == token) {
            logger.warn("token is null...");
            // 請求結束,不在繼續向下請求。
            rc.setSendZuulResponse(false);
            // 響應狀態碼,HTTP 401 錯誤代表用戶沒有訪問權限
            rc.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            // 響應類型
            rc.getResponse().setContentType("application/json; charset=utf-8");
            PrintWriter writer = null;
            try {
                writer = rc.getResponse().getWriter();
                // 響應內容
                writer.print("{\"message\":\"" + HttpStatus.UNAUTHORIZED.getReasonPhrase() + "\"}");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (null != writer)
                    writer.close();
            }
        } else {
            // 使用 token 進行身份驗證
            logger.info("token is OK!");
        }
        return null;
    }

}

訪問

訪問:http://localhost:9000/product-service/product/1 結果如下:

訪問:http://localhost:9000/product-service/product/1?token=abc123 結果如下:

Zuul 請求的生命週期

  1. HTTP 發送請求到 Zuul 網關
  2. Zuul 網關首先經過 pre filter
  3. 驗證通過後進入 routing filter,接着將請求轉發給遠程服務,遠程服務執行完返回結果,如果出錯,則執行 error filter
  4. 繼續往下執行 post filter
  5. 最後返回響應給 HTTP 客戶端

網關過濾器異常統一處理

點擊鏈接觀看:網關過濾器異常統一處理視頻(獲取更多請關注公衆號「哈嘍沃德先生」)

創建過濾器

package com.example.filter;

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

import java.io.IOException;
import java.io.PrintWriter;

/**
 * 異常過濾器
 */
@Component
public class ErrorFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);

    @Override
    public String filterType() {
        return "error";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext rc = RequestContext.getCurrentContext();
        Throwable throwable = rc.getThrowable();
        logger.error("ErrorFilter..." + throwable.getCause().getMessage(), throwable);
        // 響應狀態碼,HTTP 500 服務器錯誤
        rc.setResponseStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
        // 響應類型
        rc.getResponse().setContentType("application/json; charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = rc.getResponse().getWriter();
            // 響應內容
            writer.print("{\"message\":\"" + HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase() + "\"}");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != writer)
                writer.close();
        }
        return null;
    }

}

模擬異常

在 pre 過濾器中添加模擬異常代碼。

// 模擬異常
Integer.parseInt("zuul");

配置文件

禁用 Zuul 默認的異常處理 filter:SendErrorFilter

zuul:
  # 禁用 Zuul 默認的異常處理 filter
  SendErrorFilter:
    error:
      disable: true

訪問

訪問:http://localhost:9000/product-service/product/1 結果如下:

下一篇我們講解 Zuul 和 Hystrix 的無縫結合,實現網關監控、網關熔斷、網關限流、網關調優,記得關注噢~

本文采用 知識共享「署名-非商業性使用-禁止演繹 4.0 國際」許可協議

大家可以通過 分類 查看更多關於 Spring Cloud 的文章。


🤗 您的點贊轉發是對我最大的支持。

📢 掃碼關注 哈嘍沃德先生「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕鬆噢 ~

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