@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 开始
您可以使用此 预初始化项目 并点击生成以下载 ZIP 文件。此项目已配置为适合本教程中的示例。
手动初始化项目
-
导航到 https://start.spring.io。此服务会引入应用程序所需的所有依赖项,并为您完成大部分设置工作。
-
选择 Gradle 或 Maven 以及您要使用的语言。本指南假设您选择了 Java。
-
点击依赖项并选择Reactive Gateway、Resilience4J 和Contract Stub Runner。
-
点击生成。
-
下载生成的 ZIP 文件,它是一个使用您的选择配置的 Web 应用程序的存档。
如果您的 IDE 集成了 Spring Initializr,则可以在 IDE 中完成此过程。 |
您也可以从 Github 分叉项目并在您的 IDE 或其他编辑器中打开它。 |
创建简单路由
Spring Cloud Gateway 使用路由来处理对下游服务的请求。在本指南中,我们将所有请求路由到 HTTPBin。路由可以通过多种方式配置,但对于本指南,我们将使用 Gateway 提供的 Java API。
首先,在 Application.java
中创建一个新的 Bean
,类型为 RouteLocator
。
src/main/java/gateway/Application.java
myRoutes
方法接收一个 RouteLocatorBuilder
,该构建器可用于创建路由。除了创建路由之外,RouteLocatorBuilder
还允许您向路由添加断言和过滤器,以便您可以根据某些条件处理路由,以及根据需要更改请求/响应。
现在我们可以创建一个路由,当对网关发出 /get
请求时,将请求路由到 https://httpbin.org/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
。应用程序运行后,向 https://127.0.0.1:8080/get
发出请求。您可以通过在终端中使用以下 cURL 命令来做到这一点
$ curl https://127.0.0.1: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://127.0.0.1:8080/get"
}
请注意,HTTPBin 显示在请求中发送了值为 World
的 Hello
标头。
使用 Spring Cloud CircuitBreaker
现在我们可以做一些更有趣的事情。由于网关后面的服务可能会出现故障并影响我们的客户端,因此我们可能希望将我们创建的路由包装在断路器中。您可以在 Spring Cloud Gateway 中通过使用 Resilience4J Spring Cloud CircuitBreaker 实现来做到这一点。这是通过一个简单的过滤器实现的,您可以将其添加到您的请求中。我们可以创建另一条路由来演示这一点。
在下一个示例中,我们使用 HTTPBin 的延迟 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();
}
此新路由配置与我们之前创建的配置之间存在一些差异。首先,我们使用主机断言而不是路径断言。这意味着,只要主机是 circuitbreaker.com
,我们就将请求路由到 HTTPBin 并将其包装在断路器中。我们通过将过滤器应用于路由来做到这一点。我们可以使用配置对象配置断路器过滤器。在此示例中,我们为断路器命名为 mycmd
。
现在我们可以测试此新路由。为此,我们需要启动应用程序,但这次我们将向 /delay/3
发出请求。重要的是,我们还需要包含一个主机为 circuitbreaker.com
的 Host
标头。否则,请求不会被路由。我们可以使用以下 cURL 命令
$ curl --dump-header - --header 'Host: www.circuitbreaker.com' https://127.0.0.1:8080/delay/3
我们使用 --dump-header 来查看响应标头。--dump-header 后面的 - 告诉 cURL 将标头打印到标准输出。 |
使用此命令后,您应该在终端中看到以下内容
HTTP/1.1 504 Gateway Timeout
content-length: 0
如您所见,断路器在等待 HTTPBin 的响应时超时了。当断路器超时时,我们可以选择提供一个回退,以便客户端不会收到 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();
}
现在,当包装在断路器中的路由超时时,它会在网关应用程序中调用 /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://127.0.0.1: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("*.circuitbreaker.com")
.filters(f -> f
.circuitBreaker(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("*.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/main/test/java/gateway
中创建一个名为 ApplicationTest
的新类。在新类中,我们添加以下内容
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"httpbin=https://127.0.0.1:${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 服务器。在测试中,我们然后为通过网关调用的 HTTPBin API 设置“存根”,并模拟我们期望的行为。最后,我们使用 WebTestClient
向网关发出请求并验证响应。
总结
恭喜!您刚刚构建了第一个 Spring Cloud Gateway 应用程序!
想编写新的指南或为现有指南做出贡献?请查看我们的 贡献指南。
所有指南都使用代码的 ASLv2 许可证和文本的 署名-非商业性-禁止演绎创作共用许可证 发布。 |