什麼是閘道器
在微服務架構裡,服務的粒度被進一步細分,各個業務服務可以被獨立的設計、開發、測試、部署和管理。這時,各個獨立部署單元可以用不同的開發測試團隊維護,可以使用不同的程式語言和技術平臺進行設計,這就要求必須使用一種語言和平臺無關的服務協議作為各個單元間的通訊方式。
換句話說就是閘道器為所有的請求提供了統一的入口,方便我們對服務請求和響應做統一管理。
為什麼要用閘道器
API 閘道器是一個處於應用程式或服務(提供 REST API 介面服務)之前的系統,用來管理授權、訪問控制和流量限制等,這樣 REST API 介面服務就被 API 閘道器保護起來,對所有的呼叫者透明。
什麼是gateway
Spring Cloud Gateway是Spring官方基於Spring 5.0,Spring Boot 2.0和Project Reactor等技術開發的閘道器,Spring Cloud Gateway旨在為微服務架構提供一種簡單而有效的統一的API路由管理方式。Spring Cloud Gateway作為Spring Cloud生態系中的閘道器,目標是替代ZUUL,其不僅提供統一的路由方式,並且基於Filter鏈的方式提供了閘道器基本的功能,例如:安全,監控/埋點,和限流等。
gateway工作原理
客戶端向Spring Cloud閘道器發出請求。如果閘道器處理程式對映確定請求與路由匹配,則將其傳送到閘道器Web處理程式。該處理程式執行通過特定於請求的過濾器鏈傳送請求。過濾器由虛線分隔的原因是,過濾器可以在傳送代理請求之前或之後執行邏輯。執行所有“前置”過濾器邏輯,然後發出代理請求。發出代理請求後,將執行“後”過濾器邏輯。
路由規則
路由和過濾器是gateway中非常重要的兩個概念,gateway本身提供了非常豐富的路由規則和多種過濾器來適配我們的需求。gateway提供了11種路由規則,分別是:
- 後置路由謂詞工廠
該謂詞匹配在當前日期時間之後發生的請求。引數名為 After
- 前置路由謂詞工廠
該謂詞匹配當前日期時間之前發生的請求。引數名為 Before
- 時間段路由謂詞工廠
該謂詞匹配在datetime1之後和datetime2之前發生的請求。引數名為 Between
- cookie路由謂詞工廠
該謂詞匹配具有給定名稱的cookie,並且值匹配正規表示式。引數名為 Cookie
- 標頭路由謂詞工廠
該謂詞與具有給定名稱的標頭匹配,並且值與正規表示式匹配。引數名為 Header
- 主機路由謂詞工廠
該謂詞是指由路由進行匹配,匹配多個路由時用,隔開。引數名為 Host
- 方法路由謂詞工廠
該引數是一個或多個要匹配的HTTP方法。引數名為 Method
- 路徑路由謂詞工廠
該謂詞是指在請求路徑上加一個字首,以此來匹配。引數名為 Path
- 查詢路由謂詞工廠
- RemoteAddr路由謂詞工廠
- 重量路線謂詞工廠
其中,我們比較常用的就是路徑路由謂詞工廠,配合StripPrefix GatewayFilter工廠,實現我們的路由匹配轉發。
路徑路由謂詞工廠配置如下:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 開啟從註冊中心動態建立路由的功能,利用微服務名稱進行路由
routes:
# 路由id,建議配合服務名
- id: demo_route
#匹配路由名
uri: lb://demo-provider
predicates:
# 斷言,路徑相匹配的進行路由
- Path=/demo/**
配置的含義就是,如果請求路徑中是/demo/**,則轉發到demo-provider服務。
閘道器過濾器
在spring cloud gateway 2.2.2.RELEASE版本中,已經預設實現了30種過濾器。
序號 | 過濾器工廠 | 作用 | 引數 |
---|---|---|---|
1 | AddRequestHeader | 為原始請求新增Header | Header的名稱及值 |
2 | AddRequestParameter | 為原始請求新增請求引數 | 引數名稱及值 |
3 | AddResponseHeader | 為原始響應新增Header | Header的名稱及值 |
4 | DedupeResponseHeader | 剔除響應頭中重複的值 | 需要去重的Header名稱及去重策略 |
5 | Hystrix | 為路由引入Hystrix的斷路器保護 | HystrixCommand的名稱 |
6 | CircuitBreaker | 為路由引入Resilience4J斷路器保護 | CircuitBreaker的名稱 |
7 | FallbackHeaders | 為fallbackUri的請求頭中新增具體的異常資訊 | Header的名稱 |
MapRequestHeader | 更新原始請求中的Header | Header的值 | |
9 | PrefixPath | 為原始請求頭新增字首 | 字首路徑 |
10 | PreserveHostHeader | 為請求新增preserverHostHeader=true的屬性,路由過濾器會檢查該屬性以決定是否要傳送原始的host | 無 |
11 | RequestRateLimiter | 用於對請求限流,限流演算法為令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus |
12 | RedirectTo | 將原始請求重定向到指定的url | http狀態碼及重定向的url |
13 | RemoveHopByHopHeadersFilter | 為原始請求刪除IETF組織規定的一系列Header | 預設就會啟用,可以通過配置指定僅刪除哪些Header |
14 | RemoveRequestHeader | 為原始請求刪除某個Header | Header名稱 |
15 | RemoveResponseHeader | 為原始響應刪除某個Header | Header名稱 |
16 | RewritePath | 重寫原始的請求路徑 | 原始路徑正規表示式以及重寫後路徑的正規表示式 |
RewriteLocationResponseHeader | 重寫響應頭的Location 的值 |
||
18 | RewriteResponseHeader | 重寫原始響應中的某個Header | Header名稱,值的正規表示式,重寫後的值 |
19 | SaveSession | 在轉發請求之前,強制執行WebSession::save 操作 |
無 |
20 | SecureHeaders | 為原始響應新增一系列起安全作用的響應頭 | 無,支援修改這些安全響應頭的值 |
21 | SetPath | 修改原始的請求路徑 | 修改後的值 |
22 | SetRequestHeader | 修改原始請求中的某個Header的值 | Header名稱,修改後的值 |
23 | SetResponseHeader | 修改原始響應中某個Header的值 | Header名稱,修改後的值 |
24 | SetStatus | 修改原始響應的狀態碼 | HTTP 狀態碼,可以是數字,也可以是字串 |
25 | StripPrefix | 用於截斷原始請求的路徑 | 使用數字表示要截斷的路徑的數量 |
26 | Retry | 針對不同的響應進行重試 | retries、statuses、methods、series |
27 | RequestSize | 設定允許接收最大請求包的大小。如果請求包大小超過設定的值,則返回 413 Payload Too Large 設定允許接收最大請求包的大小。如果請求包大小超過設定的值,則返回 413 Payload Too Large
|
請求包大小,單位為位元組,預設值為5M |
28 | ModifyRequestBody | 在轉發請求之前修改原始請求體內容 | 修改後的請求體內容 |
29 | ModifyResponseBody | 修改原始響應體的內容 | 修改後的響應體內容 |
30 | Default | 為所有路由新增過濾器 | 過濾器工廠名稱及值 |
這裡比較常用的如第25種,配置如下:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 開啟從註冊中心動態建立路由的功能,利用微服務名稱進行路由
routes:
# 路由id,建議配合服務名
- id: demo_route
#匹配路由名
uri: lb://demo-provider
predicates:
# 斷言,路徑相匹配的進行路由
- Path=/demo/**
filters:
- StripPrefix=1
一般情況下我們配合path路由使用,這裡的意思是假如,我們的demo-provider服務種有一個/test的介面,實際上我們的請求路徑經過閘道器時應該時/demo/test,這樣就能把這個路由分發到demo-provider服務中,但是分發過去的路由是/demo/test,和我們實際的/test介面不一樣。這時候我們用StripPrefix=1,來擷取掉一級路由,這樣轉發過去的路由就是/test了。
自定義閘道器過濾器
除了上面提供的30種過濾器外,我們還可以實現自定義的過濾器。
1. 實現GatewayFilter介面和Ordered介面
gatewayFilter介面是為了實現請求過濾,ordered介面是為了給過濾器設定優先順序,值越大級別越低。
想要實現一個自定義的過濾器,無非就是兩個步驟:1.實現過濾器,2.將過濾器新增到具體路由上。
public class TokenGatewayFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("這裡處理自身邏輯");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
@Configuration
class RouteConfiguration{
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder){
return builder.routes().route( r->
r.path("/demo/**")
.uri("lb://demo-provider ")
.filter(new TokenGatewayFilter())
.id("demo_route "))
.build();
}
}
2.繼承AbstractGatewayFilterFactory類
@Component
public class TokenCheckGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenCheckGatewayFilterFactory.Config> {
public TokenCheckGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("enabled");
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
system.out.println("這裡處理自身邏輯")
return chain.filter(exchange);
};
}
public static class Config {
// 控制是否開啟認證
private boolean enabled = true;
public Config() {}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
}
這裡我們可以直接在application.yml中為需要過濾的路由新增這個過濾器。
spring:
cloud:
gateway:
routes:
- id: demo_route # 路由id,建議配合服務名
uri: lb://demo-provider #匹配路由名
predicates:
- Path=/demo/** # 斷言,路徑相匹配的進行路由
filters:
- TokenCheck=true
需要注意的是,這個地方自定義的過濾器名稱必須是XXGatewayFilterFactory,並且配置檔案中配置過濾器時名字必須時這個XX。
當然,我們也可以為每個路由都新增這個過濾器,可以直接這樣寫配置,而不用在每個路由上都去寫。
spring:
cloud:
gateway:
default-filters:
- TokenCheck=true
3.實現GlobalFilter和ordered
這個GlobalFilter從名字中就可以看出,是一個全域性過濾器,也就是說實現這個介面後,所有的請求都會被過濾,我們就不需要在去找往某個路由中加過濾器了。
@Component
public class TokenGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("這裡處理自身邏輯");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
以上就是實現自定義閘道器過濾器的三種方式了。實際開發中根據需求來實現合適的過濾器就可以了。