教程 — grpc-gateway
前言
app开发定制公司本文使用的认证方式为 双向认证,app开发定制公司也可改为其他认证方式,可参考 进行改造,app开发定制公司本文不再赘述。
代码
一、Grpc网关介绍
1.1 原因
etcd3 APIapp开发定制公司全面升级为gRPC后,app开发定制公司同时还要提供REST API服务,app开发定制公司维护两个版本的服务就app开发定制公司显得不太合理,所以 诞生了。通过使用protobuf的自定义optionapp开发定制公司实现了一个网关,服务端可以同时开启Grpc服务和HTTP服务,HTTP服务负责接收客户端请求,然后将请求信息转换protobuf格式作为 Grpc 请求数据,再发送至Grpc服务,HTTP服务等从Grpc服务获取响应后再转为JSON数据返回给客户端。
1.2 补充
Grpc-Gateway是Protocol Buffers编译器协议的一个插件。它读取Protobuf服务定义并生成一个反向代理服务器,该服务器将RESTful HTTP API转换为gRPC。这种服务是根据google.api.http annotations
注解生成的,所以在项目中会使用到该注解。
1.3 流程
请求流程,当客户端发送http请求时候,grpc-gateway接受请求,生成grpc的client去请求grpc的server端;grpc-gateway实际上做了反向代理的作用。因此会产生两个服务,一个是grpc-gateway产生的http服务,负责接受客户端的http请求,一个grpc的server服务,负责处理client端的请求。
1.4 流程图
二、环境配置
2.1 需要的依赖
2.1.1 proto转go
go get -u google.golang.org/protobuf/cmd/protoc-gen-go
- 1
2.1.2 grpc
go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc
- 1
2.1.3 grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
- 1
2.1.4 对客户端提供服务的API依赖
go get -u github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2
- 1
2.2 下载结果
三、代码
3.1 加入google/api/annotations.proto
在1.2中我们讲到需要 google/api/annotations.proto
,所以还需要将这些文件到项目中,在前面环境配置完成后,去项目的 External Libraries
下 将grpc-gateway包中google包下的内容全部拷贝到项目的一个单独的目录下,注意是用 v1.16.0
版本下的,v2.10.2
下没有 。
图一
图二
3.2 在代码中的位置及整个项目结构
将 google
文件夹放到了 pbfiles
下:
3.3 创建protobuf文件
新建的 ProdGrpcGateWay.proto
在 pbfiles 目录下,与google是同级。有的 创建的是 Prod.proto
,其实都一样,但是在本项目中会出现一个小问题,等后面运行时可能会报:
{"code":12, "message":"method GetProdStock not implemented", "details":[]}
- 1
出现这个问题的原因是在本项目中集成了 的代码,多个示例中可能会出现 protobuf
文件内容相同的情况,在实现 GetProdStock 方法时有可能实现的是别的示例接口中的,所以报这个错误。
解决办法
1、修改Prod.proto
里的方法名;(本文使用的是方法)
2、单独一个项目;
ProdGrpcGateWay.proto
内容:
syntax = "proto3"; //proto3的语法,不写会默认为proto2package services; //包名,通过protoc生成go文件时使用option go_package = "../service"; //添加生成go文件的路径//必须要导入import "google/api/annotations.proto";message GrpcGateWayRequest { int32 goods_id = 1; //传入的商品id}message GrpcGateWayResponse { int32 goods_stock =1; //商品库存}service GrpcGateWayService { rpc GetGrpcGateWayStock (GrpcGateWayRequest) returns (GrpcGateWayResponse){ option (google.api.http) = { get: "/v1/prod/{goods_id}" //注意这里的路径参数要和上面 GrpcGateWayRequest 中定义的保持一致 }; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
3.4 将 ProdGrpcGateWay.proto
转成相关go文件
在 pbfiles目录下执行以下命令,注意不要写错!
3.4.1 生成 ProdGrpcGateWay.pb.go
、ProdGrpcGateWay_grpc.pb.go
的命令:
protoc --go_out=. --go-grpc_out=. *.proto
- 1
3.4.2 生成 grpc-gateway使用的 ProdGrpcGateWay.pb.gw.go
命令:
protoc --grpc-gateway_out=logtostderr=true:../service ProdGrpcGateWay.proto
- 1
3.5 添加认证证书
将在 双向认证 示例中的相关证书复制一份过来,本文是复制到了 keys2
下,关于生成相关证书的讲解可看前言中的相关链接。
3.6 服务端代码
server.go
package mainimport ( "context" "crypto/tls" "crypto/x509" "go-grpc/grpc-gateway/service" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" "io/ioutil" "log" "net")const ( // Address gRPC服务地址 ServerAddress = "127.0.0.1:8888")//1、声明一个server,里面是未实现的字段type server struct { service.UnimplementedGrpcGateWayServiceServer}//2、必须要实现在 ProdGrpcGateWay.proto 里声明的远程调用接口,否则客户端会报://rpc error: code = Unimplemented desc = method GetGrpcGateWayStock not implementefunc (s *server) GetGrpcGateWayStock(ctx context.Context, in *service.GrpcGateWayRequest) (*service.GrpcGateWayResponse, error) { return &service.GrpcGateWayResponse{GoodsStock: in.GetGoodsId()}, nil}func main() { //1、创建带ca证书验证的服务端 rpcServer := grpc.NewServer(grpc.Creds(GetServeCreds())) //2、注册服务 service.RegisterGrpcGateWayServiceServer(rpcServer, &server{}) //3、设置传输协议和监听地址 listen, err := net.Listen("tcp", ServerAddress) if err != nil { log.Fatal("服务监听端口失败", err) } log.Println("Server listen on " + ServerAddress + " with TLS") //4、启动服务 rpcServer.Serve(listen)}//加入服务端认证证书func GetServeCreds() credentials.TransportCredentials { // TLS认证 //从证书相关文件中读取和解析信息,得到证书公钥、密钥对 cert, err := tls.LoadX509KeyPair("grpc-gateway/keys2/server.pem", "grpc-gateway/keys2/server.key") if err != nil { grpclog.Fatalf("Failed to find server credentials %v", err) } certPool := x509.NewCertPool() //初始化一个CertPool ca, err := ioutil.ReadFile("grpc-gateway/keys2/ca.pem") if err != nil { grpclog.Fatalf("Failed to find root credentials %v", err) } certPool.AppendCertsFromPEM(ca) //解析传入的证书,解析成功会将其加到池子中 creds := credentials.NewTLS(&tls.Config{ //构建基于TLS的TransportCredentials选项 Certificates: []tls.Certificate{cert}, //服务端证书链,可以有多个 ClientAuth: tls.RequireAndVerifyClientCert, //要求必须验证客户端证书 ClientCAs: certPool, //设置根证书的集合,校验方式使用 ClientAuth 中设定的模式 }) return creds}
- 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
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
3.7 客户端服务代码
clientServer.go
package mainimport ( "context" "crypto/tls" "crypto/x509" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "go-grpc/grpc-gateway/service" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" "io/ioutil" "log" "net/http")const ( // ServerAddress gRPC服务地址 ServerAddress = "127.0.0.1:8888" //ClientAddress 是浏览器等发送http请求时的地址 ClientAddress = "127.0.0.1:8080")func main() { ctx := context.Background() ctx, cancelFunc := context.WithCancel(ctx) defer cancelFunc() //1、创建路由 mux := runtime.NewServeMux() //2、加入认证证书 opt := []grpc.DialOption{grpc.WithTransportCredentials(GetClientCreds())} //3、注册请求服务端的方法 err := service.RegisterGrpcGateWayServiceHandlerFromEndpoint(ctx, mux, ServerAddress, opt) if err != nil { log.Fatalf("cannot start grpc gateway: %v", err) } //4、启动并监听http请求 err = http.ListenAndServe(ClientAddress, mux) if err != nil { log.Fatalf("cannot listen and server: %v", err) } log.Println("ClientServer listen on " + ServerAddress + " with TLS")}//加入客户端认证证书func GetClientCreds() credentials.TransportCredentials { // TLS连接 //从证书相关文件中读取和解析信息,得到证书公钥、密钥对 cert, err := tls.LoadX509KeyPair("grpc-gateway/keys2/client.pem", "grpc-gateway/keys2/client.key") if err != nil { grpclog.Fatalf("Failed to find client credentials %v", err) } certPool := x509.NewCertPool() ca, err := ioutil.ReadFile("grpc-gateway/keys2/ca.pem") if err != nil { grpclog.Fatalf("Failed to find root credentials %v", err) } certPool.AppendCertsFromPEM(ca) creds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, //客户端证书 ServerName: "ximu.info", //注意这里的参数为配置文件中所允许的ServerName,也就是其中配置的DNS... RootCAs: certPool, }) return creds}
- 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
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
附: client.go
(测试与服务端通信是否正常的客户端)
package mainimport ( "context" "crypto/tls" "crypto/x509" "fmt" "go-grpc/grpc-gateway/service" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "io/ioutil" "log")const ( // Address gRPC服务地址 Address = "127.0.0.1:8888")func main() { // 证书认证-双向认证 // 从证书相关文件中读取和解析信息,得到证书公钥、密钥对 cert, _ := tls.LoadX509KeyPair("grpc-gateway/keys2/client.pem", "grpc-gateway/keys2/client.key") // 创建一个新的、空的 CertPool certPool := x509.NewCertPool() ca, _ := ioutil.ReadFile("grpc-gateway/keys2/ca.pem") //注意这里只能解析pem类型的根证书,所以需要的是ca.pem // 尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用 certPool.AppendCertsFromPEM(ca) // 构建基于 TLS 的 TransportCredentials 选项 creds := credentials.NewTLS(&tls.Config{ // 设置证书链,允许包含一个或多个 Certificates: []tls.Certificate{cert}, ServerName: "ximu.info", //注意这里的参数为配置文件中所允许的ServerName,也就是其中配置的DNS... RootCAs: certPool, }) //1、 建立连接 conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds)) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() request := &service.GrpcGateWayRequest{ GoodsId: 123, } // 2. 调用 ProdGrpcGateWay_grpc.pb.go 中的 NewGrpcGateWayServiceClient 方法建立客户端 query := service.NewGrpcGateWayServiceClient(conn) //3、调用rpc方法 res, err := query.GetGrpcGateWayStock(context.Background(), request) if err != nil { log.Fatal("调用gRPC方法错误: ", err) } fmt.Println("grpc-gateway:调用gRPC方法成功,GoodsStock = ", res)}
- 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
四、测试
4.1 启动服务
先启动 server.go
,再启动 clientServer.go