github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/rpc/jsoncodec/codec.go (about)

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