proto数据验证

proto数据验证

一、前言

上篇介绍了go-grpc-middlewaregrpc_zapgrpc_authgrpc_recovery使用,本篇将介绍grpc_validator,它可以对gRPC数据的输入和输出进行验证。https://github.com/envoyproxy/protoc-gen-validate 待验证

源码

二、创建proto文件,添加验证规则

这里使用第三方插件go-proto-validators自动生成验证规则。

安装:

go get github.com/mwitkow/go-proto-validators

1.新建simple.proto文件

syntax = "proto3";// 协议为proto3

//option go_package = "path;name";
//path 表示生成的go文件的存放地址,会自动生成目录的。
//name 表示生成的go文件所属的包名


//    protoc -I G:\goproject\src -I ./ --go_out=plugins=grpc:. ./test.proto https://www.cnblogs.com/yisany/p/14875488.html//
//    protoc -I G:\goproject\src   -I ./ --grpc-gateway_out=logtostderr=true:. .\proto\test.proto


//    protoc --govalidators_out=. --go_out=plugins=grpc:./ ./simple.proto
//    protoc -I ./  --govalidators_out=.\10protoValidators\proto\ --go_out=plugins=grpc:.\10protoValidators\proto\  .\10protoValidators\proto\simple.proto

option go_package = "./;proto";
package proto;

// import "github.com/mwitkow/go-proto-validators/validator.proto";

import "10protoValidators/proto/google/validator/validator.proto";

message InnerMessage {
  // some_integer can only be in range (1, 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-z]{2,5}$"}];
  // proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.
  InnerMessage inner = 2 [(validator.field) = {msg_exists : true}];
}

service Simple{
  rpc Route (InnerMessage) returns (OuterMessage){};
}

执行编译命令:

protoc -I ./  --govalidators_out=.\10protoValidators\proto\ --go_out=plugins=grpc:.\10protoValidators\proto\  .\10protoValidators\proto\simple.proto

执行编译命令报错遇到一下问题解决方案

image-20220428205017130

image-20220508231900689

三、import google/protobuf/descriptor.proto was not found or had errors. 解决方案

执行编译报错如下:

问题1: Import "google/protobuf/descriptor.proto" was not found or had errors.

image-20220428210438914

image-20220428210341314

解决方案:

  1. 到官方下载descriptor.proto文件,保存到本地,下载google/protobuf地址;

image-20220428210933076

  1. 在创建proto目录下面创建google/protobuf目录并将将下载的descriptor.proto文件移动到该目录下面;

image-20220428211051521

问题2: github.com/mwitkow/go-proto-validators/validator.proto: File not found.

github.com/mwitkow/go-proto-validators/validator.proto: File not found.
09protoValidators/proto/simple.proto:15:1: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors

image-20220428211525161

  1. 到官方下载validator.proto文件,保存到本地,下载validator.proto地址;

    image-20220428211808328

  2. 在创建proto目录下面创建google/validator目录并将将下载的validator.proto文件移动到该目录下面;

image-20220428211951541

  1. 修改google/protobuf/descriptor.proto路径改为问题1下载的descriptor.proto路径

    // 修改前
    import "google/protobuf/descriptor.proto"
    //  修改后
    import "10protoValidators/proto/google/protobuf/descriptor.proto";
    

image-20220428212906280

最后最后修改simple.proto文件validator.proto导入

image-20220428212410258

再次执行下面命令

 # 在项目目录下一层级执行 cd grpc
 protoc -I ./  --govalidators_out=.\10protoValidators\proto\ --go_out=plugins=grpc:.\10protoValidators\proto\  .\10protoValidators\proto\simple.proto

编译完成后,自动生成simple.pb.gosimple.validator.pb.go文件,simple.pb.go文件不再介绍,我们看下simple.validator.pb.go文件。

// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: go-grpc-example/9-grpc_proto_validators/proto/simple.proto

package proto

