安装依赖 需要安装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 mainimport ( "context" "fmt" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "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)) }() conn, err := grpc.DialContext( context.Background(), "0.0.0.0:9999" , grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { fmt.Printf("连接 grpc server 错误:%v\n" , err.Error()) return } gwmux := runtime.NewServeMux() err = pb.RegisterGreeterHandler(context.Background(), gwmux, conn) if err != nil { fmt.Printf("RegisterGreeterHandler 错误:%v\n" , err.Error()) return } 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 mainimport ( "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) }
测试
用 rpc 客户端调用 rpc 服务端,确认服务正常。
使用 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" }; }