gRPC参数校验之grpc_validator
注:本文前提是已熟练使用gRPC
在http请求中,我们需要为每个请求参数进行数据验证,以防恶意请求和参数错误的情况
例如在Gin框架中,提供了github.com/go-playground/validator/v10
该库作为验证器,我们只需要在结构体中进行规则绑定
type UserCreateReq struct {
Name string `json:"name" binding:"required" label:"姓名"`
Intro string `json:"intro" binding:"required,max=60,min=1" label:"介绍"`
}
然后在解析参数时就会自动进行数据校验,而不需要我们在接收到参数后通过if进行判断了
req := &user.UserCreateReq{}
if err := ctx.ShouldBind(req); err != nil {
response.Fail(ctx, constant.ParameterIllegal, xerror.Trans(err))
return
}
注:
xerror.Trans(err)
是封装的一个对错误内容进行翻译的方法
那么在gRPC中我们如何实现这种自动的参数校验呢?
本文将基于grpc_middleware中的 validator 实现
grpc_validator
在上面链接中 doc.go
文件中提到
// While it is generic, it was intended to be used with https://github.com/mwitkow/go-proto-validators,
// a Go protocol buffers codegen plugin that creates the `Validate` methods (including nested messages)
// based on declarative options in the `.proto` files themselves. For example:
syntax = "proto3";
package validator.examples;
import "github.com/mwitkow/go-proto-validators/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}];
}
正如第一句提到的一样,我们需要先引入https://github.com/mwitkow/go-proto-validators
go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators
# 该命令会在我们的${GOPATH}/bin 下生成一个protoc-gen-govalidators可执行文件,用来自动生成我们的验证规则
go install github.com/mwitkow/go-proto-validators/protoc-gen-govalidators
注:
go install
安装是必须的,后续需要基于govalidators生成pb文件
然后编写我们的proto文件
syntax = "proto3";
option go_package = "../simple_v";
option java_multiple_files = true;
option java_package = "io.grpc.examples.protobuf";
option java_outer_classname = "SimpleVProto";
package protobuf;
import "github.com/mwitkow/go-proto-validators@v0.3.2/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 SimpleVServer {
rpc TestSimpleV (InnerMessage) returns (OuterMessage) {}
}
使用命令生成pb文件
protoc --proto_path=$GOPATH/pkg/mod --proto_path=. --go_out=. --go-grpc_out=. --govalidators_out=. ./simple.proto
这一步会在当前目录下生成一个 simple.validator.pb.go 文件,这里面就是我们的验证规则
func (this *InnerMessage) Validate() error {
if !(this.SomeInteger > 0) {
return github_com_mwitkow_go_proto_validators.FieldError("SomeInteger", fmt.Errorf(`value '%v' must be greater than '0'`, this.SomeInteger))
}
if !(this.SomeInteger < 100) {
return github_com_mwitkow_go_proto_validators.FieldError("SomeInteger", fmt.Errorf(`value '%v' must be less than '100'`, this.SomeInteger))
}
if !(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 !(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
}
安装go-grpc-middleware
go get github.com/grpc-ecosystem/go-grpc-middleware
启动服务端
type SimpleVServer struct {
pbv.UnimplementedSimpleVServerServer
}
func (s *SimpleVServer) TestSimpleV(ctx context.Context, in *pbv.InnerMessage) (resp *pbv.OuterMessage, err error) {
resp = new(pbv.OuterMessage)
fmt.Println(in.SomeFloat)
fmt.Println(in.SomeInteger)
return
}
func main() {
s := grpc.NewServer(
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_validator.StreamServerInterceptor(),
)),
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
grpc_validator.UnaryServerInterceptor(),
)),
)
pbv.RegisterSimpleVServerServer(s, &SimpleVServer{})
lis, err := net.Listen("tcp", ":1015")
if err != nil {
panic(fmt.Errorf("[App-Initialize-Error] GRPC服务Listen失败: %s \n", err))
}
if err = s.Serve(lis); err != nil {
panic(fmt.Errorf("[App-Initialize-Error] GRPC服务启动失败: %s \n", err))
}
fmt.Println("SimpleV main")
}
启动客户端
func main() {
conn, err := grpc.Dial(
":1015",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
panic(fmt.Errorf("did not connect: %v", err))
}
defer conn.Close()
c := pbv.NewSimpleVServerClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
r, err2 := c.TestSimpleV(ctx, &pbv.InnerMessage{SomeInteger: 111, SomeFloat: 20.2})
if err2 != nil {
fmt.Println("err", "c.TestSimple.err", err2)
return
}
fmt.Println(r)
}
请求参数SomeInteger值为111,此时启动服务端、客户端,客户端会看到以下输出
rpc error: code = InvalidArgument desc = invalid field SomeInteger: value '111' must be less than '100'
常见的验证规则
数字类型
参数必须大于某个数
int64 id = [(validate.rules).int64 = {gt: 0}];
参数必须某个区间内
int32 age = [(validate.rules).int64 = {gt:0, lte: 100}];
参数in某些值内
uint32 status = [(validate.rules).uint32 = {in: [1,2,3]}];
参数不能in某些值内
float money = [(validate.rules).float = {not_in: [0, 3.1415926]}];
布尔类型
参数必须为 true
bool is_show = [(validate.rules).bool.const = true];
参数必须为 false
bool is_show = [(validate.rules).bool.const = false];
文本类型
参数必须是某个值
string name = [(validate.rules).string.const = "zhangsan"];
参数长度固定
string phone = [(validate.rules).string.len = 11];
参数最小长度
string password = [(validate.rules).string.min_len = 10];
参数最小最大长度
string password = [(validate.rules).string = {min_len: 10, max_len: 20}];
参数正则校验
string card = [(validate.rules).string.pattern = "(?i)^[0-9a-f]+$"];
参数必须是 email 格式
string email = [(validate.rules).string.email = true];
答疑解惑
- GoLand中
import "github.com/mwitkow/go-proto-validators@v0.3.2/validator.proto"
以及所有的validator
,报错Cannot resolve import
解决方案:
- 进入设置:GoLand -> Settings -> Preferences | Languages & Frameworks > Protocol Buffers
- 取消 Configure automatically的勾选,并选择+号
- 找到自己电脑上 go mod所在的目录,保存即可
如果执行完这一步还是红色提示,那么需要检查该包是否在go mod目录下,并且版本号、路径也需要保持一致
总结
本文介绍了如何使用gprc_middleware中的validator来实现gRPC的请求参数校验,grpc_middleware 还提供了grpc_zap 、grpc_auth 、grpc_recovery 、grpc_ratelimit等,更多更详细使用点击 go-grpc-middleware 查看。
当然如果我们不熟悉这一套验证规则,但是非常熟悉Gin中go-playground-validator
验证器,那么也可以思考一下我们该如何实现这种验证方式呢?(后续也会出一篇文章介绍如何实现这种方式)。
好了,这篇文章到此就结束了,希望能帮助你解决你所遇到的问题。
END
本作品采用《CC 协议》,转载必须注明作者和本文链接
受教了,谢谢大佬