前言
上篇介绍了go-grpc-middleware的grpc_zap、grpc_auth和grpc_recovery使用,本篇将介绍grpc_validator,它可以对gRPC数据的输入和输出进行验证。
创建proto文件,添加验证规则
这里使用第三方插件go-proto-validators自动生成验证规则。
go get github.com/mwitkow/go-proto-validators
1.新建simple.proto文件
1syntax = "proto3"; 2 3package proto; 4 5import "github.com/mwitkow/go-proto-validators/validator.proto"; 6 7message InnerMessage { 8 // some_integer can only be in range (1, 100). 9 int32 some_integer = 1 [(validator.field) = {int_gt: 0, int_lt: 100}]; 10 // some_float can only be in range (0;1). 11 double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}]; 12} 13 14message OuterMessage { 15 // important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax). 16 string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}]; 17 // proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage. 18 InnerMessage inner = 2 [(validator.field) = {msg_exists : true}]; 19} 20 21service Simple{ 22 rpc Route (InnerMessage) returns (OuterMessage){}; 23}
代码import "github.com/mwitkow/go-proto-validators/validator.proto",文件validator.proto需要import "google/protobuf/descriptor.proto";包,不然会报错。
google/protobuf地址:https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/descriptor.proto。
把src文件夹中的protobuf目录下载到GOPATH目录下。
2.编译simple.proto文件
go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators
指令编译:protoc --govalidators_out=. --go_out=plugins=grpc:./ ./simple.proto
或者使用
VSCode-proto3插件,第一篇有介绍。只需要添加"--govalidators_out=."即可。
1 // vscode-proto3插件配置 2 "protoc": { 3 // protoc.exe所在目录 4 "path": "C:\\Go\\bin\\protoc.exe", 5 // 保存时自动编译 6 "compile_on_save": true, 7 "options": [ 8 // go编译输出指令 9 "--go_out=plugins=grpc:.", 10 "--govalidators_out=." 11 ] 12 },
编译完成后,自动生成simple.pb.go和simple.validator.pb.go文件,simple.pb.go文件不再介绍,我们看下simple.validator.pb.go文件。
1// Code generated by protoc-gen-gogo. DO NOT EDIT. 2// source: go-grpc-example/9-grpc_proto_validators/proto/simple.proto 3 4package proto 5 6import ( 7 fmt "fmt" 8 math "math" 9 proto "github.com/golang/protobuf/proto" 10 _ "github.com/mwitkow/go-proto-validators" 11 regexp "regexp" 12 github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators" 13) 14 15// Reference imports to suppress errors if they are not otherwise used. 16var _ = proto.Marshal 17var _ = fmt.Errorf 18var _ = math.Inf 19 20func (this *InnerMessage) Validate() error { 21 if !(this.SomeInteger > 0) { 22 return github_com_mwitkow_go_proto_validators.FieldError("SomeInteger", fmt.Errorf(`value '%v' must be greater than '0'`, this.SomeInteger)) 23 } 24 if !(this.SomeInteger < 100) { 25 return github_com_mwitkow_go_proto_validators.FieldError("SomeInteger", fmt.Errorf(`value '%v' must be less than '100'`, this.SomeInteger)) 26 } 27 if !(this.SomeFloat >= 0) { 28 return github_com_mwitkow_go_proto_validators.FieldError("SomeFloat", fmt.Errorf(`value '%v' must be greater than or equal to '0'`, this.SomeFloat)) 29 } 30 if !(this.SomeFloat <= 1) { 31 return github_com_mwitkow_go_proto_validators.FieldError("SomeFloat", fmt.Errorf(`value '%v' must be lower than or equal to '1'`, this.SomeFloat)) 32 } 33 return nil 34} 35 36var _regex_OuterMessage_ImportantString = regexp.MustCompile(`^[a-z]{2,5}$`) 37 38func (this *OuterMessage) Validate() error { 39 if !_regex_OuterMessage_ImportantString.MatchString(this.ImportantString) { 40 return github_com_mwitkow_go_proto_validators.FieldError("ImportantString", fmt.Errorf(`value '%v' must be a string conforming to regex "^[a-z]{2,5}$"`, this.ImportantString)) 41 } 42 if nil == this.Inner { 43 return github_com_mwitkow_go_proto_validators.FieldError("Inner", fmt.Errorf("message must exist")) 44 } 45 if this.Inner != nil { 46 if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(this.Inner); err != nil { 47 return github_com_mwitkow_go_proto_validators.FieldError("Inner", err) 48 } 49 } 50 return nil 51}
里面自动生成了message中属性的验证规则。
把grpc_validator验证拦截器添加到服务端
1grpcServer := grpc.NewServer(cred.TLSInterceptor(), 2 grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( 3 grpc_validator.StreamServerInterceptor(), 4 grpc_auth.StreamServerInterceptor(auth.AuthInterceptor), 5 grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()), 6 grpc_recovery.StreamServerInterceptor(recovery.RecoveryInterceptor()), 7 )), 8 grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( 9 grpc_validator.UnaryServerInterceptor(), 10 grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor), 11 grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()), 12 grpc_recovery.UnaryServerInterceptor(recovery.RecoveryInterceptor()), 13 )), 14 )
运行后,当输入数据验证失败后,会有以下错误返回
Call Route err: rpc error: code = InvalidArgument desc = invalid field SomeInteger: value '101' must be less than '100'
其他类型验证规则设置
enum验证
1syntax = "proto3"; 2package proto; 3import "github.com/mwitkow/go-proto-validators/validator.proto"; 4 5message SomeMsg { 6 Action do = 1 [(validator.field) = {is_in_enum : true}]; 7} 8 9enum Action { 10 ALLOW = 0; 11 DENY = 1; 12 CHILL = 2; 13}
UUID验证
1syntax = "proto3"; 2package proto; 3import "github.com/mwitkow/go-proto-validators/validator.proto"; 4 5message UUIDMsg { 6 // user_id must be a valid version 4 UUID. 7 string user_id = 1 [(validator.field) = {uuid_ver: 4, string_not_empty: true}]; 8}
总结
go-grpc-middleware中grpc_validator集成go-proto-validators,我们只需要在编写proto时设好验证规则,并把grpc_validator添加到gRPC服务端,就能完成gRPC的数据验证,很简单也很方便。