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