Spring Cloud Gateway 15 分鐘極速入門

Spring cloud gateway 是 spring 官方基於 Spring 5.0、Spring Boot2.0 和 Project Reactor 等技術開發的網關,Spring Cloud Gateway 旨在爲微服務架構提供簡單、有效和統一的API路由管理方式,Spring Cloud Gateway 作爲 Spring Cloud 生態系統中的網關,目標是替代 Netflix Zuul,其不僅提供統一的路由方式,並且還基於Filer鏈的方式提供了網關基本的功能,例如:安全、監控/埋點、限流等。

下面就以一篇簡單的 demo 程序快速入門 Spring Cloud Gateway。準備工作如下:

  • 大約花費 15 分鐘
  • 一個趁手的文本編輯器或者 IDE
  • JDK >= 1.8
  • Gradle 4+ 或者 Maven 3.2+

1、如何完全這個入門

像大多數 Spring 入門指南一樣,您可以從頭開始並完成每個步驟,也可以跳過您已經熟悉的基本設置步驟。無論哪種方式,最終得到的都是工作代碼。

從頭開始,繼續使用Gradle進行構建。

要跳過這些基本步驟,請執行以下步驟:

  • 下載並解壓縮本指南的源存儲庫,或者使用 Git Clone git clone https://github.com/spring-guides/gs-gateway.git
  • gs-gateway/initial 目錄
  • 現在開始創建一個 簡單的路由

完成後,您可以根據 gs-gateway/complete 目錄中的代碼檢查結果。

2、通過 Gradle 進行編譯

首先設置一個基本的構建腳本。在使用Spring構建應用程序時,您可以使用任何您喜歡的構建系統,但是這裏包含了使用Gradle和Maven所需的代碼。如果您不熟悉這兩種方法,請參考使用 Gradle 構建 Java 項目或使用 Maven 構建 Java項目

2.1 創建一個文件結構

在您選擇的項目目錄中,創建以下子目錄結構,例如在 *nix 系統上通過 mkdir -p src/main/java/hello 創建以下目錄結果。

└── src
    └── main
        └── java
            └── hello

2.2 創建一個 gradle 構建文件

下面就是 gradle 的構建項目

build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.1.7.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

bootJar {
    baseName = 'gs-gateway'
    version =  '0.1.0'
}

repositories {
    mavenCentral()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:Greenwich.SR2"
    }
}

dependencies {
    compile("org.springframework.cloud:spring-cloud-starter-gateway")
    compile("org.springframework.cloud:spring-cloud-starter-netflix-hystrix")
    compile("org.springframework.cloud:spring-cloud-starter-contract-stub-runner"){
        exclude group: "org.springframework.boot", module: "spring-boot-starter-web"
    }
    testCompile("org.springframework.boot:spring-boot-starter-test")
}

Spring Boot gradle 插件提供了很多方便的特性:

  • 它收集類路徑上的所有jar,並構建一個單獨的、可運行的uber-jar,這使它更方便地執行和傳輸您的服務。
  • 它搜索 public static void main() 方法,將其標記爲可運行類。
  • 它提供了一個內置的依賴項解析器,用於設置版本號以匹配 Spring Boot 的依賴項。您可以覆蓋您希望覆蓋的任何版本,否則它將使用默認 Spring Boot 選擇的版本集。

3、使用 Maven 構建項目

首先設置一個基本的構建腳本。在使用 Spring 構建應用程序時,您可以使用您喜歡的任何構建系統,但是這裏包含了使用 Maven 所需的代碼。如果您不熟悉 Maven,請參考 使用 Maven 構建 Java 項目

3.1 創建文件目錄

在您選擇的項目目錄中,創建以下子目錄結構,例如在 *nix 系統上通過 mkdir -p src/main/java/hello 創建以下目錄結果。

└── src
    └── main
        └── java
            └── hello

以下就是 maven 構建項目需要的 pom.xml 文件

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>gs-gateway</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-web</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Spring Boot gradle 插件提供了很多方便的特性:

  • 它收集類路徑上的所有jar,並構建一個單獨的、可運行的uber-jar,這使它更方便地執行和傳輸您的服務。
  • 它搜索 public static void main() 方法,將其標記爲可運行類。
  • 它提供了一個內置的依賴項解析器,用於設置版本號以匹配 Spring Boot 的依賴項。您可以覆蓋您希望覆蓋的任何版本,否則它將使用默認 Spring Boot 選擇的版本集。

4、創建一個簡單的路由

Spring Cloud Gateway 使用路由來處理對下游服務的請求。在本指南中,我們將把所有請求路由到 HTTPBin。路由可以通過多種方式配置,但在本指南中,我們將使用網關提供的Java API。

首先,在 Application.java 類中創建一個RouteLocator 類型的 @Bean

src/main/java/gateway/Application.java

@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
    return builder.routes().build();
}

上面的 myRoutes 方法採用了一個 RouteLocatorBuilder,可以很容易地使用它來創建路由。除了創建路由之外,RouteLocatorBuilder 還允許您向路由添加謂詞和過濾器,這樣您就可以根據特定的條件進行路由處理,並根據需要更改請求/響應。

讓我們創建一個路由,當向網關 發出 /get 請求時,將請求路由到 https://httpbin.org/get。在此路由的配置中,我們將添加一個過濾器,該過濾器將在路由之前向請求頭 key 需要爲 Hello,請求頭 value 需要爲 World

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();
}

