mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-15 06:11:20 +00:00
* Fixed accepting integer IDs in requests for Tendermint RPC server (#2366) * added a wrapper interface `jsonrpcid` that represents both string and int IDs in JSON-RPC requests/responses + custom JSON unmarshallers * changed client-side code in RPC that uses it * added extra tests for integer IDs * updated CHANGELOG_PENDING, as suggested by PR instructions * addressed PR comments * added table driven tests for request type marshalling/unmarshalling * expanded handler test to check IDs * changed pending changelog note * changed json rpc request/response unmarshalling to use empty interfaces and type switches on ID * some cleanup
This commit is contained in:
committed by
Ethan Buchman
parent
b487feba42
commit
b12488b5f1
@ -103,7 +103,7 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, cdc *amino.Codec, logger lo
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
WriteRPCResponseHTTP(w, types.RPCInvalidRequestError("", errors.Wrap(err, "Error reading request body")))
|
||||
WriteRPCResponseHTTP(w, types.RPCInvalidRequestError(types.JSONRPCStringID(""), errors.Wrap(err, "Error reading request body")))
|
||||
return
|
||||
}
|
||||
// if its an empty request (like from a browser),
|
||||
@ -116,12 +116,12 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, cdc *amino.Codec, logger lo
|
||||
var request types.RPCRequest
|
||||
err = json.Unmarshal(b, &request)
|
||||
if err != nil {
|
||||
WriteRPCResponseHTTP(w, types.RPCParseError("", errors.Wrap(err, "Error unmarshalling request")))
|
||||
WriteRPCResponseHTTP(w, types.RPCParseError(types.JSONRPCStringID(""), errors.Wrap(err, "Error unmarshalling request")))
|
||||
return
|
||||
}
|
||||
// A Notification is a Request object without an "id" member.
|
||||
// The Server MUST NOT reply to a Notification, including those that are within a batch request.
|
||||
if request.ID == "" {
|
||||
if request.ID == types.JSONRPCStringID("") {
|
||||
logger.Debug("HTTPJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)")
|
||||
return
|
||||
}
|
||||
@ -255,7 +255,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, cdc *amino.Codec, logger log.Logger) func
|
||||
// Exception for websocket endpoints
|
||||
if rpcFunc.ws {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError(""))
|
||||
WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError(types.JSONRPCStringID("")))
|
||||
}
|
||||
}
|
||||
// All other endpoints
|
||||
@ -263,17 +263,17 @@ func makeHTTPHandler(rpcFunc *RPCFunc, cdc *amino.Codec, logger log.Logger) func
|
||||
logger.Debug("HTTP HANDLER", "req", r)
|
||||
args, err := httpParamsToArgs(rpcFunc, cdc, r)
|
||||
if err != nil {
|
||||
WriteRPCResponseHTTP(w, types.RPCInvalidParamsError("", errors.Wrap(err, "Error converting http params to arguments")))
|
||||
WriteRPCResponseHTTP(w, types.RPCInvalidParamsError(types.JSONRPCStringID(""), errors.Wrap(err, "Error converting http params to arguments")))
|
||||
return
|
||||
}
|
||||
returns := rpcFunc.f.Call(args)
|
||||
logger.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns)
|
||||
result, err := unreflectResult(returns)
|
||||
if err != nil {
|
||||
WriteRPCResponseHTTP(w, types.RPCInternalError("", err))
|
||||
WriteRPCResponseHTTP(w, types.RPCInternalError(types.JSONRPCStringID(""), err))
|
||||
return
|
||||
}
|
||||
WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(cdc, "", result))
|
||||
WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(cdc, types.JSONRPCStringID(""), result))
|
||||
}
|
||||
}
|
||||
|
||||
@ -580,7 +580,7 @@ func (wsc *wsConnection) readRoutine() {
|
||||
err = fmt.Errorf("WSJSONRPC: %v", r)
|
||||
}
|
||||
wsc.Logger.Error("Panic in WSJSONRPC handler", "err", err, "stack", string(debug.Stack()))
|
||||
wsc.WriteRPCResponse(types.RPCInternalError("unknown", err))
|
||||
wsc.WriteRPCResponse(types.RPCInternalError(types.JSONRPCStringID("unknown"), err))
|
||||
go wsc.readRoutine()
|
||||
} else {
|
||||
wsc.baseConn.Close() // nolint: errcheck
|
||||
@ -615,13 +615,13 @@ func (wsc *wsConnection) readRoutine() {
|
||||
var request types.RPCRequest
|
||||
err = json.Unmarshal(in, &request)
|
||||
if err != nil {
|
||||
wsc.WriteRPCResponse(types.RPCParseError("", errors.Wrap(err, "Error unmarshaling request")))
|
||||
wsc.WriteRPCResponse(types.RPCParseError(types.JSONRPCStringID(""), errors.Wrap(err, "Error unmarshaling request")))
|
||||
continue
|
||||
}
|
||||
|
||||
// A Notification is a Request object without an "id" member.
|
||||
// The Server MUST NOT reply to a Notification, including those that are within a batch request.
|
||||
if request.ID == "" {
|
||||
if request.ID == types.JSONRPCStringID("") {
|
||||
wsc.Logger.Debug("WSJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)")
|
||||
continue
|
||||
}
|
||||
|
@ -47,21 +47,22 @@ func statusOK(code int) bool { return code >= 200 && code <= 299 }
|
||||
func TestRPCParams(t *testing.T) {
|
||||
mux := testMux()
|
||||
tests := []struct {
|
||||
payload string
|
||||
wantErr string
|
||||
payload string
|
||||
wantErr string
|
||||
expectedId interface{}
|
||||
}{
|
||||
// bad
|
||||
{`{"jsonrpc": "2.0", "id": "0"}`, "Method not found"},
|
||||
{`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method not found"},
|
||||
{`{"method": "c", "id": "0", "params": a}`, "invalid character"},
|
||||
{`{"method": "c", "id": "0", "params": ["a"]}`, "got 1"},
|
||||
{`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid character"},
|
||||
{`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string"},
|
||||
{`{"jsonrpc": "2.0", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")},
|
||||
{`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")},
|
||||
{`{"method": "c", "id": "0", "params": a}`, "invalid character", types.JSONRPCStringID("")}, // id not captured in JSON parsing failures
|
||||
{`{"method": "c", "id": "0", "params": ["a"]}`, "got 1", types.JSONRPCStringID("0")},
|
||||
{`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid character", types.JSONRPCStringID("0")},
|
||||
{`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string", types.JSONRPCStringID("0")},
|
||||
|
||||
// good
|
||||
{`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}`, ""},
|
||||
{`{"method": "c", "id": "0", "params": {}}`, ""},
|
||||
{`{"method": "c", "id": "0", "params": ["a", "10"]}`, ""},
|
||||
{`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}`, "", types.JSONRPCStringID("0")},
|
||||
{`{"method": "c", "id": "0", "params": {}}`, "", types.JSONRPCStringID("0")},
|
||||
{`{"method": "c", "id": "0", "params": ["a", "10"]}`, "", types.JSONRPCStringID("0")},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
@ -80,7 +81,7 @@ func TestRPCParams(t *testing.T) {
|
||||
recv := new(types.RPCResponse)
|
||||
assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob)
|
||||
assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i)
|
||||
|
||||
assert.Equal(t, tt.expectedId, recv.ID, "#%d: expected ID not matched in RPCResponse", i)
|
||||
if tt.wantErr == "" {
|
||||
assert.Nil(t, recv.Error, "#%d: not expecting an error", i)
|
||||
} else {
|
||||
@ -91,9 +92,56 @@ func TestRPCParams(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONRPCID(t *testing.T) {
|
||||
mux := testMux()
|
||||
tests := []struct {
|
||||
payload string
|
||||
wantErr bool
|
||||
expectedId interface{}
|
||||
}{
|
||||
// good id
|
||||
{`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": ["a", "10"]}`, false, types.JSONRPCStringID("0")},
|
||||
{`{"jsonrpc": "2.0", "method": "c", "id": "abc", "params": ["a", "10"]}`, false, types.JSONRPCStringID("abc")},
|
||||
{`{"jsonrpc": "2.0", "method": "c", "id": 0, "params": ["a", "10"]}`, false, types.JSONRPCIntID(0)},
|
||||
{`{"jsonrpc": "2.0", "method": "c", "id": 1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)},
|
||||
{`{"jsonrpc": "2.0", "method": "c", "id": 1.3, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)},
|
||||
{`{"jsonrpc": "2.0", "method": "c", "id": -1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(-1)},
|
||||
{`{"jsonrpc": "2.0", "method": "c", "id": null, "params": ["a", "10"]}`, false, nil},
|
||||
|
||||
// bad id
|
||||
{`{"jsonrpc": "2.0", "method": "c", "id": {}, "params": ["a", "10"]}`, true, nil},
|
||||
{`{"jsonrpc": "2.0", "method": "c", "id": [], "params": ["a", "10"]}`, true, nil},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload))
|
||||
rec := httptest.NewRecorder()
|
||||
mux.ServeHTTP(rec, req)
|
||||
res := rec.Result()
|
||||
// Always expecting back a JSONRPCResponse
|
||||
assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i)
|
||||
blob, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: err reading body: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
recv := new(types.RPCResponse)
|
||||
err = json.Unmarshal(blob, recv)
|
||||
assert.Nil(t, err, "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob)
|
||||
if !tt.wantErr {
|
||||
assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i)
|
||||
assert.Equal(t, tt.expectedId, recv.ID, "#%d: expected ID not matched in RPCResponse", i)
|
||||
assert.Nil(t, recv.Error, "#%d: not expecting an error", i)
|
||||
} else {
|
||||
assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRPCNotification(t *testing.T) {
|
||||
mux := testMux()
|
||||
body := strings.NewReader(`{"jsonrpc": "2.0"}`)
|
||||
body := strings.NewReader(`{"jsonrpc": "2.0", "id": ""}`)
|
||||
req, _ := http.NewRequest("POST", "http://localhost/", body)
|
||||
rec := httptest.NewRecorder()
|
||||
mux.ServeHTTP(rec, req)
|
||||
@ -134,7 +182,7 @@ func TestWebsocketManagerHandler(t *testing.T) {
|
||||
}
|
||||
|
||||
// check basic functionality works
|
||||
req, err := types.MapToRequest(amino.NewCodec(), "TestWebsocketManager", "c", map[string]interface{}{"s": "a", "i": 10})
|
||||
req, err := types.MapToRequest(amino.NewCodec(), types.JSONRPCStringID("TestWebsocketManager"), "c", map[string]interface{}{"s": "a", "i": 10})
|
||||
require.NoError(t, err)
|
||||
err = c.WriteJSON(req)
|
||||
require.NoError(t, err)
|
||||
|
@ -132,7 +132,7 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler
|
||||
"Panic in RPC HTTP handler", "err", e, "stack",
|
||||
string(debug.Stack()),
|
||||
)
|
||||
WriteRPCResponseHTTPError(rww, http.StatusInternalServerError, types.RPCInternalError("", e.(error)))
|
||||
WriteRPCResponseHTTPError(rww, http.StatusInternalServerError, types.RPCInternalError(types.JSONRPCStringID(""), e.(error)))
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user