gRPC gateway

概述

随着微服务架构的流行,gRPC 作为一种高性能、跨语言的远程过程调用(RPC)框架被广泛应用。但是,gRPC 并不适用于所有应用场景。例如,当客户端不支持 gRPC 协议时,或者需要将 gRPC 服务暴露给 Web 应用程序时,需要一种将 RESTful API 转换为 gRPC 的方式。因此,gRPC 网关应运而生。

gRPC 网关在 go-zero 中的实现

go-zero 中的 gRPC 网关是一个 HTTP 服务器,它将 RESTful API 转换为 gRPC 请求,然后将 gRPC 响应转换为 RESTful API。大致流程如下:

  1. 从 proto 文件中解析出 gRPC 服务的定义。
  2. 从 配置文件中解析出 gRPC 服务的 HTTP 映射规则。
  3. 根据 gRPC 服务的定义和 HTTP 映射规则,生成 gRPC 服务的 HTTP 处理器。
  4. 启动 HTTP 服务器,处理 HTTP 请求。
  5. 将 HTTP 请求转换为 gRPC 请求。
  6. 将 gRPC 响应转换为 HTTP 响应。
  7. 返回 HTTP 响应。

详细代码可参考 gateway

配置介绍

  1. type (
  2. GatewayConf struct {
  3. rest.RestConf
  4. Upstreams []Upstream
  5. Timeout time.Duration `json:",default=5s"`
  6. }
  7. RouteMapping struct {
  8. Method string
  9. Path string
  10. RpcPath string
  11. }
  12. Upstream struct {
  13. Name string `json:",optional"`
  14. Grpc zrpc.RpcClientConf
  15. ProtoSets []string `json:",optional"`
  16. Mappings []RouteMapping `json:",optional"`
  17. }
  18. )

GatewayConf

gRPC gateway - 图1名称说明类型是否必填示例
RestConfrest 服务配置RestConf参考基础服务配置
UpstreamsgRPC 服务配置[]Upstream
Timeout超时时间duration5s

Upstream

gRPC gateway - 图2名称说明类型是否必填示例
Name服务名称stringdemo1-gateway
GrpcgRPC 服务配置RpcClientConf参考RPC 配置
ProtoSetsproto 文件列表[]string[“hello.pb”]
Mappings路由映射,不填则默认映射所有 grpc 路径[]RouteMapping

RouteMapping

gRPC gateway - 图3名称说明类型是否必填示例
MethodHTTP 方法stringget
PathHTTP 路径string/ping
RpcPathgRPC 路径stringhello.Hello/Ping

使用示例

在 go-zero 中,有两种方式使用 gRPC 网关: protoDescriptor 和 grpcReflection。

  • protoDescriptor
  • grpcReflection

protoDescriptor 方式需要通过 protoc 将 proto 生成为 pb 文件,然后在 gateway 中去引用该 pb 文件做 rest-grpc 的规则映射。

gRPC gateway - 图4tip

go-zero sdk 版本 v1.5.0 的 gateway 配置会造成配置冲突,请避开此版本,当前示例使用的是 v1.4.4 版本

  1. 我们新建一个工程 demo1, 在 demo1 中新建一个 hello.proto 文件,如下:
  1. syntax = "proto3";
  2. package hello;
  3. option go_package = "./hello";
  4. message Request {
  5. }
  6. message Response {
  7. string msg = 1;
  8. }
  9. service Hello {
  10. rpc Ping(Request) returns(Response);
  11. }
  1. demo1 目录下创建 gateway 目录,然后在 demo1 目录执行如下指令生成 protoDescriptor:
  1. $ protoc --descriptor_set_out=gateway/hello.pb hello.proto
  1. demo1 目录下执行如下指令生成 grpc 服务代码:
  1. $ goctl rpc protoc hello.proto --go_out=server --go-grpc_out=server --zrpc_out=server

