proto数据验证
proto数据验证
一、前言
上篇介绍了go-grpc-middleware的grpc_zap
、grpc_auth
和grpc_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
执行编译命令报错遇到一下问题解决方案
三、import google/protobuf/descriptor.proto was not found or had errors. 解决方案
执行编译报错如下:
问题1: Import "google/protobuf/descriptor.proto" was not found or had errors.
解决方案:
- 到官方下载descriptor.proto文件,保存到本地,下载google/protobuf地址;
- 在创建proto目录下面创建
google/protobuf
目录并将将下载的descriptor.proto文件移动到该目录下面;
问题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
-
到官方下载validator.proto文件,保存到本地,下载validator.proto地址;
-
在创建proto目录下面创建
google/validator
目录并将将下载的validator.proto文件移动到该目录下面;
-
修改google/protobuf/descriptor.proto路径改为问题1下载的descriptor.proto路径
// 修改前 import "google/protobuf/descriptor.proto" // 修改后 import "10protoValidators/proto/google/protobuf/descriptor.proto";
最后最后修改simple.proto
文件validator.proto
导入
再次执行下面命令
# 在项目目录下一层级执行 cd grpc
protoc -I ./ --govalidators_out=.\10protoValidators\proto\ --go_out=plugins=grpc:.\10protoValidators\proto\ .\10protoValidators\proto\simple.proto
编译完成后,自动生成simple.pb.go
和simple.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'
五、其他类型验证规则设置
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-middleware
中grpc_validator
集成go-proto-validators
,我们只需要在编写proto时设好验证规则,并把grpc_validator
添加到gRPC服务端,就能完成gRPC的数据验证,很简单也很方便。