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 }