12

Go gRPC进阶-proto数据验证(九)

 4 years ago
source link: https://studygolang.com/articles/28156
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

原文地址

前言

上篇介绍了 go-grpc-middlewaregrpc_zapgrpc_authgrpc_recovery 使用,本篇将介绍 grpc_validator ,它可以对gRPC数据的输入和输出进行验证。

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

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

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

1.新建simple.proto文件

syntax = "proto3";

package proto;

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

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

代码 import "github.com/mwitkow/go-proto-validators/validator.proto" ,文件 validator.proto 需要 import "google/protobuf/descriptor.proto"; 包,不然会报错。

google/protobuf 地址:https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/descriptor.proto。

src 文件夹中的 protobuf 目录下载到GOPATH目录下。

2.编译simple.proto文件

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

指令编译: protoc --govalidators_out=. --go_out=plugins=grpc:./ ./simple.proto

或者使用 VSCode-proto3 插件, 第一篇 有介绍。只需要添加 "--govalidators_out=." 即可。

// vscode-proto3插件配置
    "protoc": {
        // protoc.exe所在目录
        "path": "C:\\Go\\bin\\protoc.exe",
        // 保存时自动编译
        "compile_on_save": true,
        "options": [
            // go编译输出指令
            "--go_out=plugins=grpc:.",
            "--govalidators_out=."
        ]
    },

编译完成后,自动生成 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 !(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
}

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

func (this *OuterMessage) Validate() error {
	if !_regex_OuterMessage_ImportantString.MatchString(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}$"`, 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 验证拦截器添加到服务端

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'

其他类型验证规则设置

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的数据验证,很简单也很方便。

教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK