| // Copyright 2010 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package jsonrpc implements a JSON-RPC 1.0 ClientCodec and ServerCodec |
| // for the rpc package. |
| // For JSON-RPC 2.0 support, see https://godoc.org/?q=json-rpc+2.0 |
| package jsonrpc |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "io" |
| "net" |
| "net/rpc" |
| "sync" |
| ) |
| |
| type clientCodec struct { |
| dec *json.Decoder // for reading JSON values |
| enc *json.Encoder // for writing JSON values |
| c io.Closer |
| |
| // temporary work space |
| req clientRequest |
| resp clientResponse |
| |
| // JSON-RPC responses include the request id but not the request method. |
| // Package rpc expects both. |
| // We save the request method in pending when sending a request |
| // and then look it up by request ID when filling out the rpc Response. |
| mutex sync.Mutex // protects pending |
| pending map[uint64]string // map request id to method name |
| } |
| |
| // NewClientCodec returns a new rpc.ClientCodec using JSON-RPC on conn. |
| func NewClientCodec(conn io.ReadWriteCloser) rpc.ClientCodec { |
| return &clientCodec{ |
| dec: json.NewDecoder(conn), |
| enc: json.NewEncoder(conn), |
| c: conn, |
| pending: make(map[uint64]string), |
| } |
| } |
| |
| type clientRequest struct { |
| Method string `json:"method"` |
| Params [1]any `json:"params"` |
| Id uint64 `json:"id"` |
| } |
| |
| func (c *clientCodec) WriteRequest(r *rpc.Request, param any) error { |
| c.mutex.Lock() |
| c.pending[r.Seq] = r.ServiceMethod |
| c.mutex.Unlock() |
| c.req.Method = r.ServiceMethod |
| c.req.Params[0] = param |
| c.req.Id = r.Seq |
| return c.enc.Encode(&c.req) |
| } |
| |
| type clientResponse struct { |
| Id uint64 `json:"id"` |
| Result *json.RawMessage `json:"result"` |
| Error any `json:"error"` |
| } |
| |
| func (r *clientResponse) reset() { |
| r.Id = 0 |
| r.Result = nil |
| r.Error = nil |
| } |
| |
| func (c *clientCodec) ReadResponseHeader(r *rpc.Response) error { |
| c.resp.reset() |
| if err := c.dec.Decode(&c.resp); err != nil { |
| return err |
| } |
| |
| c.mutex.Lock() |
| r.ServiceMethod = c.pending[c.resp.Id] |
| delete(c.pending, c.resp.Id) |
| c.mutex.Unlock() |
| |
| r.Error = "" |
| r.Seq = c.resp.Id |
| if c.resp.Error != nil || c.resp.Result == nil { |
| x, ok := c.resp.Error.(string) |
| if !ok { |
| return fmt.Errorf("invalid error %v", c.resp.Error) |
| } |
| if x == "" { |
| x = "unspecified error" |
| } |
| r.Error = x |
| } |
| return nil |
| } |
| |
| func (c *clientCodec) ReadResponseBody(x any) error { |
| if x == nil { |
| return nil |
| } |
| return json.Unmarshal(*c.resp.Result, x) |
| } |
| |
| func (c *clientCodec) Close() error { |
| return c.c.Close() |
| } |
| |
| // NewClient returns a new rpc.Client to handle requests to the |
| // set of services at the other end of the connection. |
| func NewClient(conn io.ReadWriteCloser) *rpc.Client { |
| return rpc.NewClientWithCodec(NewClientCodec(conn)) |
| } |
| |
| // Dial connects to a JSON-RPC server at the specified network address. |
| func Dial(network, address string) (*rpc.Client, error) { |
| conn, err := net.Dial(network, address) |
| if err != nil { |
| return nil, err |
| } |
| return NewClient(conn), err |
| } |