| // 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 ClientCodec and ServerCodec |
| // for the rpc package. |
| package jsonrpc |
| |
| import ( |
| "fmt" |
| "io" |
| "json" |
| "net" |
| "os" |
| "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 "method" |
| Params [1]interface{} "params" |
| Id uint64 "id" |
| } |
| |
| func (c *clientCodec) WriteRequest(r *rpc.Request, param interface{}) os.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 "id" |
| Result *json.RawMessage "result" |
| Error interface{} "error" |
| } |
| |
| func (r *clientResponse) reset() { |
| r.Id = 0 |
| r.Result = nil |
| r.Error = nil |
| } |
| |
| func (c *clientCodec) ReadResponseHeader(r *rpc.Response) os.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] |
| c.pending[c.resp.Id] = "", false |
| c.mutex.Unlock() |
| |
| r.Error = "" |
| r.Seq = c.resp.Id |
| if c.resp.Error != 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 interface{}) os.Error { |
| return json.Unmarshal(*c.resp.Result, x) |
| } |
| |
| func (c *clientCodec) Close() os.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, os.Error) { |
| conn, err := net.Dial(network, "", address) |
| if err != nil { |
| return nil, err |
| } |
| return NewClient(conn), err |
| } |