跳转至

gRPC-Gateway 与 gRPC-Web 跨生态互通

在构建企业级微服务时,内部系统间使用 gRPC 可以获得极致性能。但在面向外部浏览器前端、移动端第三方开发者或遗留系统时,直接暴露原始 gRPC 往往会面临协议兼容性阻碍。

本章介绍两大主流桥接方案:面向 HTTP/REST 的 gRPC-Gateway 与面向前端浏览器的 gRPC-Web


1. 为什么浏览器不能直接调用原生 gRPC?

浏览器环境中的 JavaScript 标准 API(fetchXMLHttpRequest)无法直接操作底层的 HTTP/2 通信细节: 1. 无法读取 HTTP/2 Trailers(尾部帧):gRPC 将至关重要的调用状态码(grpc-status)存放在响应结尾的 Trailer 中,而浏览器 API 默认会将 Trailers 过滤或丢弃。 2. 无法细粒度控制帧流:浏览器无法控制原生二进制 Length-prefixed 帧的半关闭(Half-Close)时机。

因此,面向前端与外部开放时,必须引入协议转换层。


2. 方案一:gRPC-Gateway (同时暴露 REST & gRPC)

grpc-gateway 是 Google 推荐的事实标准反向代理插件。它读取 .proto 中的注解配置,自动生成一个将 HTTP/JSON 请求实时翻译为 gRPC 调用 的高性能代理服务,并能同步生成 OpenAPI (Swagger) 规范文档。

graph LR
    subgraph Clients ["多端请求"]
        RESTClient["第三方开发者 / Web 前端 (HTTP/JSON)"]
        GRPCClient["内部微服务 (gRPC / HTTP/2)"]
    end

    subgraph Gateway ["gRPC-Gateway 代理层"]
        Transcoder["协议转码器 (JSON <-> Protobuf 序列化)"]
    end

    subgraph Backends ["后端 gRPC 微服务"]
        CoreSvc["核心业务微服务 (:50051)"]
    end

    RESTClient -->|POST /v1/orders| Transcoder
    Transcoder -->|gRPC: OrderService.CreateOrder| CoreSvc
    GRPCClient ==>|原生直连 gRPC| CoreSvc

编写支持 REST 注解的 .proto 文件

需引入 Google 官方的 google/api/annotations.proto

syntax = "proto3";

package commerce.order.v1;

import "google/api/annotations.proto";

option go_package = "example.com/commerce/order/v1;orderv1";

service OrderService {
  // 定义 gRPC 方法并通过 http 注解映射为 RESTful 路径
  rpc GetOrder (GetOrderRequest) returns (GetOrderResponse) {
    option (google.api.http) = {
      get: "/v1/orders/{order_id}" // 路径参数自动绑定到请求字段
    };
  }

  rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse) {
    option (google.api.http) = {
      post: "/v1/orders"
      body: "*" // 请求体 JSON 自动绑定到整条 CreateOrderRequest
    };
  }
}

message GetOrderRequest {
  string order_id = 1;
}

message GetOrderResponse {
  string order_id = 1;
  string status = 2;
  double amount = 3;
}

核心收益

  • 单一契约源(Single Source of Truth):一份 .proto 文件同时驱动内部 RPC、外部 REST API 与前端 Swagger 文档,杜绝文档与实现脱节。
  • 高性能同进程集成:在 Go 语言中,gRPC-Gateway 可以与 gRPC 服务编译进同一个二进制可执行文件中,通过同一个端口或独立端口监听,几乎无额外运维负担。

3. 方案二:gRPC-Web (前端原生调用)

如果前端研发团队希望在 TypeScript / React / Vue 中享受强类型检查与自动生成的客户端桩代码,可以采用 gRPC-Web 方案。

graph LR
    Browser["浏览器 Web 应用 (TypeScript / gRPC-Web Client)"]
    Envoy["Envoy 网关 (内置 grpc_web 过滤器)"]
    Service["后端 gRPC 节点 (原生 HTTP/2 gRPC)"]

    Browser -->|HTTP/1.1 或 HTTP/2 (base64 或二进制)| Envoy
    Envoy -->|标准 gRPC (Trailers 转换)| Service
    Service -->> Envoy
    Envoy -->>|将 Trailers 编码进响应 body| Browser

工作机制

  1. 前端使用 protoc-gen-grpc-web 编译 .proto 文件,生成 TypeScript 桩代码。
  2. 客户端发出的请求由 Envoy 网关(或 Traefik)拦截。网关将 gRPC-Web 的自定义 Framing 格式翻译为标准 gRPC。
  3. 服务端返回的 Trailers 被 Envoy 打包塞入响应体的末尾特殊数据块中,使浏览器端 JavaScript 能够完整读取错误码和状态。