前言
特别是在微服务架构中
- gRPC用于内部服务间通信
- Gin用于对外提供RESTful API。
单体架构弊端
- 一旦某个服务宕机,会引起整个应用不可用,隔离性差
- 只能整体应用进行伸缩,浪费服务器资源,可伸缩性差
- 代码耦合在一起,可维护性差
微服务架构
- 解决了单体架构的弊端
微服务架构同时引入了新的问题
- 代码冗余,例如微服务中多个应用都需要Web框架代码
- 提高了应用部署、运维、可观测复杂程度需借助云原生架构
- 微服务架构中服务之间调用关系复杂、多变,需要动态发现和高性能RPC框架
微服务带火了RPC
随着微服务架构的兴起,单体应用被拆分成微服务架构,多个微服务应用通常部署在多个不同的主机之上,被网络隔开。
不同微服务应用间想要交互通信,需要跨服务器/主机发起网络调用,做不到之前单体应用那样,在本地调用函数一样简单了。
提起网络调用,我们能立马想到就是HTTP协议,但是当前HTTP协议大多使用HTTP/1.X版本;
RPC需求催生gRPC
在微服务架构中,HTTP虽然便捷方便,但性能较低;
这时候就需要引入RPC(远程过程调用),通过自定义协议发起TCP调用,来加快传输效率。
RPC框架使得我们能在云原生架构中, 更容易地创建分布式应用和服务,go-zero微服务框架底层封装了gRPC;
gRPC一开始是由Google开发,是1款语言中立、平台中立、开源的远程过程调用(RPC)系统。
gRPC系统是基于HTTP/2协议封装的RPC框架。
gRPC默认使用protocol buffers序列化和反序列化机制,当然也可以使用其他数据格式如JSON。
gRPC 客户端和服务端可以在多种环境中运行和交互 - 从Google内部的服务器到本地笔记本,并且可以用任何 gRPC 支持的语言来编写。
可以很容易地用Java 创建1个 gRPC 服务端,用 Go、Python、Ruby 来创建客户端。
Google 最新 API 将有 gRPC 版本的接口,使你很容易地将 Google 的功能集成到你的应用里。
一、HTTP/2协议
HTTP/2 是 HTTP 协议的第二个主要版本,它在 HTTP/1.x 的基础上引入了许多改进,以提高网络性能和效率。
以下是 HTTP/2 与 HTTP/1.x 之间的一些主要区别
二进制协议
- HTTP/2:采用二进制格式,而不是 HTTP/1.x 的文本格式。这使得解析更快、更高效。
- HTTP/1.x:基于文本的协议,头部和消息体都是以文本形式传输。
多路复用
- HTTP/2:支持多路复用,即在单一的 TCP 连接上同时发送多个请求和响应,而不需要等待前一个请求完成。
- HTTP/1.x:每个请求/响应都需要自己的 TCP 连接,或者在 HTTP/1.1 中使用持久连接(Connectionkeep-alive),但仍然需要按顺序发送请求和接收响应。
头部压缩
- HTTP/2:引入了 HPACK 压缩算法,对请求和响应的头部进行压缩,减少了冗余头部信息的传输。
- HTTP/1.x:头部信息未经压缩,可能导致大量的重复数据传输。
服务器推送
- HTTP/2:服务器可以主动向客户端推送资源,而不需要客户端明确请求这些资源。
- HTTP/1.x:服务器不能主动推送资源,除非客户端请求。
流控制
- HTTP/2:使用流控制机制来防止数据过载,允许接收方控制发送方的数据流量。
- HTTP/1.x:没有内建的流控制机制。
优先级
- HTTP/2:允许客户端为请求设置优先级,这样服务器可以优先处理更重要的请求。
- HTTP/1.x:没有内建的请求优先级机制。
安全性
- HTTP/2:设计时考虑了与 TLS/SSL 的兼容性,虽然不是强制性的,但推荐使用 HTTPS。
- HTTP/1.x:也可以通过 HTTPS 使用 TLS/SSL,但在 HTTP/2 中安全性更加突出。
性能
- HTTP/2:由于上述特性,HTTP/2 通常比 HTTP/1.x 提供更好的性能,尤其是在高延迟网络环境中。
兼容性
- HTTP/2:设计时考虑了向后兼容性,但某些旧的 HTTP/1.x 特性(如管道)在 HTTP/2 中不再支持。
- HTTP/1.x:广泛支持,但性能和效率不如 HTTP/2。总的来说,HTTP/2 旨在解决 HTTP/1.x 在性能和效率方面的一些限制,特别是在高并发和高延迟的网络环境中
二、gRPC框架
gRPC框架拥有以下优势
- 支持C++、Java、Go、Python、Ruby、Node.js、Android Java、C#、Objective-C、PHP
- 序列化和反序列化速度快
- 报文体积小
- 传输效率高;
与许多RPC系统类似,gRPC也是基于以下理念:
- 定义1个服务,指定其能够被远程调用的方法(包含参数和返回类型)
- 服务端实现这个接口,运行1个gRPC服务器来处理客户端调用
- 客户端应用程序通过存根(Stub)将参数编码成二进制并通过HTTP/2传输发送给服务器。存根提供了与服务端相同的方法,客户端可以调用这些方法,存根会将调用的参数包装在适当的Protocol Buffer消息类型中,然后将请求发送到服务器,并返回服务器的Protocol Buffer响应。
RPC调用流程
下图为1个完成的RPC调用流程:
准备gRPC开发环境
1.安装protoc工具
protoc是Google Protocol Buffers的编译器,它用于将.proto
文件编译成各种编程语言的代码。
protoc支持多种语言,包括C++、Java、Python、Go等,可以直接从.proto
文件生成对应语言的数据结构代码。
下载地址:https://github.com/protocolbuffers/protobuf/releases 这里下载protoc-3.19.1-win64.zip: https://github.com/protocolbuffers/protobuf/releases/download/v3.19.1/protoc-3.19.1-win64.zip 614345/article/details/131860694
2.安装protoc-gen-go插件
protoc-gen-go是protoc的1个插件,专门用于生成Go语言的代码。
go install github.com/golang/protobuf/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
3.hello.proto
// 指定proto版本 syntax = "proto3"; // 指定包名 //package mypackage; /* 指定文件生成的路径和包名 ./hello为路径 service为生成的包名 如果指定的go_package="./hello";则包名和路径同名(如果路径有多层,则包名和路径的最后一层相同) */ option go_package = "./hello;service"; // 定义Hello服务 service Hello { // 定义SayHello方法 rpc SayHello(HelloRequest) returns (HelloReply) {} } // HelloRequest 请求结构 message HelloRequest { string name = 1; } // HelloReply 响应结构 message HelloReply { string message = 1; }
4.生成hello.pb.go和hello_grpc.pb.go文件
hello.pb.go
负责处理 Protocol Buffers 的数据结构;
// 指定proto版本 // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.35.1 // protoc v3.19.1 // source: hello.proto package service import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // HelloRequest 请求结构 type HelloRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } func (x *HelloRequest) Reset() { *x = HelloRequest{} mi := &file_hello_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *HelloRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*HelloRequest) ProtoMessage() {} func (x *HelloRequest) ProtoReflect() protoreflect.Message { mi := &file_hello_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. func (*HelloRequest) Descriptor() ([]byte, []int) { return file_hello_proto_rawDescGZIP(), []int{0} } func (x *HelloRequest) GetName() string { if x != nil { return x.Name } return "" } // HelloReply 响应结构 type HelloReply struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` } func (x *HelloReply) Reset() { *x = HelloReply{} mi := &file_hello_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *HelloReply) String() string { return protoimpl.X.MessageStringOf(x) } func (*HelloReply) ProtoMessage() {} func (x *HelloReply) ProtoReflect() protoreflect.Message { mi := &file_hello_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use HelloReply.ProtoReflect.Descriptor instead. func (*HelloReply) Descriptor() ([]byte, []int) { return file_hello_proto_rawDescGZIP(), []int{1} } func (x *HelloReply) GetMessage() string { if x != nil { return x.Message } return "" } var File_hello_proto protoreflect.FileDescriptor var file_hello_proto_rawDesc = []byte{ 0x0a, 0x0b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x31, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x28, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x0d, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x11, 0x5a, 0x0f, 0x2e, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_hello_proto_rawDescOnce sync.Once file_hello_proto_rawDescData = file_hello_proto_rawDesc ) func file_hello_proto_rawDescGZIP() []byte { file_hello_proto_rawDescOnce.Do(func() { file_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_hello_proto_rawDescData) }) return file_hello_proto_rawDescData } var file_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_hello_proto_goTypes = []any{ (*HelloRequest)(nil), // 0: HelloRequest (*HelloReply)(nil), // 1: HelloReply } var file_hello_proto_depIdxs = []int32{ 0, // 0: Hello.SayHello:input_type -> HelloRequest 1, // 1: Hello.SayHello:output_type -> HelloReply 1, // [1:2] is the sub-list for method output_type 0, // [0:1] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_hello_proto_init() } func file_hello_proto_init() { if File_hello_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_hello_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 1, }, GoTypes: file_hello_proto_goTypes, DependencyIndexes: file_hello_proto_depIdxs, MessageInfos: file_hello_proto_msgTypes, }.Build() File_hello_proto = out.File file_hello_proto_rawDesc = nil file_hello_proto_goTypes = nil file_hello_proto_depIdxs = nil }
hello_grpc.pb.go
负责处理 gRPC 服务的通信;
// 指定proto版本 // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 // - protoc v3.19.1 // source: hello.proto package service import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.64.0 or later. const _ = grpc.SupportPackageIsVersion9 const ( Hello_SayHello_FullMethodName = "/Hello/SayHello" ) // HelloClient is the client API for Hello service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // // 定义Hello服务 type HelloClient interface { // 定义SayHello方法 SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) } type helloClient struct { cc grpc.ClientConnInterface } func NewHelloClient(cc grpc.ClientConnInterface) HelloClient { return &helloClient{cc} } func (c *helloClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(HelloReply) err := c.cc.Invoke(ctx, Hello_SayHello_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // HelloServer is the server API for Hello service. // All implementations must embed UnimplementedHelloServer // for forward compatibility. // // 定义Hello服务 type HelloServer interface { // 定义SayHello方法 SayHello(context.Context, *HelloRequest) (*HelloReply, error) mustEmbedUnimplementedHelloServer() } // UnimplementedHelloServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedHelloServer struct{} func (UnimplementedHelloServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) { return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") } func (UnimplementedHelloServer) mustEmbedUnimplementedHelloServer() {} func (UnimplementedHelloServer) testEmbeddedByValue() {} // UnsafeHelloServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to HelloServer will // result in compilation errors. type UnsafeHelloServer interface { mustEmbedUnimplementedHelloServer() } func RegisterHelloServer(s grpc.ServiceRegistrar, srv HelloServer) { // If the following call pancis, it indicates UnimplementedHelloServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&Hello_ServiceDesc, srv) } func _Hello_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(HelloRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(HelloServer).SayHello(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Hello_SayHello_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HelloServer).SayHello(ctx, req.(*HelloRequest)) } return interceptor(ctx, in, info, handler) } // Hello_ServiceDesc is the grpc.ServiceDesc for Hello service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var Hello_ServiceDesc = grpc.ServiceDesc{ ServiceName: "Hello", HandlerType: (*HelloServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "SayHello", Handler: _Hello_SayHello_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "hello.proto", }
在实际开发中,您需要这两个文件来完整地实现 gRPC 服务的序列化、服务定义和通信逻辑;
protoc --go_out=. hello.proto
protoc --go-grpc_out=. hello.proto
5.服务端
package main import ( "context" "fmt" pd "gRPC-demo/gRPC-server/proto/hello" "google.golang.org/grpc" "net" ) //继承hello_grpc.pb.go中的UnimplementedHelloServer结构体 type serverMsg struct { pd.UnimplementedHelloServer } //实现hello_grpc.pb.go中的接口 func (s *serverMsg) SayHello(contxt context.Context, request *pd.HelloRequest) (*pd.HelloReply, error) { return &pd.HelloReply{Message: "hello " + request.Name}, nil } func main() { //开启端口 listen, _ := net.Listen("tcp", ":8001") //创建gRPC服务 grpcServer := grpc.NewServer() //在gRPC服务端注册我们自己编写的服务 pd.RegisterHelloServer(grpcServer, &serverMsg{}) //启动服务 err := grpcServer.Serve(listen) if err != nil { fmt.Println("服务端启动失败", err) return } }
6.客户端
package main import ( "context" "fmt" pd "gRPC-demo/gRPC-client/proto/hello" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) func main() { conn, err := grpc.Dial("127.0.0.1:8001", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { fmt.Println("连接错误", err) } defer conn.Close() //建立连接 client := pd.NewHelloClient(conn) //执行RPC调用(这个方法在服务器来实现并返回结果 response, _ := client.SayHello(context.Background(), &pd.HelloRequest{Name: "zhanggen"}) fmt.Println(response.GetMessage()) }
三、gRPC安全认证
开发了gRPC服务端和客户端之后,发现任何gRPC客户端只要知道gRPC服务端的IP和端口都可以和服务端建立连接,获取服务端数据;
gRPC框架支持以下安全认证机制,保证信息安全传输;
- SSL/TSL认证方式(采用http2协议)
- 基于Token的认证方式
1.SSL/TSL认证
key:服务器上的私钥文件,用于给Response内容加密,及Request内容解密
crt文件:由证书颁发机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者签名等信息
csr文件:证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名。
pem文件:基于Base64编码的证书格式,扩展名包扩PEM、CRT和CER
以下步骤会生成8个文件
1.生成私钥 openssl genrsa -out server.key 2048 2.生成crt证书文件 全部回车即可,可以不填 openssl req -new -x509 -key server.key -out server.crt -days 36500 3.生成csr文件 openssl req -new -key server.key -out server.csr 4.更改openss1.cnf (Linux 是openss1.cfg)1 #1)复制一份你安装的openssl的bin目录里面的openssl.cnf 文件到你项目所在的目录下 #2)找到[CA_default],打开 copy_extensions = copy (就是把前面的#去掉) #3)找到[req],打开req_extensions = v3_req # The extensions to add to a certificate request #4)找到[v3_req],添加 subjectAltName = @alt_names #5)添加新的标签[alt_names],和标签字段 [alt_names] DNS.1 =*.zhanggen.com 5.生成证书私钥test.key genpkey -algorithm RSA -out test.key 6.#通过私钥test.key生成证书请求文件test.csr(注意cfg和cnf) openssl req -new -nodes -key test.key -out test.csr -days 3650 -subj "/C=cn/OU=myorg/O=mycomp/CN=myname" -config ./openssl.cfg -extensions v3_req 7.# test.csr是上面生成的证书请求文件。ca.crt/server.key是CA证书文件和key,用来对test.csr进行签名认证。这两个文件在第一部分生成。 8.# 生成SAN证书 pem openssl x509 -req -days 365 -in test.csr -out test.pem -CA server.crt -CAkey server.key -CAcreateserial -extfile ./openssl.cfg -extensions v3_req
2.使用SSL/TSL认证后的服务端和客户端
服务端
package main import ( "context" "fmt" pd "gRPC-demo/gRPC-server/proto/hello" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "net" ) //继承hello_grpc.pb.go中的UnimplementedHelloServer结构体 type serverMsg struct { pd.UnimplementedHelloServer } //实现hello_grpc.pb.go中的接口 func (s *serverMsg) SayHello(contxt context.Context, request *pd.HelloRequest) (*pd.HelloReply, error) { return &pd.HelloReply{Message: "hello " + request.Name}, nil } func main() { //创建服务端Credential:两个参数分别是cerFile(自签证书文件)和keyFile(私钥文件) myCredential, err := credentials.NewServerTLSFromFile( "D:\\workspace\\goProject\\gRPC-demo\\key\\test.pem", "D:\\workspace\\goProject\\gRPC-demo\\key\\test.key") //开启端口 listen, _ := net.Listen("tcp", ":8001") //创建gRPC服务 grpcServer := grpc.NewServer(grpc.Creds(myCredential)) //在gRPC服务端注册我们自己编写的服务 pd.RegisterHelloServer(grpcServer, &serverMsg{}) //启动服务 err = grpcServer.Serve(listen) if err != nil { fmt.Println("服务端启动失败", err) return } }
客户端
package main import ( "context" "fmt" pd "gRPC-demo/gRPC-client/proto/hello" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) func main() { //创建客户端Credential:参数分别是cerFile和serverNameOverride clientCredential, _ := credentials.NewClientTLSFromFile( "D:\\workspace\\goProject\\gRPC-demo\\key\\test.pem", "*.zhanggen.com") conn, err := grpc.Dial("127.0.0.1:8001", grpc.WithTransportCredentials(clientCredential)) //客户端请求服务端不携带证书报错 //conn, err := grpc.Dial("127.0.0.1:8001", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { fmt.Println("连接错误", err) } defer conn.Close() //建立连接 client := pd.NewHelloClient(conn) //执行RPC调用(这个方法在服务器来实现并返回结果 response, _ := client.SayHello(context.Background(), &pd.HelloRequest{Name: "zhanggen"}) fmt.Println(response.GetMessage()) }
3.Token认证后的服务端和客户端
grpc提供的1个接口,这1个接口有2个方法,接口位于credentials包下,这个接口需要客户端来实现;
- 第一个方法作用是获取元数据信息,也就是客户端提供的key-value对,context用于控制超时和取消,uri是请求入口处的uri
- 第二个方法作用是是否需要基于TLS认证进行安全传输,如果返回值是true,则必须加上TLS验证,返回值是false则不用
type PerRPCCredentials interface { GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) RequireTransportSecurity() bool }
服务端
package main import ( "context" "errors" "fmt" pd "gRPC-demo/gRPC-server/proto/hello" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "net" ) //继承hello_grpc.pb.go中的UnimplementedHelloServer结构体 type serverMsg struct { pd.UnimplementedHelloServer } //业务代码:实现hello_grpc.pb.go中的接口 func (s *serverMsg) SayHello(ctx context.Context, request *pd.HelloRequest) (*pd.HelloReply, error) { //google.golang.org/grpc/metadata,获取客户端请求携带的元数据信息 md, ok := metadata.FromIncomingContext(ctx) if !ok { return nil, errors.New("未传输token") } var username string var password string if v, ok := md["username"]; ok { username = v[0] } if v, ok := md["password"]; ok { password = v[0] } if username != "zhanggen" || password != "123456" { return nil, errors.New("token错误") } return &pd.HelloReply{Message: "hello " + request.Name}, nil } func main() { //创建服务端Credential:两个参数分别是cerFile(自签证书文件)和keyFile(私钥文件) myCredential, err := credentials.NewServerTLSFromFile( "D:\\workspace\\goProject\\gRPC-demo\\key\\test.pem", "D:\\workspace\\goProject\\gRPC-demo\\key\\test.key") //开启端口 listen, _ := net.Listen("tcp", ":8001") //创建gRPC服务 grpcServer := grpc.NewServer(grpc.Creds(myCredential)) //在gRPC服务端注册我们自己编写的服务 pd.RegisterHelloServer(grpcServer, &serverMsg{}) //启动服务 err = grpcServer.Serve(listen) if err != nil { fmt.Println("服务端启动失败", err) return } }
客户端
package main import ( "context" "fmt" pd "gRPC-demo/gRPC-client/proto/hello" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) //google.golang.org/grpc/credentials包中Copy出来的代码 //type PerRPCCredentials interface { // GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) // RequireTransportSecurity() bool //} type ClientTokenAuth struct { } func (c *ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { return map[string]string{ "username": "zhanggen", "password": "123456", }, nil } func (c *ClientTokenAuth) RequireTransportSecurity() bool { return true } func main() { //创建客户端Credential:参数分别是cerFile和serverNameOverride clientCredential, _ := credentials.NewClientTLSFromFile( "D:\\workspace\\goProject\\gRPC-demo\\key\\test.pem", "*.zhanggen.com") //携带Token var opts []grpc.DialOption opts = append(opts, grpc.WithTransportCredentials(clientCredential)) opts = append(opts, grpc.WithPerRPCCredentials(&ClientTokenAuth{})) //请求服务端 conn, err := grpc.Dial("127.0.0.1:8001", opts...) //客户端请求服务端不携带证书报错 //conn, err := grpc.Dial("127.0.0.1:8001", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { fmt.Println("连接错误", err) } defer conn.Close() //建立连接 client := pd.NewHelloClient(conn) //执行RPC调用(这个方法在服务器来实现并返回结果 response, _ := client.SayHello(context.Background(), &pd.HelloRequest{Name: "zhanggen"}) fmt.Println(response.GetMessage()) }