// TestMiscErrors tests a few error conditions not covered elsewhere. func TestMiscErrors(t *testing.T) { t.Parallel() // Force an error in NewRequest by giving it a parameter type that is // not supported. _, err := btcjson.NewRequest(nil, "test", []interface{}{make(chan int)}) if err == nil { t.Error("NewRequest: did not receive error") return } // Force an error in MarshalResponse by giving it an id type that is not // supported. wantErr := btcjson.Error{ErrorCode: btcjson.ErrInvalidType} _, err = btcjson.MarshalResponse(make(chan int), nil, nil) if jerr, ok := err.(btcjson.Error); !ok || jerr.ErrorCode != wantErr.ErrorCode { t.Errorf("MarshalResult: did not receive expected error - got "+ "%v (%[1]T), want %v (%[2]T)", err, wantErr) return } // Force an error in MarshalResponse by giving it a result type that // can't be marshalled. _, err = btcjson.MarshalResponse(1, make(chan int), nil) if _, ok := err.(*json.UnsupportedTypeError); !ok { wantErr := &json.UnsupportedTypeError{} t.Errorf("MarshalResult: did not receive expected error - got "+ "%v (%[1]T), want %T", err, wantErr) return } }
// This example demonstrates how to marshal a JSON-RPC response. func ExampleMarshalResponse() { // Marshal a new JSON-RPC response. For example, this is a response // to a getblockheight request. marshalledBytes, err := btcjson.MarshalResponse(1, 350001, nil) if err != nil { fmt.Println(err) return } // Display the marshalled response. Ordinarily this would be sent // across the wire to the RPC client, but for this example, just display // it. fmt.Printf("%s\n", marshalledBytes) // Output: // {"result":350001,"error":null,"id":1} }
// TestMarshalResponse ensures the MarshalResponse function works as expected. func TestMarshalResponse(t *testing.T) { t.Parallel() testID := 1 tests := []struct { name string result interface{} jsonErr *btcjson.RPCError expected []byte }{ { name: "ordinary bool result with no error", result: true, jsonErr: nil, expected: []byte(`{"result":true,"error":null,"id":1}`), }, { name: "result with error", result: nil, jsonErr: func() *btcjson.RPCError { return btcjson.NewRPCError(btcjson.ErrRPCBlockNotFound, "123 not found") }(), expected: []byte(`{"result":null,"error":{"code":-5,"message":"123 not found"},"id":1}`), }, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { _, _ = i, test marshalled, err := btcjson.MarshalResponse(testID, test.result, test.jsonErr) if err != nil { t.Errorf("Test #%d (%s) unexpected error: %v", i, test.name, err) continue } if !reflect.DeepEqual(marshalled, test.expected) { t.Errorf("Test #%d (%s) mismatched result - got %s, "+ "want %s", i, test.name, marshalled, test.expected) } } }
// postClientRPC processes and replies to a JSON-RPC client request. func (s *Server) postClientRPC(w http.ResponseWriter, r *http.Request) { body := http.MaxBytesReader(w, r.Body, maxRequestSize) rpcRequest, err := ioutil.ReadAll(body) if err != nil { // TODO: what if the underlying reader errored? http.Error(w, "413 Request Too Large.", http.StatusRequestEntityTooLarge) return } // First check whether wallet has a handler for this request's method. // If unfound, the request is sent to the chain server for further // processing. While checking the methods, disallow authenticate // requests, as they are invalid for HTTP POST clients. var req btcjson.Request err = json.Unmarshal(rpcRequest, &req) if err != nil { resp, err := btcjson.MarshalResponse(req.ID, nil, btcjson.ErrRPCInvalidRequest) if err != nil { log.Errorf("Unable to marshal response: %v", err) http.Error(w, "500 Internal Server Error", http.StatusInternalServerError) return } _, err = w.Write(resp) if err != nil { log.Warnf("Cannot write invalid request request to "+ "client: %v", err) } return } // Create the response and error from the request. Two special cases // are handled for the authenticate and stop request methods. var res interface{} var jsonErr *btcjson.RPCError var stop bool switch req.Method { case "authenticate": // Drop it. return case "stop": stop = true res = "btcwallet stopping" default: res, jsonErr = s.handlerClosure(&req)() } // Marshal and send. mresp, err := btcjson.MarshalResponse(req.ID, res, jsonErr) if err != nil { log.Errorf("Unable to marshal response: %v", err) http.Error(w, "500 Internal Server Error", http.StatusInternalServerError) return } _, err = w.Write(mresp) if err != nil { log.Warnf("Unable to respond to client: %v", err) } if stop { s.requestProcessShutdown() } }
func (s *Server) websocketClientRespond(wsc *websocketClient) { // A for-select with a read of the quit channel is used instead of a // for-range to provide clean shutdown. This is necessary due to // WebsocketClientRead (which sends to the allRequests chan) not closing // allRequests during shutdown if the remote websocket client is still // connected. out: for { select { case reqBytes, ok := <-wsc.allRequests: if !ok { // client disconnected break out } var req btcjson.Request err := json.Unmarshal(reqBytes, &req) if err != nil { if !wsc.authenticated { // Disconnect immediately. break out } resp := makeResponse(req.ID, nil, btcjson.ErrRPCInvalidRequest) mresp, err := json.Marshal(resp) // We expect the marshal to succeed. If it // doesn't, it indicates some non-marshalable // type in the response. if err != nil { panic(err) } err = wsc.send(mresp) if err != nil { break out } continue } if req.Method == "authenticate" { if wsc.authenticated || s.invalidAuth(&req) { // Disconnect immediately. break out } wsc.authenticated = true resp := makeResponse(req.ID, nil, nil) // Expected to never fail. mresp, err := json.Marshal(resp) if err != nil { panic(err) } err = wsc.send(mresp) if err != nil { break out } continue } if !wsc.authenticated { // Disconnect immediately. break out } switch req.Method { case "stop": resp := makeResponse(req.ID, "btcwallet stopping.", nil) mresp, err := json.Marshal(resp) // Expected to never fail. if err != nil { panic(err) } err = wsc.send(mresp) if err != nil { break out } s.requestProcessShutdown() break default: req := req // Copy for the closure f := s.handlerClosure(&req) wsc.wg.Add(1) go func() { resp, jsonErr := f() mresp, err := btcjson.MarshalResponse(req.ID, resp, jsonErr) if err != nil { log.Errorf("Unable to marshal response: %v", err) } else { _ = wsc.send(mresp) } wsc.wg.Done() }() } case <-s.quit: break out } } // allow client to disconnect after all handler goroutines are done wsc.wg.Wait() close(wsc.responses) s.wg.Done() }