| // 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 `json:"method"` | |
| Params [1]interface{} `json:"params"` | |
| Id uint64 `json:"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 `json:"id"` | |
| Result *json.RawMessage `json:"result"` | |
| Error interface{} `json:"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 { | |
| if x == nil { | |
| return nil | |
| } | |
| 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 | |
| } |