1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 20:52:22 +00:00

ch4-02: done

This commit is contained in:
chai2010 2018-06-29 15:15:25 +08:00
parent 312d84f4ca
commit 9b1941f961
8 changed files with 804 additions and 12 deletions

View File

@ -33,7 +33,7 @@
* [3.8. 补充说明](ch3-asm/ch3-08-faq.md) * [3.8. 补充说明](ch3-asm/ch3-08-faq.md)
* [第四章 RPC和Protobuf](ch4-rpc/readme.md) * [第四章 RPC和Protobuf](ch4-rpc/readme.md)
* [4.1. RPC入门](ch4-rpc/ch4-01-rpc-intro.md) * [4.1. RPC入门](ch4-rpc/ch4-01-rpc-intro.md)
* [4.2. Protobuf简介(TODO)](ch4-rpc/ch4-02-pb-intro.md) * [4.2. Protobuf](ch4-rpc/ch4-02-pb-intro.md)
* [4.3. protorpc(TODO)](ch4-rpc/ch4-03-protorpc.md) * [4.3. protorpc(TODO)](ch4-rpc/ch4-03-protorpc.md)
* [4.4. grpc(TODO)](ch4-rpc/ch4-04-grpc.md) * [4.4. grpc(TODO)](ch4-rpc/ch4-04-grpc.md)
* [4.5. 反向rpc(TODO)](ch4-rpc/ch4-05-reverse-rpc.md) * [4.5. 反向rpc(TODO)](ch4-rpc/ch4-05-reverse-rpc.md)

View File

