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 }