gRPC-Gateway

安装依赖

需要安装protoc-gen-grpc-gateway插件来生成对应的 grpc-gateway 代码

1
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2

在 proto 文件中,我们会使用到 google/api/annotations.proto 文件,所以还需要将 https://github.com/googleapis/googleapis 下的 google/api 目录拷贝到 proto 安装目录下的 google 目录中。这样才能在 proto 文件中使用 annotations.proto,必保证在生成代码的时候不会出错。

proto 文件

完整代码示例:https://github.com/rexyan/Go-Microservice/tree/main/gRPC-Gateway

在 proto 文件中,我们要引入 annotations.proto 文件,并将需要暴露的方法转为 http 接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
syntax = "proto3";

option go_package="client/pb";

package gateway;
import "google/api/annotations.proto"; // 导入google/api/annotations.proto

service Greeter {
rpc SayHello(HelloRequest) returns (HelloResponse){
// 将 SayHello 方法暴露成 http 接口
option (google.api.http) = {
post: "/v1/hello"
body: "*"
};
}
}

message HelloRequest{
string name=1;
}

message HelloResponse{
string reply =1;
}

// protoc --proto_path=pb --go_out=pb --go_opt=paths=source_relative hello.proto
// protoc --proto_path=pb --go-grpc_out=pb --go-grpc_opt=paths=source_relative hello.proto
// protoc --proto_path=pb --grpc-gateway_out=pb --grpc-gateway_opt=paths=source_relative hello.proto 客户端可不执行生成 grpc-gateway 文件

服务端

服务端需要启动 gRPC Server(协程的方式),并且创建了一个 grpc 客户端连接到了上面创建的 gRPC Server。

然后用 gateway 生成的 pb.RegisterGreeterHandler 方法将 gwmux 和 conn 关联起来。最后将 gwmux 注册到 Http 的 server 上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package main

import (
"context"
"fmt"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime" // 注意是 v2 版本的
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
"net"
"net/http"
"server/pb"
)

type server struct {
pb.UnimplementedGreeterServer
}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
return &pb.HelloResponse{
Reply: "Hello:" + in.Name,
}, nil
}

func main() {
listen, err := net.Listen("tcp", ":9999")
if err != nil {
return
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
// 这里需要使用协程,不然会阻塞
go func() {
log.Fatalln(s.Serve(listen))
}()

// 以下为添加 gateway 的代码
// 创建一个连接到我们刚刚启动的 gRPC 服务器,gRPC-Gateway 就是通过它来代理请求(将HTTP请求转为RPC请求)
conn, err := grpc.DialContext(
context.Background(),
"0.0.0.0:9999", // 连接 server
grpc.WithBlock(), // 阻塞直到连接成功
grpc.WithTransportCredentials(insecure.NewCredentials()), // 不安全的连接
)
if err != nil {
fmt.Printf("连接 grpc server 错误:%v\n", err.Error())
return
}

// 注意这里的 runtime 是 grpc-gateway v2 版本中的
gwmux := runtime.NewServeMux()
// 将 grpc-gateway 生成的 handler 方法和 gwmux 和 conn 关联起来。
err = pb.RegisterGreeterHandler(context.Background(), gwmux, conn)
if err != nil {
fmt.Printf("RegisterGreeterHandler 错误:%v\n", err.Error())
return
}
// 启动 http server
gwServer := &http.Server{
Addr: ":8090",
Handler: gwmux,
}
log.Println("Serving gRPC-Gateway on http://0.0.0.0:8090")
log.Fatalln(gwServer.ListenAndServe())
}

客户端

客户端还和以前一样,还是普通的 rpc 客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"client/pb"
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

func main() {
conn, err := grpc.Dial("127.0.0.1:9999", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return
}
c := pb.NewGreeterClient(conn)
response, err := c.SayHello(context.Background(), &pb.HelloRequest{
Name: "张三",
})
if err != nil {
return
}
fmt.Println(response.Reply)
}

测试

  1. 用 rpc 客户端调用 rpc 服务端,确认服务正常。

  1. 使用 postman 调用 http api 接口,确保服务正常

原理

GRPC-Gateway 是 Google protocol buffers 编译器 protoc 的一个插件。它读取 Protobuf 服务定义并生成一个反向代理服务器,该服务器将 RESTful HTTP API 转换为 gRPC。该服务器是根据服务定义中的 google.api.http 注释生成的。

HttpRule

gRPC Gateway是一种HTTP反向代理服务器,可以将RESTful API请求转换为gRPC服务调用。在gRPC Gateway中,可以使用HttpRule来定义HTTP请求和gRPC方法之间的映射关系。

HttpRule是一个proto3消息,定义在google.api.http包中。它允许您为每个HTTP谓词(GET、PUT、POST等)定义一个或多个HTTP模式,并将它们映射到gRPC方法。下面是一个示例,展示如何使用HttpRule将HTTP GET请求映射到gRPC服务方法:

1
2
3
4
5
6
7
8
9
import "google/api/annotations.proto";

service MyService {
rpc MyMethod(MyRequest) returns (MyResponse) {
option (google.api.http) = {
get: "/v1/myresource/{resource_id}"
};
}
}

在上面的示例中,MyMethod是gRPC服务方法,它定义在MyService服务中。使用google.api.http选项,可以将HTTP GET请求映射到该方法。具体来说,get: "/v1/myresource/{resource_id}"指定将HTTP GET请求映射到/v1/myresource/{resource_id}路径,其中{resource_id}是一个路径参数。

GET 请求:

1
2
3
4
5
6
7
8
9
import "google/api/annotations.proto";

service MyService {
rpc GetResource(GetResourceRequest) returns (GetResourceResponse) {
option (google.api.http) = {
get: "/v1/resources/{resource_id}"
};
}
}

POST 请求:

1
2
3
4
5
6
7
8
9
10
import "google/api/annotations.proto";

service MyService {
rpc CreateResource(CreateResourceRequest) returns (CreateResourceResponse) {
option (google.api.http) = {
post: "/v1/resources"
body: "resource"
};
}
}

DELETE 请求:

1
2
3
4
5
6
7
8
9
import "google/api/annotations.proto";

service MyService {
rpc DeleteResource(DeleteResourceRequest) returns (DeleteResourceResponse) {
option (google.api.http) = {
delete: "/v1/resources/{resource_id}"
};
}
}

PUT 请求:

1
2
3
4
5
6
7
8
9
import "google/api/annotations.proto";

service MyService {
rpc UpdateResource(UpdateResourceRequest) returns (UpdateResourceResponse) {
option (google.api.http) = {
put: "/v1/resources/{resource_id}"
body: "resource"
};
}