proto plugin
Protocol Buffers 是谷歌推出的编码标准,它在传输效率和编解码性能上都要优于 JSON。但其代价则是需要依赖中间描述语言(IDL)来定义数据(和服务)的结构(通过 *.proto 文件),并且需要一整套的工具链(protoc 及其插件)来生成对应的序列化和反序列化代码。
除了谷歌官方提供的工具和插件(比如生成 go 代码的 protoc-gen-go)外,我们还可以开发或定制自己的插件,根据业务需要按照 proto 文件的定义生成代码或者文档。
1.1开发流程
开发proto plugin可以分为三步:
从 stdin 读取proto文件(ex: test.proto)
根据proto文件自定义规则构造目标文件结构 (ex: main.go)
将生成的文件通过 stdout 输出(ex: test.pb.go)
example
main.go
func main() {
m := myProtoPlugin{}
protogen.Options{}.Run(m.Generate)
}
type myProtoPlugin struct {
}
//实现Generate方法
func (m myProtoPlugin) Generate(plugin *protogen.Plugin) error {
if len(plugin.Files) < 1 {
return nil
}
f := plugin.Files[len(plugin.Files)-1]
//指定输出文件名,可以是任何自定义的文件格式
fileName := f.GeneratedFilenamePrefix + “.pb.go”
//构造目标文件
generatedFile := plugin.NewGeneratedFile(fileName, f.GoImportPath)
//向目标文件里填充内容
generatedFile.P(“// this file is generated by proto file.”)
generatedFile.P(“package main;”)
_ = generateStructFromMessage(generatedFile, f) return nil
}
func generateStructFromMessage(genFile *protogen.GeneratedFile, protoFile *protogen.File) error {
messages := protoFile.Messages
for _, message := range messages {
print(genFile.QualifiedGoIdent(message.GoIdent))
genFile.P(“type “, message.GoIdent.GoName, " struct{”)
for _, field := range message.Fields {
genFile.P(” “, field.GoName, " “, field.Desc.Kind(), “;”)
}
genFile.P(”}”)
_ = generateGetterMethod(genFile, message)
}
return nil
}
func generateGetterMethod(genFile *protogen.GeneratedFile, message *protogen.Message) error {
fields := message.Fields
for _, field := range fields {
genFile.P(“func (m *”, message.GoIdent.GoName, “) Get”, field.GoName, “() “, field.Desc.Kind(), “{”)
genFile.P(” return m.”, field.GoName)
genFile.P(“}”)
}
return nil
}
test.proto
syntax = “proto2”;
import “google/protobuf/descriptor.proto”;
import “validate.proto”;
package main;
option go_package = “./”;
message test {
optional string a = 1;
}
message People {
optional string name = 1;
optional string phone = 2;
}
test.pb.go
// this file is generated by proto file.
package main
type Test struct {
A string
}
func (m *Test) GetA() string {
return m.A
}
type People struct {
Name string
Phone string
}
func (m *People) GetName() string {
return m.Name
}
func (m *People) GetPhone() string {
return m.Phone
}
命令:
go build main.go && protoc --plugin=protoc-gen-myProtoPlugin=main --myProtoPlugin_out=./ test.proto
执行流程
func run(opts Options, f func(*Plugin) error) error {
if len(os.Args) > 1 {
return fmt.Errorf(“unknown argument %q (this program should be run by protoc, not directly)”, os.Args[1])
}
// 读入proto文件
in, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return err
}
req := &pluginpb.CodeGeneratorRequest{}
// 序列化为GodeGeneratorRequest
if err := proto.Unmarshal(in, req); err != nil {
return err
}
// 将GodeGeneratorRequest转化为Plugin
gen, err := opts.New(req)
if err != nil {
return err
}
// 执行自定义的方法,构造输出文件格式以及内容
if err := f(gen); err != nil {
gen.Error(err)
}
resp := gen.Response()
// 序列化目标文件
out, err := proto.Marshal(resp)
if err != nil {
return err
}
// 输出目标文件
if _, err := os.Stdout.Write(out); err != nil {
return err
}
return nil
}
type CodeGeneratorRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FileToGenerate []string protobuf:"bytes,1,rep,name=file_to_generate,json=fileToGenerate" json:"file_to_generate,omitempty"
Parameter *string protobuf:"bytes,2,opt,name=parameter" json:"parameter,omitempty"
ProtoFile []*descriptorpb.FileDescriptorProto protobuf:"bytes,15,rep,name=proto_file,json=protoFile" json:"proto_file,omitempty"
CompilerVersion *Version protobuf:"bytes,3,opt,name=compiler_version,json=compilerVersion" json:"compiler_version,omitempty"
}
type CodeGeneratorResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Error *string protobuf:"bytes,1,opt,name=error" json:"error,omitempty"
SupportedFeatures *uint64 protobuf:"varint,2,opt,name=supported_features,json=supportedFeatures" json:"supported_features,omitempty"
File []*CodeGeneratorResponse_File protobuf:"bytes,15,rep,name=file" json:"file,omitempty"
}
生成的主要对象
Options
除了通过定义一般的message生成目标文件,还可以通过为field或者message甚至file定义扩展属性extension来实现更加灵活、丰富的操作以满足复杂的需求。
例如,可以通过为message的filed加上options来来实现field validator的功能,如下:
通过为phone添加stringValidate option来实现限制phone的长度
message StringFieldRules {
optional int32 min_len = 1;
optional int32 max_len = 3;
}
message People {
optional string name = 1;
optional string phone = 2 [(validate.stringValidated)= {
min_len: 10, max_len: 20;
}];
}
以用field的option构造field validator插件为例,开发步骤可以分为以下几步:
构造validate.proto文件用以描述validator的规则,同时生成valiate.pb.go文件
在目标proto文件里引入validate.proto文件,在想要限定的field上加上自定义的规则,如字符长度,格式等
按照1.1的流程对field的option进行解析,获取option相关属性,如定义的min_len, max_len,然后按照自定义规则进行逻辑编码,生成validator文件
validate.proto
message StringFieldRules {
optional int32 min_len = 1;
optional int32 max_len = 3;
}
extend google.protobuf.FieldOptions {
optional StringFieldRules stringValidated = 5000;
}
validator的generate
func generateValidated(genFile *protogen.GeneratedFile, protoFile *protogen.File) error {
messages := protoFile.Messages
for _, message := range messages {
for _, field := range message.Fields {
// 获取field的options
fieldOptions := field.Desc.Options().(*descriptorpb.FieldOptions)
if fieldOptions == nil || fieldOptions.ProtoReflect() == nil {
continue
}
genFile.P(“func (m *”, message.GoIdent.GoName, “) Validated”, field.GoName, “() bool”, “{”)
// 处理string类型的valdator
if field.Desc.Kind() == protoreflect.StringKind {
ext := proto.GetExtension(fieldOptions, validate.E_StringValidated).(*validate.StringFieldRules)
if ext == nil {
continue
}
min := *ext.MinLen
max := *ext.MaxLen
genFile.P(“if len(m.”, field.GoName, “)< “, min, “|| len(m.”, field.GoName, “)>”, max, “{”)
genFile.P(” return false”)
genFile.P(“}”)
genFile.P(“return true”)
genFile.P(“}”)
}
}
}
return nil
}
生成的validator
func (m *People) ValidatedPhone() bool {
if len(m.Phone) < 10 || len(m.Phone) > 20 {
return false
}
return true
}
proto plugin相关推荐
- 使用CSharp编写Google Protobuf插件
什么是 Google Protocol Buffer? Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 ...
- gRPC官方快速上手学习笔记(c#版)
上手前准备工作 支持操作系统:windows.OS X.Linux.实例采用.net..net core sdk. The .NET Core SDK command line tools. The ...
- 基于DotNet Core的RPC框架(一) DotBPE.RPC快速开始
0x00 简介 DotBPE.RPC是一款基于dotnet core编写的RPC框架,而它的爸爸DotBPE,目标是实现一个开箱即用的微服务框架,但是它还差点意思,还仅仅在构思和尝试的阶段.但不管怎么 ...
- Unity中使用gRPC
时间20180508,使用的unity版本2017.2,unity中的.net 4.6平台还是不稳定版本. 示例代码地址:https://github.com/hiramtan/HigRPC_unit ...
- netcore 实现一个简单的Grpc 服务端和客户端
参考资料,和详细背景不做赘述. 首先定义prop 文件 syntax ="proto3"; package RouteGrpc; service HelloWorld{ rpc S ...
- tars C++ docker 环境配置
挺多小公司使用Tars作为rpc框架的.感觉主要原因有以下几点:1. Tars支持多语言,2. Tars的管理端,可以方便微服务管理和发布.3. 有现成的监控.不过说实在的,Tars 开源版本被阉割了 ...
- Drill storage plugin实现原理分析
Drill Storage Plugin介绍 Drill是一个交互式SQL查询引擎,官方默认支持的数据源有hive.hbase.kafka.kudu.mongo.opentsdb.jdbc等,其中jd ...
- protoc gen php,protoc-gen-php --php_out: protoc-gen-php: Plugin output is unparseable.
背景 业务需要用protobuffer 进行通讯. client: php server: c++ 在github 上找到 Protobuf-PHP (https://github.com/drslu ...
- proto的介绍和基础使用
内容摘抄自书籍<Netty redis zookeeper高并发实战> Protobuf使用 proto文件来预先定义的消息格式.数据包是按照proto文件所定义的消息格式完成二进制码流的 ...
最新文章
- iOS_25彩票_幸运转盘
- 第一次作业:阅读优秀博文谈感想
- 风变编程python第一关脸黑怪我喽_风变编程:Python适合编程初学者学习吗?
- 如何获取当前C#程序所有线程的调用栈信息 ?
- java控制关键字continue,break,return
- 基于tkinter的简易加减乘除计算器
- 空间中点到直线的距离
- abaqus实例手册_《ABAQUS 6.14超级学习手册》——1.5 ABAQUS帮助文档
- java poi 设置标题_java POI操作word2010简单实现多级标题结构
- 再论由内而外造就自己
- Windows10家庭版更改C盘用户user文件夹名称(小新pro13亲测有效)
- Pro Tools 贴士- 使用Snapper快速试听和导入音频素材
- 溢米辅导完成C轮1500万美元融资,将用于教研、产品以及技术三大领域
- 软件工程师的试炼之地:53道Python面试问答
- 摄像头P2P软件提供,完美解决打洞及音视频、用户码传输问题。
- php preg_PHP Preg简介
- 内容即广告是最好的移动商业模式?
- Linux学习笔记——文件大小和文件压缩命令
- 基于Detectron2的BlendMask训练 BlendMask环境配置 COCO数据集
- Java基础读书笔记