@ -1,19 +1,430 @@
# 4.2. Protobuf简介 # 4.2. Protobuf
TODO Protobuf是Protocol Buffers的简称它是Google公司开发的一种数据描述语言并于2008年对外开源。Protobuf刚开源时的定位类似于XML、JSON等数据描述语言通过附带工具生成等代码提实现将结构化数据序列化的功能。但是我们更关注的是Protobuf作为接口规范的描述语言可以作为设计安全的跨语言PRC接口的基础工具。
<!-- ## Protobuf入门
pb基本用法pb其实就是json我们不关心底层的编码和性能 对于没有用过Protobuf读者建议先从官网了解下基本用法。这里我们尝试如何将Protobuf和RPC结合在一起使用通过Protobuf来最终保证RPC的接口规范和完全。Protobuf中最基本的数据单元是message是类似Go语言中结构体的存在。在message中可以嵌套message或其它的基础数据类型的成员。
pb2和pb3区别 首先创建hello.proto文件其中包装HelloService服务中用到的字符串类型
pb和json的区别pb有些限制比如 map 只能嵌套field名字不能随机变化 ```protobuf
嵌套时只能时msg或map到一些限制 syntax = "proto3";
rpc 语法 package main;
扩展语法 message String {
string value = 1;
}
```
开头的syntax语句表示采用Protobuf第三版本的语法。第三版的Protobuf对语言进行的提炼简化所有成员均采用类似Go语言中的零值初始化不在支持自定义默认值同时消息成员也不再支持required特性。然后package指令指明当前是main包这样可以和Go的包明保持一致当然用户也可以针对不同的语言定制对应的包路径和名称。最后message关键字定义一个新的String类型在最终生成的Go语言代码中对应一个String结构体。String类型中只有一个字符串类型的value成员该成员的Protobuf编码时的成员编号为1。
在XML或JSON成数据描述语言中一遍通过成员的名字来绑定对应的数据。但是Protobuf编码却是通过成员的唯一编号来绑定对应的数据因此Protobuf编码后数据的体积会比较小但是也非常不便于人类查阅。我们目前并不关注Protobuf的编码技术最终生成的Go结构体可以自由采用JSON或gob等编码格式因此大家可以暂时忽略Protobuf的成员编号部分。
Protobuf核心的工具集是C++语言开发的在官方的protoc编译器中并不支持Go语言。要想基于上面的hello.proto文件生成相应的Go代码需要安装相应的工具。首先是安装官方的protoc工具可以从 https://github.com/google/protobuf/releases 下载。然后是安装针对Go语言的代码生成插件可以通过`go get github.com/golang/protobuf/protoc-gen-go`命令按安装。
然后通过以下命令生成相应的Go代码
```
$ protoc --go_out=. hello.proto
```
其中`go_out`参数告知protoc编译器取加载对应的protoc-gen-go工具然后通过该工具生成代码生成代码放到当前目录。最后是一系列要处理的protobuf文件的列表。
这里只生成了一个hello.pb.go文件其中String结构体内容如下
```go
type String struct {
Value string `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *String) Reset() { *m = String{} }
func (m *String) String() string { return proto.CompactTextString(m) }
func (*String) ProtoMessage() {}
func (*String) Descriptor() ([]byte, []int) {
return fileDescriptor_hello_069698f99dd8f029, []int{0}
}
func (m *String) GetValue() string {
if m != nil {
return m.Value
}
return ""
}
```
生成的结构体中有一些以`XXX_`为前缀名字的成员目前可以忽略这些成员。同时String类型还自动生成了一组方法其中ProtoMessage方法表示这是一个实现了proto.Message接口的方法。此外Protobuf还为每个成员生成了一个Get方法Get方法不仅可以处理空指针类型而且可以和Protobuf第三版的方法保持一致第二版的自定义默认值特性依赖这类方法
基于新的String类型我们可以重新实现HelloService
```go
type HelloService struct{}
func (p *HelloService) Hello(request String, reply *String) error {
reply.Value = "hello:" + request.GetValue()
return nil
}
```
其中Hello方法的输入参数和返回的参数均该用Protobuf定义的String类型表示函数的内部代码同时也做了相应的调整。
至此我们初步实现了Protobuf和RPC组合工作。在启动RPC服务时我们依然可以选择默认的gob或手工指定json编码甚至可以重新基于protobuf编码实现一个插件。虽然做了这么多工作但是似乎并没有看到什么收益
回顾第一章中更安全的PRC接口部分的内容当时我们花费了极大的力气去给RPC服务增加安全的保障。最终得到的更安全的PRC接口的代码本书就非常繁琐比利于手工维护同时全部安全相关的代码只适用于Go语言环境既然使用了Protobuf定义的输入和输出参数那么RPC服务接口是否也可以通过Protobuf定义呢其实用Protobuf定义语言无关的PRC服务接口才是它真正的价值所在
下面更新hello.proto文件通过Protobuf来定义HelloService服务
```protobuf
service HelloService {
rpc Hello (String) returns (String);
}
```
但是重新生成的Go代码并没有发生变化。这是因为世界上的RPC实现有千万种protoc编译器并不知道改如何为HelloService服务生成代码。
不过在protoc-gen-go内部已经集成了一个叫grpc的插件可以针对grpc生成代码
```
$ protoc --go_out=plugins=grpc:. hello.proto
```
在生成的代码中多了一些类似HelloServiceServer、HelloServiceClient的新类型。这些类似是为grpc服务的并不符合我们的RPC要求。
grpc插件为我们提供了改进思路下面我们将探索如何为我们的RPC生成安全的代码。
## 定制代码生成插件
Protobuf的protoc编译器是通过插件机制实现对不同语言的支持。比如protoc命令出现`--xxx_out`格式的参数那么protoc将首先查询是否有内置的xxx插件如果没有内置的xxx插件那么将继续查询当前系统中是否存在protoc-gen-xxx命名的可执行程序最终通过查询到的插件生成代码。对于Go语言的protoc-gen-go插件来说里面又实现了一层静态插件系统。比如protoc-gen-go内置了一个grpc插件用户可以通过`--go_out=plugins=grpc`参数来生成grpc相关代码否则只会针对message生成相关代码。
参考grpc插件的代码可以发现generator.RegisterPlugin函数可以用来注册插件。插件是一个generator.Plugin接口
```go
// A Plugin provides functionality to add to the output during Go code generation,
// such as to produce RPC stubs.
type Plugin interface {
// Name identifies the plugin.
Name() string
// Init is called once after data structures are built but before
// code generation begins.
Init(g *Generator)
// Generate produces the code generated by the plugin for this file,
// except for the imports, by calling the generator's methods P, In, and Out.
Generate(file *FileDescriptor)
// GenerateImports produces the import declarations for this file.
// It is called after Generate.
GenerateImports(file *FileDescriptor)
}
```
其中Name方法返回插件的名字这是Go语言的Protobuf实现的插件体系和protoc插件的名字并无关系。然后Init函数是通过g参数对插件进行初始化g参数中包含Proto文件的所有信息。最后的Generate和GenerateImports方法用于生成主体代码和对应的导入包代码。
因此我们可以设计一个netrpcPlugin插件用于为标准库的RPC框架生成代码
```go
import (
"github.com/golang/protobuf/protoc-gen-go/generator"
)
type netrpcPlugin struct{ *generator.Generator }
func (p *netrpcPlugin) Name() string { return "netrpc" }
func (p *netrpcPlugin) Init(g *generator.Generator) { p.Generator = g }
func (p *netrpcPlugin) GenerateImports(file *generator.FileDescriptor) {
if len(file.Service) > 0 {
p.P("// TODO: import code")
}
}
func (p *netrpcPlugin) Generate(file *generator.FileDescriptor) {
for _, svc := range file.Service {
p.P("// TODO: service code, Name = " + svc.GetName())
_ = svc
}
}
```
首先Name方法返回插件的名字。netrpcPlugin插件内置了一个匿名的`*generator.Generator`成员然后在Init初始化的时候用参数g进行初始化因此插件是从g参数对象继承了全部的公有方法。在GenerateImports方法中当判断表示服务数列的`file.Service`切片非空时输出一个注释信息。在Generate方法也是才有类似的测试但是遍历每个服务输出一个注释并且输出服务的名字。至此一个最简陋的自定义的protoc-gen-go静态插件已经成型了。
要使用该插件需要先通过generator.RegisterPlugin函数注册插件可以在init函数完成
```go
func init() {
generator.RegisterPlugin(new(netrpcPlugin))
}
```
因为Go语言的包只能静态导入我们无法向已经安装的protoc-gen-go添加我们新编写的插件。我们可以完整克隆protoc-gen-go对应main函数
```go
// copy from https://github.com/golang/protobuf/blob/master/protoc-gen-go/main.go
package main
import (
"io/ioutil"
"os"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/generator"
)
func main() {
// Begin by allocating a generator. The request and response structures are stored there
// so we can do error handling easily - the response structure contains the field to
// report failure.
g := generator.New()
data, err := ioutil.ReadAll(os.Stdin)
if err != nil {
g.Error(err, "reading input")
}
if err := proto.Unmarshal(data, g.Request); err != nil {
g.Error(err, "parsing input proto")
}
if len(g.Request.FileToGenerate) == 0 {
g.Fail("no files to generate")
}
g.CommandLineParameters(g.Request.GetParameter())
// Create a wrapped version of the Descriptors and EnumDescriptors that
// point to the file that defines them.
g.WrapTypes()
g.SetPackageNames()
g.BuildTypeNameMap()
g.GenerateAllFiles()
// Send back the results.
data, err = proto.Marshal(g.Response)
if err != nil {
g.Error(err, "failed to marshal output proto")
}
_, err = os.Stdout.Write(data)
if err != nil {
g.Error(err, "failed to write output proto")
}
}
```
为了避免对protoc-gen-go插件造成干扰我们将我们的可执行程序命名为protoc-gen-go-netrpc表示包含了nerpc插件。然后用以下命令重新编译hello.proto文件
```
$ protoc --go-netrpc_out=plugins=netrpc:. hello.proto
```
其中`--go-netrpc_out`参数高中protoc编译器加载名为protoc-gen-go-netrpc的插件插件中的`plugins=netrpc`指示启用内部名为netrpc的netrpcPlugin插件。
在新生成的hello.pb.go文件中将包含以下的代码
```go
// TODO: import code
// TODO: service code, Name = HelloService
```
至此手工定制的Protobuf代码生成插件终于可以工作了。
## 自动生成完整的RPC代码
在前面的例子中我们已经构件了最小化的netrpcPlugin插件并且通过克隆protoc-gen-go的主程序创建了新的protoc-gen-go-netrpc的插件程序。我们现在开始继续完善netrpcPlugin插件最终目标是生成RPC安全接口。
以下是完善后的GenerateImports和Generate方法
```go
func (p *netrpcPlugin) GenerateImports(file *generator.FileDescriptor) {
if len(file.Service) > 0 {
p.P(`import "net/rpc"`)
}
}
func (p *netrpcPlugin) Generate(file *generator.FileDescriptor) {
for _, svc := range file.Service {
p.genServiceInterface(file, svc)
p.genServiceServer(file, svc)
p.genServiceClient(file, svc)
}
}
```
在导入部分我们增加了导入net/rpc包的语句。而在每个服务部分则通过genServiceInterface方法生成服务的接口通过genServiceServer方法生成服务的注册函数通过genServiceClient方法生成客户端包装代码。
首先看看genServiceInterface如何生成服务器接口
```go
func (p *netrpcPlugin) genServiceInterface(
file *generator.FileDescriptor,
svc *descriptor.ServiceDescriptorProto,
) {
const serviceInterfaceTmpl = `
type {{.ServiceName}}Interface interface {
{{.CallMethodList}}
}
`
const callMethodTmpl = `
{{.MethodName}}(in {{.ArgsType}}, out *{{.ReplyType}}) error`
// gen call method list
var callMethodList string
for _, m := range svc.Method {
out := bytes.NewBuffer([]byte{})
t := template.Must(template.New("").Parse(callMethodTmpl))
t.Execute(out, &struct{ ServiceName, MethodName, ArgsType, ReplyType string }{
ServiceName: generator.CamelCase(svc.GetName()),
MethodName: generator.CamelCase(m.GetName()),
ArgsType: p.TypeName(p.ObjectNamed(m.GetInputType())),
ReplyType: p.TypeName(p.ObjectNamed(m.GetOutputType())),
})
callMethodList += out.String()
p.RecordTypeUse(m.GetInputType())
p.RecordTypeUse(m.GetOutputType())
}
// gen all interface code
out := bytes.NewBuffer([]byte{})
t := template.Must(template.New("").Parse(serviceInterfaceTmpl))
t.Execute(out, &struct{ ServiceName, CallMethodList string }{
ServiceName: generator.CamelCase(svc.GetName()),
CallMethodList: callMethodList,
})
p.P(out.String())
}
```
生成服务接口时首先需要服务的名字,可以通过`svc.GetName()`获取服务在Proto文件中的名字然后通过generator.CamelCase函数转为Go语言中修饰后的名字。
服务中svc.Method是一个表示方法信息的切片。要生成每个方法需要知道每个方法的名字、输入参数类型、输出参数类型。其中m.GetName()是获取原始的方法名字同样需要通过generator.CamelCase转化为Go语言中修饰后的名字。而`p.TypeName(p.ObjectNamed(m.GetInputType()))``p.TypeName(p.ObjectNamed(m.GetOutputType()))`分别用户获取输入参数和输出参数的类型名字。
然后是生成RPC注册方法的genServiceServer函数
```go
func (p *netrpcPlugin) genServiceServer(
file *generator.FileDescriptor,
svc *descriptor.ServiceDescriptorProto,
) {
const serviceHelperFunTmpl = `
func Register{{.ServiceName}}(srv *rpc.Server, x {{.ServiceName}}) error {
if err := srv.RegisterName("{{.ServiceName}}", x); err != nil {
return err
}
return nil
}
`
out := bytes.NewBuffer([]byte{})
t := template.Must(template.New("").Parse(serviceHelperFunTmpl))
t.Execute(out, &struct{ PackageName, ServiceName, ServiceRegisterName string }{
PackageName: file.GetPackage(),
ServiceName: generator.CamelCase(svc.GetName()),
})
p.P(out.String())
}
```
genServiceServer函数的实现和生成接口的代码类似依然是才有Go语言的模板生成目标代码。
最后是genServiceClient函数生成客户端包装代码
```go
func (p *netrpcPlugin) genServiceClient(
file *generator.FileDescriptor,
svc *descriptor.ServiceDescriptorProto,
) {
const clientHelperFuncTmpl = `
type {{.ServiceName}}Client struct {
*rpc.Client
}
var _ {{.ServiceName}}Interface = (*{{.ServiceName}}Client)(nil)
func Dial{{.ServiceName}}(network, address string) (*{{.ServiceName}}Client, error) {
c, err := rpc.Dial(network, address)
if err != nil {
return nil, err
}
return &{{.ServiceName}}Client{Client: c}, nil
}
{{.MethodList}}
`
const clientMethodTmpl = `
func (p *{{.ServiceName}}Client) {{.MethodName}}(in {{.ArgsType}}, out *{{.ReplyType}}) error {
return p.Client.Call("{{.ServiceName}}.{{.MethodName}}", in, out)
}
`
// gen client method list
var methodList string
for _, m := range svc.Method {
out := bytes.NewBuffer([]byte{})
t := template.Must(template.New("").Parse(clientMethodTmpl))
t.Execute(out, &struct{ ServiceName, ServiceRegisterName, MethodName, ArgsType, ReplyType string }{
ServiceName: generator.CamelCase(svc.GetName()),
ServiceRegisterName: file.GetPackage() + "." + generator.CamelCase(svc.GetName()),
MethodName: generator.CamelCase(m.GetName()),
ArgsType: p.TypeName(p.ObjectNamed(m.GetInputType())),
ReplyType: p.TypeName(p.ObjectNamed(m.GetOutputType())),
})
methodList += out.String()
}
// gen all client code
out := bytes.NewBuffer([]byte{})
t := template.Must(template.New("").Parse(clientHelperFuncTmpl))
t.Execute(out, &struct{ PackageName, ServiceName, MethodList string }{
PackageName: file.GetPackage(),
ServiceName: generator.CamelCase(svc.GetName()),
MethodList: methodList,
})
p.P(out.String())
}
```
除了模板不同,客户端的生成代码逻辑服务接口的生成函数也是类似的。
最后我们可以查看下netrpcPlugin插件生成的RPC代码
```go
type HelloServiceInterface interface {
Hello(in String, out *String) error
}
func RegisterHelloService(srv *rpc.Server, x HelloService) error {
if err := srv.RegisterName("HelloService", x); err != nil {
return err
}
return nil
}
type HelloServiceClient struct {
*rpc.Client
}
var _ HelloServiceInterface = (*HelloServiceClient)(nil)
func DialHelloService(network, address string) (*HelloServiceClient, error) {
c, err := rpc.Dial(network, address)
if err != nil {
return nil, err
}
return &HelloServiceClient{Client: c}, nil
}
func (p *HelloServiceClient) Hello(in String, out *String) error {
return p.Client.Call("HelloService.Hello", in, out)
}
```
当Protobuf的插件定制工作完成后每次hello.proto文件中RPC服务的变化都可以自动生成代码。同时才有类似的技术也可以为其它语言编写代码生成插件。
在掌握了定制Protobuf插件技术后你将彻底拥有这个技术。
包体系结构
-->

View File

View File

@ -0,0 +1,105 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: hello.proto
package main
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import "net/rpc"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type String struct {
Value string `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *String) Reset() { *m = String{} }
func (m *String) String() string { return proto.CompactTextString(m) }
func (*String) ProtoMessage() {}
func (*String) Descriptor() ([]byte, []int) {
return fileDescriptor_hello_5dd9d59ecabc789f, []int{0}
}
func (m *String) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_String.Unmarshal(m, b)
}
func (m *String) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_String.Marshal(b, m, deterministic)
}
func (dst *String) XXX_Merge(src proto.Message) {
xxx_messageInfo_String.Merge(dst, src)
}
func (m *String) XXX_Size() int {
return xxx_messageInfo_String.Size(m)
}
func (m *String) XXX_DiscardUnknown() {
xxx_messageInfo_String.DiscardUnknown(m)
}
var xxx_messageInfo_String proto.InternalMessageInfo
func (m *String) GetValue() string {
if m != nil {
return m.Value
}
return ""
}
func init() {
proto.RegisterType((*String)(nil), "main.String")
}
type HelloServiceInterface interface {
Hello(in String, out *String) error
}
func RegisterHelloService(srv *rpc.Server, x HelloService) error {
if err := srv.RegisterName("HelloService", x); err != nil {
return err
}
return nil
}
type HelloServiceClient struct {
*rpc.Client
}
var _ HelloServiceInterface = (*HelloServiceClient)(nil)
func DialHelloService(network, address string) (*HelloServiceClient, error) {
c, err := rpc.Dial(network, address)
if err != nil {
return nil, err
}
return &HelloServiceClient{Client: c}, nil
}
func (p *HelloServiceClient) Hello(in String, out *String) error {
return p.Client.Call("HelloService.Hello", in, out)
}
func init() { proto.RegisterFile("hello.proto", fileDescriptor_hello_5dd9d59ecabc789f) }
var fileDescriptor_hello_5dd9d59ecabc789f = []byte{
// 107 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0x48, 0xcd, 0xc9,
0xc9, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0x4d, 0xcc, 0xcc, 0x53, 0x92, 0xe3,
0x62, 0x0b, 0x2e, 0x29, 0xca, 0xcc, 0x4b, 0x17, 0x12, 0xe1, 0x62, 0x2d, 0x4b, 0xcc, 0x29, 0x4d,
0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x70, 0x8c, 0x8c, 0xb9, 0x78, 0x3c, 0x40, 0x9a,
0x82, 0x53, 0x8b, 0xca, 0x32, 0x93, 0x53, 0x85, 0x94, 0xb9, 0x58, 0xc1, 0x7c, 0x21, 0x1e, 0x3d,
0x90, 0x7e, 0x3d, 0x88, 0x66, 0x29, 0x14, 0x5e, 0x12, 0x1b, 0xd8, 0x06, 0x63, 0x40, 0x00, 0x00,
0x00, 0xff, 0xff, 0xa1, 0x2a, 0xa2, 0x5a, 0x70, 0x00, 0x00, 0x00,
}

