// 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}
}
Beispiel #2
0
// 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}
}