twirp简介
twirp是谷歌开源的rpc框架,默认支持golang并提供其他语言的实现版本,使用proto进行rpc定义开发。
安装
安装proto插件和twirp插件
go get github.com/twitchtv/twirp/protoc-gen-twirp
go get github.com/golang/protobuf/protoc-gen-go
开发
- 编写proto文件
//文件名test.proto syntax = "proto3"; //包名,通过protoc生成时go文件时 package main; message A{ } message B { string result = 1; } service Test{ rpc hello(A) returns(B); }
- 生成go代码
proto_path指定proto文件所在目录,twirp_out指定生成的rpc代码存放目录,go_out指定生成的modal代码存放目录,最后一个参数是编译的proto文件路径protoc --proto_path=. --twirp_out=. --go_out=. ./test.proto
- server与hook
阅读twirp_out中的代码
默认生成的Server结构体以service名称+Server作为名称
默认生成的service接口以service名称为名
主要方法有
其中,twirp.ClientOptions的Hooks成员类型为ClientHookstype Test interface { Hello(context.Context, *A) (*B, error) } type testJSONClient struct { client HTTPClient urls [1]string opts twirp.ClientOptions } //proto定义的rpc方法 func (c *testJSONClient) Hello(ctx context.Context, in *A) (*B, error) { ctx = ctxsetters.WithPackageName(ctx, "main") ctx = ctxsetters.WithServiceName(ctx, "Test") ctx = ctxsetters.WithMethodName(ctx, "Hello") out := new(B) err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) if err != nil { twerr, ok := err.(twirp.Error) if !ok { twerr = twirp.InternalErrorWith(err) } callClientError(ctx, c.opts.Hooks, twerr) return nil, err } //调用钩子函数 callClientResponseReceived(ctx, c.opts.Hooks) return out, nil } type testServer struct { Test hooks *twirp.ServerHooks } func NewTestServer(svc Test, hooks *twirp.ServerHooks) TwirpServer { return &testServer{ Test: svc, hooks: hooks, } }
而twirp.ServerHooks恰好是他的一个实现type ClientHooks struct { RequestPrepared func(context.Context, *http.Request) (context.Context, error) ResponseReceived func(context.Context) Error func(context.Context, Error) }
而TwirpServer 是一个继承http.Handler的接口type ServerHooks struct { RequestReceived func(context.Context) (context.Context, error) RequestRouted func(context.Context) (context.Context, error) ResponsePrepared func(context.Context) context.Context ResponseSent func(context.Context) Error func(context.Context, Error) context.Context }
所以核心就在NewTestServer这个方法type TwirpServer interface { http.Handler ServiceDescriptor() ([]byte, int) ProtocGenTwirpVersion() string PathPrefix() string }
他的第一个参数传递service的实现对象
第二个参数传递twirp.ServerHooks引用类型的钩子对象,实现rpc调用的拦截
最终方法返回一个http.Handler对象,就可以通过http.ListenAndServe方法,运行这个handler
实现rpc
编写实现代码,因为proto生成的代码都在相同目录,且包名都是main,所以这里面可以直接使用生成代码中的结构而不需要导入文件
- 服务端
http.ListenAndServe返回值为error,如果输出为空代表启动正常,否则为出错原因package main import ( context "context" "fmt" "net/http" twirp "github.com/twitchtv/twirp" ) type TestImpl struct{} func (t TestImpl) Hello(ctx context.Context, a *A) (*B, error) { println("hello") //返回结果Result字段为"server result" return &B{Result: "server result"}, nil } //重写RequestReceived,在请求发起时打印 var testHook = &twirp.ServerHooks{ RequestReceived: func(ctx context.Context) (context.Context, error) { println("receive ...") return ctx, nil }, ResponseSent: func(ctx context.Context) { println("response ...") }, } func main() { twirpHandler := NewTestServer(TestImpl{}, testHook) fmt.Printf("%v\n", http.ListenAndServe(":8080", twirpHandler)) }
- 客户端
接下来编写客户端看看结果//+build ignore package main import ( "context" "net/http" ) func main() { client := NewTestProtobufClient("http://localhost:8080", &http.Client{}) res, _ := client.Hello(context.Background(), &A{}) println(res.Result) }
- 运行
运行服务端程序以后再启动客户端,可以看到服务端打印
客户端输出receive ... hello response ...
server result