请求验证器中间件验很重要,也很困难。它还会导致大量的样板代码。这个中间件检查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

  1. syntax = "proto3";
  2. package common;
  3. import "github.com/mwitkow/go-proto-validators/validator.proto";
  4. message Message {
  5. string important_string = 1 [
  6. (validator.field) = {regex: "^[a-z]{2,5}$"}
  7. ];
  8. int32 age = 2 [
  9. (validator.field) = {int_gt: 0, int_lt: 100}
  10. ];
  11. }

在开源社区中,github.com/mwitkow/go-proto-validators 已经基于Protobuf的扩展特性实现了功能较为强大的验证器功能。要使用该验证器首先需要下载其提供的代码生成插件:

  1. go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators

第二步:生成go文件

  1. protoc --proto_path=E:/go/src --proto_path=. --govalidators_out=.
  2. --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
image.png
再尝试生成,还是报错

  1. google/protobuf/descriptor.proto: File not found.
  2. github.com/mwitkow/go-proto-validators/validator.proto:12:1: Import "google/protobuf/descriptor.proto" was not found or had errors.
  3. github.com/mwitkow/go-proto-validators/validator.proto:18:8: "google.protobuf.FieldOptions" is not defined.
  4. github.com/mwitkow/go-proto-validators/validator.proto:22:8: "google.protobuf.OneofOptions" is not defined.
  5. 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
image.png
把所需的proto下载下来,https://github.com/protocolbuffers/protobuf/tree/master/src,同样放到E:/go/src下,注意命令规范google/protobuf
image.png
最后再生成
image.png
看看commom.validator.pb.go的内容
image.png

ps:如果生成命令为下面这样,是会报错的

  1. protoc --proto_path=E:/go/src --govalidators_out=. --go_out=plugins=grpc:. prorotest/protos/common/common.proto

image.png
这个与—proto_path参数有关,记住:如果引用了其他地方的proto,并且需要指定—proto_path,那么一定要多加上一个”—proto_path=.”

第三步:整合

  1. grpcServer := 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. )

如果参数不符合,会返回如下错误

  1. Call Route err: rpc error: code = InvalidArgument desc = invalid field SomeInteger: value '101' must be less than '100'

字段验证规则

  1. message FieldValidator {
  2. // Uses a Golang RE2-syntax regex to match the field contents.
  3. optional string regex = 1;
  4. // Field value of integer strictly greater than this value.
  5. optional int64 int_gt = 2;
  6. // Field value of integer strictly smaller than this value.
  7. optional int64 int_lt = 3;
  8. // Used for nested message types, requires that the message type exists.
  9. optional bool msg_exists = 4;
  10. // Human error specifies a user-customizable error that is visible to the user.
  11. optional string human_error = 5;
  12. // Field value of double strictly greater than this value.
  13. // Note that this value can only take on a valid floating point
  14. // value. Use together with float_epsilon if you need something more specific.
  15. optional double float_gt = 6;
  16. // Field value of double strictly smaller than this value.
  17. // Note that this value can only take on a valid floating point
  18. // value. Use together with float_epsilon if you need something more specific.
  19. optional double float_lt = 7;
  20. // Field value of double describing the epsilon within which
  21. // any comparison should be considered to be true. For example,
  22. // when using float_gt = 0.35, using a float_epsilon of 0.05
  23. // would mean that any value above 0.30 is acceptable. It can be
  24. // thought of as a {float_value_condition} +- {float_epsilon}.
  25. // If unset, no correction for floating point inaccuracies in
  26. // comparisons will be attempted.
  27. optional double float_epsilon = 8;
  28. // Floating-point value compared to which the field content should be greater or equal.
  29. optional double float_gte = 9;
  30. // Floating-point value compared to which the field content should be smaller or equal.
  31. optional double float_lte = 10;
  32. // Used for string fields, requires the string to be not empty (i.e different from "").
  33. optional bool string_not_empty = 11;
  34. // Repeated field with at least this number of elements.
  35. optional int64 repeated_count_min = 12;
  36. // Repeated field with at most this number of elements.
  37. optional int64 repeated_count_max = 13;
  38. // Field value of length greater than this value.
  39. optional int64 length_gt = 14;
  40. // Field value of length smaller than this value.
  41. optional int64 length_lt = 15;
  42. // Field value of length strictly equal to this value.
  43. optional int64 length_eq = 16;
  44. // Requires that the value is in the enum.
  45. optional bool is_in_enum = 17;
  46. // Ensures that a string value is in UUID format.
  47. // uuid_ver specifies the valid UUID versions. Valid values are: 0-5.
  48. // If uuid_ver is 0 all UUID versions are accepted.
  49. optional int32 uuid_ver = 18;
  50. }

