Spring Cloud Gateway 与 gRPC

工程 | Alberto C. Ríos | 2021 年 12 月 08 日 | ...

从 3.1.0 版本开始,作为 Spring Cloud 2021.0.0 (又称 Jubilee) 发布列车的一部分,Spring Cloud Gateway 包含了对 gRPC 和 HTTP/2 的支持。

我们将介绍 gRPC 背后的基本概念,并结合两个示例展示如何配置它

  • 其中一个示例展示了 Spring Cloud Gateway 如何透明地重新路由 gRPC 流量,而无需了解 proto 定义,也无需修改我们现有的 gRPC 服务器。

  • 另一个示例展示了我们如何在 Spring Cloud Gateway 中创建一个自定义过滤器,将 JSON 负载转换为 gRPC 消息。

gRPC 和 HTTP/2 简介

HTTP/2 让我们的应用程序更快、更简单、更健壮。通过启用请求和响应多路复用、添加高效的 HTTP 头部字段压缩以及添加对请求优先级和服务器推送的支持来减少延迟。

连接数量的减少对于提高 HTTPS 的性能尤其重要:通过这种方式,我们可以减少昂贵的 TLS 握手,更有效地复用会话,减少客户端和服务器资源。

HTTP/2 提供了两种协商应用层协议的机制

  • H2C 支持明文的 HTTP/2.0

  • H2 支持 TLS 的 HTTP/2.0

尽管 reactor-netty 支持 H2C 明文协议,但 Spring Cloud Gateway 需要使用带 TLS 的 H2 来确保传输安全。

HTTP/2 添加了一个二进制分帧层,这是 HTTP 消息在客户端和服务器之间封装和传输的方式,从而实现了更高效的数据传输方式。

得益于 reactor-netty 及其对 HTTP/2 的支持,我们得以扩展 Spring Cloud Gateway 来支持 gRPC。

gRPC 是一个高性能的远程过程调用框架,可以在任何环境中运行。它提供双向流,并且基于 HTTP/2。

gRPC 服务可以使用 Protocol Buffers 来定义,Protocol Buffers 是一个强大的二进制序列化工具集和语言,并提供用于生成不同语言客户端和服务器的工具。

入门

为了在 Spring Cloud Gateway 中启用 gRPC,我们需要通过添加一个密钥库来启用项目中的 HTTP/2 和 SSL,这可以通过配置来实现,添加以下内容即可

server:
  http2:
    enabled: true
  ssl:
    key-store-type: PKCS12
    key-store: classpath:keystore.p12
    key-store-password: password
    key-password: password
    enabled: true

现在我们已经启用了它,我们可以创建一个路由,将流量重定向到 gRPC 服务器,并利用现有的过滤器和谓词。例如,这个路由会将所有以 grpc 开头的路径来的流量重定向到本地端口 6565 上的服务器,并添加头部 X-Request-header,其值为 header-value

spring:
  cloud:
    gateway:
      routes:
        - id: grpc
          uri: https://localhost:6565
          predicates:
            - Path=/grpc/**
          filters:
            - AddResponseHeader=X-Request-header, header-value

运行 gRPC 到 gRPC

端到端示例可以在这个仓库中找到,包含以下部分

grpc-simple-gateway

  • 一个 grpc-server,暴露了一个 HelloService,一个 gRPC 端点用于接收 HelloRequest 并返回 HelloResponse

syntax = "proto3";

message HelloRequest { string firstName = 1; string lastName = 2; }

message HelloResponse { string greeting = 1; }

service HelloService { rpc hello(HelloRequest) returns (HelloResponse); }

服务器会将称谓与 firstNamelastName 拼接起来,并以 greeting 响应。

例如,这个输入

firstName: Saul
lastName: Hudson

将输出

greeting: Hello, Saul Hudson
  • 一个 grpc-client,负责向 HelloService 发送 HelloRequest

  • grpc-simple-gateway,它根据上述配置路由请求并添加一个头部。注意,这个网关应用程序不依赖 gRPC,也不依赖客户端和服务器使用的 proto 定义。

目前只有一个路由,它会将所有内容转发到 grpc-server

      routes:
        - id: grpc
          uri: https://localhost:6565
          predicates:
            - Path=/**
          filters:
            - AddResponseHeader=X-Request-header, header-value

启动将监听请求的服务器

 ./gradlew :grpc-server:bootRun

然后,我们启动将重新路由 gRPC 请求的网关

./gradlew :grpc-simple-gateway:bootRun

最后,我们可以使用指向网关应用程序的客户端

./gradlew :grpc-client:bootRun

网关路由和过滤器可以在 grpc-simple-gateway/src/main/resources/application.yaml 中修改

使用自定义过滤器运行 JSON 到 gRPC

得益于 Spring Cloud Gateway 的灵活性,可以创建一个自定义过滤器,将 JSON 负载转换为 gRPC 消息。

尽管这会带来性能影响,因为我们必须在网关中序列化和反序列化请求并从中创建一个通道,但如果想要暴露 JSON API 同时保持内部兼容性,这是一种常见的模式。

为此,我们可以扩展我们的 grpc-json-gateway 以包含带有我们想要发送的消息的 proto 定义。

grpc-json-gateway

Spring Cloud Gateway 包含一种机制来创建自定义过滤器,允许我们拦截请求并向其添加自定义逻辑。

对于这个特定的场景,我们将反序列化 JSON 请求,并创建一个 gRPC 通道,将消息发送到 grpc-server

static class GRPCResponseDecorator extends ServerHttpResponseDecorator {

  @Override
  public Mono<Void> writeWith(Publisher<?extends DataBuffer> body) {
    exchange.getResponse().getHeaders().set("Content-Type", "application/json");

    URI requestURI = exchange.getRequest().getURI();
    ManagedChannel channel = createSecuredChannel(requestURI.getHost(), 6565);

    return getDelegate().writeWith(deserializeJSONRequest()
            .map(jsonRequest -> {
                String firstName = jsonRequest.getFirstName();
                String lastName = jsonRequest.getLastName();
                return HelloServiceGrpc.newBlockingStub(channel)
                        .hello(HelloRequest.newBuilder()
                                .setFirstName(firstName)
                                .setLastName(lastName)
                                .build());
            })
            .map(this::serialiseJSONResponse)
            .map(wrapGRPCResponse())
            .cast(DataBuffer.class)
            .last());
  }
}

完整的实现可以在以下位置找到:grpc-json-gateway/src/main/java/com/example/grpcserver/hello/JSONToGRPCFilterFactory.java

使用相同的 grpc-server,我们可以使用以下命令启动带有自定义过滤器的网关

./gradlew :grpc-json-gateway:bootRun

然后使用例如 curlgrpc-json-gateway 发送 JSON 请求

curl -XPOST 'https://localhost:8091/json/hello' -d '{"firstName":"Duff","lastName":"McKagan"}' -k -H"Content-Type: application/json" -v

我们看到网关应用程序如何转发请求并返回带有新 Content-Type 头部信息的 JSON 负载

< HTTP/2 200
< content-type: application/json
< content-length: 34
<
* Connection #0 to host localhost left intact
{"greeting":"Hello, Duff McKagan"}

下一步

在这篇文章中,我们通过几个例子探讨了 gRPC 如何与 Spring Cloud Gateway 集成。我希望了解你在实际经验中发现的其他有用的用法。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助你快速前进。

了解更多

获取支持

Tanzu Spring 通过简单的订阅提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区即将举行的所有活动。

查看全部