gRPC 网关
随着微服务架构的流行,gRPC 作为一种高性能、跨语言的远程过程调用(RPC)框架被广泛应用。但是,gRPC 并不适用于所有应用场景。例如,当客户端不支持 gRPC 协议时,或者需要将 gRPC 服务暴露给 Web 应用程序时,需要一种将 RESTful API 转换为 gRPC 的方式。因此,gRPC 网关应运而生。
gRPC 网关在 go-zero 中的实现
Section titled “gRPC 网关在 go-zero 中的实现”go-zero 中的 gRPC 网关是一个 HTTP 服务器,它将 RESTful API 转换为 gRPC 请求,然后将 gRPC 响应转换为 RESTful API。大致流程如下:
- 从 proto 文件中解析出 gRPC 服务的定义。
- 从 配置文件中解析出 gRPC 服务的 HTTP 映射规则。
- 根据 gRPC 服务的定义和 HTTP 映射规则,生成 gRPC 服务的 HTTP 处理器。
- 启动 HTTP 服务器,处理 HTTP 请求。
- 将 HTTP 请求转换为 gRPC 请求。
- 将 gRPC 响应转换为 HTTP 响应。
- 返回 HTTP 响应。
详细代码可参考 gateway。
type ( GatewayConf struct { rest.RestConf Upstreams []Upstream Timeout time.Duration `json:",default=5s"` }
RouteMapping struct { Method string Path string RpcPath string }
Upstream struct { Name string `json:",optional"` Grpc zrpc.RpcClientConf ProtoSets []string `json:",optional"` Mappings []RouteMapping `json:",optional"` })GatewayConf
Section titled “GatewayConf”| 说明 | 类型 | 是否必填 | 示例 | |
|---|---|---|---|---|
| RestConf | rest 服务配置 | RestConf | 是 | 参考基础服务配置 |
| Upstreams | gRPC 服务配置 | []Upstream | 是 | |
| Timeout | 超时时间 | duration | 否 | 5s |
Upstream
Section titled “Upstream”| 说明 | 类型 | 是否必填 | 示例 | |
|---|---|---|---|---|
| Name | 服务名称 | string | 否 | demo1-gateway |
| Grpc | gRPC 服务配置 | RpcClientConf | 是 | 参考RPC 配置 |
| ProtoSets | proto 文件列表 | []string | 否 | ["hello.pb"] |
| Mappings | 路由映射,不填则默认映射所有 grpc 路径 | []RouteMapping | 否 |
RouteMapping
Section titled “RouteMapping”| 说明 | 类型 | 是否必填 | 示例 | |
|---|---|---|---|---|
| Method | HTTP 方法 | string | 是 | get |
| Path | HTTP 路径 | string | 是 | /ping |
| RpcPath | gRPC 路径 | string | 是 | hello.Hello/Ping |
在 go-zero 中,有两种方式使用 gRPC 网关: protoDescriptor 和 grpcReflection。
protoDescriptor protoDescriptor 方式需要通过 protoc 将 proto 生成为 pb 文件,然后在 gateway 中去引用该 pb 文件做 rest-grpc 的规则映射。
- 我们新建一个工程 demo1, 在 demo1 中新建一个 hello.proto 文件,如下:
syntax = "proto3";
package hello;
option go_package = "./hello";
message Request {}
message Response { string msg = 1;}
service Hello { rpc Ping(Request) returns(Response);}- 在
demo1目录下创建gateway目录,然后在demo1目录执行如下指令生成 protoDescriptor:
$ protoc --descriptor_set_out=gateway/hello.pb hello.proto- 在
demo1目录下执行如下指令生成 grpc 服务代码:
$ goctl rpc protoc hello.proto --go_out=server --go-grpc_out=server --zrpc_out=server为 demo1/server/internal/logic/pinglogic.go 中的 Ping 方法填充逻辑,代码如下:
func (l *PingLogic) Ping(in *hello.Request) (*hello.Response, error) { return &hello.Response{ Msg: "pong", }, nil}- 修改配置文件
demo1/server/etc/hello.yaml内容如下:
Name: hello.rpcListenOn: 0.0.0.0:8080- 进入
demo1/gateway目录,创建目录etc,然后添加配置文件gateway.yaml,如下:
Name: demo1-gatewayHost: localhostPort: 8888Upstreams: - Grpc: Target: localhost:8080 # protoset mode ProtoSets: - hello.pb # Mappings can also be written in proto options Mappings: - Method: get Path: /ping RpcPath: hello.Hello/Ping- 进入
demo1/gateway目录, 新建gateway.go文件,内容如下:
package main
import ( "flag"
"github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/gateway")
var configFile = flag.String("f", "etc/gateway.yaml", "config file")
func main() { flag.Parse()
var c gateway.GatewayConf conf.MustLoad(*configFile, &c) gw := gateway.MustNewServer(c) defer gw.Stop() gw.Start()}- 分别开两个终端启动 grpc server 服务和 gateway 服务,然后访问
http://localhost:8888/ping:
# 进入 demo1/server 目录下,启动 grpc 服务$ go run hello.goStarting rpc server at 0.0.0.0:8080...# 进入 demo1/gateway 目录下,启动 gateway 服务$ go run gateway.go# 新开一个终端,访问 gateway 服务$ curl http://localhost:8888/ping{"msg":"pong"}%grpcReflection grpcReflection 方式和 protoDescriptor 方式类似,不同的是,grpcReflection 方式不需要通过 protoc 将 proto 生成为 pb 文件,而是通过 grpc 的反射机制,直接从 grpc server 中获取 proto 文件,然后在 gateway 中去引用该 proto 文件做 rest-grpc 的规则映射。
- 我们新建一个工程 demo2, 在 demo2 中新建一个 hello.proto 文件,如下:
syntax = "proto3";
package hello;
option go_package = "./hello";
message Request {}
message Response { string msg = 1;}
service Hello { rpc Ping(Request) returns(Response);}-
在
demo2目录下创建gateway目录备用 -
在
demo2目录下执行如下指令生成 grpc 服务代码:
$ goctl rpc protoc hello.proto --go_out=server --go-grpc_out=server --zrpc_out=server为 demo2/server/internal/logic/pinglogic.go 中的 Ping 方法填充逻辑,代码如下:
func (l *PingLogic) Ping(in *hello.Request) (*hello.Response, error) { return &hello.Response{ Msg: "pong", }, nil}将配置文件 demo2/server/etc/hello.yaml 修改如下:
Name: hello.rpcListenOn: 0.0.0.0:8080Mode: dev- 进入
demo2/gateway目录,创建目录etc,然后添加配置文件gateway.yaml,如下:
Name: demo1-gatewayHost: localhostPort: 8888Upstreams: - Grpc: Target: localhost:8080 # Mappings can also be written in proto options Mappings: - Method: get Path: /ping RpcPath: hello.Hello/Ping- 进入
demo2/gateway目录, 新建gateway.go文件,内容如下:
package main
import ( "flag"
"github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/gateway")
var configFile = flag.String("f", "etc/gateway.yaml", "config file")
func main() { flag.Parse()
var c gateway.GatewayConf conf.MustLoad(*configFile, &c) gw := gateway.MustNewServer(c) defer gw.Stop() gw.Start()}- 分别开两个终端启动 grpc server 服务和 gateway 服务,然后访问
http://localhost:8888/ping:
$ go run hello.goStarting rpc server at 0.0.0.0:8080...# 进入 demo1/gateway 目录下,启动 gateway 服务$ go run gateway.go# 新开一个终端,访问 gateway 服务$ curl http://localhost:8888/ping{"msg":"pong"}%