1. 簡介
在上一篇文章中,我們已經介紹了 Spring Cloud Gateway 是什麼以及如何使用內置的謂詞(predicates)來實現基本的路由規則。然而,在某些情況下,這些內置的謂詞可能不足以滿足需求。例如,我們的路由邏輯可能需要對數據庫進行查詢。
對於這些情況,Spring Cloud Gateway 允許我們定義自定義的謂詞。 只要定義好這些謂詞,我們就可以像使用任何其他謂詞一樣使用它們,這意味着我們可以使用流式 API(fluent API)和/或 DSL 來定義路由。
2. 謂詞的結構
簡而言之,Spring Cloud Gateway 中的 謂詞 (Predicate) 是一個對象,用於測試給定的請求是否滿足特定條件。對於每個路由,我們可以定義一個或多個謂詞,如果這些謂詞得到滿足,則會接受配置的後端請求,並在應用任何過濾器之後。
在編寫我們的謂詞之前,讓我們先查看現有謂詞或更準確地説,現有 PredicateFactory 的源代碼。正如名稱已經暗示的,Spring Cloud Gateway 使用流行的 工廠方法模式 作為機制,以支持以可擴展的方式創建 Predicate 實例。
我們可以選擇任何一個內置的謂詞工廠,這些工廠可在 org.springframework.cloud.gateway.handler.predicate 包中找到,該包位於 spring-cloud-gateway-core 模塊中。由於它們的名稱都以 RoutePredicateFactory 結尾,因此我們可以輕鬆地發現它們。 HeaderRouterPredicateFactory 是一個很好的例子:
public class HeaderRoutePredicateFactory extends
AbstractRoutePredicateFactory<HeaderRoutePredicateFactory.Config> {
// ... setup code omitted
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
// ... predicate logic omitted
}
};
}
@Validated
public static class Config {
public Config(boolean isGolden, String customerIdCookie ) {
// ... constructor details omitted
}
// ...getters/setters omitted
}
}我們可以觀察到以下幾個關鍵點:
- 它擴展了 AbstractRoutePredicateFactory<T>,而後者又實現了 gateway 使用的 RoutePredicateFactory 接口
- apply 方法返回實際 Predicate 的實例——在當前情況下是 GatewayPredicate
- 該 Predicate 定義了一個內部的 Config 類,用於存儲測試邏輯所使用的靜態配置參數
如果我們查看其他可用的 PredicateFactory,我們會發現基本模式基本上是相同的:
- 定義一個 Config 類來存儲配置參數
- 擴展 AbstractRoutePredicateFactory,並將配置類用作其模板參數
- 覆蓋 apply 方法,並返回一個實現所需測試邏輯的 Predicate
3. 實現自定義謂詞工廠
對於我們的實現,我們假設以下場景:對於給定的API,我們必須在兩個可能的後端之間做出選擇。 “黃金”客户,即我們最重視的客户,應路由到具有更多內存、更多CPU和快速磁盤的強大服務器。 非黃金客户則路由到較弱的服務器,從而導致響應時間更慢。
為了確定請求是否來自黃金客户,我們需要調用一個服務,該服務接受與請求相關的 customerId 並返回其狀態。 至於 customerId,在我們的簡單場景中,我們假設它存儲在cookie中。
有了這些信息,我們現在可以編寫自定義的謂詞。 我們將保持現有的命名約定,併為我們的類命名為 GoldenCustomerRoutePredicateFactory。
public class GoldenCustomerRoutePredicateFactory extends
AbstractRoutePredicateFactory<GoldenCustomerRoutePredicateFactory.Config> {
private final GoldenCustomerService goldenCustomerService;
// ... constructor omitted
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return (ServerWebExchange t) -> {
List<HttpCookie> cookies = t.getRequest()
.getCookies()
.get(config.getCustomerIdCookie());
boolean isGolden;
if ( cookies == null || cookies.isEmpty()) {
isGolden = false;
} else {
String customerId = cookies.get(0).getValue();
isGolden = goldenCustomerService.isGoldenCustomer(customerId);
}
return config.isGolden() ? isGolden : !isGolden;
};
}
@Validated
public static class Config {
boolean isGolden = true;
@NotEmpty
String customerIdCookie = "customerId";
// ...constructors and mutators omitted
}
}如我們所見,實現方式相當簡單。我們的 apply 方法返回一個 lambda,它使用傳遞給它的 ServerWebExchange 來實現所需的邏輯。首先,它檢查 customerId cookie 的是否存在。如果找不到它,則這是一個普通客户。否則,它使用 cookie 值來調用 isGoldenCustomer 服務方法。
接下來,我們結合客户端類型和配置的 isGolden 參數以確定返回值。 這允許我們使用相同的謂詞來創建之前描述的相同路由,只需更改 isGolden 參數的值即可。
4. 註冊自定義謂語工廠
在編寫自定義謂語工廠後,我們需要讓 Spring Cloud Gateway 知道如何使用它。由於我們使用了 Spring,這通常是通過聲明一個類型為 GoldenCustomerRoutePredicateFactory 的 Bean 來實現的。
由於我們的類型通過其基類 RoutePredicateFactory 實現 RoutePredicateFactory,因此在上下文初始化時會被 Spring 自動發現並提供給 Spring Cloud Gateway。
這裏,我們將使用一個 @Configuration 類來創建我們的 Bean:
@Configuration
public class CustomPredicatesConfig {
@Bean
public GoldenCustomerRoutePredicateFactory goldenCustomer(
GoldenCustomerService goldenCustomerService) {
return new GoldenCustomerRoutePredicateFactory(goldenCustomerService);
}
}我們假設這裏有一個合適的 GoldenCustomerService 實現在 Spring 的上下文中可用。 在我們的例子中,我們有一個簡單的佔位符實現,它將 customerId 值與一個固定的值進行比較——這不現實,但對於演示目的很有用。
5. 使用自定義謂詞
現在我們已經實現了並使“黃金客户”謂詞可用,並且它可供 Spring Cloud Gateway 使用,我們可以開始使用它來定義路由。首先,我們將使用流暢 API 定義一個路由,然後以聲明式方式使用 YAML。
5.1. 使用 Fluent API 定義路由
Fluent API 是一種流行的設計選擇,當我們必須通過編程方式創建複雜對象時。 在我們的例子中,我們使用一個@Bean來定義路由,該Bean創建了一個RouteLocator對象,它使用一個RouteLocatorBuilder和我們的自定義謂詞工廠:
@Bean
public RouteLocator routes(RouteLocatorBuilder builder, GoldenCustomerRoutePredicateFactory gf ) {
return builder.routes()
.route("golden_route", r -> r.path("/api/**")
.uri("https://fastserver")
.predicate(gf.apply(new Config(true, "customerId"))))
.route("common_route", r -> r.path("/api/**")
.uri("https://slowserver")
.predicate(gf.apply(new Config(false, "customerId"))))
.build();
}請注意我們如何在每個路由中使用兩種不同的 Config 配置。 在第一個案例中,第一個參數設置為 true,因此當收到黃金客户的請求時,謂詞也會評估結果為 true。 至於第二個路由,我們在構造函數中傳遞 false,以便我們的謂詞將返回 true 對於非黃金客户。
5.2. 定義路由(YAML)
我們可以使用屬性或 YAML 文件以聲明式的方式實現與之前相同的結果。這裏我們將使用 YAML,因為它更易於閲讀:
spring:
cloud:
gateway:
routes:
- id: golden_route
uri: https://fastserver
predicates:
- Path=/api/**
- GoldenCustomer=true
- id: common_route
uri: https://slowserver
predicates:
- Path=/api/**
- name: GoldenCustomer
args:
golden: false
customerIdCookie: customerId
我們已經定義了與之前相同的路由,並使用了兩種可用的選項來定義謂詞。第一個選項,golden_route,採用緊湊表示形式,其形式為Predicate=[param[,param]+]。這裏面的Predicate是謂詞的名稱,它會自動從工廠類名中派生,並移除RoutePredicateFactory後綴。在“=”符號之後,我們有用於填充關聯的Config實例的參數。
這種緊湊的語法在我們的謂詞只需要簡單值時非常有效,但並非總是如此。對於這些場景,我們可以使用長格式,如第二條路由所示。在這種情況下,我們提供一個具有兩個屬性的對象:name和args。name包含謂詞的名稱,args用於填充Config實例。由於這次args是一個對象,因此我們的配置可以根據需要變得儘可能複雜。
6. 測試
現在,讓我們使用 curl 檢查一切是否按預期工作,以測試我們的網關。對於這些測試,我們已經按照之前所示的方式設置了我們的路由,但我們將公開可用的 httpbin.org 服務作為我們的測試後端。這是一個非常實用的服務,我們可以用來快速檢查我們的規則是否按預期工作,它既可以在線使用,也可以作為 Docker 鏡像本地使用。
我們的測試配置還包括標準的 <em>AddRequestHeader</em> 過濾器。我們使用它為請求添加一個自定義 <em>Goldencustomer</em> 標頭,其值與謂詞結果相對應。我們還添加了 <em>StripPrefix</em> 過濾器,因為我們希望在調用後端之前從請求 URI 中刪除 /<em>api</em>。
首先,讓我們測試“常用客户端”場景。我們的網關已啓動並運行,我們使用 curl 調用 httpbin 的 <em>headers</em> API,該 API 將簡單地回顯所有收到的標頭:
$ curl http://localhost:8080/api/headers
{
"headers": {
"Accept": "*/*",
"Forwarded": "proto=http;host=\"localhost:8080\";for=\"127.0.0.1:51547\"",
"Goldencustomer": "false",
"Host": "httpbin.org",
"User-Agent": "curl/7.55.1",
"X-Forwarded-Host": "localhost:8080",
"X-Forwarded-Prefix": "/api"
}
}正如預期的那樣,我們看到 Goldencustomer 標頭被髮送時使用了 false 值。現在我們嘗試使用一個“Golden”客户:
$ curl -b customerId=baeldung http://localhost:8080/api/headers
{
"headers": {
"Accept": "*/*",
"Cookie": "customerId=baeldung",
"Forwarded": "proto=http;host=\"localhost:8080\";for=\"127.0.0.1:51651\"",
"Goldencustomer": "true",
"Host": "httpbin.org",
"User-Agent": "curl/7.55.1",
"X-Forwarded-Host": "localhost:8080",
"X-Forwarded-Prefix": "/api"
}
}本次,Goldencustomer 是true的,因為我們已向您發送了一個customerId cookie,其值被我們的測試服務識別為黃金客户的有效值。
7. 結論
在本文中,我們介紹瞭如何將自定義謂語工廠添加到 Spring Cloud Gateway 並使用它們定義使用任意邏輯的路由。