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 }