GRPC教程 4 - GRPC-Gateway教程与Transcoding
GRPC教程 4 - GRPC-Gateway教程与Transcoding
相关信息
GRPC-Gateway是protoc的一个插件,类似protoc-gen-go和protoc-gen-go-grpc插件,前者是生成.pb.go后者是生成.grpc.pb.go文件。
那么这两个插件是帮助proto文件生成go语言的插件,那么GRPC-Gateway呢,它是一个可以根据proto文件的定义生成一个反向代理器的,服务器可以将 RESTful JSON API 转换为 GRPC。
这里你会有三个疑惑?什么是反向代理器?为什么要根据proto文件生成反向代理器?为什么要将RESTful JSON API转换成GRPC?
其实这一个图就可以解释
基本示例
我们继续使用我们一开始的hello.proto文件来做示例
├── client
│ ├── go.mod
│ ├── main.go
│ └── pb
│ ├── hello_grpc.pb.go
│ ├── hello.pb.go
│ └── hello.proto
├── go.work
└── server
├── go.mod
├── go.sum
├── main.go
└── pb
├── hello_grpc.pb.go
├── hello.pb.go
└── hello.proto
这次我们发现我们每次调用server进程下的服务,每次都需要重新定义一个客户端再去调用,那能不能说,就是像gin这种框架一样,使用简单的JSON body请求就可以请求GRPC呢,这就是grpc gateway的作用了,我们来演示一下怎么来用grpc-gateway。
首先添加 gRPC-Gateway 注释,这些注释定义了我们GRPC服务如何映射为JSON的请求和响应。使用protobuf的时候,GRPC service必须用google.api.HTTP来定义。
这里我用POST /v1/hello 来 映射到 Hello 的GRPC服务。
修改后的service Greeter是这样
import "google/api/annotations.proto";
service Greetering {
rpc Hello (HelloReq) returns (HelloResp) {
option (google.api.http) = {
post: "/api/v1/hello"
body: "*"
};
};
}
在这里 google/api/annotations.proto 却是在go/include/下,也就是我们下载proto时,解压下来的文件夹。和include 同一目录。所以我们需要去跟github上去下载。
https://github.com/googleapis/googleapis/tree/master/google/api
随后需要重新加上grpc-gateway插件器创建对应的pb.gw.go文件。
在这里你需要下载grpc-gateway
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
--grpc-gateway_out=. --grpc-gateway_opt=paths=source_relative \
pb/hello.proto
现在我们开始编写main.go,我们加入gateway的方式就是新起一个goroutine 去开启gateway代理
go func() {
conn, err := grpc.DialContext(
context.Background(),
"0.0.0.0:7890",
grpc.WithBlock(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalln("Failed to dial server:", err)
}
gwmux := runtime.NewServeMux()
err = pb.RegisterGreeteringHandler(context.Background(), gwmux, conn)
if err != nil {
log.Fatalln("Failed to register gateway:", err)
}
gwServer := &http.Server{
Addr: ":8090",
Handler: gwmux,
}
// 8090端口提供gRPC-Gateway服务
log.Println("Serving gRPC-Gateway on http://0.0.0.0:8090")
log.Fatalln(gwServer.ListenAndServe())
}()
接着我们用curl脚本命令,或者电脑上可以下载下postman去测试一下。
curl -X POST http://127.0.0.1:8090/api/v1/hello '{"name": "Lixin"}'
Transcoding
下面接着来讲一下更多的匹配规则,在上个例子中,我们并没有讲解更多的路径匹配,比如/api/:name
或者传递query参数,我们来讲解一下这样的方式
首先我们先修改一下对应之前的pb/hello.proto
message HelloReq {
string name = 1;
int64 age = 2;
string msg = 3;
}
message HelloResp {
string msg = 1;
}
service Greetering {
rpc Hello (HelloReq) returns (HelloResp) {
option (google.api.http) = {
get: "/api/v1/hello/{name}"
};
};
}
来方便我们进行查看如何去使用http transcoding 规则查询。
我们再次进行编码 protoc ...
, 运行服务
接着启动服务,用curl去测试一下
curl --location 'http://127.0.0.1:8090/api/v1/hello/lixin'
{"msg":"0 / lixin / Hello"}
可以看到获取到了对应的name字段,那如果我们传入query参数呢?
curl --location 'http://127.0.0.1:8090/api/v1/hello/lixin?age=12'
{"msg":"12 / lixin / Hello"}
curl --location 'http://127.0.0.1:8090/api/v1/hello/lixin?age=12&msg=message'
{"msg":"12 / lixin / message Hello"}
可以看到,我们这样就可以给http 路由传递 query参数去访问对应的grpc服务。
那还有没有别的用法?,我们继续修改proto文件 get: "/api/v1/hello/{name=names/*}"
, 接着我们传递
curl --location 'http://127.0.0.1:8090/api/v1/hello/names/lixin?age=12&msg=message'
{"msg": "12 / names/lixin / message Hello"}
可以看到我们修改了对应的:name为{names/*},这样我们就让names/lixin
顺利传递给了name参数。
接下来,我们看看body的用法,我们一般不会用get方法去传递body,所以下面我们接着再来修改一下proto文件,让对应的http 方法称为post,然后加上body参数。
service Greetering {
rpc Hello (HelloReq) returns (HelloResp) {
option (google.api.http) = {
post: "/api/v1/hello/{name}"
body: "msg"
};
};
}
我们再来观察一下这个option里面的东西,post路径为/api/v1/hello/{name}
, 这意味着,我们可以给path传递name,然后给body传递msg参数,那剩下的age呢?我们可以用query路径传递进去,就是这样
message HelloReq {
string name = 1; // path
int64 age = 2; // query
string msg = 3; // body
}
curl --location 'http://127.0.0.1:8090/api/v1/hello/lixin?age=12' \
--header 'Content-Type: text/plain' \
--data '"eeeesae"'
{ "msg": "12 / lixin / eeeesae Hello"}
可以看到我们传递的data,就是对应到msg参数里面的。
下面我们再修改一下proto文件, 将body修改为*
service Greetering {
rpc Hello (HelloReq) returns (HelloResp) {
option (google.api.http) = {
post: "/api/v1/hello/{name}"
body: "*"
};
};
}
重新生成gw.go protoc ...
curl --location 'http://127.0.0.1:8090/api/v1/hello/lixin?age=12' \
--header 'Content-Type: application/json' \
--data '{
"name": "Lixinsss",
"age": 21,
"msg": "This is a message."
}'
可以看到,虽然body是*, 但是body里面只有传递age和msg有效,当body是*时,query参数就没有效果了。
{"msg": "21 / lixin / This is a message. Hello"}
总结
我们本篇文章,主要是介绍了GRPC-Gateway,下载gatwway插件,学习使用gateway,并且学习了传递HTTP的多种方式