![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SY0MjZhZTYmBjZmR2NmJTOxMmMiN2MjhDN0gDO4cDOy8CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
本篇文章為系列文章,未讀第一集的同學請猛戳這裡:
哈喽沃德先生:Spring Cloud 系列之 Netflix Zuul 服務網關(一)zhuanlan.zhihu.com
本篇文章講解 Zuul 網關過濾器實作統一鑒權以及網關過濾器異常統一處理。
網關過濾器
https://www.zhihu.com/video/1234207041360777216
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;
}
}
-
:該函數需要傳回一個字元串代表過濾器的類型,而這個類型就是在 http 請求過程中定義的各個階段。在 Zuul 中預設定義了 4 個不同的生命周期過程類型,具體如下:filterType
- pre:請求被路由之前調用
- routing: 路由請求時被調用
- post: routing 和 error 過濾器之後被調用
- error:處理請求時發生錯誤時被調用
-
:通過 int 值來定義過濾器的執行順序,數值越小優先級越高。filterOrder
-
:傳回一個 boolean 值來判斷該過濾器是否要執行。shouldFilter
-
:過濾器的具體邏輯。在該函數中,我們可以實作自定義的過濾邏輯,來确定是否要攔截目前的請求,不對其進行後續路由,或是在請求路由傳回結果之後,對處理結果做一些加工等。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 請求的生命周期
- HTTP 發送請求到 Zuul 網關
- Zuul 網關首先經過 pre filter
- 驗證通過後進入 routing filter,接着将請求轉發給遠端服務,遠端服務執行完傳回結果,如果出錯,則執行 error filter
- 繼續往下執行 post filter
- 最後傳回響應給 HTTP 用戶端
網關過濾器異常統一處理
https://www.zhihu.com/video/1234207208662945792
建立過濾器
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 的無縫結合,實作網關監控、網關熔斷、網關限流、網關調優,記得關注噢~
大家可以通過
分類
檢視更多關于
Spring Cloud
的文章。
本文采用
知識共享「署名-非商業性使用-禁止演繹 4.0 國際」許可協定
。
您的
點贊
和
轉發
是對我最大的支援。
掃碼關注
哈喽沃德先生
「文檔 + 視訊」每篇文章都配有專門視訊講解,學習更輕松噢 ~