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。大致流程如下:
- 从 proto 文件中解析出 gRPC 服务的定义。
- 从 配置文件中解析出 gRPC 服务的 HTTP 映射规则。
- 根据 gRPC 服务的定义和 HTTP 映射规则,生成 gRPC 服务的 HTTP 处理器。
- 启动 HTTP 服务器,处理 HTTP 请求。
- 将 HTTP 请求转换为 gRPC 请求。
- 将 gRPC 响应转换为 HTTP 响应。
- 返回 HTTP 响应。
详细代码可参考 gateway。
配置介绍
type (GatewayConf struct {rest.RestConfUpstreams []UpstreamTimeout time.Duration `json:",default=5s"`}RouteMapping struct {Method stringPath stringRpcPath string}Upstream struct {Name string `json:",optional"`Grpc zrpc.RpcClientConfProtoSets []string `json:",optional"`Mappings []RouteMapping `json:",optional"`})
GatewayConf
名称 | 说明 | 类型 | 是否必填 | 示例 |
|---|---|---|---|---|
| RestConf | rest 服务配置 | RestConf | 是 | 参考基础服务配置 |
| Upstreams | gRPC 服务配置 | []Upstream | 是 | |
| Timeout | 超时时间 | duration | 否 | 5s |
Upstream
名称 | 说明 | 类型 | 是否必填 | 示例 |
|---|---|---|---|---|
| Name | 服务名称 | string | 否 | demo1-gateway |
| Grpc | gRPC 服务配置 | RpcClientConf | 是 | 参考RPC 配置 |
| ProtoSets | proto 文件列表 | []string | 否 | [“hello.pb”] |
| Mappings | 路由映射,不填则默认映射所有 grpc 路径 | []RouteMapping | 否 |
RouteMapping
名称 | 说明 | 类型 | 是否必填 | 示例 |
|---|---|---|---|---|
| Method | HTTP 方法 | string | 是 | get |
| Path | HTTP 路径 | string | 是 | /ping |
| RpcPath | gRPC 路径 | string | 是 | hello.Hello/Ping |
使用示例
在 go-zero 中,有两种方式使用 gRPC 网关: protoDescriptor 和 grpcReflection。
- protoDescriptor
- grpcReflection
protoDescriptor 方式需要通过 protoc 将 proto 生成为 pb 文件,然后在 gateway 中去引用该 pb 文件做 rest-grpc 的规则映射。
tip
go-zero sdk 版本 v1.5.0 的 gateway 配置会造成配置冲突,请避开此版本,当前示例使用的是 v1.4.4 版本
- 我们新建一个工程 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 modeProtoSets:- hello.pb# Mappings can also be written in proto optionsMappings:- Method: getPath: /pingRpcPath: hello.Hello/Ping
- 进入
demo1/gateway目录, 新建gateway.go文件,内容如下:
package mainimport ("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.GatewayConfconf.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 方式和 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); }
2. 在 `demo2` 目录下创建 `gateway` 目录备用3. 在 `demo2` 目录下执行如下指令生成 grpc 服务代码:```bash$ 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
tip
因为目前 grpc 反射模式只有 dev 和 test 环境支持,所以这里需要将 Mode 设置为 dev 或者 test。
进入
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
5. 进入 `demo2/gateway` 目录, 新建 `gateway.go` 文件,内容如下:```gopackage mainimport ("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.GatewayConfconf.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"}%