github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/rpc/jsoncodec/codec.go (about)

     1  // The jsoncodec package provides a JSON codec for the rpc package.
     2  package jsoncodec
     3  
     4  import (
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"sync"
     9  	"sync/atomic"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  
    14  	"github.com/juju/juju/rpc"
    15  )
    16  
    17  var logger = loggo.GetLogger("juju.rpc.jsoncodec")
    18  
    19  // JSONConn sends and receives messages to an underlying connection
    20  // in JSON format.
    21  type JSONConn interface {
    22  	// Send sends a message.
    23  	Send(msg interface{}) error
    24  	// Receive receives a message into msg.
    25  	Receive(msg interface{}) error
    26  	Close() error
    27  }
    28  
    29  // Codec implements rpc.Codec for a connection.
    30  type Codec struct {
    31  	// msg holds the message that's just been read by ReadHeader, so
    32  	// that the body can be read by ReadBody.
    33  	msg         inMsg
    34  	conn        JSONConn
    35  	logMessages int32
    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  // SetLogging sets whether messages will be logged
    49  // by the codec.
    50  func (c *Codec) SetLogging(on bool) {
    51  	val := int32(0)
    52  	if on {
    53  		val = 1
    54  	}
    55  	atomic.StoreInt32(&c.logMessages, val)
    56  }
    57  
    58  func (c *Codec) isLogging() bool {
    59  	return atomic.LoadInt32(&c.logMessages) != 0
    60  }
    61  
    62  // inMsg holds an incoming message.  We don't know the type of the
    63  // parameters or response yet, so we delay parsing by storing them
    64  // in a RawMessage.
    65  type inMsg struct {
    66  	RequestId uint64
    67  	Type      string
    68  	Version   int
    69  	Id        string
    70  	Request   string
    71  	Params    json.RawMessage
    72  	Error     string
    73  	ErrorCode string
    74  	Response  json.RawMessage
    75  }
    76  
    77  // outMsg holds an outgoing message.
    78  type outMsg 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  func (c *Codec) Close() error {
    91  	c.mu.Lock()
    92  	c.closing = true
    93  	c.mu.Unlock()
    94  	return c.conn.Close()
    95  }
    96  
    97  func (c *Codec) isClosing() bool {
    98  	c.mu.Lock()
    99  	defer c.mu.Unlock()
   100  	return c.closing
   101  }
   102  
   103  func (c *Codec) ReadHeader(hdr *rpc.Header) error {
   104  	c.msg = inMsg{} // avoid any potential cross-message contamination.
   105  	var err error
   106  	if c.isLogging() {
   107  		var m json.RawMessage
   108  		err = c.conn.Receive(&m)
   109  		if err == nil {
   110  			logger.Tracef("<- %s", m)
   111  			err = json.Unmarshal(m, &c.msg)
   112  		} else {
   113  			logger.Tracef("<- error: %v (closing %v)", err, c.isClosing())
   114  		}
   115  	} else {
   116  		err = c.conn.Receive(&c.msg)
   117  	}
   118  	if err != nil {
   119  		// If we've closed the connection, we may get a spurious error,
   120  		// so ignore it.
   121  		if c.isClosing() || err == io.EOF {
   122  			return io.EOF
   123  		}
   124  		return errors.Annotate(err, "error receiving message")
   125  	}
   126  	hdr.RequestId = c.msg.RequestId
   127  	hdr.Request = rpc.Request{
   128  		Type:    c.msg.Type,
   129  		Version: c.msg.Version,
   130  		Id:      c.msg.Id,
   131  		Action:  c.msg.Request,
   132  	}
   133  	hdr.Error = c.msg.Error
   134  	hdr.ErrorCode = c.msg.ErrorCode
   135  	return nil
   136  }
   137  
   138  func (c *Codec) ReadBody(body interface{}, isRequest bool) error {
   139  	if body == nil {
   140  		return nil
   141  	}
   142  	var rawBody json.RawMessage
   143  	if isRequest {
   144  		rawBody = c.msg.Params
   145  	} else {
   146  		rawBody = c.msg.Response
   147  	}
   148  	if len(rawBody) == 0 {
   149  		// If the response or params are omitted, it's
   150  		// equivalent to an empty object.
   151  		return nil
   152  	}
   153  	return json.Unmarshal(rawBody, body)
   154  }
   155  
   156  // DumpRequest returns JSON-formatted data representing
   157  // the RPC message with the given header and body,
   158  // as it would be written by Codec.WriteMessage.
   159  // If the body cannot be marshalled as JSON, the data
   160  // will hold a JSON string describing the error.
   161  func DumpRequest(hdr *rpc.Header, body interface{}) []byte {
   162  	var m outMsg
   163  	m.init(hdr, body)
   164  	data, err := json.Marshal(&m)
   165  	if err != nil {
   166  		return []byte(fmt.Sprintf("%q", "marshal error: "+err.Error()))
   167  	}
   168  	return data
   169  }
   170  
   171  func (c *Codec) WriteMessage(hdr *rpc.Header, body interface{}) error {
   172  	var m outMsg
   173  	m.init(hdr, body)
   174  	if c.isLogging() {
   175  		data, err := json.Marshal(&m)
   176  		if err != nil {
   177  			logger.Tracef("-> marshal error: %v", err)
   178  			return err
   179  		}
   180  		logger.Tracef("-> %s", data)
   181  	}
   182  	return c.conn.Send(&m)
   183  }
   184  
   185  // init fills out the receiving outMsg with information from the given
   186  // header and body.
   187  func (m *outMsg) init(hdr *rpc.Header, body interface{}) {
   188  	m.RequestId = hdr.RequestId
   189  	m.Type = hdr.Request.Type
   190  	m.Version = hdr.Request.Version
   191  	m.Id = hdr.Request.Id
   192  	m.Request = hdr.Request.Action
   193  	m.Error = hdr.Error
   194  	m.ErrorCode = hdr.ErrorCode
   195  	if hdr.IsRequest() {
   196  		m.Params = body
   197  	} else {
   198  		m.Response = body
   199  	}
   200  }