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://: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://: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://: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 社群即將舉行的所有活動。

檢視全部