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