[Spring官方教程] - 使用spring-cloud-gateway构建一个网关

本指南将指导你如何使用Spring Cloud Gateway来创建一个网关。

你需要准备的东西

  1. 大约15分钟
  2. 一个喜欢的编辑器或者IDE
  3. JDK1.8及其以上版本
  4. Gradle 4+ 或者 Maven 3.2+
  5. 你也可以直接将代码导入你的IDE。

创建一个简单的路由

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。在这个路由的配置中,我们将添加一个过滤器,在路由之前将请求头HelloWorld加入到请求中。

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”。

使用 Hystrix

现在让我们做一些更有趣的事情。由于网关背后的服务有可能表现得很差,影响到我们的客户,我们可能想把我们创建的路由包在断路器里。你可以使用HystrixSpring Cloud Gateway中这样做。这是通过一个简单的过滤器实现的,你可以把它添加到你的请求中。让我们创建另一个路由来证明这一点。

在这个例子中,我们将利用HTTPBin的延迟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();
}

这个新的路由配置与我们之前创建的路由有一些不同。首先,我们使用的是host谓词而不是path谓词。这意味着,只要主机是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超时时,我们可以选择提供一个fallback ,这样客户就不会只是收到一个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();
}

COPY

Now when the Hystrix wrapped route times out it will call /fallback in the Gateway app. Lets add the /fallback endpoint to our application.

In Application.java add the class level annotation @RestController , then add the following @RequestMapping to the class.

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

有了fallback之后,我们现在看到,我们从网关得到一个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("*.hystrix.com")
      .filters(f -> f
        .hystrix(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("*.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 Contract的WireMock,以便建立一个可以模拟HTTPBin的API的服务器。首先要注意的是@AutoConfigureWireMock(port = 0)的使用。这个注解将在一个随机的端口上为我们启动WireMock。

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

总结

祝贺你! 你刚刚建立了你的第一个Spring Coud Gateway应用程序!

完整代码


原文: