四. grpc-gateway 支持
- 什么是grpc-gateway: 在上方我们编写了一个grpc服务端,并且编写了grpc客户端用来访问服务端指定接口,那么这个grpc服务端中的接口如何才能被外部通过http,RESTful 请求访问,支持基于json传递数据
- 也可以简单理解为实现一个grpc网关代理服务,可以通过http,RESTful风格访问这个代理服务,由这个代理服务通过grpc方式访问实际的grpc服务,最终实现grpc服务可以支持http,RESTful,json
- 实现方式:
- 运行grpc服务,并拿到访问地址,作为最终的目标服务(当前就是上方编写的grpc服务端)
- 下载安装grpc-gateway需要相关插件
- 因为是让目标服务支持http,拿到目标服务的.proto文件,改写使其支持http, 支持RESTful, 支持json
- 执行编译命令,通过安装的grpc-gateway插件,编译改写的.ptoto文件,生成支持http的相关go文件,也就是一个以".pb.gw.go"结尾的文件
- 基于新生成的".pb.gw.go"文件,创建grpc网关代理服务,设置代理服务访问地址,代理服务持有访问目标服务的地址
- 使用http RESTful访问代理服务,传递json数据,由代理服务将http请求转换为protobuf格式作为grpc数据发送到目标服务
- 简单一句话就是grpc-gateway是Protocol Buffers编译器协议的一个插件,通过这个插件读取Protobuf服务定义并生成一个反向代理服务器,该服务器将RESTful HTTP API转换为grpc,请求实际的grpc服务,这个代理服务底层时通过google.api.http annotations注解生成的
- 请求流程图
![在这里插入图片描述](https://img-blog.csdnimg.cn/408231abdbd54422bb08f9718ae5300a.png)
1. grpc服务
- 提供一个grpc服务端作为目标服务, 该服务是由grpc通信,现在的需求是让这个服务中的某个接口支持http RESTful访问
- 当前使用上方"服务端代码示例"中写的grpc服务作为目标服务,现在需要做的是
- 明确需求: 让该服务中的MethodOne()接口支持http RESTful访问,
- 拿到该服务的访问地址,作为目标服务,访问地址是: “localhost:8080”
- 获取到生成该服务端".proto"文件,后面要改写这个文件,通过改写后的文件生成创建网关代理服务需要的以".pb.gw.go"结尾的文件
2. 安装grpc-gateway相关插件
- 参考博客
- 参考博客1
- 参考博客2
- 参考博客3
- 参考博客4
- 下载protoc-gen-grpc-gateway包
go get -u github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
- 安装
go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
- 此时查看"/GOPATH/bin/"目录下加上前面grpc相关文件一共有四个相关的.exe可执行文件
![在这里插入图片描述](https://img-blog.csdnimg.cn/71c4fa7f5a6146c58694edc6c5fcefd9.png)
3. 改写.proto文件
- 在第一步已经拿到了grpc目标服务的.proto文件,并且知道目标服务中的MethodOne()接口要支持http访问
- 改写.proto文件,设置指定接口支持http
- 通过import导入"google/api/annotations.proto"原因是grpc-gateway是基于该注解实现的
- 目标服务的MethodOne()接口要支持http,修改.proto中的这个接口,设置支持http,设置接口请求方式,请求路径,与传参格式
- 发送http, post请求访问代理服务的"/v1/sayHello"时,代理服务会将请求代理到grpc目标服务的MethodOne()接口
- 其它不变
syntax = "proto3";
package test;
option go_package = ".;test";
import "google/api/annotations.proto";
message TestRequest {
string message = 1;
}
message TestResponse {
string message = 1;
int32 code = 2;
}
service MyGrpc {
rpc MethodOne(TestRequest) returns (TestResponse) {
option (google.api.http) = {
post: "/v1/sayHello"
body: "*"
};
}
rpc ServerStreaming(TestRequest) returns (stream TestResponse) {}
rpc ClientStreaming(stream TestRequest) returns (TestResponse) {}
rpc BidirectionalStreaming(stream TestRequest) returns (stream TestResponse) {}
}
4. 执行protoc命令生成用来创建代理服务的文件
- 回到安装grpc-gateway相关插件位置,在上方我们首先执行了一个go get命令,下载了一个工程到本地
go get -u github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
- 当该命令执行完毕后,会下载一个包到本地的"$GOPATH\pkg\mod\github.com"下,在goland编辑工具当前项目的External Libraries位置也可以看到,我们编译.proto生成支持grpc-gateway功能的文件,就是通过该包下的几个文件实现的
![在这里插入图片描述](https://img-blog.csdnimg.cn/491a7a0c622a4cba8a2ee7241265565f.png)
- 编写的.proto文件中import导入的包与接口支持http用到的就在"github.com\grpc-ecosystem\grpc-gateway@v1.16.0\third_party\googleapis\google"这个文件夹中
- 正常情况下切换到.proto所在文件执行下方命令编译生成文件即可(我这里一直找报找不到需要的导入到文件)
proto编译引用外部包问题
protoc -I $GOPATH/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.16.0/third_party/googleapis --go_out=plugins=grpc:. ./my_test.proto
- 由于上方一直报错,解决这个问题,创建一个文件夹例如"pbfiles"将"$GOPATH/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.16.0/third_party/googleapis/google"这个文件夹复制到"pbfiles"中,或者在goland编辑工具当前项目的External Libraries位置—> github.com\grpc-ecosystem\grpc-gateway@v1.16.0—>third_party—>googleapis—>google,复制google文件夹到自己创建的"pbfiles"文件夹中
![在这里插入图片描述](https://img-blog.csdnimg.cn/6a0c12f1ffb945fd8fa532ed1e437702.png)
- 将需要编译的.proto文件也放到自定义创建的"pbfiles"文件夹,最终效果(注意需要编译的"my_test.proto"文件与goole文件夹是平级的)
![在这里插入图片描述](https://img-blog.csdnimg.cn/c59b139baf004518a4d85312ad052718.png)
- 切换到自定义创建的"pbfiles"文件夹执行编译命令开始编译
protoc --go_out=. --go-grpc_out=. my_test.proto
protoc --grpc-gateway_out=logtostderr=true:. my_test.proto
- 编译完成后会生成3个文件
![在这里插入图片描述](https://img-blog.csdnimg.cn/bb75477e01a8464883fcaddf9073aec5.png)
- 通过生成的"文件名.pb.gw.go"文件构建支持http的grpc-gateway服务
5. 解释用来创建grpc-gateway网关代理服务的".pb.gw.go"文件
- 查看生成的用来构建 grpc-gateway服务的"文件名.pb.gw.go"文件,内部有一个"RegisterXXXxxHandlerFromEndpoint()"函数(XXXxx通常时目标grpc服务的服务名),在创建代理服务时通过该方法,让代理服务与目标服务绑定:
- mux *runtime.ServeMux: 代理服务自己的路由
- endpoint string: 目标服务地址
- 通过该方法将目标服务注册到代理服务路由中
func RegisterMyGrpcHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.Dial(endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterMyGrpcHandler(ctx, mux, conn)
}
- 其它的还没看
6. 编写grpc-gateway网关代理服务
- 用来创建grpc-gateway网关代理服务的以".pb.gw.go"结尾的文件已经生成,目标服务也已经启动运行,并且拿到了目标服务的访问地址"localhost:50055",接下来开始编写代理服务,代理服务持有目标服务,通过http RESTful访问代理服务,代理服务接收到请求后,将http请求转换为protobuf格式作为grpc数据发送到目标服务
package main
import (
"context"
"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"google.golang.org/grpc"
"net/http"
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
grpcServerAddr := "localhost:8080"
err := RegisterMyGrpcHandlerFromEndpoint(ctx, mux, grpcServerAddr, opts)
if err != nil {
return err
}
return http.ListenAndServe(":8081", mux)
}
func main() {
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
- 此时代理服务的地址是"localhost:8081",持有的目标服务地址是"localhost:8080",发送http post请求访问代理服务的"localhost:8081/v1/sayHello"接口时,代理服务会将请求代理到grpc服务的MethodOne()接口