├── README.md ├── contrib └── proto │ ├── service.protolist │ ├── debug │ └── ping.proto │ ├── test │ └── test.proto │ └── base │ └── pack.proto ├── daisy ├── debug_service.go └── main.go ├── Makefile ├── pb ├── rpc │ ├── service.go │ ├── client.go │ ├── server.go │ ├── bridge.go │ ├── codec.go │ ├── rpc.go │ ├── descriptor.go │ └── context.go ├── generator.go └── parser │ ├── protolist_test.go │ └── protolist.go ├── gen ├── descriptor │ └── descriptor.go └── proto │ ├── debug │ └── ping.pb.go │ ├── test │ └── test.pb.go │ └── base │ └── pack.pb.go ├── client └── main.go └── project └── pb-gen.sh /README.md: -------------------------------------------------------------------------------- 1 | [daisy](http://blog.441a.com/html/daisy.html) 2 | -------------------------------------------------------------------------------- /contrib/proto/service.protolist: -------------------------------------------------------------------------------- 1 | debug { 2 | ping = 1 3 | } 4 | 5 | test { 6 | echo = 100001 7 | strobe:[] = 100002 8 | } 9 | 10 | -------------------------------------------------------------------------------- /contrib/proto/debug/ping.proto: -------------------------------------------------------------------------------- 1 | package proto.debug; 2 | 3 | message Ping { 4 | optional string ping = 1; 5 | message Response { 6 | optional string pong = 1; 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /contrib/proto/test/test.proto: -------------------------------------------------------------------------------- 1 | package proto.test; 2 | 3 | message Echo { 4 | optional string req = 1; 5 | message Response { 6 | optional string resp = 1; 7 | } 8 | } 9 | 10 | message Strobe { 11 | optional string msg = 2; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /daisy/debug_service.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/xjdrew/daisy/pb/rpc" 7 | 8 | "github.com/xjdrew/daisy/gen/proto/debug" 9 | ) 10 | 11 | type Debug int 12 | 13 | func (d *Debug) Ping(context *rpc.Context, req *proto_debug.Ping, rsp *proto_debug.Ping_Response) *rpc.CallError { 14 | log.Printf("Ping: %+v", req) 15 | rsp.Pong = req.Ping 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /contrib/proto/base/pack.proto: -------------------------------------------------------------------------------- 1 | package proto.base; 2 | 3 | message Error { 4 | optional bool failed = 1; 5 | optional int32 code = 2; 6 | optional string error = 3; 7 | } 8 | 9 | message Pack { 10 | optional int32 session = 1; // 客户端生成,服务器相应时原样返回 11 | optional int32 type = 2; // 请求时为接口id,响应时为0 12 | optional Error error = 3; // 若处理请求出错返回给客户端 13 | optional bytes data = 4; // 请求内容 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all install test clean 2 | 3 | all: build protobuf descriptor install 4 | 5 | build: 6 | -mkdir -p ./gen/proto 7 | -mkdir -p ./gen/descriptor 8 | 9 | %.pb.go: %.proto 10 | protoc --go_out=$(PB_DIR) $< 11 | 12 | descriptor: 13 | cat ./contrib/proto/service.protolist | go run ./pb/generator.go > ./gen/descriptor/descriptor.go 14 | 15 | protobuf: 16 | project/pb-gen.sh 17 | 18 | install: 19 | go install ./daisy 20 | go install ./client 21 | 22 | test: 23 | go test ./... 24 | 25 | clean: 26 | go clean -i ./... 27 | -------------------------------------------------------------------------------- /pb/rpc/service.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | type service struct { 8 | rcvr reflect.Value 9 | method reflect.Method 10 | dptor *Descriptor 11 | } 12 | 13 | func (s *service) hasReply() bool { 14 | return s.dptor.HasReply() 15 | } 16 | 17 | // have response 18 | func (s *service) call(c *Context, argv, replyv reflect.Value) *CallError { 19 | function := s.method.Func 20 | returnValues := function.Call([]reflect.Value{s.rcvr, reflect.ValueOf(c), argv, replyv}) 21 | 22 | inter := returnValues[0].Interface() 23 | if inter == nil { 24 | return nil 25 | } 26 | return inter.(*CallError) 27 | } 28 | 29 | // no response 30 | func (s *service) invoke(c *Context, argv reflect.Value) { 31 | function := s.method.Func 32 | function.Call([]reflect.Value{s.rcvr, reflect.ValueOf(c), argv}) 33 | } 34 | -------------------------------------------------------------------------------- /pb/rpc/client.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "log" 5 | "net" 6 | 7 | "github.com/xjdrew/daisy/gen/proto/base" 8 | ) 9 | 10 | type Client struct { 11 | *Rpc 12 | *Context 13 | } 14 | 15 | func NewClient(bridge *Bridge, conn net.Conn) *Client { 16 | cli := new(Client) 17 | cli.Rpc = &Rpc{bridge: bridge, serviceMap: make(map[int32]*service)} 18 | cli.Context = NewContext(cli, conn) 19 | return cli 20 | } 21 | 22 | func (client *Client) onIoError(context *Context, err error) { 23 | log.Println("onIoError:", context, err) 24 | } 25 | 26 | // return true if ignore 27 | func (client *Client) onUnknownPack(context *Context, pack *proto_base.Pack) bool { 28 | log.Println("onUnknownPack:", context, pack) 29 | return true 30 | } 31 | 32 | func (client *Client) Serve() { 33 | client.Context.serve() 34 | } 35 | 36 | func (client *Client) Close() error { 37 | return client.Context.Close() 38 | } 39 | -------------------------------------------------------------------------------- /gen/descriptor/descriptor.go: -------------------------------------------------------------------------------- 1 | // Code generated by protolist-generator. 2 | // DO NOT EDIT! 3 | 4 | package descriptor 5 | 6 | import ( 7 | "reflect" 8 | 9 | "github.com/xjdrew/daisy/pb/rpc" 10 | 11 | "github.com/xjdrew/daisy/gen/proto/debug" 12 | "github.com/xjdrew/daisy/gen/proto/test" 13 | ) 14 | 15 | var Descriptors = []rpc.Descriptor{ 16 | { 17 | Id: 1, 18 | NormalName: "debug.ping", 19 | MethodName: "Debug.Ping", 20 | ArgType: reflect.TypeOf(&proto_debug.Ping{}), 21 | ReplyType: reflect.TypeOf(&proto_debug.Ping_Response{}), 22 | }, 23 | 24 | { 25 | Id: 100001, 26 | NormalName: "test.echo", 27 | MethodName: "Test.Echo", 28 | ArgType: reflect.TypeOf(&proto_test.Echo{}), 29 | ReplyType: reflect.TypeOf(&proto_test.Echo_Response{}), 30 | }, 31 | 32 | { 33 | Id: 100002, 34 | NormalName: "test.strobe", 35 | MethodName: "Test.Strobe", 36 | ArgType: reflect.TypeOf(&proto_test.Strobe{}), 37 | ReplyType: nil, 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /pb/rpc/server.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "log" 5 | "net" 6 | 7 | "github.com/xjdrew/daisy/gen/proto/base" 8 | ) 9 | 10 | type Server struct { 11 | Rpc 12 | } 13 | 14 | func newServer(bridge *Bridge) *Server { 15 | return &Server{ 16 | Rpc: Rpc{bridge: bridge, serviceMap: make(map[int32]*service)}, 17 | } 18 | } 19 | 20 | func (r *Rpc) onIoError(context *Context, err error) { 21 | log.Println("onIoError:", context, err) 22 | } 23 | 24 | // return true if ignore 25 | func (r *Rpc) onUnknownPack(context *Context, pack *proto_base.Pack) bool { 26 | log.Println("onUnknownPack:", context, pack) 27 | return false 28 | } 29 | 30 | func (server *Server) Accept(lis net.Listener) error { 31 | for { 32 | conn, err := lis.Accept() 33 | if err != nil { 34 | if opErr, ok := err.(*net.OpError); ok { 35 | if !opErr.Temporary() { 36 | return err 37 | } 38 | } 39 | continue 40 | } 41 | 42 | context := NewContext(server, conn) 43 | go context.serve() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /daisy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | 7 | "github.com/golang/protobuf/proto" 8 | 9 | "github.com/xjdrew/daisy/gen/descriptor" 10 | "github.com/xjdrew/daisy/gen/proto/test" 11 | "github.com/xjdrew/daisy/pb/rpc" 12 | ) 13 | 14 | type Test int 15 | 16 | func (t *Test) Echo(context *rpc.Context, req *proto_test.Echo, rsp *proto_test.Echo_Response) *rpc.CallError { 17 | log.Printf("Echo: %+v", req) 18 | context.MustInvoke("test.strobe", &proto_test.Strobe{Msg: proto.String("recv:" + req.GetReq())}) 19 | rsp.Resp = req.Req 20 | return nil 21 | } 22 | 23 | func register(server *rpc.Server, rcvr interface{}) { 24 | err := server.RegisterModule(rcvr) 25 | if err != nil { 26 | log.Fatal("register error:", err) 27 | } 28 | } 29 | 30 | func main() { 31 | bridge := rpc.NewBridge(descriptor.Descriptors) 32 | server := bridge.NewServer() 33 | register(server, new(Debug)) 34 | register(server, new(Test)) 35 | l, err := net.Listen("tcp", ":1234") 36 | if err != nil { 37 | log.Fatal("listen error:", err) 38 | } 39 | server.Accept(l) 40 | } 41 | -------------------------------------------------------------------------------- /client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/golang/protobuf/proto" 7 | 8 | "github.com/xjdrew/daisy/gen/descriptor" 9 | "github.com/xjdrew/daisy/gen/proto/test" 10 | "github.com/xjdrew/daisy/pb/rpc" 11 | ) 12 | 13 | type Test int 14 | 15 | func (t *Test) Strobe(context *rpc.Context, req *proto_test.Strobe) { 16 | log.Printf("Strobe: %+v", req) 17 | } 18 | 19 | func main() { 20 | bridge := rpc.NewBridge(descriptor.Descriptors) 21 | client, err := bridge.Dail("tcp", "127.0.0.1:1234") 22 | if err != nil { 23 | log.Fatal("dialing:", err) 24 | } 25 | defer client.Close() 26 | 27 | // register callbacks 28 | if err := client.RegisterModule(new(Test)); err != nil { 29 | log.Fatal("register error:", err) 30 | } 31 | 32 | go client.Serve() 33 | // echo 34 | req := &proto_test.Echo{Req: proto.String("hello")} 35 | echod := proto_test.Echo_Response{} 36 | callErr := client.MustCall("test.echo", req, &echod) 37 | if callErr != nil { 38 | log.Fatal("call test.echo:", callErr) 39 | } 40 | log.Printf("echo response:%s", echod.GetResp()) 41 | } 42 | -------------------------------------------------------------------------------- /pb/rpc/bridge.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "log" 5 | "net" 6 | ) 7 | 8 | type Bridge struct { 9 | nameMap map[string]*Descriptor 10 | methodMap map[string]*Descriptor 11 | idMap map[int32]*Descriptor 12 | } 13 | 14 | func NewBridge(descriptors []Descriptor) *Bridge { 15 | idMap := make(map[int32]*Descriptor) 16 | nameMap := make(map[string]*Descriptor) 17 | methodMap := make(map[string]*Descriptor) 18 | for i := range descriptors { 19 | dptor := descriptors[i] 20 | if _, ok := idMap[dptor.Id]; ok { 21 | log.Panicf("repeated service:%#v", dptor) 22 | } else { 23 | idMap[dptor.Id] = &dptor 24 | } 25 | 26 | nameMap[dptor.NormalName] = &dptor 27 | methodMap[dptor.MethodName] = &dptor 28 | } 29 | 30 | return &Bridge{ 31 | idMap: idMap, 32 | nameMap: nameMap, 33 | methodMap: methodMap, 34 | } 35 | } 36 | 37 | func (bridge *Bridge) NewServer() *Server { 38 | return newServer(bridge) 39 | } 40 | 41 | func (bridge *Bridge) Dail(network, address string) (cli *Client, err error) { 42 | var conn net.Conn 43 | conn, err = net.Dial(network, address) 44 | if err != nil { 45 | return 46 | } 47 | cli = bridge.NewClient(conn) 48 | return 49 | } 50 | 51 | func (bridge *Bridge) NewClient(conn net.Conn) *Client { 52 | return NewClient(bridge, conn) 53 | } 54 | 55 | func (bridge *Bridge) getDescriptorByName(name string) *Descriptor { 56 | return bridge.nameMap[name] 57 | } 58 | 59 | func (bridge *Bridge) getDescriptor(module, method string) *Descriptor { 60 | return bridge.methodMap[module+"."+method] 61 | } 62 | -------------------------------------------------------------------------------- /gen/proto/debug/ping.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: debug/ping.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package proto_debug is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | debug/ping.proto 10 | 11 | It has these top-level messages: 12 | Ping 13 | */ 14 | package proto_debug 15 | 16 | import proto "github.com/golang/protobuf/proto" 17 | import math "math" 18 | 19 | // Reference imports to suppress errors if they are not otherwise used. 20 | var _ = proto.Marshal 21 | var _ = math.Inf 22 | 23 | type Ping struct { 24 | Ping *string `protobuf:"bytes,1,opt,name=ping" json:"ping,omitempty"` 25 | XXX_unrecognized []byte `json:"-"` 26 | } 27 | 28 | func (m *Ping) Reset() { *m = Ping{} } 29 | func (m *Ping) String() string { return proto.CompactTextString(m) } 30 | func (*Ping) ProtoMessage() {} 31 | 32 | func (m *Ping) GetPing() string { 33 | if m != nil && m.Ping != nil { 34 | return *m.Ping 35 | } 36 | return "" 37 | } 38 | 39 | type Ping_Response struct { 40 | Pong *string `protobuf:"bytes,1,opt,name=pong" json:"pong,omitempty"` 41 | XXX_unrecognized []byte `json:"-"` 42 | } 43 | 44 | func (m *Ping_Response) Reset() { *m = Ping_Response{} } 45 | func (m *Ping_Response) String() string { return proto.CompactTextString(m) } 46 | func (*Ping_Response) ProtoMessage() {} 47 | 48 | func (m *Ping_Response) GetPong() string { 49 | if m != nil && m.Pong != nil { 50 | return *m.Pong 51 | } 52 | return "" 53 | } 54 | 55 | func init() { 56 | } 57 | -------------------------------------------------------------------------------- /pb/rpc/codec.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/golang/protobuf/proto" 9 | 10 | "github.com/xjdrew/daisy/gen/proto/base" 11 | ) 12 | 13 | const ( 14 | BUFLEN = 65535 15 | ) 16 | 17 | type Codec struct { 18 | rwc io.ReadWriteCloser 19 | rdbuf [BUFLEN]byte 20 | } 21 | 22 | func NewCodec(rwc io.ReadWriteCloser) *Codec { 23 | return &Codec{ 24 | rwc: rwc, 25 | } 26 | } 27 | 28 | func (c *Codec) ReadPack(p *proto_base.Pack) error { 29 | if p == nil { 30 | return nil 31 | } 32 | 33 | var sz uint16 34 | if err := binary.Read(c.rwc, binary.BigEndian, &sz); err != nil { 35 | return err 36 | } 37 | 38 | var to uint16 = 0 39 | rdbuf := c.rdbuf[:sz] 40 | for to < sz { 41 | n, err := c.rwc.Read(rdbuf[to:]) 42 | if err != nil { 43 | return err 44 | } 45 | to += uint16(n) 46 | } 47 | 48 | if err := proto.Unmarshal(rdbuf[:], p); err != nil { 49 | return err 50 | } 51 | return nil 52 | } 53 | 54 | func (c *Codec) WritePack(p *proto_base.Pack) error { 55 | if p == nil { 56 | return nil 57 | } 58 | 59 | data, err := proto.Marshal(p) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | sz := uint16(len(data)) 65 | if sz > BUFLEN { 66 | return fmt.Errorf("WritePack: overflow packet size(%d)", sz) 67 | } 68 | 69 | if err = binary.Write(c.rwc, binary.BigEndian, sz); err != nil { 70 | return err 71 | } 72 | 73 | if _, err = c.rwc.Write(data); err != nil { 74 | return err 75 | } 76 | return nil 77 | } 78 | 79 | func (c *Codec) Close() error { 80 | return c.rwc.Close() 81 | } 82 | -------------------------------------------------------------------------------- /project/pb-gen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # dependences: protoc-gen-go 4 | # install: go get -u github.com/golang/protobuf/protoc-gen-go 5 | # The compiler plugin, protoc-gen-go, will be installed in $GOBIN, 6 | # defaulting to $GOPATH/bin. It must be in your $PATH for the protocol 7 | # compiler, protoc, to find it. 8 | 9 | echo "------------- compile protobuffer file -------------" 10 | set -xe 11 | OUT_DIR=gen/proto 12 | 13 | PB_IN_DIR=$PWD/contrib/proto 14 | PB_OUT_DIR=$PWD/$OUT_DIR 15 | PB_IMPORT_PREFIX="github.com/xjdrew/daisy/"$OUT_DIR 16 | 17 | # convert *.proto to *.pb.go 18 | PROTO_FILES= 19 | cd $PB_IN_DIR 20 | subdirs=`find -L . -type d -printf '%P\n'` 21 | for dir in $subdirs;do 22 | proto_file=$(find $dir -name '*.proto') 23 | if [ "$proto_file" ];then 24 | protoc --go_out=$PB_OUT_DIR $proto_file 25 | PROTO_FILES=${PROTO_FILES}" "${proto_file} 26 | fi 27 | done 28 | cd - 29 | 30 | # convert import XXX "path/to/file.pb" to import XXX $PB_IMPORT_PREFIX 31 | function normalize_import() { 32 | if [ -z "$1" ];then 33 | return 34 | fi 35 | 36 | local file=$1 37 | local tmpfile=${file}.old 38 | for i in ${PROTO_FILES};do 39 | local src=$(echo $i | sed 's/proto$/pb/') 40 | local dst=${PB_IMPORT_PREFIX}$(echo $src | sed 's/\/.*//') 41 | mv $file $tmpfile 42 | sed "/^import/s;$src;$dst;" $tmpfile > $file 43 | rm $tmpfile 44 | done 45 | } 46 | 47 | GO_FILES=$(find $PB_OUT_DIR -name "*.pb.go") 48 | for src in ${GO_FILES};do 49 | normalize_import ${src} 50 | done 51 | 52 | set +xe 53 | echo "----------------------- end -----------------------" 54 | -------------------------------------------------------------------------------- /pb/rpc/rpc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Rpc struct discribe common things between Client and Rpc 3 | */ 4 | package rpc 5 | 6 | import ( 7 | "fmt" 8 | "reflect" 9 | "sync" 10 | ) 11 | 12 | type Rpc struct { 13 | mu sync.RWMutex 14 | serviceMap map[int32]*service 15 | bridge *Bridge 16 | } 17 | 18 | func NewRpc(bridge *Bridge) Rpc { 19 | return Rpc{ 20 | bridge: bridge, 21 | serviceMap: make(map[int32]*service), 22 | } 23 | } 24 | 25 | /* 26 | 注册模块 27 | 模块中的函数必须满足以下几点才能成为接口函数 28 | 1. defined in protolist 29 | 2. 函数原型满足protolist里面对应接口的定义 30 | 如果还有其他类型的函数,则返回error 31 | */ 32 | func (r *Rpc) RegisterModule(receiver interface{}) error { 33 | r.mu.Lock() 34 | defer r.mu.Unlock() 35 | 36 | typ := reflect.TypeOf(receiver) 37 | rcvr := reflect.ValueOf(receiver) 38 | 39 | if err := r.register(rcvr, typ); err != nil { 40 | return err 41 | } 42 | 43 | if err := r.register(rcvr, reflect.PtrTo(typ)); err != nil { 44 | return err 45 | } 46 | 47 | return nil 48 | } 49 | 50 | func (r *Rpc) register(rcvr reflect.Value, typ reflect.Type) error { 51 | module := reflect.Indirect(rcvr).Type().Name() 52 | for m := 0; m < typ.NumMethod(); m++ { 53 | method := typ.Method(m) 54 | 55 | dptor := r.bridge.getDescriptor(module, method.Name) 56 | if dptor == nil { 57 | return fmt.Errorf("undefined method %s.%s", module, method.Name) 58 | } 59 | 60 | if err := dptor.MatchMethod(method); err != nil { 61 | return err 62 | } 63 | 64 | if _, present := r.serviceMap[dptor.Id]; present { 65 | return fmt.Errorf("repeated method %s", dptor.MethodName) 66 | } 67 | 68 | r.serviceMap[dptor.Id] = &service{ 69 | rcvr: rcvr, 70 | method: method, 71 | dptor: dptor, 72 | } 73 | } 74 | return nil 75 | } 76 | 77 | func (r *Rpc) getService(typ int32) *service { 78 | r.mu.RLock() 79 | defer r.mu.RUnlock() 80 | s := r.serviceMap[typ] 81 | return s 82 | } 83 | 84 | func (r *Rpc) getDescriptor(name string) *Descriptor { 85 | return r.bridge.getDescriptorByName(name) 86 | } 87 | -------------------------------------------------------------------------------- /gen/proto/test/test.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: test/test.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package proto_test is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | test/test.proto 10 | 11 | It has these top-level messages: 12 | Echo 13 | Strobe 14 | */ 15 | package proto_test 16 | 17 | import proto "github.com/golang/protobuf/proto" 18 | import math "math" 19 | 20 | // Reference imports to suppress errors if they are not otherwise used. 21 | var _ = proto.Marshal 22 | var _ = math.Inf 23 | 24 | type Echo struct { 25 | Req *string `protobuf:"bytes,1,opt,name=req" json:"req,omitempty"` 26 | XXX_unrecognized []byte `json:"-"` 27 | } 28 | 29 | func (m *Echo) Reset() { *m = Echo{} } 30 | func (m *Echo) String() string { return proto.CompactTextString(m) } 31 | func (*Echo) ProtoMessage() {} 32 | 33 | func (m *Echo) GetReq() string { 34 | if m != nil && m.Req != nil { 35 | return *m.Req 36 | } 37 | return "" 38 | } 39 | 40 | type Echo_Response struct { 41 | Resp *string `protobuf:"bytes,1,opt,name=resp" json:"resp,omitempty"` 42 | XXX_unrecognized []byte `json:"-"` 43 | } 44 | 45 | func (m *Echo_Response) Reset() { *m = Echo_Response{} } 46 | func (m *Echo_Response) String() string { return proto.CompactTextString(m) } 47 | func (*Echo_Response) ProtoMessage() {} 48 | 49 | func (m *Echo_Response) GetResp() string { 50 | if m != nil && m.Resp != nil { 51 | return *m.Resp 52 | } 53 | return "" 54 | } 55 | 56 | type Strobe struct { 57 | Msg *string `protobuf:"bytes,2,opt,name=msg" json:"msg,omitempty"` 58 | XXX_unrecognized []byte `json:"-"` 59 | } 60 | 61 | func (m *Strobe) Reset() { *m = Strobe{} } 62 | func (m *Strobe) String() string { return proto.CompactTextString(m) } 63 | func (*Strobe) ProtoMessage() {} 64 | 65 | func (m *Strobe) GetMsg() string { 66 | if m != nil && m.Msg != nil { 67 | return *m.Msg 68 | } 69 | return "" 70 | } 71 | 72 | func init() { 73 | } 74 | -------------------------------------------------------------------------------- /gen/proto/base/pack.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: base/pack.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package proto_base is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | base/pack.proto 10 | 11 | It has these top-level messages: 12 | Error 13 | Pack 14 | */ 15 | package proto_base 16 | 17 | import proto "github.com/golang/protobuf/proto" 18 | import math "math" 19 | 20 | // Reference imports to suppress errors if they are not otherwise used. 21 | var _ = proto.Marshal 22 | var _ = math.Inf 23 | 24 | type Error struct { 25 | Failed *bool `protobuf:"varint,1,opt,name=failed" json:"failed,omitempty"` 26 | Code *int32 `protobuf:"varint,2,opt,name=code" json:"code,omitempty"` 27 | Error *string `protobuf:"bytes,3,opt,name=error" json:"error,omitempty"` 28 | XXX_unrecognized []byte `json:"-"` 29 | } 30 | 31 | func (m *Error) Reset() { *m = Error{} } 32 | func (m *Error) String() string { return proto.CompactTextString(m) } 33 | func (*Error) ProtoMessage() {} 34 | 35 | func (m *Error) GetFailed() bool { 36 | if m != nil && m.Failed != nil { 37 | return *m.Failed 38 | } 39 | return false 40 | } 41 | 42 | func (m *Error) GetCode() int32 { 43 | if m != nil && m.Code != nil { 44 | return *m.Code 45 | } 46 | return 0 47 | } 48 | 49 | func (m *Error) GetError() string { 50 | if m != nil && m.Error != nil { 51 | return *m.Error 52 | } 53 | return "" 54 | } 55 | 56 | type Pack struct { 57 | Session *int32 `protobuf:"varint,1,opt,name=session" json:"session,omitempty"` 58 | Type *int32 `protobuf:"varint,2,opt,name=type" json:"type,omitempty"` 59 | Error *Error `protobuf:"bytes,3,opt,name=error" json:"error,omitempty"` 60 | Data []byte `protobuf:"bytes,4,opt,name=data" json:"data,omitempty"` 61 | XXX_unrecognized []byte `json:"-"` 62 | } 63 | 64 | func (m *Pack) Reset() { *m = Pack{} } 65 | func (m *Pack) String() string { return proto.CompactTextString(m) } 66 | func (*Pack) ProtoMessage() {} 67 | 68 | func (m *Pack) GetSession() int32 { 69 | if m != nil && m.Session != nil { 70 | return *m.Session 71 | } 72 | return 0 73 | } 74 | 75 | func (m *Pack) GetType() int32 { 76 | if m != nil && m.Type != nil { 77 | return *m.Type 78 | } 79 | return 0 80 | } 81 | 82 | func (m *Pack) GetError() *Error { 83 | if m != nil { 84 | return m.Error 85 | } 86 | return nil 87 | } 88 | 89 | func (m *Pack) GetData() []byte { 90 | if m != nil { 91 | return m.Data 92 | } 93 | return nil 94 | } 95 | 96 | func init() { 97 | } 98 | -------------------------------------------------------------------------------- /pb/rpc/descriptor.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/golang/protobuf/proto" 8 | ) 9 | 10 | type CallError struct { 11 | Code int32 12 | Msg string 13 | RpcError bool // is rpc error 14 | } 15 | 16 | func NewCallError(code int32, format string, a ...interface{}) *CallError { 17 | err := new(CallError) 18 | err.Code = code 19 | err.Msg = fmt.Sprintf(format, a...) 20 | err.RpcError = false 21 | return err 22 | } 23 | 24 | func NewRpcCallError(code int32, format string, a ...interface{}) *CallError { 25 | err := new(CallError) 26 | err.Code = code 27 | err.Msg = fmt.Sprintf(format, a...) 28 | err.RpcError = true 29 | return err 30 | } 31 | 32 | func (e *CallError) String() string { 33 | if e == nil { 34 | return "" 35 | } 36 | var tag string 37 | if e.RpcError { 38 | tag = "rpc" 39 | } else { 40 | tag = "local" 41 | } 42 | return fmt.Sprintf("%s error: code:%d, msg:%s", tag, e.Code, e.Msg) 43 | } 44 | 45 | func (e *CallError) IsRpcError() bool { 46 | if e == nil { 47 | return false 48 | } 49 | return e.RpcError 50 | } 51 | 52 | type Descriptor struct { 53 | Id int32 54 | NormalName string 55 | MethodName string 56 | ArgType reflect.Type 57 | ReplyType reflect.Type 58 | } 59 | 60 | var typeOfProtoMessage = reflect.TypeOf((*proto.Message)(nil)).Elem() 61 | var typeOfContext = reflect.TypeOf(&Context{}) 62 | var typeOfError = reflect.TypeOf(&CallError{}) 63 | 64 | func (d *Descriptor) HasReply() bool { 65 | return d.ReplyType != nil 66 | } 67 | 68 | func (d *Descriptor) MatchArgType(typ reflect.Type) bool { 69 | if typ.Kind() != reflect.Ptr || !typ.Implements(typeOfProtoMessage) || typ != d.ArgType { 70 | return false 71 | } 72 | return true 73 | } 74 | 75 | func (d *Descriptor) MatchReplyType(typ reflect.Type) bool { 76 | if typ.Kind() != reflect.Ptr || !typ.Implements(typeOfProtoMessage) || typ != d.ReplyType { 77 | return false 78 | } 79 | return true 80 | } 81 | 82 | /* 83 | 如果接口有返回值,则需要两个参数,类型为指针,有一个error类型的返回值 84 | 如果接口没有返回值,则只需要一个参数,类型为指针, 没有返回值 85 | */ 86 | func (d *Descriptor) MatchMethod(method reflect.Method) error { 87 | mtyp := method.Type 88 | 89 | // defautl args: rcvr, context, req 90 | numIn := 3 91 | numOut := 0 92 | if d.ReplyType != nil { 93 | // extra arg: reply 94 | numIn += 1 95 | // extra return: *CallError 96 | numOut += 1 97 | } 98 | 99 | if mtyp.NumIn() != numIn { 100 | return fmt.Errorf("method %s should have %d arguments", d.MethodName, numIn) 101 | } 102 | 103 | if mtyp.NumOut() != numOut { 104 | return fmt.Errorf("method %s should have %d return values", d.MethodName, numOut) 105 | } 106 | 107 | contextType := mtyp.In(1) 108 | if contextType != typeOfContext { 109 | return fmt.Errorf("method %s arg%d should be %s", d.MethodName, 1, typeOfContext.String()) 110 | } 111 | 112 | argType := mtyp.In(2) 113 | if !d.MatchArgType(argType) { 114 | return fmt.Errorf("method %s arg%d should be %s", d.MethodName, 2, d.ArgType.String()) 115 | } 116 | 117 | if numIn > 3 { 118 | replyType := mtyp.In(3) 119 | if !d.MatchReplyType(replyType) { 120 | return fmt.Errorf("method %s arg%d should be %s", d.MethodName, 3, d.ReplyType.String()) 121 | } 122 | } 123 | 124 | if numOut > 0 { 125 | if returnType := mtyp.Out(0); returnType != typeOfError { 126 | return fmt.Errorf("method %s returns %s not %s", d.MethodName, returnType.String(), typeOfError.String()) 127 | } 128 | } 129 | return nil 130 | } 131 | -------------------------------------------------------------------------------- /pb/generator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/format" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "reflect" 11 | "strings" 12 | 13 | "github.com/xjdrew/daisy/pb/parser" 14 | "github.com/xjdrew/daisy/pb/rpc" 15 | ) 16 | 17 | const ( 18 | PkgPathBase = "github.com/xjdrew/daisy/gen/proto" 19 | PackageName = "descriptor" 20 | ExportedVarname = "Descriptors" 21 | ) 22 | 23 | type Descriptor struct { 24 | Id int32 25 | NormalName string 26 | MethodName string 27 | ArgType string `type:"expr"` 28 | ReplyType string `type:"expr"` 29 | } 30 | 31 | func printError(err error, msgs ...string) { 32 | s := strings.Join(msgs, " ") + ":" + err.Error() 33 | log.Print("protolist-generator error:", s) 34 | os.Exit(1) 35 | } 36 | 37 | func extractPkg(name string) string { 38 | if name == "" { 39 | return "" 40 | } 41 | 42 | pos1 := strings.Index(name, parser.PathSep) 43 | pos2 := strings.Index(name, parser.NameSep) 44 | if pos1 == -1 || pos2 == -1 { 45 | printError(fmt.Errorf("illegal input name:%s", name), "extractPkg") 46 | } 47 | 48 | return name[pos1+1 : pos2] 49 | } 50 | 51 | func genDependences(modules []parser.Module) []string { 52 | pkgSet := make(map[string]bool) 53 | for _, module := range modules { 54 | for _, service := range module.Services { 55 | if pkg := extractPkg(service.Input); pkg != "" { 56 | pkgSet[pkg] = true 57 | } 58 | 59 | if pkg := extractPkg(service.Output); pkg != "" { 60 | pkgSet[pkg] = true 61 | } 62 | } 63 | } 64 | 65 | var a []string 66 | for pkg := range pkgSet { 67 | a = append(a, pkg) 68 | } 69 | return a 70 | } 71 | 72 | func genDescriptors(modules []parser.Module) []Descriptor { 73 | var a []Descriptor 74 | for _, module := range modules { 75 | for _, service := range module.Services { 76 | var d Descriptor 77 | d.Id = service.Id 78 | d.NormalName = service.NormalName 79 | d.MethodName = service.MethodName 80 | d.ArgType = fmt.Sprintf("reflect.TypeOf(&%s{})", service.Input) 81 | if service.Output != "" { 82 | d.ReplyType = fmt.Sprintf("reflect.TypeOf(&%s{})", service.Output) 83 | } else { 84 | d.ReplyType = "nil" 85 | } 86 | a = append(a, d) 87 | } 88 | } 89 | return a 90 | } 91 | 92 | func serializeDescriptor(d Descriptor) string { 93 | var a []string 94 | typ := reflect.TypeOf(d) 95 | v := reflect.ValueOf(d) 96 | for i := 0; i < typ.NumField(); i++ { 97 | f := typ.Field(i) 98 | var str string 99 | fv := v.FieldByName(f.Name) 100 | if f.Type.Kind() == reflect.String && f.Tag.Get("type") == "expr" { 101 | str = fmt.Sprintf("%s:%s", f.Name, fv.String()) 102 | } else { 103 | str = fmt.Sprintf("%s:%#v", f.Name, fv.Interface()) 104 | } 105 | a = append(a, str) 106 | } 107 | return strings.Join(a, ",\n") 108 | } 109 | 110 | func generate(modules []parser.Module) []byte { 111 | b := new(bytes.Buffer) 112 | 113 | typ := reflect.TypeOf(rpc.Descriptor{}) 114 | arrayTyp := reflect.TypeOf([]rpc.Descriptor{}) 115 | // file header 116 | b.WriteString("// Code generated by protolist-generator.\n") 117 | b.WriteString("// DO NOT EDIT!\n") 118 | b.WriteString("\n") 119 | 120 | // package 121 | b.WriteString(fmt.Sprintf("package %s\n", PackageName)) 122 | 123 | // import 124 | deps := genDependences(modules) 125 | b.WriteString("import (\n") 126 | b.WriteString("\"reflect\"\n") 127 | b.WriteString("\n") 128 | b.WriteString(fmt.Sprintf("%q\n", typ.PkgPath())) 129 | b.WriteString("\n") 130 | for _, dep := range deps { 131 | b.WriteString(fmt.Sprintf("\"%s/%s\"\n", PkgPathBase, dep)) 132 | } 133 | b.WriteString(")\n") 134 | b.WriteString("\n") 135 | 136 | // body 137 | descriptors := genDescriptors(modules) 138 | b.WriteString(fmt.Sprintf("var %s = %s{", ExportedVarname, arrayTyp.String())) 139 | for _, dptor := range descriptors { 140 | b.WriteString("\n") 141 | b.WriteString(fmt.Sprintf("{\n")) 142 | b.WriteString(serializeDescriptor(dptor)) 143 | b.WriteString(",\n") 144 | b.WriteString(fmt.Sprintf("},")) 145 | b.WriteString("\n") 146 | } 147 | b.WriteString("}\n") 148 | b.WriteString("\n") 149 | 150 | data, err := format.Source(b.Bytes()) 151 | if err != nil { 152 | printError(err, "format output", b.String()) 153 | } 154 | return data 155 | } 156 | 157 | func main() { 158 | data, err := ioutil.ReadAll(os.Stdin) 159 | if err != nil { 160 | printError(err, "reading input") 161 | } 162 | 163 | modules, err := parser.ParseData(string(data)) 164 | if err != nil { 165 | printError(err, "reading input") 166 | } 167 | 168 | data = generate(modules) 169 | if _, err = os.Stdout.Write(data); err != nil { 170 | printError(err, "write output") 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /pb/parser/protolist_test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestCamelCase(t *testing.T) { 12 | var m = map[string]string{ 13 | "lua_out": "LuaOut", 14 | "query_hirable_hero": "QueryHirableHero", 15 | } 16 | for k, v := range m { 17 | v1 := camelCase(k) 18 | if v != v1 { 19 | t.Fatalf("camel case %s failed: %s", k, v1) 20 | } 21 | } 22 | } 23 | 24 | func TestSplitData(t *testing.T) { 25 | var lines = []string{ 26 | "test {", 27 | "service1 = 1", 28 | "} test2 {} test3 {", 29 | "service2 = 2", 30 | "}\n", 31 | } 32 | 33 | data := strings.Join(lines, "# comment 1\r\n # comment 2 \r\n") 34 | result := splitData(data) 35 | if len(lines) != len(result) { 36 | t.Errorf("split data error, lines change:%d -> %d", len(lines), len(result)) 37 | } 38 | for i, line := range result { 39 | if line != strings.TrimSpace(lines[i]) { 40 | t.Errorf("split data error, line:%d, %s -> %s", i, lines[i], line) 41 | } 42 | } 43 | 44 | modules := splitModules(result) 45 | n := strings.Count(data, "}") 46 | m := 0 47 | for _, line := range modules { 48 | if line == "}" { 49 | m += 1 50 | } 51 | t.Logf("%v", line) 52 | } 53 | if n != m { 54 | t.Errorf("split modules failed:%d -> %d", n, m) 55 | } 56 | } 57 | 58 | type Case struct { 59 | lines []string 60 | module Module 61 | } 62 | 63 | var case1 = Case{ 64 | lines: []string{ 65 | "test {", 66 | "service1 = 1", 67 | "service2:input1 = 2", 68 | "service3:input1[] = 3", 69 | "service4:input1[output1]=4", 70 | "service5:[ output1 ]=5", 71 | "service6:[]=6", 72 | "service7:.proto.test2.Service7 = 7", 73 | "service8:.proto.test2.Service7 [.proto.test2.Service8]= 8", 74 | "}", 75 | }, 76 | module: Module{ 77 | Name: "test", 78 | Services: []Service{ 79 | {1, "service1", "proto_test.Service1", "proto_test.Service1_Response"}, 80 | {2, "service2", "proto_test.Input1", "proto_test.Input1_Response"}, 81 | {3, "service3", "proto_test.Input1", ""}, 82 | {4, "service4", "proto_test.Input1", "proto_test.Output1"}, 83 | {5, "service5", "proto_test.Service5", "proto_test.Output1"}, 84 | {6, "service6", "proto_test.Service6", ""}, 85 | {7, "service7", "proto_test2.Service7", "proto_test2.Service7_Response"}, 86 | {8, "service8", "proto_test2.Service7", "proto_test2.Service8"}, 87 | }, 88 | }, 89 | } 90 | 91 | var case2 = Case{ 92 | lines: []string{ 93 | "test1 {", 94 | "service11 = 11", 95 | "service12:input1 = 12", 96 | "service13:input1[] = 13", 97 | "service14:input1[output1]=14", 98 | "service15:[ output1 ]=15", 99 | "service16:[]=16", 100 | "}", 101 | }, 102 | module: Module{ 103 | Name: "test1", 104 | Services: []Service{ 105 | {11, "service11", "proto_test1.Service11", "proto_test1.Service11_Response"}, 106 | {12, "service12", "proto_test1.Input1", "proto_test1.Input1_Response"}, 107 | {13, "service13", "proto_test1.Input1", ""}, 108 | {14, "service14", "proto_test1.Input1", "proto_test1.Output1"}, 109 | {15, "service15", "proto_test1.Service15", "proto_test1.Output1"}, 110 | {16, "service16", "proto_test1.Service16", ""}, 111 | }, 112 | }, 113 | } 114 | 115 | var cases = []Case{case1, case2} 116 | 117 | func checkService(s1, s2 Service) bool { 118 | if s1.Id != s2.Id || s1.Name != s2.Name || s1.Input != s2.Input || s1.Output != s2.Output { 119 | fmt.Printf("service:%+v -> %+v\n", s1, s2) 120 | return false 121 | } 122 | return true 123 | } 124 | 125 | func checkModule(m1, m2 Module) bool { 126 | if m1.Name != m2.Name { 127 | return false 128 | } 129 | if len(m1.Services) != len(m2.Services) { 130 | return false 131 | } 132 | for i := 0; i < len(m1.Services); i++ { 133 | if !checkService(m1.Services[i], m2.Services[i]) { 134 | return false 135 | } 136 | } 137 | return true 138 | } 139 | 140 | func TestParseModule(t *testing.T) { 141 | for _, c := range cases { 142 | lines := c.lines 143 | module := c.module 144 | data := strings.Join(lines, "# test \r\n") 145 | m, err := ParseData(data) 146 | if err != nil { 147 | t.Fatal(err) 148 | } 149 | // t.Errorf("%+v", m) 150 | if !checkModule(m[0], module) { 151 | t.Fatalf("parse module failed, %+v ------------> %+v", module, m) 152 | } 153 | } 154 | } 155 | 156 | func TestParseFile(t *testing.T) { 157 | tmpfile, err := ioutil.TempFile(os.TempDir(), "proto") 158 | if err != nil { 159 | t.Fatal(err) 160 | } 161 | var all []string 162 | for _, c := range cases { 163 | all = append(all, strings.Join(c.lines, "# test\n")) 164 | } 165 | 166 | data := strings.Join(all, "\n") 167 | if _, err := tmpfile.Write([]byte(data)); err != nil { 168 | t.Fatal(err) 169 | } 170 | filePath := tmpfile.Name() 171 | tmpfile.Close() 172 | m, err := ParseFile(filePath) 173 | if err != nil { 174 | t.Fatal(err) 175 | } 176 | if len(m) != len(cases) { 177 | t.Fatalf("modules less than expected:%d -> %d", len(cases), len(m)) 178 | } 179 | for i, c := range cases { 180 | if !checkModule(c.module, m[i]) { 181 | t.Fatalf("parse module failed, %+v ------------> %+v", c.module, m[i]) 182 | } 183 | } 184 | os.Remove(filePath) 185 | } 186 | -------------------------------------------------------------------------------- /pb/rpc/context.go: -------------------------------------------------------------------------------- 1 | /* 2 | one connection one context 3 | */ 4 | package rpc 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "net" 10 | "reflect" 11 | "sync" 12 | "sync/atomic" 13 | "unsafe" 14 | 15 | "github.com/golang/protobuf/proto" 16 | 17 | "github.com/xjdrew/daisy/gen/proto/base" 18 | ) 19 | 20 | type ContextOwner interface { 21 | getDescriptor(name string) *Descriptor 22 | getService(int32) *service 23 | onIoError(*Context, error) 24 | onUnknownPack(*Context, *proto_base.Pack) bool 25 | } 26 | 27 | type Call struct { 28 | Dptor *Descriptor 29 | Argv interface{} 30 | Reply interface{} 31 | Error *CallError 32 | Done chan *Call 33 | } 34 | 35 | func (call *Call) done() { 36 | select { 37 | case call.Done <- call: 38 | default: 39 | // done channel is unbuffered, it's a error 40 | // it's a error 41 | log.Panicf("method %s block", call.Dptor.NormalName) 42 | } 43 | } 44 | 45 | type Context struct { 46 | owner ContextOwner 47 | conn net.Conn 48 | codec *Codec 49 | session int32 50 | sessLock sync.Mutex 51 | sessions map[int32]*Call 52 | 53 | // err context 54 | err *error 55 | } 56 | 57 | func NewContext(owner ContextOwner, conn net.Conn) *Context { 58 | return &Context{ 59 | owner: owner, 60 | conn: conn, 61 | codec: NewCodec(conn), 62 | sessions: make(map[int32]*Call), 63 | } 64 | } 65 | 66 | func (c *Context) nextSession() int32 { 67 | return atomic.AddInt32(&c.session, 1) 68 | } 69 | 70 | func (c *Context) setSession(session int32, call *Call) { 71 | c.sessLock.Lock() 72 | c.sessions[session] = call 73 | c.sessLock.Unlock() 74 | } 75 | 76 | func (c *Context) grabSession(session int32) *Call { 77 | c.sessLock.Lock() 78 | defer c.sessLock.Unlock() 79 | if call, ok := c.sessions[session]; ok { 80 | delete(c.sessions, session) 81 | return call 82 | } 83 | return nil 84 | } 85 | 86 | func (c *Context) closeAllSessions() { 87 | c.sessLock.Lock() 88 | defer c.sessLock.Unlock() 89 | 90 | msg := "connection down" 91 | if c.err != nil { 92 | msg += ": " + (*c.err).Error() 93 | } 94 | 95 | for k, call := range c.sessions { 96 | delete(c.sessions, k) 97 | 98 | call.Error = NewCallError(0, msg) 99 | call.done() 100 | } 101 | } 102 | 103 | func (c *Context) setError(err error) { 104 | atomic.CompareAndSwapPointer((*unsafe.Pointer)((unsafe.Pointer)(&c.err)), nil, unsafe.Pointer(&err)) 105 | } 106 | 107 | // unblock call a service which has a reply 108 | // if method, argv and reply do not match, return return a error 109 | func (c *Context) Go(method string, argv interface{}, reply interface{}, done chan *Call) (*Call, error) { 110 | dptor := c.owner.getDescriptor(method) 111 | if dptor == nil { 112 | return nil, fmt.Errorf("call unknown method:%s", method) 113 | } 114 | 115 | if !dptor.HasReply() { 116 | return nil, fmt.Errorf("canot call method %s, use invoke instead", method) 117 | } 118 | 119 | if !dptor.MatchArgType(reflect.TypeOf(argv)) || !dptor.MatchReplyType(reflect.TypeOf(reply)) { 120 | return nil, fmt.Errorf("call method %s with unmatch arg or reply", method) 121 | } 122 | 123 | var pack proto_base.Pack 124 | session := c.nextSession() 125 | pack.Session = proto.Int32(session) 126 | pack.Type = proto.Int32(dptor.Id) 127 | pack.Data, _ = proto.Marshal(argv.(proto.Message)) 128 | 129 | if done == nil { 130 | done = make(chan *Call, 1) 131 | } else { 132 | if cap(done) == 0 { 133 | return nil, fmt.Errorf("call %s: done channel is unbuffered", method) 134 | } 135 | } 136 | 137 | call := &Call{ 138 | Dptor: dptor, 139 | Argv: argv, 140 | Reply: reply, 141 | Done: done, 142 | } 143 | c.setSession(session, call) 144 | c.writePack(&pack) 145 | return call, nil 146 | } 147 | 148 | func (c *Context) MustGo(method string, argv interface{}, reply interface{}, done chan *Call) *Call { 149 | call, err := c.Go(method, argv, reply, done) 150 | if err != nil { 151 | log.Panic("MustGo failed: method:%s, argv:%v", method, argv) 152 | } 153 | return call 154 | } 155 | 156 | // block call a service which has a reply 157 | // if method, argv and reply do not match, return return a error 158 | func (c *Context) Call(method string, argv interface{}, reply interface{}) (*CallError, error) { 159 | call, err := c.Go(method, argv, reply, nil) 160 | if err != nil { 161 | return nil, err 162 | } 163 | call = <-call.Done 164 | return call.Error, nil 165 | } 166 | 167 | // same as Call except it panics if method, argv and reply do not match 168 | func (c *Context) MustCall(method string, argv interface{}, reply interface{}) *CallError { 169 | callError, err := c.Call(method, argv, reply) 170 | if err != nil { 171 | log.Panic("MustCall failed: method:%s, argv:%v", method, argv) 172 | } 173 | return callError 174 | } 175 | 176 | // invoke a service which has not a reply 177 | func (c *Context) Invoke(method string, argv interface{}) error { 178 | dptor := c.owner.getDescriptor(method) 179 | if dptor == nil { 180 | return fmt.Errorf("invoke unknown method:%s", method) 181 | } 182 | 183 | if dptor.HasReply() { 184 | return fmt.Errorf("canot invoke method %s, use call instead", method) 185 | } 186 | 187 | if !dptor.MatchArgType(reflect.TypeOf(argv)) { 188 | return fmt.Errorf("invoke method %s with unmatch argv", method) 189 | } 190 | 191 | var pack proto_base.Pack 192 | pack.Session = proto.Int32(0) 193 | pack.Type = proto.Int32(dptor.Id) 194 | pack.Data, _ = proto.Marshal(argv.(proto.Message)) 195 | 196 | c.writePack(&pack) 197 | return nil 198 | } 199 | 200 | // same as invoke except it panics if any error happen 201 | func (c *Context) MustInvoke(method string, argv interface{}) { 202 | err := c.Invoke(method, argv) 203 | if err != nil { 204 | log.Panic("MustInvoke failed: method:%s, argv:%v", method, argv) 205 | } 206 | } 207 | 208 | func (c *Context) writePack(pack *proto_base.Pack) { 209 | log.Printf("write pack:%d %d %d", pack.GetSession(), pack.GetType(), len(pack.GetData())) 210 | err := c.codec.WritePack(pack) 211 | if err != nil { 212 | c.setError(err) 213 | c.Close() 214 | } 215 | } 216 | 217 | func (c *Context) dispatchResponse(pack *proto_base.Pack) bool { 218 | log.Printf("dispatch response:%d", pack.GetSession()) 219 | call := c.grabSession(pack.GetSession()) 220 | if call == nil { 221 | return c.owner.onUnknownPack(c, pack) 222 | } 223 | 224 | packError := pack.GetError() 225 | if packError.GetFailed() { 226 | call.Error = NewRpcCallError(packError.GetCode(), packError.GetError()) 227 | } else { 228 | if err := proto.Unmarshal(pack.GetData(), call.Reply.(proto.Message)); err != nil { 229 | call.Error = NewCallError(0, err.Error()) 230 | } 231 | } 232 | 233 | log.Printf("response done:%s, %v", call.Dptor.NormalName, call.Error) 234 | call.done() 235 | return true 236 | } 237 | 238 | func (c *Context) dispatchRequest(pack *proto_base.Pack) bool { 239 | log.Printf("dispatch request:%d %d %d", pack.GetSession(), pack.GetType(), len(pack.GetData())) 240 | typ := pack.GetType() 241 | s := c.owner.getService(typ) 242 | if s == nil { 243 | return c.owner.onUnknownPack(c, pack) 244 | } 245 | 246 | argv := reflect.New(s.dptor.ArgType.Elem()) 247 | if err := proto.Unmarshal(pack.GetData(), argv.Interface().(proto.Message)); err != nil { 248 | return c.owner.onUnknownPack(c, pack) 249 | } 250 | 251 | var replyv reflect.Value 252 | if s.hasReply() { 253 | replyv = reflect.New(s.dptor.ReplyType.Elem()) 254 | } 255 | 256 | go func() { 257 | if s.hasReply() { 258 | callError := s.call(c, argv, replyv) 259 | pack.Type = proto.Int32(0) 260 | pack.Data = nil 261 | if callError != nil { 262 | pack.GetError().Failed = proto.Bool(false) 263 | pack.GetError().Code = proto.Int32(callError.Code) 264 | pack.GetError().Error = proto.String(callError.Msg) 265 | } else { 266 | pack.Data, _ = proto.Marshal(replyv.Interface().(proto.Message)) 267 | } 268 | c.writePack(pack) 269 | } else { 270 | s.invoke(c, argv) 271 | } 272 | }() 273 | log.Printf("request done:%d %d %d", pack.GetSession(), pack.GetType(), len(pack.GetData())) 274 | return true 275 | } 276 | 277 | func (c *Context) Close() error { 278 | c.closeAllSessions() 279 | return c.codec.Close() 280 | } 281 | 282 | func (c *Context) serve() { 283 | var err error 284 | for { 285 | var pack proto_base.Pack 286 | if err = c.codec.ReadPack(&pack); err != nil { 287 | c.owner.onIoError(c, err) 288 | break 289 | } 290 | 291 | typ := pack.GetType() 292 | var keepServing bool 293 | if typ == 0 { // response 294 | keepServing = c.dispatchResponse(&pack) 295 | } else { 296 | keepServing = c.dispatchRequest(&pack) 297 | } 298 | if !keepServing { 299 | break 300 | } 301 | } 302 | 303 | c.setError(err) 304 | c.Close() 305 | } 306 | -------------------------------------------------------------------------------- /pb/parser/protolist.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // exported struct 12 | type Service struct { 13 | Id int32 14 | Name string 15 | NormalName string 16 | MethodName string 17 | Input string 18 | Output string 19 | } 20 | 21 | type Module struct { 22 | Name string 23 | GoName string 24 | Services []Service 25 | } 26 | 27 | const ( 28 | ProtoPrefix = "proto" 29 | ProtoResponseSuffix = "_Response" 30 | PathSep = "_" 31 | NameSep = "." 32 | ) 33 | 34 | // exported interfaces 35 | func ParseFile(path string) ([]Module, error) { 36 | data, err := ioutil.ReadFile(path) 37 | if err != nil { 38 | return nil, err 39 | } 40 | return ParseData(string(data)) 41 | } 42 | 43 | func ParseData(data string) ([]Module, error) { 44 | a := splitModules(splitData(data)) 45 | mcount := 0 46 | for _, l := range a { 47 | if l == MODULE_END { 48 | mcount += 1 49 | } 50 | } 51 | 52 | var d decodeState 53 | d.init(a) 54 | 55 | modules := make([]Module, mcount) 56 | for i := 0; i < mcount; i++ { 57 | module, err := d.nextModule() 58 | if err != nil { 59 | return nil, err 60 | } 61 | modules[i] = *module 62 | } 63 | 64 | if !d.eof() { 65 | return nil, fmt.Errorf("unexpected tail:%s", d.lines[d.off]) 66 | } 67 | 68 | if err := normalizeModules(modules); err != nil { 69 | return nil, err 70 | } 71 | return modules, nil 72 | } 73 | 74 | // internal implemention 75 | // split data line by line and trim comments 76 | const ( 77 | COMMENT = "#" 78 | NEWLINE = "\r\n" 79 | MODULE_START = "{" 80 | MODULE_END = "}" 81 | EMPTY_OUTPUT = "[]" 82 | ) 83 | 84 | // camel case 85 | func camelCase(src string) string { 86 | a := strings.Split(src, "_") 87 | for i, v := range a { 88 | a[i] = strings.Title(v) 89 | } 90 | return strings.Join(a, "") 91 | } 92 | 93 | func addPrefix(str, prefix string) string { 94 | if strings.HasPrefix(str, NameSep) { 95 | // .proto.module.interface -> proto_module.interface 96 | return strings.Replace(str[1:], NameSep, PathSep, 1) 97 | } else { 98 | return ProtoPrefix + PathSep + prefix + NameSep + camelCase(str) 99 | } 100 | } 101 | 102 | func normalizeModule(module *Module) { 103 | mname := camelCase(module.Name) 104 | for i, _ := range module.Services { 105 | service := &module.Services[i] 106 | 107 | name := service.Name 108 | service.NormalName = module.Name + NameSep + name 109 | service.MethodName = mname + NameSep + camelCase(name) 110 | 111 | if service.Input == "" { 112 | service.Input = addPrefix(service.Name, module.Name) 113 | } else { 114 | service.Input = addPrefix(service.Input, module.Name) 115 | } 116 | if service.Output == "" { 117 | service.Output = service.Input + ProtoResponseSuffix 118 | } else if service.Output == EMPTY_OUTPUT { 119 | service.Output = "" 120 | } else { 121 | service.Output = addPrefix(service.Output[1:len(service.Output)-1], module.Name) 122 | } 123 | } 124 | } 125 | 126 | func normalizeModules(modules []Module) error { 127 | moduleMap := make(map[string]bool) 128 | idMap := make(map[int32]bool) 129 | serviceMap := make(map[string]bool) 130 | for i := range modules { 131 | module := &(modules[i]) 132 | goName := camelCase(module.Name) 133 | if moduleMap[goName] { 134 | return fmt.Errorf("repeated module name:%s", goName) 135 | } 136 | module.GoName, moduleMap[goName] = goName, true 137 | 138 | for _, service := range module.Services { 139 | if idMap[service.Id] || serviceMap[service.Name] { 140 | return fmt.Errorf("repeated service:(%s:%d)", service.Name, service.Id) 141 | } 142 | idMap[service.Id] = true 143 | service.Name, serviceMap[service.Name] = service.Name, true 144 | } 145 | normalizeModule(module) 146 | } 147 | return nil 148 | } 149 | 150 | // append if s is not empty 151 | func appendString(a []string, s string) []string { 152 | if s = strings.TrimSpace(s); len(s) > 0 { 153 | a = append(a, s) 154 | } 155 | return a 156 | } 157 | 158 | // strip all comment 159 | func trimComment(line string) string { 160 | if n := strings.Index(line, COMMENT); n != -1 { 161 | line = line[0:n] 162 | } 163 | return line 164 | } 165 | 166 | func splitData(data string) []string { 167 | var a []string 168 | for { 169 | pos := strings.IndexAny(data, NEWLINE) 170 | if pos == -1 { 171 | a = appendString(a, trimComment(data)) 172 | break 173 | } 174 | 175 | if pos > 0 { 176 | a = appendString(a, trimComment(data[0:pos])) 177 | } 178 | data = data[pos+1:] 179 | } 180 | return a 181 | } 182 | 183 | // keep "{" and "}" as a seprate line 184 | func splitLines(lines []string, sep string) []string { 185 | var a []string 186 | for _, line := range lines { 187 | data := line 188 | for { 189 | n := strings.Index(data, sep) 190 | if n == -1 { 191 | a = appendString(a, data) 192 | break 193 | } 194 | if n > 0 { 195 | a = appendString(a, data[0:n]) 196 | } 197 | a = appendString(a, data[n:n+1]) 198 | if n < len(data)-1 { 199 | data = data[n+1:] 200 | } else { 201 | break 202 | } 203 | } 204 | } 205 | return a 206 | } 207 | 208 | func splitModules(lines []string) []string { 209 | a := splitLines(lines, MODULE_START) 210 | return splitLines(a, MODULE_END) 211 | } 212 | 213 | func isSpace(c rune) bool { 214 | return c == ' ' || c == '\t' 215 | } 216 | 217 | var moduleNameRegex = regexp.MustCompile("^[a-z][a-z0-9_]*$") 218 | var inputNameRegex = regexp.MustCompile("^[A-Z][a-zA-Z0-9]*$") 219 | 220 | func checkModuleName(data string) bool { 221 | return moduleNameRegex.MatchString(data) 222 | } 223 | 224 | func checkInputName(data string) bool { 225 | return inputNameRegex.MatchString(data) 226 | } 227 | 228 | func checkInput(str string) bool { 229 | if str == "" { 230 | return true 231 | } 232 | 233 | // Name 234 | if checkModuleName(str) { 235 | return true 236 | } 237 | 238 | // .proto.Module.Name 239 | sections := strings.Split(str, NameSep) 240 | if len(sections) == 4 && sections[0] == "" && sections[1] == ProtoPrefix && 241 | checkModuleName(sections[2]) && checkInputName(sections[3]) { 242 | return true 243 | } 244 | return false 245 | } 246 | 247 | func checkOutput(str string) bool { 248 | if str == "" { 249 | return true 250 | } 251 | 252 | return checkInput(str[1 : len(str)-1]) 253 | } 254 | 255 | // name = id 256 | // name:input = id 257 | // name:input[] = id 258 | // name:input[output] = id 259 | // name:[output] = id 260 | // name:[] = id 261 | var serviceRegex = regexp.MustCompile("^\\s*([a-zA-Z0-9\\._]+)\\s*(?:\\:\\s*([a-zA-Z0-9\\._]*)\\s*(\\[\\s*[a-zA-Z0-9\\._]*\\s*\\])?)?\\s*=\\s*([0-9]+)\\s*$") 262 | 263 | func parseService(data string) (s Service, err error) { 264 | sections := serviceRegex.FindStringSubmatch(data) 265 | if sections == nil || len(sections) != 5 { 266 | err = fmt.Errorf("invalid service(%s)", data) 267 | return 268 | } 269 | var id uint64 270 | if id, err = strconv.ParseUint(sections[4], 0, 32); err != nil { 271 | err = fmt.Errorf("invalid service id(%s) in line %s", sections[3], data) 272 | return 273 | } 274 | s.Id = int32(id) 275 | s.Name = sections[1] 276 | s.Input = sections[2] 277 | // normalize output 278 | // "" or "[]" or "[output]" 279 | s.Output = strings.Map(func(r rune) rune { 280 | if isSpace(r) { 281 | return -1 282 | } 283 | return r 284 | }, sections[3]) 285 | 286 | if !checkInput(s.Input) { 287 | err = fmt.Errorf("invalid input format:%s", s.Input) 288 | return 289 | } 290 | if !checkOutput(s.Output) { 291 | err = fmt.Errorf("invalid output format:%s", s.Output) 292 | return 293 | } 294 | return 295 | } 296 | 297 | type decodeState struct { 298 | lines []string 299 | off int 300 | state int 301 | } 302 | 303 | func (d *decodeState) init(lines []string) { 304 | d.lines = lines 305 | d.off = 0 306 | } 307 | 308 | func (d *decodeState) scanLine(line string) int { 309 | for i := d.off; i < len(d.lines); i++ { 310 | if d.lines[i] == line { 311 | return i 312 | } 313 | } 314 | return -1 315 | } 316 | 317 | func (d *decodeState) nextModule() (m *Module, err error) { 318 | if d.off >= len(d.lines) { 319 | err = fmt.Errorf("eof") 320 | return 321 | } 322 | mstart := d.scanLine(MODULE_START) 323 | mend := d.scanLine(MODULE_END) 324 | name := d.lines[d.off] 325 | if mstart >= mend || mstart != d.off+1 || !checkModuleName(name) { 326 | err = fmt.Errorf("illegal module struct:%s", name) 327 | return 328 | } 329 | 330 | m = new(Module) 331 | m.Name = name 332 | m.Services = make([]Service, mend-mstart-1) 333 | var service Service 334 | for i := mstart + 1; i < mend; i++ { 335 | service, err = parseService(d.lines[i]) 336 | if err != nil { 337 | return 338 | } 339 | m.Services[i-mstart-1] = service 340 | } 341 | d.off = mend + 1 342 | return 343 | } 344 | 345 | func (d *decodeState) eof() bool { 346 | return d.off == len(d.lines) 347 | } 348 | --------------------------------------------------------------------------------