import (
	fmt "fmt"
	math "math"
	proto "github.com/golang/protobuf/proto"
	_ "github.com/mwitkow/go-proto-validators"
	regexp "regexp"
	github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

func (this *InnerMessage) Validate() error {
	if !(https://img2022.cnblogs.com/blog/1739642/202205/1739642-20220515174944366-470187240.png 'this.SomeInteger > 0') {
		return github_com_mwitkow_go_proto_validators.FieldError("SomeInteger", fmt.Errorf(`value '%v' must be greater than '0'`, this.SomeInteger))
	}
	if !(https://img2022.cnblogs.com/blog/1739642/202205/1739642-20220515174944774-868825746.png 'this.SomeInteger < 100') {
		return github_com_mwitkow_go_proto_validators.FieldError("SomeInteger", fmt.Errorf(`value '%v' must be less than '100'`, this.SomeInteger))
	}
	if !(https://img2022.cnblogs.com/blog/1739642/202205/1739642-20220515174945183-1110311093.png 'this.SomeFloat >= 0') {
		return github_com_mwitkow_go_proto_validators.FieldError("SomeFloat", fmt.Errorf(`value '%v' must be greater than or equal to '0'`, this.SomeFloat))
	}
	if !(https://img2022.cnblogs.com/blog/1739642/202205/1739642-20220515174945606-134640571.png 'this.SomeFloat <= 1') {
		return github_com_mwitkow_go_proto_validators.FieldError("SomeFloat", fmt.Errorf(`value '%v' must be lower than or equal to '1'`, this.SomeFloat))
	}
	return nil
}

var _regex_OuterMessage_ImportantString = regexp.MustCompile(`^[a-z]{2,5}$`)

func (this *OuterMessage) Validate() error {
	if !_regex_OuterMessage_ImportantString.MatchString(https://img2022.cnblogs.com/blog/1739642/202205/1739642-20220515174945967-1159649638.png 'this.ImportantString') {
		return github_com_mwitkow_go_proto_validators.FieldError("ImportantString", fmt.Errorf(`value '%v' must be a string conforming to regex "^[a-z]{2,5}$"`, https://img2022.cnblogs.com/blog/1739642/202205/1739642-20220515174945967-1159649638.png 'this.ImportantString'))
	}
	if nil == this.Inner {
		return github_com_mwitkow_go_proto_validators.FieldError("Inner", fmt.Errorf("message must exist"))
	}
	if this.Inner != nil {
		if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(this.Inner); err != nil {
			return github_com_mwitkow_go_proto_validators.FieldError("Inner", err)
		}
	}
	return nil
}

里面自动生成了message中属性的验证规则。

四、把grpc_validator验证拦截器添加到服务端

客户端

package main

import (
	"context"
	pb "go-grpc-example/10protoValidators/proto"
	"log"

	"google.golang.org/grpc/credentials/insecure"

	"google.golang.org/grpc"
)

/*
@author RandySun
@create 2022-05-08-23:20
*/

// Address 连接地址
const Address string = ":8000"

var grpcClient pb.SimpleClient

func main() {
	//从输入的证书文件中为客户端构造TLS凭证

	// 连接服务器 grpc.WithTransportCredentials(insecure.NewCredentials())
	conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("net.Connect err: %v", err)
	}
	defer conn.Close()

	// 建立gRPC连接
	grpcClient = pb.NewSimpleClient(conn)
	route()
}

// route 调用服务端Route方法
func route() {
	// 创建发送结构体
	req := pb.InnerMessage{
		SomeInteger: 200,
		SomeFloat:   1,
	}
	// 调用我们的服务(Route方法)
	// 同时传入了一个 context.Context ,在有需要时可以让我们改变RPC的行为,比如超时/取消一个正在运行的RPC
	res, err := grpcClient.Route(context.Background(), &req)
	if err != nil {
		log.Fatalf("Call Route err: %v", err)
	}
	// 打印返回值
	log.Println(res)
}

服务端:

package main

import (
	"context"
	pb "go-grpc-example/10protoValidators/proto"
	"log"
	"net"

	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_validator "github.com/grpc-ecosystem/go-grpc-middleware/validator"
	"google.golang.org/grpc"
)

/*
@author RandySun
@create 2022-05-08-23:20
*/

// SimpleService 定义我们的服务
type SimpleService struct{}

// Route 实现Route方法
func (s *SimpleService) Route(ctx context.Context, req *pb.InnerMessage) (*pb.OuterMessage, error) {
	// 添加拦截器后,方法里省略Token认证
	// //检测Token是否有效
	// if err := Check(ctx); err != nil {
	// 	return nil, err
	// }
	res := pb.OuterMessage{
		ImportantString: "hello grpc validator",
		Inner:           req,
	}
	return &res, nil
}

const (
	// Address 监听地址
	Address string = ":8000"
	// Network 网络通信协议
	Network string = "tcp"
)

func main() {
	// 监听本地端口
	listener, err := net.Listen(Network, Address)
	if err != nil {
		log.Fatalf("net.Listen err: %v", err)
	}

	// 新建gRPC服务器实例
	grpcServer := grpc.NewServer(
		grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
			grpc_validator.StreamServerInterceptor(), // 校验器

		)),
		grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
			grpc_validator.UnaryServerInterceptor(), // 校验器

		)),
	)
	// 在gRPC服务器注册我们的服务
	pb.RegisterSimpleServer(grpcServer, &SimpleService{})
	log.Println(Address + " net.Listing whth TLS and token...")
	//用服务器 Serve() 方法以及我们的端口信息区实现阻塞等待,直到进程被杀死或者 Stop() 被调用
	err = grpcServer.Serve(listener)
	if err != nil {
		log.Fatalf("grpcServer.Serve err: %v", err)
	}
}

运行后,当输入数据验证失败后,会有以下错误返回

2022/04/28 21:00:22 Call Route err: rpc error: code = InvalidArgument desc = invalid field SomeInteger: value '200' must be less than '100'

image-20220428210046708

五、其他类型验证规则设置

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}];
}

六、总结

go-grpc-middlewaregrpc_validator集成go-proto-validators,我们只需要在编写proto时设好验证规则,并把grpc_validator添加到gRPC服务端,就能完成gRPC的数据验证,很简单也很方便。

posted @ 2022-05-15 18:26  RandySun  阅读(374)  评论(0编辑  收藏  举报