1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-28 07:22:20 +00:00
2018-07-14 23:15:48 +08:00

4.9 KiB
Raw Blame History

4.6. GRPC和Protobuf扩展

目前开源社区已经围绕Protobuf和GRPC开发出众多扩展形成了庞大的生态。本节我们将简单介绍验证器、REST接口和Nginx代理等几个比较流行的扩展。

验证器

到目前位置我们接触的全部是第三版的Protobuf语法。第二版的Protobuf有个默认值特性可以为字符串或数值类型的成员定义默认值。

我们采用第二版的Protobuf语法创建文件

syntax = "proto2";

package main;

message Message {
	optional string name = 1 [default = "gopher"];
	optional int32 age = 2 [default = 10];
}

默认值语法内置其实是通过Protobuf的扩展选项特性实现。在第三版的Protobuf中不再支持默认值特性但是我们可以通过扩展选项自己定制默认值。

下面是用proto3语法的扩展特性重新改写上述的proto文件

syntax = "proto3";

package main;

import "google/protobuf/descriptor.proto";

extend google.protobuf.FieldOptions {
	string default_string = 50000;
	int32 default_int = 50001;
}

message Message {
	string name = 1 [(default_string) = "gopher"];
	int32 age = 2[(default_int) = 10];
}

其中成员后面的方括号内部的就是扩展语法。重新生成Go语言代码里面会包含扩展选项相关的元信息

var E_DefaultString = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.FieldOptions)(nil),
	ExtensionType: (*string)(nil),
	Field:         50000,
	Name:          "main.default_string",
	Tag:           "bytes,50000,opt,name=default_string,json=defaultString",
	Filename:      "helloworld.proto",
}

var E_DefaultInt = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.FieldOptions)(nil),
	ExtensionType: (*int32)(nil),
	Field:         50001,
	Name:          "main.default_int",
	Tag:           "varint,50001,opt,name=default_int,json=defaultInt",
	Filename:      "helloworld.proto",
}

我们可以在运行时通过类似反射的技术解析出Message每个成员定义的扩展选项然后从每个扩展的相关联的信息中解析出我们定义的默认值。

在开源社区中github.com/mwitkow/go-proto-validators 基于Protobuf的扩展特性实现了功能较为强大的验证器功能。要使用该验证器首先需要下载其提供的代码生成插件

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

然后基于go-proto-validators验证器重新定义上述的Message成员的验证规则

syntax = "proto3";

package main;

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

在方括弧表示的成员扩展中validator.field表示扩展是validator包中定义的名为field扩展选项。validator.field的类型是FieldValidator结构体在导入的validator.proto文件中定义。

validator.proto文件的内容如下

syntax = "proto2";
package validator;

import "google/protobuf/descriptor.proto";

extend google.protobuf.FieldOptions {
	optional FieldValidator field = 65020;
}

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;

	// ... more ...
}

从FieldValidator定义的注释中我们可以知道验证器扩展的一些语法其中regex表示用于字符串验证的正则表达式int_gt和int_lt表示数值的范围。

采用以下的命令生成验证函数代码:

protoc  \
	--proto_path=${GOPATH}/src \
	--proto_path=${GOPATH}/src/github.com/google/protobuf/src \
	--proto_path=. \
	--govalidators_out=. \
	hello.proto

以上的命令会调用protoc-gen-govalidators程序生成一个名为hello.validator.pb.go的代码

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

func (this *Message) Validate() error {
	if !_regex_Message_ImportantString.MatchString(this.ImportantString) {
		return go_proto_validators.FieldError("ImportantString", fmt.Errorf(`value '%v' must be a string conforming to regex "^[a-z]{2,5}$"`, this.ImportantString))
	}
	if !(this.Age > 0) {
		return go_proto_validators.FieldError("Age", fmt.Errorf(`value '%v' must be greater than '0'`, this.Age))
	}
	if !(this.Age < 100) {
		return go_proto_validators.FieldError("Age", fmt.Errorf(`value '%v' must be less than '100'`, this.Age))
	}
	return nil
}

也就是为Message结构体增加了一个Validate方法用于验证该成员是否满足Protobuf中定义的条件约束。

通过生成的验证函数并结合GRPC的截取器我们可以很容易为每个方法的输入参数和返回值进行验证。