// handleSendPostMessage handles performing the passed HTTP request, reading the // result, unmarshalling it, and delivering the unmarhsalled result to the // provided response channel. func (c *Client) handleSendPostMessage(details *sendPostDetails) { // Post the request. cmd := details.command log.Tracef("Sending command [%s] with id %d", cmd.Method(), cmd.Id()) httpResponse, err := c.httpClient.Do(details.request) if err != nil { details.responseChan <- &futureResult{reply: nil, err: err} return } // Read the raw bytes and close the response. respBytes, err := btcjson.GetRaw(httpResponse.Body) if err != nil { details.responseChan <- &futureResult{reply: nil, err: err} return } // Unmarshal the reply into a concrete result if possible. reply, err := btcjson.ReadResultCmd(cmd.Method(), respBytes) if err != nil { details.responseChan <- &futureResult{reply: nil, err: err} return } details.responseChan <- &futureResult{reply: &reply, err: nil} }
// TestReadResultCmd tests that readResultCmd can properly unmarshall the // returned []byte that contains a json reply for both known and unknown // messages. func TestReadResultCmd(t *testing.T) { for i, tt := range resulttests { result, err := btcjson.ReadResultCmd(tt.cmd, tt.msg) if tt.pass { if err != nil { t.Errorf("Should read result: %d %v", i, err) } // Due to the pointer for the Error and other structs, // we can't always guarantee byte for byte comparison. if tt.comp { msg2, err := json.Marshal(result) if err != nil { t.Errorf("Should unmarshal result: %d %v", i, err) } if !bytes.Equal(tt.msg, msg2) { t.Errorf("json byte arrays differ. %d %v %v", i, tt.msg, msg2) } } } else { if err == nil { t.Errorf("Should fail: %d, %s", i, tt.msg) } } } return }
// handleMessage is the main handler for incoming requests. It enforces // authentication, parses the incoming json, looks up and executes handlers // (including pass through for standard RPC commands), sends the appropriate // response. It also detects commands which are marked as long-running and // sends them off to the asyncHander for processing. func (c *Client) handleMessage(msg []byte) { // Attempt to unmarshal the message as a known JSON-RPC command. if cmd, err := btcjson.ParseMarshaledCmd(msg); err == nil { // Commands that have an ID associated with them are not // notifications. Since this is a client, it should not // be receiving non-notifications. if cmd.Id() != nil { // Invalid response log.Warnf("Remote server sent a non-notification "+ "JSON-RPC Request (Id: %v)", cmd.Id()) return } // Deliver the notification. log.Tracef("Received notification [%s]", cmd.Method()) c.handleNotification(cmd) return } // The message was not a command/notification, so it should be a reply // to a previous request. var r btcjson.Reply if err := json.Unmarshal([]byte(msg), &r); err != nil { log.Warnf("Unable to unmarshal inbound message as " + "notification or response") return } // Ensure the reply has an id. if r.Id == nil { log.Warnf("Received response with no id") return } // Ensure the id is the expected type. fid, ok := (*r.Id).(float64) if !ok { log.Warnf("Received unexpected id type: %T (value %v)", *r.Id, *r.Id) return } id := uint64(fid) log.Tracef("Received response for id %d (result %v)", id, r.Result) request := c.removeRequest(id) // Nothing more to do if there is no request associated with this reply. if request == nil || request.responseChan == nil { log.Warnf("Received unexpected reply: %s (id %d)", r.Result, id) return } // Unmarshal the reply into a concrete result if possible and deliver // it to the associated channel. reply, err := btcjson.ReadResultCmd(request.cmd.Method(), []byte(msg)) if err != nil { log.Warnf("Failed to unmarshal reply to command [%s] "+ "(id %d): %v", request.cmd.Method(), id, err) request.responseChan <- &futureResult{reply: nil, err: err} return } // Since the command was successful, examine it to see if it's a // notification, and if is, add it to the notification state so it // can automatically be re-established on reconnect. c.trackRegisteredNtfns(request.cmd) // Deliver the reply. request.responseChan <- &futureResult{reply: &reply, err: nil} }