• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2010 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package jsonrpc implements a JSON-RPC 1.0 ClientCodec and ServerCodec
6// for the rpc package.
7// For JSON-RPC 2.0 support, see https://godoc.org/?q=json-rpc+2.0
8package jsonrpc
9
10import (
11	"encoding/json"
12	"fmt"
13	"io"
14	"net"
15	"net/rpc"
16	"sync"
17)
18
19type clientCodec struct {
20	dec *json.Decoder // for reading JSON values
21	enc *json.Encoder // for writing JSON values
22	c   io.Closer
23
24	// temporary work space
25	req  clientRequest
26	resp clientResponse
27
28	// JSON-RPC responses include the request id but not the request method.
29	// Package rpc expects both.
30	// We save the request method in pending when sending a request
31	// and then look it up by request ID when filling out the rpc Response.
32	mutex   sync.Mutex        // protects pending
33	pending map[uint64]string // map request id to method name
34}
35
36// NewClientCodec returns a new [rpc.ClientCodec] using JSON-RPC on conn.
37func NewClientCodec(conn io.ReadWriteCloser) rpc.ClientCodec {
38	return &clientCodec{
39		dec:     json.NewDecoder(conn),
40		enc:     json.NewEncoder(conn),
41		c:       conn,
42		pending: make(map[uint64]string),
43	}
44}
45
46type clientRequest struct {
47	Method string `json:"method"`
48	Params [1]any `json:"params"`
49	Id     uint64 `json:"id"`
50}
51
52func (c *clientCodec) WriteRequest(r *rpc.Request, param any) error {
53	c.mutex.Lock()
54	c.pending[r.Seq] = r.ServiceMethod
55	c.mutex.Unlock()
56	c.req.Method = r.ServiceMethod
57	c.req.Params[0] = param
58	c.req.Id = r.Seq
59	return c.enc.Encode(&c.req)
60}
61
62type clientResponse struct {
63	Id     uint64           `json:"id"`
64	Result *json.RawMessage `json:"result"`
65	Error  any              `json:"error"`
66}
67
68func (r *clientResponse) reset() {
69	r.Id = 0
70	r.Result = nil
71	r.Error = nil
72}
73
74func (c *clientCodec) ReadResponseHeader(r *rpc.Response) error {
75	c.resp.reset()
76	if err := c.dec.Decode(&c.resp); err != nil {
77		return err
78	}
79
80	c.mutex.Lock()
81	r.ServiceMethod = c.pending[c.resp.Id]
82	delete(c.pending, c.resp.Id)
83	c.mutex.Unlock()
84
85	r.Error = ""
86	r.Seq = c.resp.Id
87	if c.resp.Error != nil || c.resp.Result == nil {
88		x, ok := c.resp.Error.(string)
89		if !ok {
90			return fmt.Errorf("invalid error %v", c.resp.Error)
91		}
92		if x == "" {
93			x = "unspecified error"
94		}
95		r.Error = x
96	}
97	return nil
98}
99
100func (c *clientCodec) ReadResponseBody(x any) error {
101	if x == nil {
102		return nil
103	}
104	return json.Unmarshal(*c.resp.Result, x)
105}
106
107func (c *clientCodec) Close() error {
108	return c.c.Close()
109}
110
111// NewClient returns a new [rpc.Client] to handle requests to the
112// set of services at the other end of the connection.
113func NewClient(conn io.ReadWriteCloser) *rpc.Client {
114	return rpc.NewClientWithCodec(NewClientCodec(conn))
115}
116
117// Dial connects to a JSON-RPC server at the specified network address.
118func Dial(network, address string) (*rpc.Client, error) {
119	conn, err := net.Dial(network, address)
120	if err != nil {
121		return nil, err
122	}
123	return NewClient(conn), err
124}
125