@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes().build();
}
構建閘道器
本指南將引導您瞭解如何使用 Spring Cloud Gateway
您將構建什麼
您將使用 Spring Cloud Gateway 構建一個閘道器。
您需要什麼
-
大約 15 分鐘
-
一個喜歡的文字編輯器或 IDE
如何完成本指南
像大多數 Spring 入門指南一樣,您可以從頭開始完成每個步驟,或者繞過您已經熟悉的基本設定步驟。無論哪種方式,您最終都會得到可工作的程式碼。
要從頭開始,請繼續閱讀 從 Spring Initializr 開始。
要跳過基礎知識,請執行以下操作
-
下載並解壓本指南的原始碼倉庫,或者使用 Git 克隆它:
git clone https://github.com/spring-guides/gs-gateway.git
-
進入目錄
gs-gateway/initial
-
直接跳到 建立簡單路由。
完成後,您可以對照 gs-gateway/complete
中的程式碼檢查您的結果。
從 Spring Initializr 開始
您可以使用這個預配置的專案,然後點選 Generate 下載 ZIP 檔案。該專案已配置好以適用於本教程中的示例。
手動初始化專案
-
訪問 https://start.spring.io。該服務會自動拉取應用程式所需的所有依賴項,併為您完成大部分設定。
-
選擇 Gradle 或 Maven,以及您想使用的語言。本指南假設您選擇了 Java。
-
點選 Dependencies(依賴),然後選擇 Reactive Gateway、Resilience4J 和 Contract Stub Runner。
-
點選 Generate(生成)。
-
下載生成的 ZIP 檔案,這是一個根據您的選擇配置好的 Web 應用程式壓縮包。
如果您的 IDE 集成了 Spring Initializr,您可以在 IDE 中完成此過程。 |
您也可以從 Github Fork 該專案,並在您的 IDE 或其他編輯器中開啟它。 |
建立簡單路由
Spring Cloud Gateway 使用路由來處理傳送到下游服務的請求。在本指南中,我們將所有請求路由到 HTTPBin。路由可以透過多種方式配置,但本指南中,我們使用 Gateway 提供的 Java API。
首先,在 Application.java
中建立一個 RouteLocator
型別的新的 Bean
。
src/main/java/gateway/Application.java
myRoutes
方法接收一個 RouteLocatorBuilder
,該構建器可用於建立路由。除了建立路由外,RouteLocatorBuilder
還允許您向路由新增謂詞和過濾器,以便您可以根據特定條件處理路由,並根據需要修改請求/響應。
現在我們可以建立一個路由,當向 Gateway 傳送對 /get
的請求時,該路由會將請求轉發到 https://httpbin.org/get
。在此路由的配置中,我們添加了一個過濾器,在轉發請求之前向請求新增一個值為 World
的 Hello
請求頭
src/main/java/gateway/Application.java
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p
.path("/get")
.filters(f -> f.addRequestHeader("Hello", "World"))
.uri("http://httpbin.org:80"))
.build();
}
要測試我們的簡單 Gateway,可以在埠 8080
上執行 Application.java
。應用程式執行後,向 https://:8080/get
傳送請求。您可以在終端中使用以下 cURL 命令來完成此操作
$ curl https://:8080/get
您應該會收到一個類似於以下輸出的響應
{
"args": {},
"headers": {
"Accept": "*/*",
"Connection": "close",
"Forwarded": "proto=http;host=\"localhost:8080\";for=\"0:0:0:0:0:0:0:1:56207\"",
"Hello": "World",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0",
"X-Forwarded-Host": "localhost:8080"
},
"origin": "0:0:0:0:0:0:0:1, 73.68.251.70",
"url": "https://:8080/get"
}
請注意,HTTPBin 顯示請求中傳送了值為 World
的 Hello
頭。
使用 Spring Cloud CircuitBreaker
現在我們可以做一些更有趣的事情。由於 Gateway 後面的服務可能會出現不良行為並影響我們的客戶端,我們可能希望將建立的路由包裝在斷路器中。您可以透過在 Spring Cloud Gateway 中使用 Resilience4J Spring Cloud CircuitBreaker 實現來做到這一點。這是透過一個簡單的過濾器實現的,您可以將其新增到您的請求中。我們可以建立另一個路由來演示這一點。
在下一個示例中,我們使用 HTTPBin 的 delay API,該 API 會等待一定秒數後再發送響應。由於此 API 可能會花費很長時間傳送響應,我們可以將使用此 API 的路由包裝在斷路器中。以下列表向我們的 RouteLocator
物件添加了一個新路由
src/main/java/gateway/Application.java
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p
.path("/get")
.filters(f -> f.addRequestHeader("Hello", "World"))
.uri("http://httpbin.org:80"))
.route(p -> p
.host("*.circuitbreaker.com")
.filters(f -> f.circuitBreaker(config -> config.setName("mycmd")))
.uri("http://httpbin.org:80")).
build();
}
這個新的路由配置與我們之前建立的配置之間有一些區別。首先,我們使用 host 謂詞而不是 path 謂詞。這意味著,只要主機是 circuitbreaker.com
,我們就將請求路由到 HTTPBin 並將該請求包裝在斷路器中。我們透過向路由應用過濾器來實現這一點。我們可以使用配置物件來配置斷路器過濾器。在此示例中,我們將斷路器命名為 mycmd
。
現在我們可以測試這個新路由了。為此,我們需要啟動應用程式,但這一次,我們將向 /delay/3
傳送請求。同樣重要的是,我們需要包含一個主機為 circuitbreaker.com
的 Host
頭。否則,請求將不會被路由。我們可以使用以下 cURL 命令
$ curl --dump-header - --header 'Host: www.circuitbreaker.com' https://:8080/delay/3
我們使用 --dump-header 來檢視響應頭。--dump-header 後面的 - 告訴 cURL 將頭資訊列印到標準輸出。 |
使用此命令後,您應該在終端中看到以下內容
HTTP/1.1 504 Gateway Timeout
content-length: 0
如您所見,斷路器在等待 HTTPBin 的響應時超時了。當斷路器超時時,我們可以選擇提供一個回退(fallback)以便客戶端不會收到 504
錯誤,而是收到更有意義的內容。在生產環境中,您可能會返回快取中的一些資料,例如,但在我們的簡單示例中,我們返回一個響應體為 fallback
的響應。
為此,我們可以修改我們的斷路器過濾器,使其在超時時提供一個要呼叫的 URL
src/main/java/gateway/Application.java
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p
.path("/get")
.filters(f -> f.addRequestHeader("Hello", "World"))
.uri("http://httpbin.org:80"))
.route(p -> p
.host("*.circuitbreaker.com")
.filters(f -> f.circuitBreaker(config -> config
.setName("mycmd")
.setFallbackUri("forward:/fallback")))
.uri("http://httpbin.org:80"))
.build();
}
現在,當被斷路器包裝的路由超時時,它將呼叫 Gateway 應用程式中的 /fallback
。現在我們可以將 /fallback
端點新增到我們的應用程式中。
在 Application.java
中,我們新增類級別的 @RestController
註解,然後向該類新增以下 @RequestMapping
src/main/java/gateway/Application.java
@RequestMapping("/fallback")
public Mono<String> fallback() {
return Mono.just("fallback");
}
要測試這個新的回退功能,請重新啟動應用程式,然後再次發出以下 cURL 命令
$ curl --dump-header - --header 'Host: www.circuitbreaker.com' https://:8080/delay/3
有了回退功能,我們現在可以看到從 Gateway 收到了一個 200
響應,響應體為 fallback
。
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/plain;charset=UTF-8
fallback
編寫測試
作為一個優秀的開發者,我們應該編寫一些測試來確保我們的 Gateway 按照預期工作。在大多數情況下,我們希望限制對外部資源的依賴,尤其是在單元測試中,因此不應依賴 HTTPBin。解決這個問題的一個方法是使我們路由中的 URI 可配置,這樣如果需要,我們可以更改 URI。
為此,在 Application.java
中,我們可以建立一個名為 UriConfiguration
的新類
@ConfigurationProperties
class UriConfiguration {
private String httpbin = "http://httpbin.org:80";
public String getHttpbin() {
return httpbin;
}
public void setHttpbin(String httpbin) {
this.httpbin = httpbin;
}
}
要啟用 ConfigurationProperties
,我們還需要向 Application.java
新增一個類級別的註解。
@EnableConfigurationProperties(UriConfiguration.class)
有了新的配置類,我們就可以在 myRoutes
方法中使用它了
src/main/java/gateway/Application.java
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder, UriConfiguration uriConfiguration) {
String httpUri = uriConfiguration.getHttpbin();
return builder.routes()
.route(p -> p
.path("/get")
.filters(f -> f.addRequestHeader("Hello", "World"))
.uri(httpUri))
.route(p -> p
.host("*.circuitbreaker.com")
.filters(f -> f
.circuitBreaker(config -> config
.setName("mycmd")
.setFallbackUri("forward:/fallback")))
.uri(httpUri))
.build();
}
我們不再硬編碼 HTTPBin 的 URL,而是從新的配置類中獲取 URL。
以下列表顯示了 Application.java
的完整內容
src/main/java/gateway/Application.java
@SpringBootApplication
@EnableConfigurationProperties(UriConfiguration.class)
@RestController
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder, UriConfiguration uriConfiguration) {
String httpUri = uriConfiguration.getHttpbin();
return builder.routes()
.route(p -> p
.path("/get")
.filters(f -> f.addRequestHeader("Hello", "World"))
.uri(httpUri))
.route(p -> p
.host("*.circuitbreaker.com")
.filters(f -> f
.circuitBreaker(config -> config
.setName("mycmd")
.setFallbackUri("forward:/fallback")))
.uri(httpUri))
.build();
}
@RequestMapping("/fallback")
public Mono<String> fallback() {
return Mono.just("fallback");
}
}
@ConfigurationProperties
class UriConfiguration {
private String httpbin = "http://httpbin.org:80";
public String getHttpbin() {
return httpbin;
}
public void setHttpbin(String httpbin) {
this.httpbin = httpbin;
}
}
現在我們可以在 src/test/java/gateway
中建立一個名為 ApplicationTest
的新類。在新類中,我們新增以下內容
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"httpbin=https://:${wiremock.server.port}"})
@AutoConfigureWireMock(port = 0)
public class ApplicationTest {
@Autowired
private WebTestClient webClient;
@Test
public void contextLoads() throws Exception {
//Stubs
stubFor(get(urlEqualTo("/get"))
.willReturn(aResponse()
.withBody("{\"headers\":{\"Hello\":\"World\"}}")
.withHeader("Content-Type", "application/json")));
stubFor(get(urlEqualTo("/delay/3"))
.willReturn(aResponse()
.withBody("no fallback")
.withFixedDelay(3000)));
webClient
.get().uri("/get")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.headers.Hello").isEqualTo("World");
webClient
.get().uri("/delay/3")
.header("Host", "www.circuitbreaker.com")
.exchange()
.expectStatus().isOk()
.expectBody()
.consumeWith(
response -> assertThat(response.getResponseBody()).isEqualTo("fallback".getBytes()));
}
}
我們的測試利用 Spring Cloud Contract 的 WireMock 搭建了一個可以模擬 HTTPBin API 的伺服器。首先要注意的是使用了 @AutoConfigureWireMock(port = 0)
。這個註解會為我們在一個隨機埠上啟動 WireMock。
接下來,請注意我們利用了 UriConfiguration
類,並在 @SpringBootTest
註解中將 httpbin
屬性設定為本地執行的 WireMock 伺服器。在測試中,我們為透過 Gateway 呼叫的 HTTPBin API 設定“樁(stubs)”並模擬預期的行為。最後,我們使用 WebTestClient
向 Gateway 傳送請求並驗證響應。
總結
恭喜!您剛剛構建了您的第一個 Spring Cloud Gateway 應用程式!
想編寫新的指南或貢獻現有指南?請檢視我們的貢獻指南。
所有指南的程式碼均以 ASLv2 許可證釋出,文字內容以署名-禁止演繹知識共享許可證釋出。 |