enum验证

  1. syntax = "proto3";
  2. package proto;
  3. import "github.com/mwitkow/go-proto-validators/validator.proto";
  4. message SomeMsg {
  5. Action do = 1 [(validator.field) = {is_in_enum : true}];
  6. }
  7. enum Action {
  8. ALLOW = 0;
  9. DENY = 1;
  10. CHILL = 2;
  11. }

UUID验证

  1. syntax = "proto3";
  2. package proto;
  3. import "github.com/mwitkow/go-proto-validators/validator.proto";
  4. message UUIDMsg {
  5. // user_id must be a valid version 4 UUID.
  6. string user_id = 1 [(validator.field) = {uuid_ver: 4, string_not_empty: true}];
  7. }

嵌套验证

  1. syntax = "proto3";
  2. package validator.examples;
  3. import "github.com/mwitkow/go-proto-validators/validator.proto";
  4. message InnerMessage {
  5. // some_integer can only be in range (0, 100).
  6. int32 some_integer = 1 [(validator.field) = {int_gt: 0, int_lt: 100}];
  7. // some_float can only be in range (0;1).
  8. double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}];
  9. }
  10. message OuterMessage {
  11. // important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).
  12. string important_string = 1 [(validator.field) = {regex: "^[a-z0-9]{5,30}$"}];
  13. // proto3没有'required', 使用msg_exist 验证InnerMessage
  14. InnerMessage inner = 2 [(validator.field) = {msg_exists : true}];
  15. }

更好的选择

使用更多的验证中间件应该是protoc-gen-validate,https://www.yuque.com/jw-go/mqm3eb/slxava

看validator的源码
定义了一个validator接口,只要实现了该接口就行,正好protoc-gen-validate生成的验证代码实现了该接口,因此可直接使用

  1. type validator interface {
  2. Validate() error
  3. }
  4. func UnaryServerInterceptor() grpc.UnaryServerInterceptor {
  5. return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
  6. if v, ok := req.(validator); ok {
  7. if err := v.Validate(); err != nil {
  8. return nil, status.Errorf(codes.InvalidArgument, err.Error())
  9. }
  10. }
  11. return handler(ctx, req)
  12. }
  13. }

例子:
proto

  1. syntax = "proto3";
  2. package protobuf;
  3. import "google/validate/validate.proto";
  4. service pingService {
  5. rpc Ping(PingReq) returns (PingRsp){}
  6. }
  7. message PingReq {
  8. string ping = 1 [(validate.rules).string = {min_len: 2, max_len: 5}];
  9. }
  10. message PingRsp {
  11. string pong = 1;
  12. }

client.go

  1. package main
  2. import (
  3. pb "commons/grpc/protobuf"
  4. "context"
  5. "fmt"
  6. "log"
  7. "google.golang.org/grpc"
  8. )
  9. func main() {
  10. conn, err := grpc.Dial("127.0.0.1:8888", grpc.WithInsecure())
  11. if err != nil {
  12. fmt.Printf("did not connect: %v", err)
  13. }
  14. defer conn.Close()
  15. client := pb.NewPingServiceClient(conn)
  16. rsp, err := client.Ping(context.Background(), &pb.PingReq{
  17. Ping: "x",
  18. })
  19. if err != nil {
  20. log.Fatalf("err: %v", err)
  21. }
  22. fmt.Println(rsp.Pong)
  23. }

service.go

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. "net"
  6. pb "commons/grpc/protobuf"
  7. grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
  8. grpc_validator "github.com/grpc-ecosystem/go-grpc-middleware/validator"
  9. "google.golang.org/grpc"
  10. )
  11. func main() {
  12. lis, err := net.Listen("tcp", ":8888")
  13. if err != nil {
  14. log.Fatalf("failed to listen: %v", err)
  15. }
  16. var grpcServer = grpc.NewServer(
  17. grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
  18. grpc_validator.UnaryServerInterceptor(),
  19. )),
  20. )
  21. pb.RegisterPingServiceServer(grpcServer, &S{})
  22. if err := grpcServer.Serve(lis); err != nil {
  23. log.Fatalf("failed to serve: %v", err)
  24. }
  25. }
  26. type S struct {
  27. }
  28. func (s S) Ping(ctx context.Context, req *pb.PingReq) (*pb.PingRsp, error) {
  29. return &pb.PingRsp{Pong: req.Ping}, nil
  30. }

如果参数不符合,会返回以下错误:

  1. err: rpc error: code = InvalidArgument desc = invalid PingReq.Ping: value length must be between 2 and 5 runes, inclusive