demo1/server/internal/logic/pinglogic.go 中的 Ping 方法填充逻辑,代码如下:

  1. func (l *PingLogic) Ping(in *hello.Request) (*hello.Response, error) {
  2. return &hello.Response{
  3. Msg: "pong",
  4. }, nil
  5. }
  1. 修改配置文件 demo1/server/etc/hello.yaml 内容如下:
  1. Name: hello.rpc
  2. ListenOn: 0.0.0.0:8080
  1. 进入 demo1/gateway 目录,创建目录 etc,然后添加配置文件 gateway.yaml,如下:
  1. Name: demo1-gateway
  2. Host: localhost
  3. Port: 8888
  4. Upstreams:
  5. - Grpc:
  6. Target: 0.0.0.0:8080
  7. # protoset mode
  8. ProtoSets:
  9. - hello.pb
  10. # Mappings can also be written in proto options
  11. Mappings:
  12. - Method: get
  13. Path: /ping
  14. RpcPath: hello.Hello/Ping
  1. 进入 demo1/gateway 目录, 新建 gateway.go 文件,内容如下:
  1. package main
  2. import (
  3. "flag"
  4. "github.com/zeromicro/go-zero/core/conf"
  5. "github.com/zeromicro/go-zero/gateway"
  6. )
  7. var configFile = flag.String("f", "etc/gateway.yaml", "config file")
  8. func main() {
  9. flag.Parse()
  10. var c gateway.GatewayConf
  11. conf.MustLoad(*configFile, &c)
  12. gw := gateway.MustNewServer(c)
  13. defer gw.Stop()
  14. gw.Start()
  15. }
  1. 分别开两个终端启动 grpc server 服务和 gateway 服务,然后访问 http://localhost:8888/ping
  1. # 进入 demo1/server 目录下,启动 grpc 服务
  2. $ go run hello.go
  3. Starting rpc server at 0.0.0.0:8080...
  1. # 进入 demo1/gateway 目录下,启动 gateway 服务
  2. $ go run gateway.go
  1. # 新开一个终端,访问 gateway 服务
  2. $ curl http://localhost:8888/ping
  3. {"msg":"pong"}%

grpcReflection 方式和 protoDescriptor 方式类似,不同的是,grpcReflection 方式不需要通过 protoc 将 proto 生成为 pb 文件,而是通过 grpc 的反射机制,直接从 grpc server 中获取 proto 文件,然后在 gateway 中去引用该 proto 文件做 rest-grpc 的规则映射。

  1. 我们新建一个工程 demo2, 在 demo2 中新建一个 hello.proto 文件,如下:
  1. syntax = "proto3";
  2. package hello;
  3. option go_package = "./hello";
  4. message Request {
  5. }
  6. message Response {
  7. string msg = 1;
  8. }
  9. service Hello {
  10. rpc Ping(Request) returns(Response);
  11. }
  1. demo2 目录下创建 gateway 目录备用

  2. demo2 目录下执行如下指令生成 grpc 服务代码:

  1. $ goctl rpc protoc hello.proto --go_out=server --go-grpc_out=server --zrpc_out=server

demo2/server/internal/logic/pinglogic.go 中的 Ping 方法填充逻辑,代码如下:

  1. func (l *PingLogic) Ping(in *hello.Request) (*hello.Response, error) {
  2. return &hello.Response{
  3. Msg: "pong",
  4. }, nil
  5. }

将配置文件 demo2/server/etc/hello.yaml 修改如下:

  1. Name: hello.rpc
  2. ListenOn: 0.0.0.0:8080
  3. Mode: dev
gRPC gateway - 图5tip

因为目前 grpc 反射模式只有 devtest 环境支持,所以这里需要将 Mode 设置为 dev 或者 test

  1. 进入 demo2/gateway 目录,创建目录 etc,然后添加配置文件 gateway.yaml,如下:
  1. Name: demo1-gateway
  2. Host: localhost
  3. Port: 8888
  4. Upstreams:
  5. - Grpc:
  6. Target: 0.0.0.0:8080
  7. # Mappings can also be written in proto options
  8. Mappings:
  9. - Method: get
  10. Path: /ping
  11. RpcPath: hello.Hello/Ping
  1. 进入 demo2/gateway 目录, 新建 gateway.go 文件,内容如下:
  1. package main
  2. import (
  3. "flag"
  4. "github.com/zeromicro/go-zero/core/conf"
  5. "github.com/zeromicro/go-zero/gateway"
  6. )
  7. var configFile = flag.String("f", "etc/gateway.yaml", "config file")
  8. func main() {
  9. flag.Parse()
  10. var c gateway.GatewayConf
  11. conf.MustLoad(*configFile, &c)
  12. gw := gateway.MustNewServer(c)
  13. defer gw.Stop()
  14. gw.Start()
  15. }
  1. 分别开两个终端启动 grpc server 服务和 gateway 服务,然后访问 http://localhost:8888/ping
  1. # 进入 demo1/server 目录下,启动 grpc 服务
  2. $ go run hello.go
  3. Starting rpc server at 0.0.0.0:8080...
  1. # 进入 demo1/gateway 目录下,启动 gateway 服务
  2. $ go run gateway.go
  1. # 新开一个终端,访问 gateway 服务
  2. $ curl http://localhost:8888/ping
  3. {"msg":"pong"}%

参考文献