官网简介
概览:
本项目提供了一个基于Spring MVC构建API网关的库。Spring Cloud Gateway旨在提供一个简单而有效的方式对API进行路由,并提供横切层面的关注,例如:安全、监控/度量以及弹性。
特性:
- 基于Spring 5,Reactor 和 Spring Boot 2.0
- 能够在任何请求属性上匹配路由
- 断言和过滤器是特定于路由的
- Hystrix断路器集成
- Spring Cloud DiscoveryClient集成
- 易于编写断言和过滤器
- 请求速率限制
- 路径重写
快速开始:
@SpringBootApplication
public class DemogatewayApplication {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/get")
.uri("http://httpbin.org"))
.route("host_route", r -> r.host("*.myhost.org")
.uri("http://httpbin.org"))
.route("rewrite_route", r -> r.host("*.rewrite.org")
.filters(f -> f.rewritePath("/foo/(?.*)", "/${segment}"))
.uri("http://httpbin.org"))
.route("hystrix_route", r -> r.host("*.hystrix.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
.uri("http://httpbin.org"))
.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
.uri("http://httpbin.org"))
.route("limit_route", r -> r
.host("*.limited.org").and().path("/anything/**")
.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
.uri("http://httpbin.org"))
.build();
}
}
指南
SpringCloudGateway使用路由来处理对下游服务的请求。在本指南中,我们将把所有请求路由到 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。在这个路由的配置中,我们将添加一个过滤器,它将在路由请求之前添加一个名为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
现在让我们做一些更有趣的事情。由于网关后面的服务可能表现不佳,从而对客户端造成影响,我们可能希望将我们创建的路由封装在断路器中。您可以在Spring Cloud Gateway中使用 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();
}
这个新的路由配置和我们创建的上一个路由配置之间存在一些差异。首先,我们使用的是host断言而不是路径断言。这意味着,只要主机是hystrix.com
,我们就将请求路由到HTTPBin,并将该请求包装在HystrixCommand
中。我们通过对路由应用过滤器来实现这一点。可以使用配置对象配置Hystrix过滤器。在这个例子中,我们只给出了HystrixCommand
的名称mycmd
。
让我们测试这个新路由。启动应用程序,但这次我们将向/delay/3
发出请求。同样重要的是,我们要包含一个名为Host
的指向hystrix.com
的host地址的请求头,否则请求将不会被路由。在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 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
编写测试
作为一个优秀的开发人员,我们应该编写一些测试来确保网关按照我们期望的方式工作。在大多数情况下,我们希望限制对外部资源的依赖性,特别是在单元测试中,所以我们不应该依赖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 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
实际向网关发出请求并验证响应。
翻译自:
https://spring.io/projects/spring-cloud-gateway