本指南将引导您逐步了解如何使用Spring Cloud Gateway

你会建立什么

您将使用Spring Cloud Gateway构建一个网关。

你需要什么

如何完成本指南

像大多数Spring 入门指南一样,您可以从头开始并完成每个步骤,也可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都可以使用工作代码。

从头开始 ,请继续使用Gradle构建

跳过基础知识 ,请执行以下操作:

完成后 ,您可以根据中的代码检查结果gs-gateway/complete

用Gradle构建

用Gradle构建

首先,您设置一个基本的构建脚本。在使用Spring构建应用程序时,可以使用任何喜欢的构建系统,但是此处包含使用GradleMaven所需的代码。如果您都不熟悉,请参阅使用Gradle 构建Java项目使用Maven构建Java项目

创建目录结构

在您选择的项目目录中,创建以下子目录结构;例如, mkdir -p src/main/java/hello在* nix系统上:

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

创建一个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,并构建一个可运行的单个“über-jar”,这使执行和传输服务更加方便。

  • 它搜索public static void main()标记为可运行类的方法。

  • 它提供了一个内置的依赖项解析器,用于设置版本号以匹配Spring Boot依赖项 。您可以覆盖所需的任何版本,但是它将默认为Boot选择的一组版本。

用Maven构建

用Maven构建

首先,您设置一个基本的构建脚本。使用Spring构建应用程序时,可以使用任何喜欢的构建系统,但是此处包含了使用Maven所需的代码。如果您不熟悉Maven,请参阅使用Maven 构建Java项目

创建目录结构

在您选择的项目目录中,创建以下子目录结构;例如, mkdir -p src/main/java/hello在* nix系统上:

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

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 Maven插件提供了许多方便的功能:

  • 它收集类路径上的所有jar,并构建一个可运行的单个“über-jar”,这使执行和传输服务更加方便。

  • 它搜索public static void main()标记为可运行类的方法。

  • 它提供了一个内置的依赖项解析器,用于设置版本号以匹配Spring Boot依赖项 。您可以覆盖所需的任何版本,但是它将默认为Boot选择的一组版本。

使用您的IDE进行构建

使用您的IDE进行构建

创建一条简单的路线

Spring Cloud Gateway使用路由来处理对下游服务的请求。在本指南中,我们会将所有请求路由到HTTPBin 。可以通过多种方式配置路由,但是对于本指南,我们将使用网关提供的Java API。

首先,创建一个新的Bean类型的RouteLocatorApplication.java

src/main/java/gateway/Application.java

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

以上myRoutes方法采用RouteLocatorBuilder可以轻松用于创建路线。除了创建路线之外, RouteLocatorBuilder允许您在路由中添加谓词和过滤器,以便您可以根据特定条件路由句柄,并根据需要更改请求/响应。

让我们创建一条路由,将请求路由到https://httpbin.org/get在以下位置向网关提出请求时/get 。在此路由的配置中,我们将添加一个过滤器,该过滤器将添加请求标头Hello具有价值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显示了标头Hello具有价值World已在请求中发送。

使用Hystrix

现在,让我们做一些更有趣的事情。由于网关后面的服务可能会对客户产生不良影响,因此我们可能希望将在断路器中创建的路由包装起来。您可以使用Hystrix在Spring Cloud Gateway中执行此操作。这是通过可以添加到请求中的简单过滤器实现的。让我们创建另一条路线来证明这一点。

在此示例中,我们将利用HTTPBin的延迟API,该API在发送响应之前等待一定秒数。由于此API可能会花费很长时间来发送其响应,因此我们可以将使用此API的路由包装在HystrixCommand 。为我们添加一条新路线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 。我们加入一个Host具有以下内容的标头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");
}

要测试此新的后备功能,请重新启动应用程序,然后再次发出以下cURL命令

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

有了后备功能,我们现在看到了200从网关返回的响应主体fallback

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

fallback

写作测试

作为一名优秀的开发人员,我们应该编写一些测试以确保我们的网关能够按预期运行。在大多数情况下,我们希望限制对外部资源的依赖,尤其是在单元测试中,因此我们不应该依赖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;
    }
}

创建一个名为ApplicationTestsrc/main/test/java/gateway 。在新类中添加以下内容。

@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 Contract中的WireMock来建立可以模拟HTTPBin API的服务器。首先要注意的是@AutoConfigureWireMock(port = 0) 。此注释将为我们在随机端口上启动WireMock。

接下来请注意,我们正在利用我们的优势UriConfiguration类和设置httpbin物业@SpringBootTest本地运行的WireMock服务器的注释。然后在测试中,我们为通过网关调用的HTTPBin API设置“存根”,并模拟我们期望的行为。最后我们用WebTestClient实际向网关发出请求并验证响应。

摘要

恭喜你!您刚刚构建了第一个Spring Coud Gateway应用程序!

是否要编写新指南或为现有指南做出贡献?查看我们的贡献准则

所有指南均以代码的ASLv2许可证和写作的Attribution,NoDerivatives创作共用许可证发布