├── go.mod ├── examples └── localcall.go ├── src ├── dataserial │ └── data.go ├── transport │ ├── transport_test.go │ └── transport.go ├── client │ └── client.go └── server │ └── server.go ├── LICENSE ├── main.go └── README.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ankur-anand/simple-go-rpc 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /examples/localcall.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | 4 | import "fmt" 5 | 6 | type User struct { 7 | Name string 8 | Age int 9 | } 10 | 11 | var userDB = map[int]User{ 12 | 1: User{"Ankur", 85}, 13 | 9: User{"Anand", 25}, 14 | 8: User{"Ankur Anand", 27}, 15 | } 16 | 17 | 18 | func QueryUser(id int) (User, error) { 19 | if u, ok := userDB[id]; ok { 20 | return u, nil 21 | } 22 | 23 | return User{}, fmt.Errorf("id %d not in user db", id) 24 | } 25 | 26 | 27 | func main() { 28 | u , err := QueryUser(8) 29 | if err != nil { 30 | fmt.Println(err) 31 | return 32 | } 33 | 34 | fmt.Printf("name: %s, age: %d \n", u.Name, u.Age) 35 | } -------------------------------------------------------------------------------- /src/dataserial/data.go: -------------------------------------------------------------------------------- 1 | package dataserial 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | ) 7 | 8 | // RPCdata represents the serializing format of structured data 9 | type RPCdata struct { 10 | Name string // name of the function 11 | Args []interface{} // request's or response's body expect error. 12 | Err string // Error any executing remote server 13 | } 14 | 15 | // Encode The RPCdata in binary format which can 16 | // be sent over the network. 17 | func Encode(data RPCdata) ([]byte, error) { 18 | var buf bytes.Buffer 19 | encoder := gob.NewEncoder(&buf) 20 | if err := encoder.Encode(data); err != nil { 21 | return nil, err 22 | } 23 | return buf.Bytes(), nil 24 | } 25 | 26 | // Decode the binary data into the Go RPC struct 27 | func Decode(b []byte) (RPCdata, error) { 28 | buf := bytes.NewBuffer(b) 29 | decoder := gob.NewDecoder(buf) 30 | var data RPCdata 31 | if err := decoder.Decode(&data); err != nil { 32 | return RPCdata{}, err 33 | } 34 | return data, nil 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ankur Anand 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/transport/transport_test.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestTransport_ReadWrite(t *testing.T) { 11 | addr := "localhost:3212" 12 | dataToSend := "Hello TLV World" 13 | wg := sync.WaitGroup{} 14 | wg.Add(2) 15 | go func() { 16 | 17 | defer wg.Done() 18 | l, err := net.Listen("tcp", addr) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | defer l.Close() 23 | conn, _ := l.Accept() 24 | time.Sleep(100 * time.Millisecond) 25 | s := NewTransport(conn) 26 | // wait for some second before sending message 27 | // so that the go routine accepting the connection becomes live 28 | err = s.Send([]byte(dataToSend)) 29 | t.Log("listen and accept") 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | }() 34 | 35 | go func() { 36 | defer wg.Done() 37 | conn, err := net.Dial("tcp", addr) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | tp := NewTransport(conn) 42 | data, err := tp.Read() 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | if string(data) != dataToSend { 47 | t.FailNow() 48 | } 49 | }() 50 | wg.Wait() 51 | } 52 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/gob" 5 | "fmt" 6 | "net" 7 | "time" 8 | 9 | "github.com/ankur-anand/simple-go-rpc/src/client" 10 | 11 | "github.com/ankur-anand/simple-go-rpc/src/server" 12 | ) 13 | 14 | type User struct { 15 | Name string 16 | Age int 17 | } 18 | 19 | var userDB = map[int]User{ 20 | 1: User{"Ankur", 85}, 21 | 9: User{"Anand", 25}, 22 | 8: User{"Ankur Anand", 27}, 23 | } 24 | 25 | func QueryUser(id int) (User, error) { 26 | if u, ok := userDB[id]; ok { 27 | return u, nil 28 | } 29 | 30 | return User{}, fmt.Errorf("id %d not in user db", id) 31 | } 32 | 33 | func main() { 34 | // new Type needs to be registered 35 | gob.Register(User{}) 36 | addr := "localhost:3212" 37 | srv := server.NewServer(addr) 38 | 39 | // start server 40 | srv.Register("QueryUser", QueryUser) 41 | go srv.Run() 42 | 43 | // wait for server to start. 44 | time.Sleep(1 * time.Second) 45 | 46 | // start client 47 | conn, err := net.Dial("tcp", addr) 48 | if err != nil { 49 | panic(err) 50 | } 51 | cli := client.NewClient(conn) 52 | 53 | var Query func(int) (User, error) 54 | cli.CallRPC("QueryUser", &Query) 55 | 56 | u, err := Query(1) 57 | if err != nil { 58 | panic(err) 59 | } 60 | fmt.Println(u) 61 | 62 | u2, err := Query(8) 63 | if err != nil { 64 | panic(err) 65 | } 66 | fmt.Println(u2) 67 | } 68 | -------------------------------------------------------------------------------- /src/transport/transport.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | ) 8 | 9 | const headerLen = 4 10 | 11 | // Transport will use TLV protocol 12 | type Transport struct { 13 | conn net.Conn // Conn is a generic stream-oriented network connection. 14 | } 15 | 16 | // NewTransport creates a Transport 17 | func NewTransport(conn net.Conn) *Transport { 18 | return &Transport{conn} 19 | } 20 | 21 | // Send TLV encoded data over the network 22 | func (t *Transport) Send(data []byte) error { 23 | // we will need 4 more byte then the len of data 24 | // as TLV header is 4bytes and in this header 25 | // we will encode how much byte of data 26 | // we are sending for this request. 27 | buf := make([]byte, headerLen+len(data)) 28 | binary.BigEndian.PutUint32(buf[:headerLen], uint32(len(data))) 29 | copy(buf[headerLen:], data) 30 | _, err := t.conn.Write(buf) 31 | if err != nil { 32 | return err 33 | } 34 | return nil 35 | } 36 | 37 | // Read TLV Data sent over the wire 38 | func (t *Transport) Read() ([]byte, error) { 39 | header := make([]byte, headerLen) 40 | _, err := io.ReadFull(t.conn, header) 41 | if err != nil { 42 | return nil, err 43 | } 44 | dataLen := binary.BigEndian.Uint32(header) 45 | data := make([]byte, dataLen) 46 | _, err = io.ReadFull(t.conn, data) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return data, nil 51 | } 52 | -------------------------------------------------------------------------------- /src/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "reflect" 7 | 8 | "github.com/ankur-anand/simple-go-rpc/src/dataserial" 9 | "github.com/ankur-anand/simple-go-rpc/src/transport" 10 | ) 11 | 12 | // Client struct 13 | type Client struct { 14 | conn net.Conn 15 | } 16 | 17 | // NewClient creates a new client 18 | func NewClient(conn net.Conn) *Client { 19 | return &Client{conn} 20 | } 21 | 22 | // CallRPC Method 23 | func (c *Client) CallRPC(rpcName string, fPtr interface{}) { 24 | container := reflect.ValueOf(fPtr).Elem() 25 | f := func(req []reflect.Value) []reflect.Value { 26 | cReqTransport := transport.NewTransport(c.conn) 27 | errorHandler := func(err error) []reflect.Value { 28 | outArgs := make([]reflect.Value, container.Type().NumOut()) 29 | for i := 0; i < len(outArgs)-1; i++ { 30 | outArgs[i] = reflect.Zero(container.Type().Out(i)) 31 | } 32 | outArgs[len(outArgs)-1] = reflect.ValueOf(&err).Elem() 33 | return outArgs 34 | } 35 | 36 | // Process input parameters 37 | inArgs := make([]interface{}, 0, len(req)) 38 | for _, arg := range req { 39 | inArgs = append(inArgs, arg.Interface()) 40 | } 41 | 42 | // ReqRPC 43 | reqRPC := dataserial.RPCdata{Name: rpcName, Args: inArgs} 44 | b, err := dataserial.Encode(reqRPC) 45 | if err != nil { 46 | panic(err) 47 | } 48 | err = cReqTransport.Send(b) 49 | if err != nil { 50 | return errorHandler(err) 51 | } 52 | // receive response from server 53 | rsp, err := cReqTransport.Read() 54 | if err != nil { // local network error or decode error 55 | return errorHandler(err) 56 | } 57 | rspDecode, _ := dataserial.Decode(rsp) 58 | if rspDecode.Err != "" { // remote server error 59 | return errorHandler(errors.New(rspDecode.Err)) 60 | } 61 | 62 | if len(rspDecode.Args) == 0 { 63 | rspDecode.Args = make([]interface{}, container.Type().NumOut()) 64 | } 65 | // unpack response arguments 66 | numOut := container.Type().NumOut() 67 | outArgs := make([]reflect.Value, numOut) 68 | for i := 0; i < numOut; i++ { 69 | if i != numOut-1 { // unpack arguments (except error) 70 | if rspDecode.Args[i] == nil { // if argument is nil (gob will ignore "Zero" in transmission), set "Zero" value 71 | outArgs[i] = reflect.Zero(container.Type().Out(i)) 72 | } else { 73 | outArgs[i] = reflect.ValueOf(rspDecode.Args[i]) 74 | } 75 | } else { // unpack error argument 76 | outArgs[i] = reflect.Zero(container.Type().Out(i)) 77 | } 78 | } 79 | 80 | return outArgs 81 | } 82 | container.Set(reflect.MakeFunc(container.Type(), f)) 83 | } 84 | -------------------------------------------------------------------------------- /src/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ankur-anand/simple-go-rpc/src/dataserial" 6 | "github.com/ankur-anand/simple-go-rpc/src/transport" 7 | "io" 8 | "log" 9 | "net" 10 | "reflect" 11 | ) 12 | 13 | // RPCServer ... 14 | type RPCServer struct { 15 | addr string 16 | funcs map[string]reflect.Value 17 | } 18 | 19 | // NewServer creates a new server 20 | func NewServer(addr string) *RPCServer { 21 | return &RPCServer{addr: addr, funcs: make(map[string]reflect.Value)} 22 | } 23 | 24 | // Register the name of the function and its entries 25 | func (s *RPCServer) Register(fnName string, fFunc interface{}) { 26 | if _, ok := s.funcs[fnName]; ok { 27 | return 28 | } 29 | 30 | s.funcs[fnName] = reflect.ValueOf(fFunc) 31 | } 32 | 33 | // Execute the given function if present 34 | func (s *RPCServer) Execute(req dataserial.RPCdata) dataserial.RPCdata { 35 | // get method by name 36 | f, ok := s.funcs[req.Name] 37 | if !ok { 38 | // since method is not present 39 | e := fmt.Sprintf("func %s not Registered", req.Name) 40 | log.Println(e) 41 | return dataserial.RPCdata{Name: req.Name, Args: nil, Err: e} 42 | } 43 | 44 | log.Printf("func %s is called\n", req.Name) 45 | // unpack request arguments 46 | inArgs := make([]reflect.Value, len(req.Args)) 47 | for i := range req.Args { 48 | inArgs[i] = reflect.ValueOf(req.Args[i]) 49 | } 50 | 51 | // invoke requested method 52 | out := f.Call(inArgs) 53 | // now since we have followed the function signature style where last argument will be an error 54 | // so we will pack the response arguments expect error. 55 | resArgs := make([]interface{}, len(out)-1) 56 | for i := 0; i < len(out)-1; i++ { 57 | // Interface returns the constant value stored in v as an interface{}. 58 | resArgs[i] = out[i].Interface() 59 | } 60 | 61 | // pack error argument 62 | var er string 63 | if _, ok := out[len(out)-1].Interface().(error); ok { 64 | // convert the error into error string value 65 | er = out[len(out)-1].Interface().(error).Error() 66 | } 67 | return dataserial.RPCdata{Name: req.Name, Args: resArgs, Err: er} 68 | } 69 | 70 | // Run server 71 | func (s *RPCServer) Run() { 72 | l, err := net.Listen("tcp", s.addr) 73 | if err != nil { 74 | log.Printf("listen on %s err: %v\n", s.addr, err) 75 | return 76 | } 77 | for { 78 | conn, err := l.Accept() 79 | if err != nil { 80 | log.Printf("accept err: %v\n", err) 81 | continue 82 | } 83 | go func() { 84 | connTransport := transport.NewTransport(conn) 85 | for { 86 | // read request 87 | req, err := connTransport.Read() 88 | if err != nil { 89 | if err != io.EOF { 90 | log.Printf("read err: %v\n", err) 91 | return 92 | } 93 | } 94 | 95 | // decode the data and pass it to execute 96 | decReq, err := dataserial.Decode(req) 97 | if err != nil { 98 | log.Printf("Error Decoding the Payload err: %v\n", err) 99 | return 100 | } 101 | // get the executed result. 102 | resP := s.Execute(decReq) 103 | // encode the data back 104 | b, err := dataserial.Encode(resP) 105 | if err != nil { 106 | log.Printf("Error Encoding the Payload for response err: %v\n", err) 107 | return 108 | } 109 | // send response to client 110 | err = connTransport.Send(b) 111 | if err != nil { 112 | log.Printf("transport write err: %v\n", err) 113 | } 114 | } 115 | }() 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Simple GoRPC 2 | 3 | Learning RPC basic building blocks by building a simple RPC framework in Golang from scratch. 4 | 5 | ## RPC 6 | 7 | In Simple Term **Service A** wants to call **Service B** functions. But those two services are not in the same memory space. So it cannot be called directly. 8 | 9 | So, in order to make this call happen, we need to express the semantics of how to call and also how to pass the communication through the network. 10 | 11 | ### Let's think what we do when we call function in the same memory space (local call) 12 | 13 | ```go 14 | type User struct { 15 | Name string 16 | Age int 17 | } 18 | 19 | var userDB = map[int]User{ 20 | 1: User{"Ankur", 85}, 21 | 9: User{"Anand", 25}, 22 | 8: User{"Ankur Anand", 27}, 23 | } 24 | 25 | 26 | func QueryUser(id int) (User, error) { 27 | if u, ok := userDB[id]; ok { 28 | return u, nil 29 | } 30 | 31 | return User{}, fmt.Errorf("id %d not in user db", id) 32 | } 33 | 34 | 35 | func main() { 36 | u , err := QueryUser(8) 37 | if err != nil { 38 | fmt.Println(err) 39 | return 40 | } 41 | 42 | fmt.Printf("name: %s, age: %d \n", u.Name, u.Age) 43 | } 44 | ``` 45 | 46 | Now, how do we do the same function call over the network? 47 | 48 | **Client** will call _QueryUser(id int)_ function over the network and there will be one server which will Serve the Call to this function and return the Response _User{"Name", id}, nil_. 49 | 50 | ## Network Transmission Data format. 51 | 52 | Simple-gorpc will do TLV (fixed-length header + variable-length message body) encoding scheme to regulate the transmission of data, over the tcp. 53 | **More on this later** 54 | 55 | ### Before we send our data over the network we need to define the structure how we are going to send the data over the network. 56 | 57 | This helps us to define a common protocol that, the client and server both can understand. (protobuf IDL define what both server and client understand). 58 | 59 | ### So data received by the server needs to have: 60 | 61 | - the name of the function to be called 62 | - list of parameters to be passed to that function 63 | 64 | Also let's agree that the second return value is of type error, indicating the RPC call result. 65 | 66 | ```go 67 | // RPCdata transmission format 68 | type RPCdata struct { 69 | Name string // name of the function 70 | Args []interface{} // request's or response's body expect error. 71 | Err string // Error any executing remote server 72 | } 73 | ``` 74 | 75 | So now that we have a format, we need to serialize this so that we can send it over the network. 76 | In our case we will use the `go` default binary serialization protocol for encoding and decoding. 77 | 78 | ```go 79 | // be sent over the network. 80 | func Encode(data RPCdata) ([]byte, error) { 81 | var buf bytes.Buffer 82 | encoder := gob.NewEncoder(&buf) 83 | if err := encoder.Encode(data); err != nil { 84 | return nil, err 85 | } 86 | return buf.Bytes(), nil 87 | } 88 | 89 | // Decode the binary data into the Go struct 90 | func Decode(b []byte) (RPCdata, error) { 91 | buf := bytes.NewBuffer(b) 92 | decoder := gob.NewDecoder(buf) 93 | var data RPCdata 94 | if err := decoder.Decode(&data); err != nil { 95 | return Data{}, err 96 | } 97 | return data, nil 98 | } 99 | ``` 100 | 101 | ### Network Transmission 102 | 103 | The reason for choosing the TLV protocol is due to the fact that it's very simple to implement, and it also fullfills our need over identification of the length of data to read, as we need to identify the number of bytes to read for this request over the stream of incoming request. `Send and Receive does the same` 104 | 105 | ```go 106 | // Transport will use TLV protocol 107 | type Transport struct { 108 | conn net.Conn // Conn is a generic stream-oriented network connection. 109 | } 110 | 111 | // NewTransport creates a Transport 112 | func NewTransport(conn net.Conn) *Transport { 113 | return &Transport{conn} 114 | } 115 | 116 | // Send TLV data over the network 117 | func (t *Transport) Send(data []byte) error { 118 | // we will need 4 more byte then the len of data 119 | // as TLV header is 4bytes and in this header 120 | // we will encode how much byte of data 121 | // we are sending for this request. 122 | buf := make([]byte, 4+len(data)) 123 | binary.BigEndian.PutUint32(buf[:4], uint32(len(data))) 124 | copy(buf[4:], data) 125 | _, err := t.conn.Write(buf) 126 | if err != nil { 127 | return err 128 | } 129 | return nil 130 | } 131 | 132 | // Read TLV sent over the wire 133 | func (t *Transport) Read() ([]byte, error) { 134 | header := make([]byte, 4) 135 | _, err := io.ReadFull(t.conn, header) 136 | if err != nil { 137 | return nil, err 138 | } 139 | dataLen := binary.BigEndian.Uint32(header) 140 | data := make([]byte, dataLen) 141 | _, err = io.ReadFull(t.conn, data) 142 | if err != nil { 143 | return nil, err 144 | } 145 | return data, nil 146 | } 147 | ``` 148 | 149 | Now that we have the DataFormat and Transport protocol defined. We need and **RPC Server** and **RPC CLient** 150 | 151 | ## RPC SERVER 152 | 153 | RPC Server will receive the `RPCData` which will have an function Name. 154 | **So we need to maintain and map that contains an function name to actual function mapping** 155 | 156 | ```go 157 | // RPCServer ... 158 | type RPCServer struct { 159 | addr string 160 | funcs map[string] reflect.Value 161 | } 162 | 163 | // Register the name of the function and its entries 164 | func (s *RPCServer) Register(fnName string, fFunc interface{}) { 165 | if _,ok := s.funcs[fnName]; ok { 166 | return 167 | } 168 | 169 | s.funcs[fnName] = reflect.ValueOf(fFunc) 170 | } 171 | ``` 172 | 173 | Now that we have the func registered, when we receive the request we will check if the name of func passed during the execution of the function is present or not. and then will execute it accordingly 174 | 175 | ```go 176 | // Execute the given function if present 177 | func (s *RPCServer) Execute(req RPCdata) RPCdata { 178 | // get method by name 179 | f, ok := s.funcs[req.Name] 180 | if !ok { 181 | // since method is not present 182 | e := fmt.Sprintf("func %s not Registered", req.Name) 183 | log.Println(e) 184 | return RPCdata{Name: req.Name, Args: nil, Err: e} 185 | } 186 | 187 | log.Printf("func %s is called\n", req.Name) 188 | // unpackage request arguments 189 | inArgs := make([]reflect.Value, len(req.Args)) 190 | for i := range req.Args { 191 | inArgs[i] = reflect.ValueOf(req.Args[i]) 192 | } 193 | 194 | // invoke requested method 195 | out := f.Call(inArgs) 196 | // now since we have followed the function signature style where last argument will be an error 197 | // so we will pack the response arguments expect error. 198 | resArgs := make([]interface{}, len(out) - 1) 199 | for i := 0; i < len(out) - 1; i ++ { 200 | // Interface returns the constant value stored in v as an interface{}. 201 | resArgs[i] = out[i].Interface() 202 | } 203 | 204 | // pack error argument 205 | var er string 206 | if e, ok := out[len(out) - 1].Interface().(error); ok { 207 | // convert the error into error string value 208 | er = e.Error() 209 | } 210 | return RPCdata{Name: req.Name, Args: resArgs, Err: er} 211 | } 212 | ``` 213 | 214 | ## RPC CLIENT 215 | 216 | Since the concrete implementation of the function is on the server side, the client only has the prototype of the function, so we need complete prototype of the calling function, so that we can call it. 217 | 218 | ```go 219 | func (c *Client) callRPC(rpcName string, fPtr interface{}) { 220 | container := reflect.ValueOf(fPtr).Elem() 221 | f := func(req []reflect.Value) []reflect.Value { 222 | cReqTransport := NewTransport(c.conn) 223 | errorHandler := func(err error) []reflect.Value { 224 | outArgs := make([]reflect.Value, container.Type().NumOut()) 225 | for i := 0; i < len(outArgs)-1; i++ { 226 | outArgs[i] = reflect.Zero(container.Type().Out(i)) 227 | } 228 | outArgs[len(outArgs)-1] = reflect.ValueOf(&err).Elem() 229 | return outArgs 230 | } 231 | 232 | // Process input parameters 233 | inArgs := make([]interface{}, 0, len(req)) 234 | for _, arg := range req { 235 | inArgs = append(inArgs, arg.Interface()) 236 | } 237 | 238 | // ReqRPC 239 | reqRPC := RPCdata{Name: rpcName, Args: inArgs} 240 | b, err := Encode(reqRPC) 241 | if err != nil { 242 | panic(err) 243 | } 244 | err = cReqTransport.Send(b) 245 | if err != nil { 246 | return errorHandler(err) 247 | } 248 | // receive response from server 249 | rsp, err := cReqTransport.Read() 250 | if err != nil { // local network error or decode error 251 | return errorHandler(err) 252 | } 253 | rspDecode, _ := Decode(rsp) 254 | if rspDecode.Err != "" { // remote server error 255 | return errorHandler(errors.New(rspDecode.Err)) 256 | } 257 | 258 | if len(rspDecode.Args) == 0 { 259 | rspDecode.Args = make([]interface{}, container.Type().NumOut()) 260 | } 261 | // unpackage response arguments 262 | numOut := container.Type().NumOut() 263 | outArgs := make([]reflect.Value, numOut) 264 | for i := 0; i < numOut; i++ { 265 | if i != numOut-1 { // unpackage arguments (except error) 266 | if rspDecode.Args[i] == nil { // if argument is nil (gob will ignore "Zero" in transmission), set "Zero" value 267 | outArgs[i] = reflect.Zero(container.Type().Out(i)) 268 | } else { 269 | outArgs[i] = reflect.ValueOf(rspDecode.Args[i]) 270 | } 271 | } else { // unpackage error argument 272 | outArgs[i] = reflect.Zero(container.Type().Out(i)) 273 | } 274 | } 275 | 276 | return outArgs 277 | } 278 | container.Set(reflect.MakeFunc(container.Type(), f)) 279 | } 280 | ``` 281 | 282 | ### Testing our framework 283 | 284 | ```go 285 | package main 286 | 287 | import ( 288 | "encoding/gob" 289 | "fmt" 290 | "net" 291 | ) 292 | 293 | type User struct { 294 | Name string 295 | Age int 296 | } 297 | 298 | var userDB = map[int]User{ 299 | 1: User{"Ankur", 85}, 300 | 9: User{"Anand", 25}, 301 | 8: User{"Ankur Anand", 27}, 302 | } 303 | 304 | func QueryUser(id int) (User, error) { 305 | if u, ok := userDB[id]; ok { 306 | return u, nil 307 | } 308 | 309 | return User{}, fmt.Errorf("id %d not in user db", id) 310 | } 311 | 312 | func main() { 313 | // new Type needs to be registered 314 | gob.Register(User{}) 315 | addr := "localhost:3212" 316 | srv := NewServer(addr) 317 | 318 | // start server 319 | srv.Register("QueryUser", QueryUser) 320 | go srv.Run() 321 | 322 | // wait for server to start. 323 | time.Sleep(1 * time.Second) 324 | 325 | // start client 326 | conn, err := net.Dial("tcp", addr) 327 | if err != nil { 328 | panic(err) 329 | } 330 | cli := NewClient(conn) 331 | 332 | var Query func(int) (User, error) 333 | cli.callRPC("QueryUser", &Query) 334 | 335 | u, err := Query(1) 336 | if err != nil { 337 | panic(err) 338 | } 339 | fmt.Println(u) 340 | 341 | u2, err := Query(8) 342 | if err != nil { 343 | panic(err) 344 | } 345 | fmt.Println(u2) 346 | } 347 | ``` 348 | 349 | Output 350 | 351 | ``` 352 | 2019/07/23 20:26:18 func QueryUser is called 353 | {Ankur 85} 354 | 2019/07/23 20:26:18 func QueryUser is called 355 | {Ankur Anand 27} 356 | ``` 357 | 358 | `go run main.go` 359 | --------------------------------------------------------------------------------