请求验证器中间件验很重要,也很困难。它还会导致大量的样板代码。这个中间件检查gRPC请求的每个消息是否存在“Validate”方法。这包括“一元”调用的单个请求,以及入站流调用的每个消息。如果验证失败,则返回’ InvalidArgument ‘ gRPC状态,并描述验证失败。虽然它是通用的,它的目的是用于https://github.com/mwitkow/go-proto-validators,一个Go协议缓冲代码根插件
func StreamServerInterceptor() grpc.StreamServerInterceptor 服务端流拦截器
func UnaryServerInterceptor() grpc.UnaryServerInterceptor 服务端一元拦截器
func UnaryClientInterceptor() grpc.UnaryClientInterceptor 无效的消息将在发送请求到服务器之前被拒绝
官方例子
结合”github.com/mwitkow/go-proto-validators/validator.proto”来做
第一步:定义proto
syntax = "proto3";package common;import "github.com/mwitkow/go-proto-validators/validator.proto";message Message {string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}];int32 age = 2 [(validator.field) = {int_gt: 0, int_lt: 100}];}
在开源社区中,github.com/mwitkow/go-proto-validators 已经基于Protobuf的扩展特性实现了功能较为强大的验证器功能。要使用该验证器首先需要下载其提供的代码生成插件:
go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators
第二步:生成go文件
protoc --proto_path=E:/go/src --proto_path=. --govalidators_out=.--go_out=plugins=grpc:. prorotest/protos/common/common.proto
报错:github.com/mwitkow/go-proto-validators/validator.proto: File not found.
提示找不到validator.proto,我们将这个项目clone下来,放到E:/go/src
再尝试生成,还是报错
google/protobuf/descriptor.proto: File not found.github.com/mwitkow/go-proto-validators/validator.proto:12:1: Import "google/protobuf/descriptor.proto" was not found or had errors.github.com/mwitkow/go-proto-validators/validator.proto:18:8: "google.protobuf.FieldOptions" is not defined.github.com/mwitkow/go-proto-validators/validator.proto:22:8: "google.protobuf.OneofOptions" is not defined.prorotest/protos/common/common.proto:5:1: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
这是因为validator.proto import了 google/protobuf/descriptor.proto
把所需的proto下载下来,https://github.com/protocolbuffers/protobuf/tree/master/src,同样放到E:/go/src下,注意命令规范google/protobuf
最后再生成
看看commom.validator.pb.go的内容
ps:如果生成命令为下面这样,是会报错的
protoc --proto_path=E:/go/src --govalidators_out=. --go_out=plugins=grpc:. prorotest/protos/common/common.proto

