Spring Cloud Gateway

官网简介

概览:

本项目提供了一个基于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>.*)", "/${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显示请求中发送了头HelloWorld

使用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<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

编写测试

作为一个优秀的开发人员,我们应该编写一些测试来确保网关按照我们期望的方式工作。在大多数情况下,我们希望限制对外部资源的依赖性,特别是在单元测试中,所以我们不应该依赖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 Contract中的WireMock ,以支持一个可以模拟HTTPBin的API的服务器。首先要注意的是使用@AutoConfigureWireMock(port=0)。这个注释将为我们在一个随机端口上启动WireMock 。
下一步注意,我们将利用我们的UriConfiguration 类,并将@SpringBootTest注释中的httpbin属性设置为本地运行的WireMock 服务器。然后在测试中,我们为通过网关调用的HTTPBin API设置“存根”,并模拟我们期望的行为。最后,我们使用WebTestClient 实际向网关发出请求并验证响应。

翻译自:
https://spring.io/projects/spring-cloud-gateway

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352