第五节——实现一元GRPC
Posted 想学习安全的小白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第五节——实现一元GRPC相关的知识,希望对你有一定的参考价值。
第五章——使用一元GRPC传递pcbook
5.1、定义一个 proto 服务和一个一元 RPC
- 创建新文件——
proto/laptop_service.proto
- 定义一条
CreateLaptopRequest
消息,它只包含一个字段:我们要创建的笔记本电脑
message CreateLaptopRequest
Laptop laptop = 1;
- 定义
CreateLaptopResponse
消息也只有 1 个字段:创建的笔记本电脑的 ID
message CreateLaptopResponse
string id = 1;
- 定义一元GRPC
service LaptopService
rpc CreateLaptop(CreateLaptopRequest) returns (CreateLaptopResponse) ;
5.2、配置服务端
- 创建service文件夹,创建
service/laptop_server.go
文件 - 声明一个
LaptopServer
结构和一个NewLaptopServer()
函数来返回它的一个新实例
type LaptopServer struct
pb.UnimplementedLaptopServiceServer
func NewLaptopServer() *LaptopServer
return &LaptopServer
- 在
laptop_service.proto
中定义了一个CreateLaptop
一元服务,在此go文件中需要去定义一个CreateLaptop
函数实现服务
func (server *LaptopServer) CreateLaptop(ctx context.Context, req *pb.CreateLaptopRequest) (*pb.CreateLaptopResponse, error)
return nil, nil
5.3、完善服务端代码
5.3.1、通过req获取Laptop结构体
- 由于在proto中定义的Request消息携带Laptop结构体,所以可以在函数中调用
GetLaptop
函数从请求中获取笔记本电脑对象
func (server *LaptopServer) CreateLaptop(ctx context.Context, req *pb.CreateLaptopRequest) (*pb.CreateLaptopResponse, error)
laptop := req.GetLaptop()
log.Printf("receive a create-laptop request with id: %s", laptop.Id)
return nil, nil
5.3.2、如果客户端已经生成了笔记本电脑ID,检测UUID是否有效
- 使用Google UUID包,命令:
go get github.com/google/uuid
, - 使用
uuid.Parse()
函数来解析笔记本电脑的 ID。
if len(laptop.Id) > 0
_, err := uuid.Parse(laptop.Id)
if err != nil
return nil, status.Errorf(codes.InvalidArgument, "laptop ID is not a valid UUID: %v", err)
5.3.3、如果客户端没有发送笔记本电脑 ID,我们将在服务器上使用uuid.NewRandom()
命令生成它。
else
id, err := uuid.NewRandom()
if err != nil
return nil, status.Errorf(codes.Internal, "cannot generate a new laptop ID: %v", err)
laptop.Id = id.String()
5.3.4、将获取到的Laptop进行存储
- 创建新文件
service/laptop_store.go
- 定义一个LaptopStore接口,此接口需要实现一个save方法,这样后面可以定义存储在文件中的结构体与存储在数据库中的结构体
type LaptopStore interface
save(laptop *pb.Laptop) error
- 实现接口,定义为存储在磁盘上的结构体
- 需要一个读写互斥体来处理多个并发请求以保存笔记本电脑
type InMemoryLaptopStore struct
mutex sync.RWMutex
data map[string]*pb.Laptop
- 声明一个函数
NewInMemoryLaptopStore
来返回一个新的InMemoryLaptopStore
,并初始化其中的数据映射
func NewInMemoryLaptopStore() *InMemoryLaptopStore
return &InMemoryLaptopStore
data: make(map[string]*pb.Laptop),
- 实现
InMemoryLaptopStore
结构体的save功能- 需要在添加新对象之前获取写锁
- 我们检查笔记本电脑 ID 是否已经存在于地图中
- 如果笔记本电脑不存在,先深度拷贝这个Laptop结构体后将此结构体进行存储
func (store *InMemoryLaptopStore) Save(laptop *pb.Laptop) error
store.mutex.Lock()
defer store.mutex.Unlock()
if store.data[laptop.Id] != nil
return errors.New("record already exists")
other,_ := deepCopy(laptop)
store.data[other.Id] = other
return nil
- 实现deepCopy函数
- 使用包,命令:
go get github.com/jinzhu/copier
- 使用包,命令:
func deepCopy(laptop *pb.Laptop) (*pb.Laptop, error)
other := &pb.Laptop
err := copier.Copy(other, laptop)
if err != nil
return nil, fmt.Errorf("cannot copy laptop data: %w", err)
return other, nil
5.3.5、在server端使用存储结构体进行存储
- 在laptop_server.go文件中的LaptopServer结构体添加新字段
LaptopServer
type LaptopServer struct
pb.UnimplementedLaptopServiceServer
laptopStore LaptopStore
- 在
CreateLaptop()
函数中,我们可以调用server.laptopStore.Save()
将输入的笔记本电脑保存到商店- 使用codes.Internal获取保存错误
- 调用
errors.Is()
函数检查是否存在
err := server.laptopStore.save(laptop)
if err != nil
code := codes.Internal
if errors.Is(err, errors.New("record already exists"))
code = codes.AlreadyExists
return nil, status.Errorf(code, "cannot save laptop to the store: %v", err)
- 最后,如果没有发生错误,我们可以使用笔记本电脑 ID 创建一个新的响应对象并将其返回给调用者
log.Printf("saved laptop with id: %s", laptop.Id)
res := &pb.CreateLaptopResponse
Id: laptop.Id,
5.3.6、完整的server端代码
func (server *LaptopServer) CreateLaptop(ctx context.Context, req *pb.CreateLaptopRequest) (*pb.CreateLaptopResponse, error)
laptop := req.GetLaptop()
log.Printf("receive a create-laptop request with id: %s", laptop.Id)
if len(laptop.Id) > 0
_, err := uuid.Parse(laptop.Id)
if err != nil
return nil, status.Errorf(codes.InvalidArgument, "laptop ID is not a valid UUID: %v", err)
else
id, err := uuid.NewRandom()
if err != nil
return nil, status.Errorf(codes.Internal, "cannot generate a new laptop ID: %v", err)
laptop.Id = id.String()
err := server.laptopStore.save(laptop)
if err != nil
code := codes.Internal
if errors.Is(err, errors.New("record already exists"))
code = codes.AlreadyExists
return nil, status.Errorf(code, "cannot save laptop to the store: %v", err)
log.Printf("saved laptop with id: %s", laptop.Id)
res := &pb.CreateLaptopResponse
Id: laptop.Id,
return res, nil
5.4、编写主服务器和客户端
- 创建一个新cmd文件夹,创建
cmd/server/main.go
文件与cmd/client/main.go
文件 - 编写Makefile文件
server:
go run cmd/server/main.go
client:
go run cmd/client/main.go
- 编写服务端代码
func main()
laptopServer := service.NewLaptopServer(service.NewInMemoryLaptopStore())
grpcServer := grpc.NewServer()
pb.RegisterLaptopServiceServer(grpcServer, laptopServer)
listener, _ := net.Listen("tcp", ":8888")
grpcServer.Serve(listener)
以上是关于第五节——实现一元GRPC的主要内容,如果未能解决你的问题,请参考以下文章