├── .github └── workflows │ └── go.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── client.go ├── codec.go ├── debug.go ├── go.mod ├── go.sum ├── jsonrpc ├── jsonrpc.go └── jsonrpc_test.go ├── rpc2_test.go ├── server.go └── state.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.20' 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.15 5 | - tip 6 | 7 | arch: 8 | - amd64 9 | - ppc64le 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Cenk Altı 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rpc2 2 | ==== 3 | 4 | [![GoDoc](https://godoc.org/github.com/cenkalti/rpc2?status.png)](https://godoc.org/github.com/cenkalti/rpc2) 5 | 6 | rpc2 is a fork of net/rpc package in the standard library. 7 | The main goal is to add bi-directional support to calls. 8 | That means server can call the methods of client. 9 | This is not possible with net/rpc package. 10 | In order to do this it adds a `*Client` argument to method signatures. 11 | 12 | Install 13 | -------- 14 | 15 | go get github.com/cenkalti/rpc2 16 | 17 | Example server 18 | --------------- 19 | 20 | ```go 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | "net" 26 | 27 | "github.com/cenkalti/rpc2" 28 | ) 29 | 30 | type Args struct{ A, B int } 31 | type Reply int 32 | 33 | func main() { 34 | srv := rpc2.NewServer() 35 | srv.Handle("add", func(client *rpc2.Client, args *Args, reply *Reply) error { 36 | 37 | // Reversed call (server to client) 38 | var rep Reply 39 | client.Call("mult", Args{2, 3}, &rep) 40 | fmt.Println("mult result:", rep) 41 | 42 | *reply = Reply(args.A + args.B) 43 | return nil 44 | }) 45 | 46 | lis, _ := net.Listen("tcp", "127.0.0.1:5000") 47 | srv.Accept(lis) 48 | } 49 | ``` 50 | 51 | Example Client 52 | --------------- 53 | 54 | ```go 55 | package main 56 | 57 | import ( 58 | "fmt" 59 | "net" 60 | 61 | "github.com/cenkalti/rpc2" 62 | ) 63 | 64 | type Args struct{ A, B int } 65 | type Reply int 66 | 67 | func main() { 68 | conn, _ := net.Dial("tcp", "127.0.0.1:5000") 69 | 70 | clt := rpc2.NewClient(conn) 71 | clt.Handle("mult", func(client *rpc2.Client, args *Args, reply *Reply) error { 72 | *reply = Reply(args.A * args.B) 73 | return nil 74 | }) 75 | go clt.Run() 76 | 77 | var rep Reply 78 | clt.Call("add", Args{1, 2}, &rep) 79 | fmt.Println("add result:", rep) 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | // Package rpc2 provides bi-directional RPC client and server similar to net/rpc. 2 | package rpc2 3 | 4 | import ( 5 | "context" 6 | "errors" 7 | "io" 8 | "log" 9 | "reflect" 10 | "sync" 11 | ) 12 | 13 | // Client represents an RPC Client. 14 | // There may be multiple outstanding Calls associated 15 | // with a single Client, and a Client may be used by 16 | // multiple goroutines simultaneously. 17 | type Client struct { 18 | mutex sync.Mutex // protects pending, seq, request 19 | sending sync.Mutex 20 | request Request // temp area used in send() 21 | seq uint64 22 | pending map[uint64]*Call 23 | closing bool 24 | shutdown bool 25 | server bool 26 | codec Codec 27 | handlers map[string]*handler 28 | disconnect chan struct{} 29 | State *State // additional information to associate with client 30 | blocking bool // whether to block request handling 31 | } 32 | 33 | // NewClient returns a new Client to handle requests to the 34 | // set of services at the other end of the connection. 35 | // It adds a buffer to the write side of the connection so 36 | // the header and payload are sent as a unit. 37 | func NewClient(conn io.ReadWriteCloser) *Client { 38 | return NewClientWithCodec(NewGobCodec(conn)) 39 | } 40 | 41 | // NewClientWithCodec is like NewClient but uses the specified 42 | // codec to encode requests and decode responses. 43 | func NewClientWithCodec(codec Codec) *Client { 44 | return &Client{ 45 | codec: codec, 46 | pending: make(map[uint64]*Call), 47 | handlers: make(map[string]*handler), 48 | disconnect: make(chan struct{}), 49 | seq: 1, // 0 means notification. 50 | } 51 | } 52 | 53 | // SetBlocking puts the client in blocking mode. 54 | // In blocking mode, received requests are processes synchronously. 55 | // If you have methods that may take a long time, other subsequent requests may time out. 56 | func (c *Client) SetBlocking(blocking bool) { 57 | c.blocking = blocking 58 | } 59 | 60 | // Run the client's read loop. 61 | // You must run this method before calling any methods on the server. 62 | func (c *Client) Run() { 63 | c.readLoop() 64 | } 65 | 66 | // DisconnectNotify returns a channel that is closed 67 | // when the client connection has gone away. 68 | func (c *Client) DisconnectNotify() chan struct{} { 69 | return c.disconnect 70 | } 71 | 72 | // Handle registers the handler function for the given method. If a handler already exists for method, Handle panics. 73 | func (c *Client) Handle(method string, handlerFunc interface{}) { 74 | addHandler(c.handlers, method, handlerFunc) 75 | } 76 | 77 | // readLoop reads messages from codec. 78 | // It reads a reqeust or a response to the previous request. 79 | // If the message is request, calls the handler function. 80 | // If the message is response, sends the reply to the associated call. 81 | func (c *Client) readLoop() { 82 | var err error 83 | var req Request 84 | var resp Response 85 | for err == nil { 86 | req = Request{} 87 | resp = Response{} 88 | if err = c.codec.ReadHeader(&req, &resp); err != nil { 89 | break 90 | } 91 | 92 | if req.Method != "" { 93 | // request comes to server 94 | if err = c.readRequest(&req); err != nil { 95 | debugln("rpc2: error reading request:", err.Error()) 96 | } 97 | } else { 98 | // response comes to client 99 | if err = c.readResponse(&resp); err != nil { 100 | debugln("rpc2: error reading response:", err.Error()) 101 | } 102 | } 103 | } 104 | // Terminate pending calls. 105 | c.sending.Lock() 106 | c.mutex.Lock() 107 | c.shutdown = true 108 | closing := c.closing 109 | if err == io.EOF { 110 | if closing { 111 | err = ErrShutdown 112 | } else { 113 | err = io.ErrUnexpectedEOF 114 | } 115 | } 116 | for _, call := range c.pending { 117 | call.Error = err 118 | call.done() 119 | } 120 | c.mutex.Unlock() 121 | c.sending.Unlock() 122 | if err != io.EOF && !closing && !c.server { 123 | debugln("rpc2: client protocol error:", err) 124 | } 125 | close(c.disconnect) 126 | if !closing { 127 | c.codec.Close() 128 | } 129 | } 130 | 131 | func (c *Client) handleRequest(req Request, method *handler, argv reflect.Value) { 132 | // Invoke the method, providing a new value for the reply. 133 | replyv := reflect.New(method.replyType.Elem()) 134 | 135 | returnValues := method.fn.Call([]reflect.Value{reflect.ValueOf(c), argv, replyv}) 136 | 137 | // Do not send response if request is a notification. 138 | if req.Seq == 0 { 139 | return 140 | } 141 | 142 | // The return value for the method is an error. 143 | errInter := returnValues[0].Interface() 144 | errmsg := "" 145 | if errInter != nil { 146 | errmsg = errInter.(error).Error() 147 | } 148 | resp := &Response{ 149 | Seq: req.Seq, 150 | Error: errmsg, 151 | } 152 | if err := c.codec.WriteResponse(resp, replyv.Interface()); err != nil { 153 | debugln("rpc2: error writing response:", err.Error()) 154 | } 155 | } 156 | 157 | func (c *Client) readRequest(req *Request) error { 158 | method, ok := c.handlers[req.Method] 159 | if !ok { 160 | resp := &Response{ 161 | Seq: req.Seq, 162 | Error: "rpc2: can't find method " + req.Method, 163 | } 164 | return c.codec.WriteResponse(resp, resp) 165 | } 166 | 167 | // Decode the argument value. 168 | var argv reflect.Value 169 | argIsValue := false // if true, need to indirect before calling. 170 | if method.argType.Kind() == reflect.Ptr { 171 | argv = reflect.New(method.argType.Elem()) 172 | } else { 173 | argv = reflect.New(method.argType) 174 | argIsValue = true 175 | } 176 | // argv guaranteed to be a pointer now. 177 | if err := c.codec.ReadRequestBody(argv.Interface()); err != nil { 178 | return err 179 | } 180 | if argIsValue { 181 | argv = argv.Elem() 182 | } 183 | 184 | if c.blocking { 185 | c.handleRequest(*req, method, argv) 186 | } else { 187 | go c.handleRequest(*req, method, argv) 188 | } 189 | 190 | return nil 191 | } 192 | 193 | func (c *Client) readResponse(resp *Response) error { 194 | seq := resp.Seq 195 | c.mutex.Lock() 196 | call := c.pending[seq] 197 | delete(c.pending, seq) 198 | c.mutex.Unlock() 199 | 200 | var err error 201 | switch { 202 | case call == nil: 203 | // We've got no pending call. That usually means that 204 | // WriteRequest partially failed, and call was already 205 | // removed; response is a server telling us about an 206 | // error reading request body. We should still attempt 207 | // to read error body, but there's no one to give it to. 208 | err = c.codec.ReadResponseBody(nil) 209 | if err != nil { 210 | err = errors.New("reading error body: " + err.Error()) 211 | } 212 | case resp.Error != "": 213 | // We've got an error response. Give this to the request; 214 | // any subsequent requests will get the ReadResponseBody 215 | // error if there is one. 216 | call.Error = ServerError(resp.Error) 217 | err = c.codec.ReadResponseBody(nil) 218 | if err != nil { 219 | err = errors.New("reading error body: " + err.Error()) 220 | } 221 | call.done() 222 | default: 223 | err = c.codec.ReadResponseBody(call.Reply) 224 | if err != nil { 225 | call.Error = errors.New("reading body " + err.Error()) 226 | } 227 | call.done() 228 | } 229 | 230 | return err 231 | } 232 | 233 | // Close waits for active calls to finish and closes the codec. 234 | func (c *Client) Close() error { 235 | c.mutex.Lock() 236 | if c.shutdown || c.closing { 237 | c.mutex.Unlock() 238 | return ErrShutdown 239 | } 240 | c.closing = true 241 | c.mutex.Unlock() 242 | return c.codec.Close() 243 | } 244 | 245 | // Go invokes the function asynchronously. It returns the Call structure representing 246 | // the invocation. The done channel will signal when the call is complete by returning 247 | // the same Call object. If done is nil, Go will allocate a new channel. 248 | // If non-nil, done must be buffered or Go will deliberately crash. 249 | func (c *Client) Go(method string, args interface{}, reply interface{}, done chan *Call) *Call { 250 | call := new(Call) 251 | call.Method = method 252 | call.Args = args 253 | call.Reply = reply 254 | if done == nil { 255 | done = make(chan *Call, 10) // buffered. 256 | } else { 257 | // If caller passes done != nil, it must arrange that 258 | // done has enough buffer for the number of simultaneous 259 | // RPCs that will be using that channel. If the channel 260 | // is totally unbuffered, it's best not to run at all. 261 | if cap(done) == 0 { 262 | log.Panic("rpc2: done channel is unbuffered") 263 | } 264 | } 265 | call.Done = done 266 | c.send(call) 267 | return call 268 | } 269 | 270 | // CallWithContext invokes the named function, waits for it to complete, and 271 | // returns its error status, or an error from Context timeout. 272 | func (c *Client) CallWithContext(ctx context.Context, method string, args interface{}, reply interface{}) error { 273 | call := c.Go(method, args, reply, make(chan *Call, 1)) 274 | select { 275 | case <-call.Done: 276 | return call.Error 277 | case <-ctx.Done(): 278 | return ctx.Err() 279 | } 280 | return nil 281 | } 282 | 283 | // Call invokes the named function, waits for it to complete, and returns its error status. 284 | func (c *Client) Call(method string, args interface{}, reply interface{}) error { 285 | return c.CallWithContext(context.Background(), method, args, reply) 286 | } 287 | 288 | func (call *Call) done() { 289 | select { 290 | case call.Done <- call: 291 | // ok 292 | default: 293 | // We don't want to block here. It is the caller's responsibility to make 294 | // sure the channel has enough buffer space. See comment in Go(). 295 | debugln("rpc2: discarding Call reply due to insufficient Done chan capacity") 296 | } 297 | } 298 | 299 | // ServerError represents an error that has been returned from 300 | // the remote side of the RPC connection. 301 | type ServerError string 302 | 303 | func (e ServerError) Error() string { 304 | return string(e) 305 | } 306 | 307 | // ErrShutdown is returned when the connection is closing or closed. 308 | var ErrShutdown = errors.New("connection is shut down") 309 | 310 | // Call represents an active RPC. 311 | type Call struct { 312 | Method string // The name of the service and method to call. 313 | Args interface{} // The argument to the function (*struct). 314 | Reply interface{} // The reply from the function (*struct). 315 | Error error // After completion, the error status. 316 | Done chan *Call // Strobes when call is complete. 317 | } 318 | 319 | func (c *Client) send(call *Call) { 320 | c.sending.Lock() 321 | defer c.sending.Unlock() 322 | 323 | // Register this call. 324 | c.mutex.Lock() 325 | if c.shutdown || c.closing { 326 | call.Error = ErrShutdown 327 | c.mutex.Unlock() 328 | call.done() 329 | return 330 | } 331 | seq := c.seq 332 | c.seq++ 333 | c.pending[seq] = call 334 | c.mutex.Unlock() 335 | 336 | // Encode and send the request. 337 | c.request.Seq = seq 338 | c.request.Method = call.Method 339 | err := c.codec.WriteRequest(&c.request, call.Args) 340 | if err != nil { 341 | c.mutex.Lock() 342 | call = c.pending[seq] 343 | delete(c.pending, seq) 344 | c.mutex.Unlock() 345 | if call != nil { 346 | call.Error = err 347 | call.done() 348 | } 349 | } 350 | } 351 | 352 | // Notify sends a request to the receiver but does not wait for a return value. 353 | func (c *Client) Notify(method string, args interface{}) error { 354 | c.sending.Lock() 355 | defer c.sending.Unlock() 356 | 357 | if c.shutdown || c.closing { 358 | return ErrShutdown 359 | } 360 | 361 | c.request.Seq = 0 362 | c.request.Method = method 363 | return c.codec.WriteRequest(&c.request, args) 364 | } 365 | -------------------------------------------------------------------------------- /codec.go: -------------------------------------------------------------------------------- 1 | package rpc2 2 | 3 | import ( 4 | "bufio" 5 | "encoding/gob" 6 | "io" 7 | "sync" 8 | ) 9 | 10 | // A Codec implements reading and writing of RPC requests and responses. 11 | // The client calls ReadHeader to read a message header. 12 | // The implementation must populate either Request or Response argument. 13 | // Depending on which argument is populated, ReadRequestBody or 14 | // ReadResponseBody is called right after ReadHeader. 15 | // ReadRequestBody and ReadResponseBody may be called with a nil 16 | // argument to force the body to be read and then discarded. 17 | type Codec interface { 18 | // ReadHeader must read a message and populate either the request 19 | // or the response by inspecting the incoming message. 20 | ReadHeader(*Request, *Response) error 21 | 22 | // ReadRequestBody into args argument of handler function. 23 | ReadRequestBody(interface{}) error 24 | 25 | // ReadResponseBody into reply argument of handler function. 26 | ReadResponseBody(interface{}) error 27 | 28 | // WriteRequest must be safe for concurrent use by multiple goroutines. 29 | WriteRequest(*Request, interface{}) error 30 | 31 | // WriteResponse must be safe for concurrent use by multiple goroutines. 32 | WriteResponse(*Response, interface{}) error 33 | 34 | // Close is called when client/server finished with the connection. 35 | Close() error 36 | } 37 | 38 | // Request is a header written before every RPC call. 39 | type Request struct { 40 | Seq uint64 // sequence number chosen by client 41 | Method string 42 | } 43 | 44 | // Response is a header written before every RPC return. 45 | type Response struct { 46 | Seq uint64 // echoes that of the request 47 | Error string // error, if any. 48 | } 49 | 50 | type gobCodec struct { 51 | rwc io.ReadWriteCloser 52 | dec *gob.Decoder 53 | enc *gob.Encoder 54 | encBuf *bufio.Writer 55 | mutex sync.Mutex 56 | } 57 | 58 | type message struct { 59 | Seq uint64 60 | Method string 61 | Error string 62 | } 63 | 64 | // NewGobCodec returns a new rpc2.Codec using gob encoding/decoding on conn. 65 | func NewGobCodec(conn io.ReadWriteCloser) Codec { 66 | buf := bufio.NewWriter(conn) 67 | return &gobCodec{ 68 | rwc: conn, 69 | dec: gob.NewDecoder(conn), 70 | enc: gob.NewEncoder(buf), 71 | encBuf: buf, 72 | } 73 | } 74 | 75 | func (c *gobCodec) ReadHeader(req *Request, resp *Response) error { 76 | var msg message 77 | if err := c.dec.Decode(&msg); err != nil { 78 | return err 79 | } 80 | 81 | if msg.Method != "" { 82 | req.Seq = msg.Seq 83 | req.Method = msg.Method 84 | } else { 85 | resp.Seq = msg.Seq 86 | resp.Error = msg.Error 87 | } 88 | return nil 89 | } 90 | 91 | func (c *gobCodec) ReadRequestBody(body interface{}) error { 92 | return c.dec.Decode(body) 93 | } 94 | 95 | func (c *gobCodec) ReadResponseBody(body interface{}) error { 96 | return c.dec.Decode(body) 97 | } 98 | 99 | func (c *gobCodec) WriteRequest(r *Request, body interface{}) (err error) { 100 | c.mutex.Lock() 101 | defer c.mutex.Unlock() 102 | if err = c.enc.Encode(r); err != nil { 103 | return 104 | } 105 | if err = c.enc.Encode(body); err != nil { 106 | return 107 | } 108 | return c.encBuf.Flush() 109 | } 110 | 111 | func (c *gobCodec) WriteResponse(r *Response, body interface{}) (err error) { 112 | c.mutex.Lock() 113 | defer c.mutex.Unlock() 114 | if err = c.enc.Encode(r); err != nil { 115 | return 116 | } 117 | if err = c.enc.Encode(body); err != nil { 118 | return 119 | } 120 | return c.encBuf.Flush() 121 | } 122 | 123 | func (c *gobCodec) Close() error { 124 | return c.rwc.Close() 125 | } 126 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | package rpc2 2 | 3 | import "log" 4 | 5 | // DebugLog controls the printing of internal and I/O errors. 6 | var DebugLog = false 7 | 8 | func debugln(v ...interface{}) { 9 | if DebugLog { 10 | log.Println(v...) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cenkalti/rpc2 2 | 3 | go 1.20 4 | 5 | require github.com/cenkalti/hub v1.0.2 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cenkalti/hub v1.0.2 h1:Nqv9TNaA9boeO2wQFW8o87BY3zKthtnzXmWGmJqhAV8= 2 | github.com/cenkalti/hub v1.0.2/go.mod h1:8LAFAZcCasb83vfxatMUnZHRoQcffho2ELpHb+kaTJU= 3 | -------------------------------------------------------------------------------- /jsonrpc/jsonrpc.go: -------------------------------------------------------------------------------- 1 | // Package jsonrpc implements a JSON-RPC ClientCodec and ServerCodec for the rpc2 package. 2 | // 3 | // Beside struct types, JSONCodec allows using positional arguments. 4 | // Use []interface{} as the type of argument when sending and receiving methods. 5 | // 6 | // Positional arguments example: 7 | // server.Handle("add", func(client *rpc2.Client, args []interface{}, result *float64) error { 8 | // *result = args[0].(float64) + args[1].(float64) 9 | // return nil 10 | // }) 11 | // 12 | // var result float64 13 | // client.Call("add", []interface{}{1, 2}, &result) 14 | // 15 | package jsonrpc 16 | 17 | import ( 18 | "encoding/json" 19 | "errors" 20 | "fmt" 21 | "io" 22 | "reflect" 23 | "sync" 24 | 25 | "github.com/cenkalti/rpc2" 26 | ) 27 | 28 | type jsonCodec struct { 29 | dec *json.Decoder // for reading JSON values 30 | enc *json.Encoder // for writing JSON values 31 | c io.Closer 32 | 33 | // temporary work space 34 | msg message 35 | serverRequest serverRequest 36 | clientResponse clientResponse 37 | 38 | // JSON-RPC clients can use arbitrary json values as request IDs. 39 | // Package rpc expects uint64 request IDs. 40 | // We assign uint64 sequence numbers to incoming requests 41 | // but save the original request ID in the pending map. 42 | // When rpc responds, we use the sequence number in 43 | // the response to find the original request ID. 44 | mutex sync.Mutex // protects seq, pending 45 | pending map[uint64]*json.RawMessage 46 | seq uint64 47 | } 48 | 49 | // NewJSONCodec returns a new rpc2.Codec using JSON-RPC on conn. 50 | func NewJSONCodec(conn io.ReadWriteCloser) rpc2.Codec { 51 | return &jsonCodec{ 52 | dec: json.NewDecoder(conn), 53 | enc: json.NewEncoder(conn), 54 | c: conn, 55 | pending: make(map[uint64]*json.RawMessage), 56 | } 57 | } 58 | 59 | // serverRequest and clientResponse combined 60 | type message struct { 61 | Method string `json:"method"` 62 | Params *json.RawMessage `json:"params"` 63 | Id *json.RawMessage `json:"id"` 64 | Result *json.RawMessage `json:"result"` 65 | Error interface{} `json:"error"` 66 | } 67 | 68 | // Unmarshal to 69 | type serverRequest struct { 70 | Method string `json:"method"` 71 | Params *json.RawMessage `json:"params"` 72 | Id *json.RawMessage `json:"id"` 73 | } 74 | type clientResponse struct { 75 | Id uint64 `json:"id"` 76 | Result *json.RawMessage `json:"result"` 77 | Error interface{} `json:"error"` 78 | } 79 | 80 | // to Marshal 81 | type serverResponse struct { 82 | Id *json.RawMessage `json:"id"` 83 | Result interface{} `json:"result"` 84 | Error interface{} `json:"error"` 85 | } 86 | type clientRequest struct { 87 | Method string `json:"method"` 88 | Params interface{} `json:"params"` 89 | Id *uint64 `json:"id"` 90 | } 91 | 92 | func (c *jsonCodec) ReadHeader(req *rpc2.Request, resp *rpc2.Response) error { 93 | c.msg = message{} 94 | if err := c.dec.Decode(&c.msg); err != nil { 95 | return err 96 | } 97 | 98 | if c.msg.Method != "" { 99 | // request comes to server 100 | c.serverRequest.Id = c.msg.Id 101 | c.serverRequest.Method = c.msg.Method 102 | c.serverRequest.Params = c.msg.Params 103 | 104 | req.Method = c.serverRequest.Method 105 | 106 | // JSON request id can be any JSON value; 107 | // RPC package expects uint64. Translate to 108 | // internal uint64 and save JSON on the side. 109 | if c.serverRequest.Id == nil { 110 | // Notification 111 | } else { 112 | c.mutex.Lock() 113 | c.seq++ 114 | c.pending[c.seq] = c.serverRequest.Id 115 | c.serverRequest.Id = nil 116 | req.Seq = c.seq 117 | c.mutex.Unlock() 118 | } 119 | } else { 120 | // response comes to client 121 | err := json.Unmarshal(*c.msg.Id, &c.clientResponse.Id) 122 | if err != nil { 123 | return err 124 | } 125 | c.clientResponse.Result = c.msg.Result 126 | c.clientResponse.Error = c.msg.Error 127 | 128 | resp.Error = "" 129 | resp.Seq = c.clientResponse.Id 130 | if c.clientResponse.Error != nil || c.clientResponse.Result == nil { 131 | x, ok := c.clientResponse.Error.(string) 132 | if !ok { 133 | return fmt.Errorf("invalid error %v", c.clientResponse.Error) 134 | } 135 | if x == "" { 136 | x = "unspecified error" 137 | } 138 | resp.Error = x 139 | } 140 | } 141 | return nil 142 | } 143 | 144 | var errMissingParams = errors.New("jsonrpc: request body missing params") 145 | 146 | func (c *jsonCodec) ReadRequestBody(x interface{}) error { 147 | if x == nil { 148 | return nil 149 | } 150 | if c.serverRequest.Params == nil { 151 | return errMissingParams 152 | } 153 | 154 | var err error 155 | 156 | // Check if x points to a slice of any kind 157 | rt := reflect.TypeOf(x) 158 | if rt.Kind() == reflect.Ptr && rt.Elem().Kind() == reflect.Slice { 159 | // If it's a slice, unmarshal as is 160 | err = json.Unmarshal(*c.serverRequest.Params, x) 161 | } else { 162 | // Anything else unmarshal into a slice containing x 163 | params := &[]interface{}{x} 164 | err = json.Unmarshal(*c.serverRequest.Params, params) 165 | } 166 | 167 | return err 168 | } 169 | 170 | func (c *jsonCodec) ReadResponseBody(x interface{}) error { 171 | if x == nil { 172 | return nil 173 | } 174 | return json.Unmarshal(*c.clientResponse.Result, x) 175 | } 176 | 177 | func (c *jsonCodec) WriteRequest(r *rpc2.Request, param interface{}) error { 178 | req := &clientRequest{Method: r.Method} 179 | 180 | // Check if param is a slice of any kind 181 | if param != nil && reflect.TypeOf(param).Kind() == reflect.Slice { 182 | // If it's a slice, leave as is 183 | req.Params = param 184 | } else { 185 | // Put anything else into a slice 186 | req.Params = []interface{}{param} 187 | } 188 | 189 | if r.Seq == 0 { 190 | // Notification 191 | req.Id = nil 192 | } else { 193 | seq := r.Seq 194 | req.Id = &seq 195 | } 196 | return c.enc.Encode(req) 197 | } 198 | 199 | var null = json.RawMessage([]byte("null")) 200 | 201 | func (c *jsonCodec) WriteResponse(r *rpc2.Response, x interface{}) error { 202 | c.mutex.Lock() 203 | b, ok := c.pending[r.Seq] 204 | if !ok { 205 | c.mutex.Unlock() 206 | return errors.New("invalid sequence number in response") 207 | } 208 | delete(c.pending, r.Seq) 209 | c.mutex.Unlock() 210 | 211 | if b == nil { 212 | // Invalid request so no id. Use JSON null. 213 | b = &null 214 | } 215 | resp := serverResponse{Id: b} 216 | if r.Error == "" { 217 | resp.Result = x 218 | } else { 219 | resp.Error = r.Error 220 | } 221 | return c.enc.Encode(resp) 222 | } 223 | 224 | func (c *jsonCodec) Close() error { 225 | return c.c.Close() 226 | } 227 | -------------------------------------------------------------------------------- /jsonrpc/jsonrpc_test.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net" 7 | "testing" 8 | "time" 9 | 10 | "github.com/cenkalti/rpc2" 11 | ) 12 | 13 | const ( 14 | network = "tcp4" 15 | addr = "127.0.0.1:5001" 16 | ) 17 | 18 | func TestJSONRPC(t *testing.T) { 19 | type Args struct{ A, B int } 20 | type Reply int 21 | 22 | lis, err := net.Listen(network, addr) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | 27 | srv := rpc2.NewServer() 28 | srv.Handle("add", func(client *rpc2.Client, args *Args, reply *Reply) error { 29 | *reply = Reply(args.A + args.B) 30 | 31 | var rep Reply 32 | err := client.Call("mult", Args{2, 3}, &rep) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | if rep != 6 { 38 | t.Fatalf("not expected: %d", rep) 39 | } 40 | 41 | return nil 42 | }) 43 | srv.Handle("addPos", func(client *rpc2.Client, args []interface{}, result *float64) error { 44 | *result = args[0].(float64) + args[1].(float64) 45 | return nil 46 | }) 47 | srv.Handle("rawArgs", func(client *rpc2.Client, args []json.RawMessage, reply *[]string) error { 48 | for _, p := range args { 49 | var str string 50 | json.Unmarshal(p, &str) 51 | *reply = append(*reply, str) 52 | } 53 | return nil 54 | }) 55 | srv.Handle("typedArgs", func(client *rpc2.Client, args []int, reply *[]string) error { 56 | for _, p := range args { 57 | *reply = append(*reply, fmt.Sprintf("%d", p)) 58 | } 59 | return nil 60 | }) 61 | srv.Handle("nilArgs", func(client *rpc2.Client, args []interface{}, reply *[]string) error { 62 | for _, v := range args { 63 | if v == nil { 64 | *reply = append(*reply, "nil") 65 | } 66 | } 67 | return nil 68 | }) 69 | number := make(chan int, 1) 70 | srv.Handle("set", func(client *rpc2.Client, i int, _ *struct{}) error { 71 | number <- i 72 | return nil 73 | }) 74 | 75 | go func() { 76 | conn, err := lis.Accept() 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | srv.ServeCodec(NewJSONCodec(conn)) 81 | }() 82 | 83 | conn, err := net.Dial(network, addr) 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | 88 | clt := rpc2.NewClientWithCodec(NewJSONCodec(conn)) 89 | clt.Handle("mult", func(client *rpc2.Client, args *Args, reply *Reply) error { 90 | *reply = Reply(args.A * args.B) 91 | return nil 92 | }) 93 | go clt.Run() 94 | 95 | // Test Call. 96 | var rep Reply 97 | err = clt.Call("add", Args{1, 2}, &rep) 98 | if err != nil { 99 | t.Fatal(err) 100 | } 101 | if rep != 3 { 102 | t.Fatalf("not expected: %d", rep) 103 | } 104 | 105 | // Test notification. 106 | err = clt.Notify("set", 6) 107 | if err != nil { 108 | t.Fatal(err) 109 | } 110 | select { 111 | case i := <-number: 112 | if i != 6 { 113 | t.Fatalf("unexpected number: %d", i) 114 | } 115 | case <-time.After(time.Second): 116 | t.Fatal("did not get notification") 117 | } 118 | 119 | // Test undefined method. 120 | err = clt.Call("foo", 1, &rep) 121 | if err.Error() != "rpc2: can't find method foo" { 122 | t.Fatal(err) 123 | } 124 | 125 | // Test Positional arguments. 126 | var result float64 127 | err = clt.Call("addPos", []interface{}{1, 2}, &result) 128 | if err != nil { 129 | t.Fatal(err) 130 | } 131 | if result != 3 { 132 | t.Fatalf("not expected: %f", result) 133 | } 134 | 135 | testArgs := func(expected, reply []string) error { 136 | if len(reply) != len(expected) { 137 | return fmt.Errorf("incorrect reply length: %d", len(reply)) 138 | } 139 | for i := range expected { 140 | if reply[i] != expected[i] { 141 | return fmt.Errorf("not expected reply[%d]: %s", i, reply[i]) 142 | } 143 | } 144 | return nil 145 | } 146 | 147 | // Test raw arguments (partial unmarshal) 148 | var reply []string 149 | var expected []string = []string{"arg1", "arg2"} 150 | rawArgs := json.RawMessage(`["arg1", "arg2"]`) 151 | err = clt.Call("rawArgs", rawArgs, &reply) 152 | if err != nil { 153 | t.Fatal(err) 154 | } 155 | 156 | if err = testArgs(expected, reply); err != nil { 157 | t.Fatal(err) 158 | } 159 | 160 | // Test typed arguments 161 | reply = []string{} 162 | expected = []string{"1", "2"} 163 | typedArgs := []int{1, 2} 164 | err = clt.Call("typedArgs", typedArgs, &reply) 165 | if err != nil { 166 | t.Fatal(err) 167 | } 168 | if err = testArgs(expected, reply); err != nil { 169 | t.Fatal(err) 170 | } 171 | 172 | // Test nil args 173 | reply = []string{} 174 | expected = []string{"nil"} 175 | err = clt.Call("nilArgs", nil, &reply) 176 | if err != nil { 177 | t.Fatal(err) 178 | } 179 | if err = testArgs(expected, reply); err != nil { 180 | t.Fatal(err) 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /rpc2_test.go: -------------------------------------------------------------------------------- 1 | package rpc2 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | const ( 10 | network = "tcp4" 11 | addr = "127.0.0.1:5000" 12 | ) 13 | 14 | func TestTCPGOB(t *testing.T) { 15 | type Args struct{ A, B int } 16 | type Reply int 17 | 18 | lis, err := net.Listen(network, addr) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | srv := NewServer() 24 | srv.Handle("add", func(client *Client, args *Args, reply *Reply) error { 25 | *reply = Reply(args.A + args.B) 26 | 27 | var rep Reply 28 | err := client.Call("mult", Args{2, 3}, &rep) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | if rep != 6 { 34 | t.Fatalf("not expected: %d", rep) 35 | } 36 | 37 | return nil 38 | }) 39 | number := make(chan int, 1) 40 | srv.Handle("set", func(client *Client, i int, _ *struct{}) error { 41 | number <- i 42 | return nil 43 | }) 44 | go srv.Accept(lis) 45 | 46 | conn, err := net.Dial(network, addr) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | clt := NewClient(conn) 52 | clt.Handle("mult", func(client *Client, args *Args, reply *Reply) error { 53 | *reply = Reply(args.A * args.B) 54 | return nil 55 | }) 56 | go clt.Run() 57 | defer clt.Close() 58 | 59 | // Test Call. 60 | var rep Reply 61 | err = clt.Call("add", Args{1, 2}, &rep) 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | if rep != 3 { 66 | t.Fatalf("not expected: %d", rep) 67 | } 68 | 69 | // Test notification. 70 | err = clt.Notify("set", 6) 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | select { 75 | case i := <-number: 76 | if i != 6 { 77 | t.Fatalf("unexpected number: %d", i) 78 | } 79 | case <-time.After(time.Second): 80 | t.Fatal("did not get notification") 81 | } 82 | 83 | // Test blocked request 84 | clt.SetBlocking(true) 85 | err = clt.Call("add", Args{1, 2}, &rep) 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | if rep != 3 { 90 | t.Fatalf("not expected: %d", rep) 91 | } 92 | 93 | // Test undefined method. 94 | err = clt.Call("foo", 1, &rep) 95 | if err.Error() != "rpc2: can't find method foo" { 96 | t.Fatal(err) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package rpc2 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "log" 7 | "net" 8 | "reflect" 9 | "unicode" 10 | "unicode/utf8" 11 | 12 | "github.com/cenkalti/hub" 13 | ) 14 | 15 | // Precompute the reflect type for error. Can't use error directly 16 | // because Typeof takes an empty interface value. This is annoying. 17 | var typeOfError = reflect.TypeOf((*error)(nil)).Elem() 18 | var typeOfClient = reflect.TypeOf((*Client)(nil)) 19 | 20 | const ( 21 | clientConnected hub.Kind = iota 22 | clientDisconnected 23 | ) 24 | 25 | // Server responds to RPC requests made by Client. 26 | type Server struct { 27 | handlers map[string]*handler 28 | eventHub *hub.Hub 29 | } 30 | 31 | type handler struct { 32 | fn reflect.Value 33 | argType reflect.Type 34 | replyType reflect.Type 35 | } 36 | 37 | type connectionEvent struct { 38 | Client *Client 39 | } 40 | 41 | type disconnectionEvent struct { 42 | Client *Client 43 | } 44 | 45 | func (connectionEvent) Kind() hub.Kind { return clientConnected } 46 | func (disconnectionEvent) Kind() hub.Kind { return clientDisconnected } 47 | 48 | // NewServer returns a new Server. 49 | func NewServer() *Server { 50 | return &Server{ 51 | handlers: make(map[string]*handler), 52 | eventHub: &hub.Hub{}, 53 | } 54 | } 55 | 56 | // Handle registers the handler function for the given method. If a handler already exists for method, Handle panics. 57 | func (s *Server) Handle(method string, handlerFunc interface{}) { 58 | addHandler(s.handlers, method, handlerFunc) 59 | } 60 | 61 | func addHandler(handlers map[string]*handler, mname string, handlerFunc interface{}) { 62 | if _, ok := handlers[mname]; ok { 63 | panic("rpc2: multiple registrations for " + mname) 64 | } 65 | 66 | method := reflect.ValueOf(handlerFunc) 67 | mtype := method.Type() 68 | // Method needs three ins: *client, *args, *reply. 69 | if mtype.NumIn() != 3 { 70 | log.Panicln("method", mname, "has wrong number of ins:", mtype.NumIn()) 71 | } 72 | // First arg must be a pointer to rpc2.Client. 73 | clientType := mtype.In(0) 74 | if clientType.Kind() != reflect.Ptr { 75 | log.Panicln("method", mname, "client type not a pointer:", clientType) 76 | } 77 | if clientType != typeOfClient { 78 | log.Panicln("method", mname, "first argument", clientType.String(), "not *rpc2.Client") 79 | } 80 | // Second arg need not be a pointer. 81 | argType := mtype.In(1) 82 | if !isExportedOrBuiltinType(argType) { 83 | log.Panicln(mname, "argument type not exported:", argType) 84 | } 85 | // Third arg must be a pointer. 86 | replyType := mtype.In(2) 87 | if replyType.Kind() != reflect.Ptr { 88 | log.Panicln("method", mname, "reply type not a pointer:", replyType) 89 | } 90 | // Reply type must be exported. 91 | if !isExportedOrBuiltinType(replyType) { 92 | log.Panicln("method", mname, "reply type not exported:", replyType) 93 | } 94 | // Method needs one out. 95 | if mtype.NumOut() != 1 { 96 | log.Panicln("method", mname, "has wrong number of outs:", mtype.NumOut()) 97 | } 98 | // The return type of the method must be error. 99 | if returnType := mtype.Out(0); returnType != typeOfError { 100 | log.Panicln("method", mname, "returns", returnType.String(), "not error") 101 | } 102 | handlers[mname] = &handler{ 103 | fn: method, 104 | argType: argType, 105 | replyType: replyType, 106 | } 107 | } 108 | 109 | // Is this type exported or a builtin? 110 | func isExportedOrBuiltinType(t reflect.Type) bool { 111 | for t.Kind() == reflect.Ptr { 112 | t = t.Elem() 113 | } 114 | // PkgPath will be non-empty even for an exported type, 115 | // so we need to check the type name as well. 116 | return isExported(t.Name()) || t.PkgPath() == "" 117 | } 118 | 119 | // Is this an exported - upper case - name? 120 | func isExported(name string) bool { 121 | rune, _ := utf8.DecodeRuneInString(name) 122 | return unicode.IsUpper(rune) 123 | } 124 | 125 | // OnConnect registers a function to run when a client connects. 126 | func (s *Server) OnConnect(f func(*Client)) { 127 | s.eventHub.Subscribe(clientConnected, func(e hub.Event) { 128 | go f(e.(connectionEvent).Client) 129 | }) 130 | } 131 | 132 | // OnDisconnect registers a function to run when a client disconnects. 133 | func (s *Server) OnDisconnect(f func(*Client)) { 134 | s.eventHub.Subscribe(clientDisconnected, func(e hub.Event) { 135 | go f(e.(disconnectionEvent).Client) 136 | }) 137 | } 138 | 139 | // Accept accepts connections on the listener and serves requests 140 | // for each incoming connection. Accept blocks; the caller typically 141 | // invokes it in a go statement. 142 | func (s *Server) Accept(lis net.Listener) { 143 | for { 144 | conn, err := lis.Accept() 145 | if err != nil { 146 | if !errors.Is(err, net.ErrClosed) { 147 | log.Print("rpc.Serve: accept:", err.Error()) 148 | } 149 | return 150 | } 151 | go s.ServeConn(conn) 152 | } 153 | } 154 | 155 | // ServeConn runs the server on a single connection. 156 | // ServeConn blocks, serving the connection until the client hangs up. 157 | // The caller typically invokes ServeConn in a go statement. 158 | // ServeConn uses the gob wire format (see package gob) on the 159 | // connection. To use an alternate codec, use ServeCodec. 160 | func (s *Server) ServeConn(conn io.ReadWriteCloser) { 161 | s.ServeCodec(NewGobCodec(conn)) 162 | } 163 | 164 | // ServeCodec is like ServeConn but uses the specified codec to 165 | // decode requests and encode responses. 166 | func (s *Server) ServeCodec(codec Codec) { 167 | s.ServeCodecWithState(codec, NewState()) 168 | } 169 | 170 | // ServeCodecWithState is like ServeCodec but also gives the ability to 171 | // associate a state variable with the client that persists across RPC calls. 172 | func (s *Server) ServeCodecWithState(codec Codec, state *State) { 173 | defer codec.Close() 174 | 175 | // Client also handles the incoming connections. 176 | c := NewClientWithCodec(codec) 177 | c.server = true 178 | c.handlers = s.handlers 179 | c.State = state 180 | 181 | s.eventHub.Publish(connectionEvent{c}) 182 | c.Run() 183 | s.eventHub.Publish(disconnectionEvent{c}) 184 | } 185 | -------------------------------------------------------------------------------- /state.go: -------------------------------------------------------------------------------- 1 | package rpc2 2 | 3 | import "sync" 4 | 5 | type State struct { 6 | store map[string]interface{} 7 | m sync.RWMutex 8 | } 9 | 10 | func NewState() *State { 11 | return &State{store: make(map[string]interface{})} 12 | } 13 | 14 | func (s *State) Get(key string) (value interface{}, ok bool) { 15 | s.m.RLock() 16 | value, ok = s.store[key] 17 | s.m.RUnlock() 18 | return 19 | } 20 | 21 | func (s *State) Set(key string, value interface{}) { 22 | s.m.Lock() 23 | s.store[key] = value 24 | s.m.Unlock() 25 | } 26 | --------------------------------------------------------------------------------