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