pax_global_header00006660000000000000000000000064145612606140014517gustar00rootroot0000000000000052 comment=e4256715c45c698474cd0e3747b4437c6531c7c3 golang-github-cenkalti-rpc2-1.0.2/000077500000000000000000000000001456126061400167025ustar00rootroot00000000000000golang-github-cenkalti-rpc2-1.0.2/.gitignore000066400000000000000000000004031456126061400206670ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test golang-github-cenkalti-rpc2-1.0.2/.travis.yml000066400000000000000000000001021456126061400210040ustar00rootroot00000000000000language: go go: - 1.15 - tip arch: - amd64 - ppc64le golang-github-cenkalti-rpc2-1.0.2/LICENSE000066400000000000000000000020641456126061400177110ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Cenk Altı Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.golang-github-cenkalti-rpc2-1.0.2/README.md000066400000000000000000000030231456126061400201570ustar00rootroot00000000000000rpc2 ==== [![GoDoc](https://godoc.org/github.com/cenkalti/rpc2?status.png)](https://godoc.org/github.com/cenkalti/rpc2) [![Build Status](https://travis-ci.org/cenkalti/rpc2.png)](https://travis-ci.org/cenkalti/rpc2) rpc2 is a fork of net/rpc package in the standard library. The main goal is to add bi-directional support to calls. That means server can call the methods of client. This is not possible with net/rpc package. In order to do this it adds a `*Client` argument to method signatures. Install -------- go get github.com/cenkalti/rpc2 Example server --------------- ```go package main import ( "fmt" "net" "github.com/cenkalti/rpc2" ) type Args struct{ A, B int } type Reply int func main() { srv := rpc2.NewServer() srv.Handle("add", func(client *rpc2.Client, args *Args, reply *Reply) error { // Reversed call (server to client) var rep Reply client.Call("mult", Args{2, 3}, &rep) fmt.Println("mult result:", rep) *reply = Reply(args.A + args.B) return nil }) lis, _ := net.Listen("tcp", "127.0.0.1:5000") srv.Accept(lis) } ``` Example Client --------------- ```go package main import ( "fmt" "net" "github.com/cenkalti/rpc2" ) type Args struct{ A, B int } type Reply int func main() { conn, _ := net.Dial("tcp", "127.0.0.1:5000") clt := rpc2.NewClient(conn) clt.Handle("mult", func(client *rpc2.Client, args *Args, reply *Reply) error { *reply = Reply(args.A * args.B) return nil }) go clt.Run() var rep Reply clt.Call("add", Args{1, 2}, &rep) fmt.Println("add result:", rep) } ``` golang-github-cenkalti-rpc2-1.0.2/client.go000066400000000000000000000233041456126061400205110ustar00rootroot00000000000000// Package rpc2 provides bi-directional RPC client and server similar to net/rpc. package rpc2 import ( "context" "errors" "io" "log" "reflect" "sync" ) // Client represents an RPC Client. // There may be multiple outstanding Calls associated // with a single Client, and a Client may be used by // multiple goroutines simultaneously. type Client struct { mutex sync.Mutex // protects pending, seq, request sending sync.Mutex request Request // temp area used in send() seq uint64 pending map[uint64]*Call closing bool shutdown bool server bool codec Codec handlers map[string]*handler disconnect chan struct{} State *State // additional information to associate with client blocking bool // whether to block request handling } // NewClient returns a new Client to handle requests to the // set of services at the other end of the connection. // It adds a buffer to the write side of the connection so // the header and payload are sent as a unit. func NewClient(conn io.ReadWriteCloser) *Client { return NewClientWithCodec(NewGobCodec(conn)) } // NewClientWithCodec is like NewClient but uses the specified // codec to encode requests and decode responses. func NewClientWithCodec(codec Codec) *Client { return &Client{ codec: codec, pending: make(map[uint64]*Call), handlers: make(map[string]*handler), disconnect: make(chan struct{}), seq: 1, // 0 means notification. } } // SetBlocking puts the client in blocking mode. // In blocking mode, received requests are processes synchronously. // If you have methods that may take a long time, other subsequent requests may time out. func (c *Client) SetBlocking(blocking bool) { c.blocking = blocking } // Run the client's read loop. // You must run this method before calling any methods on the server. func (c *Client) Run() { c.readLoop() } // DisconnectNotify returns a channel that is closed // when the client connection has gone away. func (c *Client) DisconnectNotify() chan struct{} { return c.disconnect } // Handle registers the handler function for the given method. If a handler already exists for method, Handle panics. func (c *Client) Handle(method string, handlerFunc interface{}) { addHandler(c.handlers, method, handlerFunc) } // readLoop reads messages from codec. // It reads a reqeust or a response to the previous request. // If the message is request, calls the handler function. // If the message is response, sends the reply to the associated call. func (c *Client) readLoop() { var err error var req Request var resp Response for err == nil { req = Request{} resp = Response{} if err = c.codec.ReadHeader(&req, &resp); err != nil { break } if req.Method != "" { // request comes to server if err = c.readRequest(&req); err != nil { debugln("rpc2: error reading request:", err.Error()) } } else { // response comes to client if err = c.readResponse(&resp); err != nil { debugln("rpc2: error reading response:", err.Error()) } } } // Terminate pending calls. c.sending.Lock() c.mutex.Lock() c.shutdown = true closing := c.closing if err == io.EOF { if closing { err = ErrShutdown } else { err = io.ErrUnexpectedEOF } } for _, call := range c.pending { call.Error = err call.done() } c.mutex.Unlock() c.sending.Unlock() if err != io.EOF && !closing && !c.server { debugln("rpc2: client protocol error:", err) } close(c.disconnect) if !closing { c.codec.Close() } } func (c *Client) handleRequest(req Request, method *handler, argv reflect.Value) { // Invoke the method, providing a new value for the reply. replyv := reflect.New(method.replyType.Elem()) returnValues := method.fn.Call([]reflect.Value{reflect.ValueOf(c), argv, replyv}) // Do not send response if request is a notification. if req.Seq == 0 { return } // The return value for the method is an error. errInter := returnValues[0].Interface() errmsg := "" if errInter != nil { errmsg = errInter.(error).Error() } resp := &Response{ Seq: req.Seq, Error: errmsg, } if err := c.codec.WriteResponse(resp, replyv.Interface()); err != nil { debugln("rpc2: error writing response:", err.Error()) } } func (c *Client) readRequest(req *Request) error { method, ok := c.handlers[req.Method] if !ok { resp := &Response{ Seq: req.Seq, Error: "rpc2: can't find method " + req.Method, } return c.codec.WriteResponse(resp, resp) } // Decode the argument value. var argv reflect.Value argIsValue := false // if true, need to indirect before calling. if method.argType.Kind() == reflect.Ptr { argv = reflect.New(method.argType.Elem()) } else { argv = reflect.New(method.argType) argIsValue = true } // argv guaranteed to be a pointer now. if err := c.codec.ReadRequestBody(argv.Interface()); err != nil { return err } if argIsValue { argv = argv.Elem() } if c.blocking { c.handleRequest(*req, method, argv) } else { go c.handleRequest(*req, method, argv) } return nil } func (c *Client) readResponse(resp *Response) error { seq := resp.Seq c.mutex.Lock() call := c.pending[seq] delete(c.pending, seq) c.mutex.Unlock() var err error switch { case call == nil: // We've got no pending call. That usually means that // WriteRequest partially failed, and call was already // removed; response is a server telling us about an // error reading request body. We should still attempt // to read error body, but there's no one to give it to. err = c.codec.ReadResponseBody(nil) if err != nil { err = errors.New("reading error body: " + err.Error()) } case resp.Error != "": // We've got an error response. Give this to the request; // any subsequent requests will get the ReadResponseBody // error if there is one. call.Error = ServerError(resp.Error) err = c.codec.ReadResponseBody(nil) if err != nil { err = errors.New("reading error body: " + err.Error()) } call.done() default: err = c.codec.ReadResponseBody(call.Reply) if err != nil { call.Error = errors.New("reading body " + err.Error()) } call.done() } return err } // Close waits for active calls to finish and closes the codec. func (c *Client) Close() error { c.mutex.Lock() if c.shutdown || c.closing { c.mutex.Unlock() return ErrShutdown } c.closing = true c.mutex.Unlock() return c.codec.Close() } // Go invokes the function asynchronously. It returns the Call structure representing // the invocation. The done channel will signal when the call is complete by returning // the same Call object. If done is nil, Go will allocate a new channel. // If non-nil, done must be buffered or Go will deliberately crash. func (c *Client) Go(method string, args interface{}, reply interface{}, done chan *Call) *Call { call := new(Call) call.Method = method call.Args = args call.Reply = reply if done == nil { done = make(chan *Call, 10) // buffered. } else { // If caller passes done != nil, it must arrange that // done has enough buffer for the number of simultaneous // RPCs that will be using that channel. If the channel // is totally unbuffered, it's best not to run at all. if cap(done) == 0 { log.Panic("rpc2: done channel is unbuffered") } } call.Done = done c.send(call) return call } // CallWithContext invokes the named function, waits for it to complete, and // returns its error status, or an error from Context timeout. func (c *Client) CallWithContext(ctx context.Context, method string, args interface{}, reply interface{}) error { call := c.Go(method, args, reply, make(chan *Call, 1)) select { case <-call.Done: return call.Error case <-ctx.Done(): return ctx.Err() } return nil } // Call invokes the named function, waits for it to complete, and returns its error status. func (c *Client) Call(method string, args interface{}, reply interface{}) error { return c.CallWithContext(context.Background(), method, args, reply) } func (call *Call) done() { select { case call.Done <- call: // ok default: // We don't want to block here. It is the caller's responsibility to make // sure the channel has enough buffer space. See comment in Go(). debugln("rpc2: discarding Call reply due to insufficient Done chan capacity") } } // ServerError represents an error that has been returned from // the remote side of the RPC connection. type ServerError string func (e ServerError) Error() string { return string(e) } // ErrShutdown is returned when the connection is closing or closed. var ErrShutdown = errors.New("connection is shut down") // Call represents an active RPC. type Call struct { Method string // The name of the service and method to call. Args interface{} // The argument to the function (*struct). Reply interface{} // The reply from the function (*struct). Error error // After completion, the error status. Done chan *Call // Strobes when call is complete. } func (c *Client) send(call *Call) { c.sending.Lock() defer c.sending.Unlock() // Register this call. c.mutex.Lock() if c.shutdown || c.closing { call.Error = ErrShutdown c.mutex.Unlock() call.done() return } seq := c.seq c.seq++ c.pending[seq] = call c.mutex.Unlock() // Encode and send the request. c.request.Seq = seq c.request.Method = call.Method err := c.codec.WriteRequest(&c.request, call.Args) if err != nil { c.mutex.Lock() call = c.pending[seq] delete(c.pending, seq) c.mutex.Unlock() if call != nil { call.Error = err call.done() } } } // Notify sends a request to the receiver but does not wait for a return value. func (c *Client) Notify(method string, args interface{}) error { c.sending.Lock() defer c.sending.Unlock() if c.shutdown || c.closing { return ErrShutdown } c.request.Seq = 0 c.request.Method = method return c.codec.WriteRequest(&c.request, args) } golang-github-cenkalti-rpc2-1.0.2/codec.go000066400000000000000000000057401456126061400203140ustar00rootroot00000000000000package rpc2 import ( "bufio" "encoding/gob" "io" "sync" ) // A Codec implements reading and writing of RPC requests and responses. // The client calls ReadHeader to read a message header. // The implementation must populate either Request or Response argument. // Depending on which argument is populated, ReadRequestBody or // ReadResponseBody is called right after ReadHeader. // ReadRequestBody and ReadResponseBody may be called with a nil // argument to force the body to be read and then discarded. type Codec interface { // ReadHeader must read a message and populate either the request // or the response by inspecting the incoming message. ReadHeader(*Request, *Response) error // ReadRequestBody into args argument of handler function. ReadRequestBody(interface{}) error // ReadResponseBody into reply argument of handler function. ReadResponseBody(interface{}) error // WriteRequest must be safe for concurrent use by multiple goroutines. WriteRequest(*Request, interface{}) error // WriteResponse must be safe for concurrent use by multiple goroutines. WriteResponse(*Response, interface{}) error // Close is called when client/server finished with the connection. Close() error } // Request is a header written before every RPC call. type Request struct { Seq uint64 // sequence number chosen by client Method string } // Response is a header written before every RPC return. type Response struct { Seq uint64 // echoes that of the request Error string // error, if any. } type gobCodec struct { rwc io.ReadWriteCloser dec *gob.Decoder enc *gob.Encoder encBuf *bufio.Writer mutex sync.Mutex } type message struct { Seq uint64 Method string Error string } // NewGobCodec returns a new rpc2.Codec using gob encoding/decoding on conn. func NewGobCodec(conn io.ReadWriteCloser) Codec { buf := bufio.NewWriter(conn) return &gobCodec{ rwc: conn, dec: gob.NewDecoder(conn), enc: gob.NewEncoder(buf), encBuf: buf, } } func (c *gobCodec) ReadHeader(req *Request, resp *Response) error { var msg message if err := c.dec.Decode(&msg); err != nil { return err } if msg.Method != "" { req.Seq = msg.Seq req.Method = msg.Method } else { resp.Seq = msg.Seq resp.Error = msg.Error } return nil } func (c *gobCodec) ReadRequestBody(body interface{}) error { return c.dec.Decode(body) } func (c *gobCodec) ReadResponseBody(body interface{}) error { return c.dec.Decode(body) } func (c *gobCodec) WriteRequest(r *Request, body interface{}) (err error) { c.mutex.Lock() defer c.mutex.Unlock() if err = c.enc.Encode(r); err != nil { return } if err = c.enc.Encode(body); err != nil { return } return c.encBuf.Flush() } func (c *gobCodec) WriteResponse(r *Response, body interface{}) (err error) { c.mutex.Lock() defer c.mutex.Unlock() if err = c.enc.Encode(r); err != nil { return } if err = c.enc.Encode(body); err != nil { return } return c.encBuf.Flush() } func (c *gobCodec) Close() error { return c.rwc.Close() } golang-github-cenkalti-rpc2-1.0.2/debug.go000066400000000000000000000002711456126061400203170ustar00rootroot00000000000000package rpc2 import "log" // DebugLog controls the printing of internal and I/O errors. var DebugLog = false func debugln(v ...interface{}) { if DebugLog { log.Println(v...) } } golang-github-cenkalti-rpc2-1.0.2/go.mod000066400000000000000000000001211456126061400200020ustar00rootroot00000000000000module github.com/cenkalti/rpc2 go 1.20 require github.com/cenkalti/hub v1.0.2 golang-github-cenkalti-rpc2-1.0.2/go.sum000066400000000000000000000002451456126061400200360ustar00rootroot00000000000000github.com/cenkalti/hub v1.0.2 h1:Nqv9TNaA9boeO2wQFW8o87BY3zKthtnzXmWGmJqhAV8= github.com/cenkalti/hub v1.0.2/go.mod h1:8LAFAZcCasb83vfxatMUnZHRoQcffho2ELpHb+kaTJU= golang-github-cenkalti-rpc2-1.0.2/jsonrpc/000077500000000000000000000000001456126061400203605ustar00rootroot00000000000000golang-github-cenkalti-rpc2-1.0.2/jsonrpc/jsonrpc.go000066400000000000000000000130661456126061400223730ustar00rootroot00000000000000// Package jsonrpc implements a JSON-RPC ClientCodec and ServerCodec for the rpc2 package. // // Beside struct types, JSONCodec allows using positional arguments. // Use []interface{} as the type of argument when sending and receiving methods. // // Positional arguments example: // server.Handle("add", func(client *rpc2.Client, args []interface{}, result *float64) error { // *result = args[0].(float64) + args[1].(float64) // return nil // }) // // var result float64 // client.Call("add", []interface{}{1, 2}, &result) // package jsonrpc import ( "encoding/json" "errors" "fmt" "io" "reflect" "sync" "github.com/cenkalti/rpc2" ) type jsonCodec struct { dec *json.Decoder // for reading JSON values enc *json.Encoder // for writing JSON values c io.Closer // temporary work space msg message serverRequest serverRequest clientResponse clientResponse // JSON-RPC clients can use arbitrary json values as request IDs. // Package rpc expects uint64 request IDs. // We assign uint64 sequence numbers to incoming requests // but save the original request ID in the pending map. // When rpc responds, we use the sequence number in // the response to find the original request ID. mutex sync.Mutex // protects seq, pending pending map[uint64]*json.RawMessage seq uint64 } const RPCVersion = "2.0" // NewJSONCodec returns a new rpc2.Codec using JSON-RPC on conn. func NewJSONCodec(conn io.ReadWriteCloser) rpc2.Codec { return &jsonCodec{ dec: json.NewDecoder(conn), enc: json.NewEncoder(conn), c: conn, pending: make(map[uint64]*json.RawMessage), } } // serverRequest and clientResponse combined type message struct { Method string `json:"method"` Params *json.RawMessage `json:"params"` Id *json.RawMessage `json:"id"` Result *json.RawMessage `json:"result"` Error interface{} `json:"error"` } // Unmarshal to type serverRequest struct { Method string `json:"method"` Params *json.RawMessage `json:"params"` Id *json.RawMessage `json:"id"` } type clientResponse struct { Id uint64 `json:"id"` Result *json.RawMessage `json:"result"` Error interface{} `json:"error"` } // to Marshal type serverResponse struct { Id *json.RawMessage `json:"id"` Result interface{} `json:"result"` Error interface{} `json:"error"` } type clientRequest struct { Version string `json:"jsonrpc"` Method string `json:"method"` Params interface{} `json:"params"` Id *uint64 `json:"id"` } func (c *jsonCodec) ReadHeader(req *rpc2.Request, resp *rpc2.Response) error { c.msg = message{} if err := c.dec.Decode(&c.msg); err != nil { return err } if c.msg.Method != "" { // request comes to server c.serverRequest.Id = c.msg.Id c.serverRequest.Method = c.msg.Method c.serverRequest.Params = c.msg.Params req.Method = c.serverRequest.Method // JSON request id can be any JSON value; // RPC package expects uint64. Translate to // internal uint64 and save JSON on the side. if c.serverRequest.Id == nil { // Notification } else { c.mutex.Lock() c.seq++ c.pending[c.seq] = c.serverRequest.Id c.serverRequest.Id = nil req.Seq = c.seq c.mutex.Unlock() } } else { // response comes to client err := json.Unmarshal(*c.msg.Id, &c.clientResponse.Id) if err != nil { return err } c.clientResponse.Result = c.msg.Result c.clientResponse.Error = c.msg.Error resp.Error = "" resp.Seq = c.clientResponse.Id if c.clientResponse.Error != nil || c.clientResponse.Result == nil { x, ok := c.clientResponse.Error.(string) if !ok { return fmt.Errorf("invalid error %v", c.clientResponse.Error) } if x == "" { x = "unspecified error" } resp.Error = x } } return nil } var errMissingParams = errors.New("jsonrpc: request body missing params") func (c *jsonCodec) ReadRequestBody(x interface{}) error { if x == nil { return nil } if c.serverRequest.Params == nil { return errMissingParams } var err error // Check if x points to a slice of any kind rt := reflect.TypeOf(x) if rt.Kind() == reflect.Ptr && rt.Elem().Kind() == reflect.Slice { // If it's a slice, unmarshal as is err = json.Unmarshal(*c.serverRequest.Params, x) } else { // Anything else unmarshal into a slice containing x params := &[]interface{}{x} err = json.Unmarshal(*c.serverRequest.Params, params) } return err } func (c *jsonCodec) ReadResponseBody(x interface{}) error { if x == nil { return nil } return json.Unmarshal(*c.clientResponse.Result, x) } func (c *jsonCodec) WriteRequest(r *rpc2.Request, param interface{}) error { req := &clientRequest{Method: r.Method, Version: RPCVersion} // Check if param is a slice of any kind if param != nil && reflect.TypeOf(param).Kind() == reflect.Slice { // If it's a slice, leave as is req.Params = param } else { // Put anything else into a slice req.Params = []interface{}{param} } if r.Seq == 0 { // Notification req.Id = nil } else { seq := r.Seq req.Id = &seq } return c.enc.Encode(req) } var null = json.RawMessage([]byte("null")) func (c *jsonCodec) WriteResponse(r *rpc2.Response, x interface{}) error { c.mutex.Lock() b, ok := c.pending[r.Seq] if !ok { c.mutex.Unlock() return errors.New("invalid sequence number in response") } delete(c.pending, r.Seq) c.mutex.Unlock() if b == nil { // Invalid request so no id. Use JSON null. b = &null } resp := serverResponse{Id: b} if r.Error == "" { resp.Result = x } else { resp.Error = r.Error } return c.enc.Encode(resp) } func (c *jsonCodec) Close() error { return c.c.Close() } golang-github-cenkalti-rpc2-1.0.2/jsonrpc/jsonrpc_test.go000066400000000000000000000072521456126061400234320ustar00rootroot00000000000000package jsonrpc import ( "encoding/json" "fmt" "net" "testing" "time" "github.com/cenkalti/rpc2" ) const ( network = "tcp4" addr = "127.0.0.1:5001" ) func TestJSONRPC(t *testing.T) { type Args struct{ A, B int } type Reply int lis, err := net.Listen(network, addr) if err != nil { t.Fatal(err) } srv := rpc2.NewServer() srv.Handle("add", func(client *rpc2.Client, args *Args, reply *Reply) error { *reply = Reply(args.A + args.B) var rep Reply err := client.Call("mult", Args{2, 3}, &rep) if err != nil { t.Fatal(err) } if rep != 6 { t.Fatalf("not expected: %d", rep) } return nil }) srv.Handle("addPos", func(client *rpc2.Client, args []interface{}, result *float64) error { *result = args[0].(float64) + args[1].(float64) return nil }) srv.Handle("rawArgs", func(client *rpc2.Client, args []json.RawMessage, reply *[]string) error { for _, p := range args { var str string json.Unmarshal(p, &str) *reply = append(*reply, str) } return nil }) srv.Handle("typedArgs", func(client *rpc2.Client, args []int, reply *[]string) error { for _, p := range args { *reply = append(*reply, fmt.Sprintf("%d", p)) } return nil }) srv.Handle("nilArgs", func(client *rpc2.Client, args []interface{}, reply *[]string) error { for _, v := range args { if v == nil { *reply = append(*reply, "nil") } } return nil }) number := make(chan int, 1) srv.Handle("set", func(client *rpc2.Client, i int, _ *struct{}) error { number <- i return nil }) go func() { conn, err := lis.Accept() if err != nil { t.Fatal(err) } srv.ServeCodec(NewJSONCodec(conn)) }() conn, err := net.Dial(network, addr) if err != nil { t.Fatal(err) } clt := rpc2.NewClientWithCodec(NewJSONCodec(conn)) clt.Handle("mult", func(client *rpc2.Client, args *Args, reply *Reply) error { *reply = Reply(args.A * args.B) return nil }) go clt.Run() // Test Call. var rep Reply err = clt.Call("add", Args{1, 2}, &rep) if err != nil { t.Fatal(err) } if rep != 3 { t.Fatalf("not expected: %d", rep) } // Test notification. err = clt.Notify("set", 6) if err != nil { t.Fatal(err) } select { case i := <-number: if i != 6 { t.Fatalf("unexpected number: %d", i) } case <-time.After(time.Second): t.Fatal("did not get notification") } // Test undefined method. err = clt.Call("foo", 1, &rep) if err.Error() != "rpc2: can't find method foo" { t.Fatal(err) } // Test Positional arguments. var result float64 err = clt.Call("addPos", []interface{}{1, 2}, &result) if err != nil { t.Fatal(err) } if result != 3 { t.Fatalf("not expected: %f", result) } testArgs := func(expected, reply []string) error { if len(reply) != len(expected) { return fmt.Errorf("incorrect reply length: %d", len(reply)) } for i := range expected { if reply[i] != expected[i] { return fmt.Errorf("not expected reply[%d]: %s", i, reply[i]) } } return nil } // Test raw arguments (partial unmarshal) var reply []string var expected []string = []string{"arg1", "arg2"} rawArgs := json.RawMessage(`["arg1", "arg2"]`) err = clt.Call("rawArgs", rawArgs, &reply) if err != nil { t.Fatal(err) } if err = testArgs(expected, reply); err != nil { t.Fatal(err) } // Test typed arguments reply = []string{} expected = []string{"1", "2"} typedArgs := []int{1, 2} err = clt.Call("typedArgs", typedArgs, &reply) if err != nil { t.Fatal(err) } if err = testArgs(expected, reply); err != nil { t.Fatal(err) } // Test nil args reply = []string{} expected = []string{"nil"} err = clt.Call("nilArgs", nil, &reply) if err != nil { t.Fatal(err) } if err = testArgs(expected, reply); err != nil { t.Fatal(err) } } golang-github-cenkalti-rpc2-1.0.2/rpc2_test.go000066400000000000000000000032401456126061400211350ustar00rootroot00000000000000package rpc2 import ( "net" "testing" "time" ) const ( network = "tcp4" addr = "127.0.0.1:5000" ) func TestTCPGOB(t *testing.T) { type Args struct{ A, B int } type Reply int lis, err := net.Listen(network, addr) if err != nil { t.Fatal(err) } srv := NewServer() srv.Handle("add", func(client *Client, args *Args, reply *Reply) error { *reply = Reply(args.A + args.B) var rep Reply err := client.Call("mult", Args{2, 3}, &rep) if err != nil { t.Fatal(err) } if rep != 6 { t.Fatalf("not expected: %d", rep) } return nil }) number := make(chan int, 1) srv.Handle("set", func(client *Client, i int, _ *struct{}) error { number <- i return nil }) go srv.Accept(lis) conn, err := net.Dial(network, addr) if err != nil { t.Fatal(err) } clt := NewClient(conn) clt.Handle("mult", func(client *Client, args *Args, reply *Reply) error { *reply = Reply(args.A * args.B) return nil }) go clt.Run() defer clt.Close() // Test Call. var rep Reply err = clt.Call("add", Args{1, 2}, &rep) if err != nil { t.Fatal(err) } if rep != 3 { t.Fatalf("not expected: %d", rep) } // Test notification. err = clt.Notify("set", 6) if err != nil { t.Fatal(err) } select { case i := <-number: if i != 6 { t.Fatalf("unexpected number: %d", i) } case <-time.After(time.Second): t.Fatal("did not get notification") } // Test blocked request clt.SetBlocking(true) err = clt.Call("add", Args{1, 2}, &rep) if err != nil { t.Fatal(err) } if rep != 3 { t.Fatalf("not expected: %d", rep) } // Test undefined method. err = clt.Call("foo", 1, &rep) if err.Error() != "rpc2: can't find method foo" { t.Fatal(err) } } golang-github-cenkalti-rpc2-1.0.2/server.go000066400000000000000000000121131456126061400205350ustar00rootroot00000000000000package rpc2 import ( "io" "log" "net" "reflect" "unicode" "unicode/utf8" "github.com/cenkalti/hub" ) // Precompute the reflect type for error. Can't use error directly // because Typeof takes an empty interface value. This is annoying. var typeOfError = reflect.TypeOf((*error)(nil)).Elem() var typeOfClient = reflect.TypeOf((*Client)(nil)) const ( clientConnected hub.Kind = iota clientDisconnected ) // Server responds to RPC requests made by Client. type Server struct { handlers map[string]*handler eventHub *hub.Hub } type handler struct { fn reflect.Value argType reflect.Type replyType reflect.Type } type connectionEvent struct { Client *Client } type disconnectionEvent struct { Client *Client } func (connectionEvent) Kind() hub.Kind { return clientConnected } func (disconnectionEvent) Kind() hub.Kind { return clientDisconnected } // NewServer returns a new Server. func NewServer() *Server { return &Server{ handlers: make(map[string]*handler), eventHub: &hub.Hub{}, } } // Handle registers the handler function for the given method. If a handler already exists for method, Handle panics. func (s *Server) Handle(method string, handlerFunc interface{}) { addHandler(s.handlers, method, handlerFunc) } func addHandler(handlers map[string]*handler, mname string, handlerFunc interface{}) { if _, ok := handlers[mname]; ok { panic("rpc2: multiple registrations for " + mname) } method := reflect.ValueOf(handlerFunc) mtype := method.Type() // Method needs three ins: *client, *args, *reply. if mtype.NumIn() != 3 { log.Panicln("method", mname, "has wrong number of ins:", mtype.NumIn()) } // First arg must be a pointer to rpc2.Client. clientType := mtype.In(0) if clientType.Kind() != reflect.Ptr { log.Panicln("method", mname, "client type not a pointer:", clientType) } if clientType != typeOfClient { log.Panicln("method", mname, "first argument", clientType.String(), "not *rpc2.Client") } // Second arg need not be a pointer. argType := mtype.In(1) if !isExportedOrBuiltinType(argType) { log.Panicln(mname, "argument type not exported:", argType) } // Third arg must be a pointer. replyType := mtype.In(2) if replyType.Kind() != reflect.Ptr { log.Panicln("method", mname, "reply type not a pointer:", replyType) } // Reply type must be exported. if !isExportedOrBuiltinType(replyType) { log.Panicln("method", mname, "reply type not exported:", replyType) } // Method needs one out. if mtype.NumOut() != 1 { log.Panicln("method", mname, "has wrong number of outs:", mtype.NumOut()) } // The return type of the method must be error. if returnType := mtype.Out(0); returnType != typeOfError { log.Panicln("method", mname, "returns", returnType.String(), "not error") } handlers[mname] = &handler{ fn: method, argType: argType, replyType: replyType, } } // Is this type exported or a builtin? func isExportedOrBuiltinType(t reflect.Type) bool { for t.Kind() == reflect.Ptr { t = t.Elem() } // PkgPath will be non-empty even for an exported type, // so we need to check the type name as well. return isExported(t.Name()) || t.PkgPath() == "" } // Is this an exported - upper case - name? func isExported(name string) bool { rune, _ := utf8.DecodeRuneInString(name) return unicode.IsUpper(rune) } // OnConnect registers a function to run when a client connects. func (s *Server) OnConnect(f func(*Client)) { s.eventHub.Subscribe(clientConnected, func(e hub.Event) { go f(e.(connectionEvent).Client) }) } // OnDisconnect registers a function to run when a client disconnects. func (s *Server) OnDisconnect(f func(*Client)) { s.eventHub.Subscribe(clientDisconnected, func(e hub.Event) { go f(e.(disconnectionEvent).Client) }) } // Accept accepts connections on the listener and serves requests // for each incoming connection. Accept blocks; the caller typically // invokes it in a go statement. func (s *Server) Accept(lis net.Listener) { for { conn, err := lis.Accept() if err != nil { log.Print("rpc.Serve: accept:", err.Error()) return } go s.ServeConn(conn) } } // ServeConn runs the server on a single connection. // ServeConn blocks, serving the connection until the client hangs up. // The caller typically invokes ServeConn in a go statement. // ServeConn uses the gob wire format (see package gob) on the // connection. To use an alternate codec, use ServeCodec. func (s *Server) ServeConn(conn io.ReadWriteCloser) { s.ServeCodec(NewGobCodec(conn)) } // ServeCodec is like ServeConn but uses the specified codec to // decode requests and encode responses. func (s *Server) ServeCodec(codec Codec) { s.ServeCodecWithState(codec, NewState()) } // ServeCodecWithState is like ServeCodec but also gives the ability to // associate a state variable with the client that persists across RPC calls. func (s *Server) ServeCodecWithState(codec Codec, state *State) { defer codec.Close() // Client also handles the incoming connections. c := NewClientWithCodec(codec) c.server = true c.handlers = s.handlers c.State = state s.eventHub.Publish(connectionEvent{c}) c.Run() s.eventHub.Publish(disconnectionEvent{c}) } golang-github-cenkalti-rpc2-1.0.2/state.go000066400000000000000000000006351456126061400203550ustar00rootroot00000000000000package rpc2 import "sync" type State struct { store map[string]interface{} m sync.RWMutex } func NewState() *State { return &State{store: make(map[string]interface{})} } func (s *State) Get(key string) (value interface{}, ok bool) { s.m.RLock() value, ok = s.store[key] s.m.RUnlock() return } func (s *State) Set(key string, value interface{}) { s.m.Lock() s.store[key] = value s.m.Unlock() }