View File

@ -0,0 +1,11 @@
syntax = "proto3";
package main;
message String {
string value = 1;
}
service HelloService {
rpc Hello (String) returns (String);
}

View File

@ -0,0 +1,12 @@
package main
type HelloService struct{}
func (p *HelloService) Hello(request String, reply *String) error {
reply.Value = "hello:" + request.GetValue()
return nil
}
func main() {
}

View File

@ -0,0 +1,98 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2010 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// protoc-gen-go is a plugin for the Google protocol buffer compiler to generate
// Go code. Run it by building this program and putting it in your path with
// the name
// protoc-gen-go
// That word 'go' at the end becomes part of the option string set for the
// protocol compiler, so once the protocol compiler (protoc) is installed
// you can run
// protoc --go_out=output_directory input_directory/file.proto
// to generate Go bindings for the protocol defined by file.proto.
// With that input, the output will be written to
// output_directory/file.pb.go
//
// The generated code is documented in the package comment for
// the library.
//
// See the README and documentation for protocol buffers to learn more:
// https://developers.google.com/protocol-buffers/
package main
import (
"io/ioutil"
"os"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/generator"
)
func main() {
// Begin by allocating a generator. The request and response structures are stored there
// so we can do error handling easily - the response structure contains the field to
// report failure.
g := generator.New()
data, err := ioutil.ReadAll(os.Stdin)
if err != nil {
g.Error(err, "reading input")
}
if err := proto.Unmarshal(data, g.Request); err != nil {
g.Error(err, "parsing input proto")
}
if len(g.Request.FileToGenerate) == 0 {
g.Fail("no files to generate")
}
g.CommandLineParameters(g.Request.GetParameter())
// Create a wrapped version of the Descriptors and EnumDescriptors that
// point to the file that defines them.
g.WrapTypes()
g.SetPackageNames()
g.BuildTypeNameMap()
g.GenerateAllFiles()
// Send back the results.
data, err = proto.Marshal(g.Response)
if err != nil {
g.Error(err, "failed to marshal output proto")
}
_, err = os.Stdout.Write(data)
if err != nil {
g.Error(err, "failed to write output proto")
}
}