这个与—proto_path参数有关,记住:如果引用了其他地方的proto,并且需要指定—proto_path,那么一定要多加上一个”—proto_path=.”
第三步:整合
grpcServer := grpc.NewServer(cred.TLSInterceptor(),grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(grpc_validator.StreamServerInterceptor(),grpc_auth.StreamServerInterceptor(auth.AuthInterceptor),grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),grpc_recovery.StreamServerInterceptor(recovery.RecoveryInterceptor()),)),grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(grpc_validator.UnaryServerInterceptor(),grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor),grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),grpc_recovery.UnaryServerInterceptor(recovery.RecoveryInterceptor()),)),)
如果参数不符合,会返回如下错误
Call Route err: rpc error: code = InvalidArgument desc = invalid field SomeInteger: value '101' must be less than '100'
字段验证规则
message FieldValidator {// Uses a Golang RE2-syntax regex to match the field contents.optional string regex = 1;// Field value of integer strictly greater than this value.optional int64 int_gt = 2;// Field value of integer strictly smaller than this value.optional int64 int_lt = 3;// Used for nested message types, requires that the message type exists.optional bool msg_exists = 4;// Human error specifies a user-customizable error that is visible to the user.optional string human_error = 5;// Field value of double strictly greater than this value.// Note that this value can only take on a valid floating point// value. Use together with float_epsilon if you need something more specific.optional double float_gt = 6;// Field value of double strictly smaller than this value.// Note that this value can only take on a valid floating point// value. Use together with float_epsilon if you need something more specific.optional double float_lt = 7;// Field value of double describing the epsilon within which// any comparison should be considered to be true. For example,// when using float_gt = 0.35, using a float_epsilon of 0.05// would mean that any value above 0.30 is acceptable. It can be// thought of as a {float_value_condition} +- {float_epsilon}.// If unset, no correction for floating point inaccuracies in// comparisons will be attempted.optional double float_epsilon = 8;// Floating-point value compared to which the field content should be greater or equal.optional double float_gte = 9;// Floating-point value compared to which the field content should be smaller or equal.optional double float_lte = 10;// Used for string fields, requires the string to be not empty (i.e different from "").optional bool string_not_empty = 11;// Repeated field with at least this number of elements.optional int64 repeated_count_min = 12;// Repeated field with at most this number of elements.optional int64 repeated_count_max = 13;// Field value of length greater than this value.optional int64 length_gt = 14;// Field value of length smaller than this value.optional int64 length_lt = 15;// Field value of length strictly equal to this value.optional int64 length_eq = 16;// Requires that the value is in the enum.optional bool is_in_enum = 17;// Ensures that a string value is in UUID format.// uuid_ver specifies the valid UUID versions. Valid values are: 0-5.// If uuid_ver is 0 all UUID versions are accepted.optional int32 uuid_ver = 18;}
enum验证
syntax = "proto3";package proto;import "github.com/mwitkow/go-proto-validators/validator.proto";message SomeMsg {Action do = 1 [(validator.field) = {is_in_enum : true}];}enum Action {ALLOW = 0;DENY = 1;CHILL = 2;}
UUID验证
syntax = "proto3";package proto;import "github.com/mwitkow/go-proto-validators/validator.proto";message UUIDMsg {// user_id must be a valid version 4 UUID.string user_id = 1 [(validator.field) = {uuid_ver: 4, string_not_empty: true}];}
嵌套验证
syntax = "proto3";package validator.examples;import "github.com/mwitkow/go-proto-validators/validator.proto";message InnerMessage {// some_integer can only be in range (0, 100).int32 some_integer = 1 [(validator.field) = {int_gt: 0, int_lt: 100}];// some_float can only be in range (0;1).double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}];}message OuterMessage {// important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).string important_string = 1 [(validator.field) = {regex: "^[a-z0-9]{5,30}$"}];// proto3没有'required', 使用msg_exist 验证InnerMessageInnerMessage inner = 2 [(validator.field) = {msg_exists : true}];}
更好的选择
使用更多的验证中间件应该是protoc-gen-validate,https://www.yuque.com/jw-go/mqm3eb/slxava
看validator的源码
定义了一个validator接口,只要实现了该接口就行,正好protoc-gen-validate生成的验证代码实现了该接口,因此可直接使用
type validator interface {Validate() error}func UnaryServerInterceptor() grpc.UnaryServerInterceptor {return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {if v, ok := req.(validator); ok {if err := v.Validate(); err != nil {return nil, status.Errorf(codes.InvalidArgument, err.Error())}}return handler(ctx, req)}}
例子:
proto
syntax = "proto3";package protobuf;import "google/validate/validate.proto";service pingService {rpc Ping(PingReq) returns (PingRsp){}}message PingReq {string ping = 1 [(validate.rules).string = {min_len: 2, max_len: 5}];}message PingRsp {string pong = 1;}
client.go
package mainimport (pb "commons/grpc/protobuf""context""fmt""log""google.golang.org/grpc")func main() {conn, err := grpc.Dial("127.0.0.1:8888", grpc.WithInsecure())if err != nil {fmt.Printf("did not connect: %v", err)}defer conn.Close()client := pb.NewPingServiceClient(conn)rsp, err := client.Ping(context.Background(), &pb.PingReq{Ping: "x",})if err != nil {log.Fatalf("err: %v", err)}fmt.Println(rsp.Pong)}
service.go
package mainimport ("context""log""net"pb "commons/grpc/protobuf"grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"grpc_validator "github.com/grpc-ecosystem/go-grpc-middleware/validator""google.golang.org/grpc")func main() {lis, err := net.Listen("tcp", ":8888")if err != nil {log.Fatalf("failed to listen: %v", err)}var grpcServer = grpc.NewServer(grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(grpc_validator.UnaryServerInterceptor(),)),)pb.RegisterPingServiceServer(grpcServer, &S{})if err := grpcServer.Serve(lis); err != nil {log.Fatalf("failed to serve: %v", err)}}type S struct {}func (s S) Ping(ctx context.Context, req *pb.PingReq) (*pb.PingRsp, error) {return &pb.PingRsp{Pong: req.Ping}, nil}
如果参数不符合,会返回以下错误:
err: rpc error: code = InvalidArgument desc = invalid PingReq.Ping: value length must be between 2 and 5 runes, inclusive