爲了要測試我們非常簡單的網關,只需運行 Application.java,它應該在端口 8080 上運行。應用程序運行後,向http://localhost:8080/get發出請求。您可以通過在終端中發出以下命令來使用 cURL 實現這一點。

$ curl http://localhost: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": "http://localhost:8080/get"
}

注意,HTTPBin 顯示在請求中發送了帶值 World 的報文頭 Hello

5、 使用 Hystrix

現在讓我們做一些更有趣的事情。由於網關背後的服務可能會對我們的客戶產生不良影響,所以我們可能希望將我們創建的路由封裝在斷路器中。您可以在 Spring Cloud Gatewa y中使用 Hystrix 來實現這一點。這是通過一個簡單的過濾器實現的,您可以將該過濾器添加到您的請求中。讓我們創建另一個路由來演示這一點。

在本例中,我們將利用 HTTPBin 的延遲API,該API在發送響應之前等待一定的時間。因爲這個API可能會花費很長時間來發送它的響應,所以我們可以在一個 HystrixCommand 中包裝使用這個 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("*.hystrix.com")
            .filters(f -> f.hystrix(config -> config.setName("mycmd")))
            .uri("http://httpbin.org:80")).
        build();
}

這個新路由配置與我們之前創建的路由配置之間存在一些差異。首先,我們使用的是主機謂詞而不是路徑謂詞。這意味着只要主機是 hystrix.com,我們就會將請求路由到HTTPBin,並將該請求包裝在 HystrixCommand 中。我們通過對路由應用一個過濾器來實現這一點。可以使用配置對象配置 Hystrix 過濾器。在本例中,我們只是將 HystrixCommand命名爲 mycmd

讓我們測試一下這條新路由。啓動應用程序,但是這次我們將向/delay/3發出請求。同樣重要的是,我們應該包含一個主機報文頭,其中包含 hystrix.com 的主機,否則請求將不會被路由。在 cURL 中是這樣的:

$ curl --dump-header - --header 'Host: www.hystrix.com' http://localhost:8080/delay/3

我們使用 ——dump-header 來查看響應標頭,後面的 ——dump-header 告訴cURL將標頭打印到 stdout。

執行此命令後,您應該在終端中看到以下內容:

HTTP/1.1 504 Gateway Timeout
content-length: 0

可以看到,Hystrix 超時等待來自 HTTPBin 的響應。當 Hystrix 超時時,我們可以選擇提供一個後備方案,這樣客戶不僅可以收到 504,還可以收到更有意義的東西。在生產場景中,您可能會從緩存中返回一些數據,但是在我們的簡單示例中,我們將返回一個 fallback 響應,而不是返回正文。

爲此,讓我們修改 Hystrix 過濾器,以提供一個 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("*.hystrix.com")
            .filters(f -> f.hystrix(config -> config
                .setName("mycmd")
                .setFallbackUri("forward:/fallback")))
            .uri("http://httpbin.org:80"))
        .build();
}

現在,當Hystrix 包裝路由超時時,它將在網關應用程序中調用 /fallback。讓我們將 /fallback 端點添加到我們的應用程序中。

Application.java 中添加類級註釋@RestController,然後將下面的 @RequestMapping 添加到類中。

src/main/java/gateway/Application.java

@RequestMapping("/fallback")
public Mono<String> fallback() {
  return Mono.just("fallback");
}

要測試這個新的 fallback 功能,請重新啓動應用程序,並再次發出以下cURL命令:

$ curl --dump-header - --header 'Host: www.hystrix.com' http://localhost:8080/delay/3

有了適當的 fallback,我們現在看到我們從的網關獲得了狀態爲 200 的 fallback 的響應體。

HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/plain;charset=UTF-8

fallback

6、測試用例

作爲一名優秀的開發人員,我們應該編寫一些測試來確保我們的網關正在做我們期望它應該做的事情。在大多數情況下,我們希望限制對外部資源的依賴,特別是在單元測試中,因此我們不應該依賴 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("*.hystrix.com")
      .filters(f -> f
        .hystrix(config -> config
          .setName("mycmd")
          .setFallbackUri("forward:/fallback")))
      .uri(httpUri))
    .build();
}

正如您所看到的,我們不是將 URL 硬編碼到 HTTPBin,而是從我們的新配置類獲取 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("*.hystrix.com")
        .filters(f -> f
          .hystrix(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/main/test/java/gateway創建一個新的類命名爲 ApplicationTest ,這個類的內部如下所示:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
    properties = {"httpbin=http://localhost:${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.hystrix.com")
      .exchange()
      .expectStatus().isOk()
      .expectBody()
      .consumeWith(
        response -> assertThat(response.getResponseBody()).isEqualTo("fallback".getBytes()));
  }
}

我們的測試實際上利用了來自Spring Cloud 契約的 WireMock,以便讓服務器能夠模擬來自 HTTPBin 的 api 。首先要注意的是 @AutoConfigureWireMock(port = 0) 的使用,該註釋將在一個隨機端口上爲我們啓動WireMock。

接下來請注意,我們正在利用我們的 UriConfiguration 類,並將@SpringBootTest 註釋中的 httpbin 屬性設置爲本地運行的 WireMock 服務器。在測試中,我們爲通過網關調用的 HTTPBin api 設置“stubs”,並模擬我們期望的行爲。最後,我們使用 WebTestClient 實際向網關發出請求並驗證響應。

7、總結

恭喜你!您剛剛構建了您的第一個Spring Coud Gateway 應用程序!

想寫一本新的指南還是對現有的指南做些貢獻?請查看我們的 投稿指南

參考地址:

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