gRPC 验证器(Validator)是一种用于在 gRPC 通信过程中进行数据验证的工具,通过在 .proto 文件中定义验证规则(例如长度限制、格式检查等),确保客户端和服务器之间传递的数据符合预期的格式和约束条件。它使用 Protocol Buffers 作为序列化机制,并通过生成的代码在传输过程中自动执行这些验证规则,从而提高系统的可靠性和安全性。
这一小节我们利用开源gRPC验证器项目protoc-gen-validate来实现gRPC在客户端与服务端之间的通信。
1. 安装protoc-gen-validate
# fetches this repo into $GOPATH
go get -d github.com/envoyproxy/protoc-gen-validate
2. protoc-gen-validate可支持的校验类型
类型 | 校验规则 |
---|---|
字符串 | min_len (最小长度)、max_len (最大长度)、length (固定长度)、pattern (正则表达式模式)、prefix (前缀)、suffix (后缀)、contains (包含)、in (在集合中)、not_in (不在集合中)、email (电子邮件)、hostname (主机名)、ip (IP地址)、ipv4 (IPv4地址)、ipv6 (IPv6地址)、uri (URI)、uri_ref (URI引用)、address (地址)、uuid (UUID)、well_known_regex (已知正则表达式)、well_known_regex_pattern (已知正则表达式模式) |
Int32 | const (常量)、lt (小于)、lte (小于等于)、gt (大于)、gte (大于等于)、in (在集合中)、not_in (不在集合中)、ignore_empty (忽略空值) |
Int64 | const (常量)、lt (小于)、lte (小于等于)、gt (大于)、gte (大于等于)、in (在集合中)、not_in (不在集合中)、ignore_empty (忽略空值) |
UInt32 | const (常量)、lt (小于)、lte (小于等于)、gt (大于)、gte (大于等于)、in (在集合中)、not_in (不在集合中)、ignore_empty (忽略空值) |
UInt64 | const (常量)、lt (小于)、lte (小于等于)、gt (大于)、gte (大于等于)、in (在集合中)、not_in (不在集合中)、ignore_empty (忽略空值) |
SInt32 | const (常量)、lt (小于)、lte (小于等于)、gt (大于)、gte (大于等于)、in (在集合中)、not_in (不在集合中)、ignore_empty (忽略空值) |
SInt64 | const (常量)、lt (小于)、lte (小于等于)、gt (大于)、gte (大于等于)、in (在集合中)、not_in (不在集合中)、ignore_empty (忽略空值) |
Fixed32 | const (常量)、lt (小于)、lte (小于等于)、gt (大于)、gte (大于等于)、in (在集合中)、not_in (不在集合中)、ignore_empty (忽略空值) |
Fixed64 | const (常量)、lt (小于)、lte (小于等于)、gt (大于)、gte (大于等于)、in (在集合中)、not_in (不在集合中)、ignore_empty (忽略空值) |
SFixed32 | const (常量)、lt (小于)、lte (小于等于)、gt (大于)、gte (大于等于)、in (在集合中)、not_in (不在集合中)、ignore_empty (忽略空值) |
SFixed64 | const (常量)、lt (小于)、lte (小于等于)、gt (大于)、gte (大于等于)、in (在集合中)、not_in (不在集合中)、ignore_empty (忽略空值) |
布尔 | const (常量) |
字节 | min_len (最小长度)、max_len (最大长度)、length (固定长度)、pattern (正则表达式模式)、prefix (前缀)、suffix (后缀)、contains (包含)、in (在集合中)、not_in (不在集合中) |
3. protoc-gen-validate的语法
以部分string语法为例,详见请见官方文档
// x must be set to "foo"
string x = 1 [(validate.rules).string.const = "foo"];
// x must be exactly 5 characters long
string x = 1 [(validate.rules).string.len = 5];
// x must be at least 3 characters long
string x = 1 [(validate.rules).string.min_len = 3];
// x must be between 5 and 10 characters, inclusive
string x = 1 [(validate.rules).string = {min_len: 5, max_len: 10}];
// x must be at most 15 bytes long
string x = 1 [(validate.rules).string.max_bytes = 15];
// x must be between 128 and 1024 bytes long
string x = 1 [(validate.rules).string = {min_bytes: 128, max_bytes: 1024}];
// x must be a non-empty, case-insensitive hexadecimal string
string x = 1 [(validate.rules).string.pattern = "(?i)^[0-9a-f]+$"];
// x must begin with "foo"
string x = 1 [(validate.rules).string.prefix = "foo"];
// x must end with "bar"
string x = 1 [(validate.rules).string.suffix = "bar"];
// x must contain "baz" anywhere inside it
string x = 1 [(validate.rules).string.contains = "baz"];
// x cannot contain "baz" anywhere inside it
string x = 1 [(validate.rules).string.not_contains = "baz"];
// x must be a valid email address (via RFC 5322)
string x = 1 [(validate.rules).string.email = true];
// x must be a valid address (IP or Hostname).
string x = 1 [(validate.rules).string.address = true];
// x must be a valid hostname (via RFC 1034)
string x = 1 [(validate.rules).string.hostname = true];
4. 使用示例
0. 目录树如下
.
├── client
│ └── client.go
├── proto
│ ├── student_grpc.pb.go
│ ├── student.pb.go
│ ├── student.pb.validate.go
│ ├── student.proto
│ └── validate.proto
└── server
└── server.go
1. 建立proto/student.proto
代码如下:
syntax = "proto3";
import "validate.proto";
option go_package = ".;proto";
service StudentService {
rpc GetStudent(Student) returns (Student);
}
message Student {
// uid 是学生的唯一标识符,它必须大于999
uint64 uid = 1 [(validate.rules).uint64.gt = 999];
// email 是学生的电子邮件地址,它必须是一个有效的电子邮件地址
string email = 2 [(validate.rules).string.email = true];
// phone 是学生的电话号码,它必须符合中国大陆的手机号码格式
string phone = 3 [(validate.rules).string = {pattern: "^1[3456789]\\d{9}$"}];
}
此时import语句会爆红,显示Cannot resolve import 'validate.proto'
,所以我们需要建立validate.proto
文件
2. 建立proto/validate.proto
使用protoc-gen-validate需要我们自己cv代码或者用import语句引入它的validate.proto
文件。这里我们以用cv大法自己建立validate.proto
文件为例。
建立proto/validate.proto后,代码可以从两种方式进行cv。
- 在GO Modules内找到
github.com/envoyproxy/[email protected]
这个库中的validate/validate.proto
文件,利用cv大法复制到proto/validate.proto
文件中。 - 从GitHub找到此项目该文件的代码:validate/validate.proto,利用cv大法复制到
proto/validate.proto
文件中。
在解决掉proto/validate.proto问题后。我们利用生成命令生成对应的go文件。
protoc student.proto --go_out=. --go-grpc_out=. --validate_out="lang=go:."
3.编写server/server.go代码
package main
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net"
"google.golang.org/grpc"
"go-try/grpc_validate/proto"
)
type StudentServer struct {
proto.UnimplementedStudentServiceServer
}
// Validate接口定义了一个需要被实现的方法Validate。
// 任何类型只要实现了Validate方法(返回一个error)就满足了这个接口。
// 在这个demo中,它被用于在运行时检查请求是否可以进行验证。
type Validate interface {
Validate() error
}
func (s *StudentServer) GetStudent(ctx context.Context, request *proto.Student) (*proto.Student, error) {
return &proto.Student{
Uid: 1,
Email: "[email protected]",
Phone: "12345678912",
}, nil
}
// 拦截器的函数
var interceptor = func(ctx context.Context, req any,
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
// 尝试将请求转换为Validate接口类型
if r, ok := req.(Validate); ok {
// 如果转换成功,调用Validate方法进行验证
if err := r.Validate(); err != nil {
// 如果验证失败,返回一个错误状态
return nil, status.Error(codes.InvalidArgument, err.Error())
}
}
return handler(ctx, req)
}
func main() {
var opts []grpc.ServerOption
opts = append(opts, grpc.UnaryInterceptor(interceptor))
server := grpc.NewServer(opts...)
proto.RegisterStudentServiceServer(server, &StudentServer{})
listen, err := net.Listen("tcp", "0.0.0.0:8080")
if err != nil {
panic("faild to listen : " + err.Error())
}
err = server.Serve(listen)
if err != nil {
panic("faild to start grpc : " + err.Error())
}
}
4.编写client/client.go代码
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"go-try/grpc_validate/proto"
)
func main() {
conn, err := grpc.NewClient("0.0.0.0:8080", grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
cli := proto.NewStudentServiceClient(conn)
msg, err := cli.GetStudent(context.Background(), &proto.Student{Uid: 1234})
// 报错信息:panic: rpc error: code = InvalidArgument desc = invalid Student.Uid: value must be less than 999
msg, err := cli.GetStudent(context.Background(), &proto.Student{Email: "1234"})
// 报错信息:panic: rpc error: code = InvalidArgument desc = invalid Student.Email: value must be a valid email address | caused by: mail: missing '@' or angle-addr
msg, err := cli.GetStudent(context.Background(), &proto.Student{Phone: "1234"})
// 报错信息:panic: rpc error: code = InvalidArgument desc = invalid Student.Email: value must be a valid email address | caused by: mail: no address
msg, err := cli.GetStudent(context.Background(), &proto.Student{
Uid: 1,
Email: "[email protected]",
Phone: "19536486248",
})
// 运行成功!运行结果:uid:1 email:"[email protected]" phone:"12345678912"
if err != nil {
panic(err)
}
fmt.Println(msg)
}
5.分别运行server.go和client.go
运行结果在client代码内已标明,至此,一个gRPC验证器简单的demo项目已经完成,相信你已经学会了如何使用gRPC验证器。
标签:string,err,proto,gRPC,验证,rules,Validator,go,validate From: https://blog.csdn.net/MPY_3/article/details/140420543