github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/rpc/jsoncodec/codec.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package jsoncodec
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"sync"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  
    15  	"github.com/juju/juju/rpc"
    16  )
    17  
    18  var logger = loggo.GetLogger("juju.rpc.jsoncodec")
    19  
    20  // JSONConn sends and receives messages to an underlying connection
    21  // in JSON format.
    22  type JSONConn interface {
    23  	// Send sends a message.
    24  	Send(msg interface{}) error
    25  	// Receive receives a message into msg.
    26  	Receive(msg interface{}) error
    27  	Close() error
    28  }
    29  
    30  // Codec implements rpc.Codec for a connection.
    31  type Codec struct {
    32  	// msg holds the message that's just been read by ReadHeader, so
    33  	// that the body can be read by ReadBody.
    34  	msg     inMsgV1
    35  	conn    JSONConn
    36  	mu      sync.Mutex
    37  	closing bool
    38  }
    39  
    40  // New returns an rpc codec that uses conn to send and receive
    41  // messages.
    42  func New(conn JSONConn) *Codec {
    43  	return &Codec{
    44  		conn: conn,
    45  	}
    46  }
    47  
    48  // inMsg holds an incoming message.  We don't know the type of the
    49  // parameters or response yet, so we delay parsing by storing them
    50  // in a RawMessage.
    51  type inMsgV0 struct {
    52  	RequestId uint64
    53  	Type      string
    54  	Version   int
    55  	Id        string
    56  	Request   string
    57  	Params    json.RawMessage
    58  	Error     string
    59  	ErrorCode string
    60  	Response  json.RawMessage
    61  }
    62  
    63  type inMsgV1 struct {
    64  	RequestId uint64                 `json:"request-id"`
    65  	Type      string                 `json:"type"`
    66  	Version   int                    `json:"version"`
    67  	Id        string                 `json:"id"`
    68  	Request   string                 `json:"request"`
    69  	Params    json.RawMessage        `json:"params"`
    70  	Error     string                 `json:"error"`
    71  	ErrorCode string                 `json:"error-code"`
    72  	ErrorInfo map[string]interface{} `json:"error-info"`
    73  	Response  json.RawMessage        `json:"response"`
    74  }
    75  
    76  // outMsg holds an outgoing message.
    77  type outMsgV0 struct {
    78  	RequestId uint64
    79  	Type      string      `json:",omitempty"`
    80  	Version   int         `json:",omitempty"`
    81  	Id        string      `json:",omitempty"`
    82  	Request   string      `json:",omitempty"`
    83  	Params    interface{} `json:",omitempty"`
    84  	Error     string      `json:",omitempty"`
    85  	ErrorCode string      `json:",omitempty"`
    86  	Response  interface{} `json:",omitempty"`
    87  }
    88  
    89  type outMsgV1 struct {
    90  	RequestId uint64                 `json:"request-id,omitempty"`
    91  	Type      string                 `json:"type,omitempty"`
    92  	Version   int                    `json:"version,omitempty"`
    93  	Id        string                 `json:"id,omitempty"`
    94  	Request   string                 `json:"request,omitempty"`
    95  	Params    interface{}            `json:"params,omitempty"`
    96  	Error     string                 `json:"error,omitempty"`
    97  	ErrorCode string                 `json:"error-code,omitempty"`
    98  	ErrorInfo map[string]interface{} `json:"error-info,omitempty"`
    99  	Response  interface{}            `json:"response,omitempty"`
   100  }
   101  
   102  func (c *Codec) Close() error {
   103  	c.mu.Lock()
   104  	c.closing = true
   105  	c.mu.Unlock()
   106  	return c.conn.Close()
   107  }
   108  
   109  func (c *Codec) isClosing() bool {
   110  	c.mu.Lock()
   111  	defer c.mu.Unlock()
   112  	return c.closing
   113  }
   114  
   115  func (c *Codec) ReadHeader(hdr *rpc.Header) error {
   116  	var m json.RawMessage
   117  	var version int
   118  	err := c.conn.Receive(&m)
   119  	if err == nil {
   120  		logger.Tracef("<- %s", m)
   121  		c.msg, version, err = c.readMessage(m)
   122  	} else {
   123  		logger.Tracef("<- error: %v (closing %v)", err, c.isClosing())
   124  	}
   125  	if err != nil {
   126  		// If we've closed the connection, we may get a spurious error,
   127  		// so ignore it.
   128  		if c.isClosing() || err == io.EOF {
   129  			return io.EOF
   130  		}
   131  		return errors.Annotate(err, "error receiving message")
   132  	}
   133  	hdr.RequestId = c.msg.RequestId
   134  	hdr.Request = rpc.Request{
   135  		Type:    c.msg.Type,
   136  		Version: c.msg.Version,
   137  		Id:      c.msg.Id,
   138  		Action:  c.msg.Request,
   139  	}
   140  	hdr.Error = c.msg.Error
   141  	hdr.ErrorCode = c.msg.ErrorCode
   142  	hdr.ErrorInfo = c.msg.ErrorInfo
   143  	hdr.Version = version
   144  	return nil
   145  }
   146  
   147  func (c *Codec) readMessage(m json.RawMessage) (inMsgV1, int, error) {
   148  	var msg inMsgV1
   149  	if err := json.Unmarshal(m, &msg); err != nil {
   150  		return msg, -1, errors.Trace(err)
   151  	}
   152  	// In order to support both new style tags (lowercase) and the old style tags (camelcase)
   153  	// we look at the request id. The request id is always greater than one. If the value is
   154  	// zero, it means that there wasn't a match for the "request-id" tag. This most likely
   155  	// means that it was "RequestId" which was from the old style.
   156  	if msg.RequestId == 0 {
   157  		return c.readV0Message(m)
   158  	}
   159  	return msg, 1, nil
   160  }
   161  
   162  func (c *Codec) readV0Message(m json.RawMessage) (inMsgV1, int, error) {
   163  	var msg inMsgV0
   164  	if err := json.Unmarshal(m, &msg); err != nil {
   165  		return inMsgV1{}, -1, errors.Trace(err)
   166  	}
   167  	return inMsgV1{
   168  		RequestId: msg.RequestId,
   169  		Type:      msg.Type,
   170  		Version:   msg.Version,
   171  		Id:        msg.Id,
   172  		Request:   msg.Request,
   173  		Params:    msg.Params,
   174  		Error:     msg.Error,
   175  		ErrorCode: msg.ErrorCode,
   176  		Response:  msg.Response,
   177  	}, 0, nil
   178  }
   179  
   180  func (c *Codec) ReadBody(body interface{}, isRequest bool) error {
   181  	if body == nil {
   182  		return nil
   183  	}
   184  	var rawBody json.RawMessage
   185  	if isRequest {
   186  		rawBody = c.msg.Params
   187  	} else {
   188  		rawBody = c.msg.Response
   189  	}
   190  	if len(rawBody) == 0 {
   191  		// If the response or params are omitted, it's
   192  		// equivalent to an empty object.
   193  		return nil
   194  	}
   195  	return json.Unmarshal(rawBody, body)
   196  }
   197  
   198  // DumpRequest returns JSON-formatted data representing
   199  // the RPC message with the given header and body,
   200  // as it would be written by Codec.WriteMessage.
   201  // If the body cannot be marshalled as JSON, the data
   202  // will hold a JSON string describing the error.
   203  func DumpRequest(hdr *rpc.Header, body interface{}) []byte {
   204  	msg, err := response(hdr, body)
   205  	if err != nil {
   206  		return []byte(fmt.Sprintf("%q", err.Error()))
   207  	}
   208  	data, err := json.Marshal(msg)
   209  	if err != nil {
   210  		return []byte(fmt.Sprintf("%q", "marshal error: "+err.Error()))
   211  	}
   212  	return data
   213  }
   214  
   215  func (c *Codec) WriteMessage(hdr *rpc.Header, body interface{}) error {
   216  	msg, err := response(hdr, body)
   217  	if err != nil {
   218  		return errors.Trace(err)
   219  	}
   220  	if logger.IsTraceEnabled() {
   221  		data, err := json.Marshal(msg)
   222  		if err != nil {
   223  			logger.Tracef("-> marshal error: %v", err)
   224  			return err
   225  		}
   226  		logger.Tracef("-> %s", data)
   227  	}
   228  	return c.conn.Send(msg)
   229  }
   230  
   231  func response(hdr *rpc.Header, body interface{}) (interface{}, error) {
   232  	switch hdr.Version {
   233  	case 0:
   234  		return newOutMsgV0(hdr, body), nil
   235  	case 1:
   236  		return newOutMsgV1(hdr, body), nil
   237  	default:
   238  		return nil, errors.Errorf("unsupported version %d", hdr.Version)
   239  	}
   240  }
   241  
   242  // newOutMsgV0 fills out a outMsgV0 with information from the given
   243  // header and body.
   244  func newOutMsgV0(hdr *rpc.Header, body interface{}) outMsgV0 {
   245  	result := outMsgV0{
   246  		RequestId: hdr.RequestId,
   247  		Type:      hdr.Request.Type,
   248  		Version:   hdr.Request.Version,
   249  		Id:        hdr.Request.Id,
   250  		Request:   hdr.Request.Action,
   251  		Error:     hdr.Error,
   252  		ErrorCode: hdr.ErrorCode,
   253  	}
   254  	if hdr.IsRequest() {
   255  		result.Params = body
   256  	} else {
   257  		result.Response = body
   258  	}
   259  	return result
   260  }
   261  
   262  // newOutMsgV1 fills out a outMsgV1 with information from the given header and
   263  // body. This might look a lot like the v0 method, and that is because it is.
   264  // However, since Go determines structs to be sufficiently different if the
   265  // tags are different, we can't use the same code. Theoretically we could use
   266  // reflect, but no.
   267  func newOutMsgV1(hdr *rpc.Header, body interface{}) outMsgV1 {
   268  	result := outMsgV1{
   269  		RequestId: hdr.RequestId,
   270  		Type:      hdr.Request.Type,
   271  		Version:   hdr.Request.Version,
   272  		Id:        hdr.Request.Id,
   273  		Request:   hdr.Request.Action,
   274  		Error:     hdr.Error,
   275  		ErrorCode: hdr.ErrorCode,
   276  		ErrorInfo: hdr.ErrorInfo,
   277  	}
   278  	if hdr.IsRequest() {
   279  		result.Params = body
   280  	} else {
   281  		result.Response = body
   282  	}
   283  	return result
   284  }