View File

@ -0,0 +1,155 @@
// Copyright 2013 <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"text/template"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/golang/protobuf/protoc-gen-go/generator"
)
type netrpcPlugin struct{ *generator.Generator }
func (p *netrpcPlugin) Name() string { return "netrpc" }
func (p *netrpcPlugin) Init(g *generator.Generator) { p.Generator = g }
func (p *netrpcPlugin) GenerateImports(file *generator.FileDescriptor) {
if len(file.Service) > 0 {
p.P(`import "net/rpc"`)
}
}
func (p *netrpcPlugin) Generate(file *generator.FileDescriptor) {
for _, svc := range file.Service {
p.genServiceInterface(file, svc)
p.genServiceServer(file, svc)
p.genServiceClient(file, svc)
}
}
func (p *netrpcPlugin) genServiceInterface(
file *generator.FileDescriptor,
svc *descriptor.ServiceDescriptorProto,
) {
const serviceInterfaceTmpl = `
type {{.ServiceName}}Interface interface {
{{.CallMethodList}}
}
`
const callMethodTmpl = `
{{.MethodName}}(in {{.ArgsType}}, out *{{.ReplyType}}) error`
// gen call method list
var callMethodList string
for _, m := range svc.Method {
out := bytes.NewBuffer([]byte{})
t := template.Must(template.New("").Parse(callMethodTmpl))
t.Execute(out, &struct{ ServiceName, MethodName, ArgsType, ReplyType string }{
ServiceName: generator.CamelCase(svc.GetName()),
MethodName: generator.CamelCase(m.GetName()),
ArgsType: p.TypeName(p.ObjectNamed(m.GetInputType())),
ReplyType: p.TypeName(p.ObjectNamed(m.GetOutputType())),
})
callMethodList += out.String()
p.RecordTypeUse(m.GetInputType())
p.RecordTypeUse(m.GetOutputType())
}
// gen all interface code
{
out := bytes.NewBuffer([]byte{})
t := template.Must(template.New("").Parse(serviceInterfaceTmpl))
t.Execute(out, &struct{ ServiceName, CallMethodList string }{
ServiceName: generator.CamelCase(svc.GetName()),
CallMethodList: callMethodList,
})
p.P(out.String())
}
}
func (p *netrpcPlugin) genServiceServer(
file *generator.FileDescriptor,
svc *descriptor.ServiceDescriptorProto,
) {
const serviceHelperFunTmpl = `
func Register{{.ServiceName}}(srv *rpc.Server, x {{.ServiceName}}) error {
if err := srv.RegisterName("{{.ServiceName}}", x); err != nil {
return err
}
return nil
}
`
{
out := bytes.NewBuffer([]byte{})
t := template.Must(template.New("").Parse(serviceHelperFunTmpl))
t.Execute(out, &struct{ PackageName, ServiceName, ServiceRegisterName string }{
PackageName: file.GetPackage(),
ServiceName: generator.CamelCase(svc.GetName()),
})
p.P(out.String())
}
}
func (p *netrpcPlugin) genServiceClient(
file *generator.FileDescriptor,
svc *descriptor.ServiceDescriptorProto,
) {
const clientHelperFuncTmpl = `
type {{.ServiceName}}Client struct {
*rpc.Client
}
var _ {{.ServiceName}}Interface = (*{{.ServiceName}}Client)(nil)
func Dial{{.ServiceName}}(network, address string) (*{{.ServiceName}}Client, error) {
c, err := rpc.Dial(network, address)
if err != nil {
return nil, err
}
return &{{.ServiceName}}Client{Client: c}, nil
}
{{.MethodList}}
`
const clientMethodTmpl = `
func (p *{{.ServiceName}}Client) {{.MethodName}}(in {{.ArgsType}}, out *{{.ReplyType}}) error {
return p.Client.Call("{{.ServiceName}}.{{.MethodName}}", in, out)
}
`
// gen client method list
var methodList string
for _, m := range svc.Method {
out := bytes.NewBuffer([]byte{})
t := template.Must(template.New("").Parse(clientMethodTmpl))
t.Execute(out, &struct{ ServiceName, ServiceRegisterName, MethodName, ArgsType, ReplyType string }{
ServiceName: generator.CamelCase(svc.GetName()),
ServiceRegisterName: file.GetPackage() + "." + generator.CamelCase(svc.GetName()),
MethodName: generator.CamelCase(m.GetName()),
ArgsType: p.TypeName(p.ObjectNamed(m.GetInputType())),
ReplyType: p.TypeName(p.ObjectNamed(m.GetOutputType())),
})
methodList += out.String()
}
// gen all client code
{
out := bytes.NewBuffer([]byte{})
t := template.Must(template.New("").Parse(clientHelperFuncTmpl))
t.Execute(out, &struct{ PackageName, ServiceName, MethodList string }{
PackageName: file.GetPackage(),
ServiceName: generator.CamelCase(svc.GetName()),
MethodList: methodList,
})
p.P(out.String())
}
}
func init() {
generator.RegisterPlugin(new(netrpcPlugin))
}