You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

207 lines
7.5 KiB

  1. package rpcserver_test
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "io/ioutil"
  6. "net/http"
  7. "net/http/httptest"
  8. "strings"
  9. "testing"
  10. "github.com/gorilla/websocket"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/stretchr/testify/require"
  13. amino "github.com/tendermint/go-amino"
  14. "github.com/tendermint/tendermint/libs/log"
  15. rs "github.com/tendermint/tendermint/rpc/lib/server"
  16. types "github.com/tendermint/tendermint/rpc/lib/types"
  17. )
  18. //////////////////////////////////////////////////////////////////////////////
  19. // HTTP REST API
  20. // TODO
  21. //////////////////////////////////////////////////////////////////////////////
  22. // JSON-RPC over HTTP
  23. func testMux() *http.ServeMux {
  24. funcMap := map[string]*rs.RPCFunc{
  25. "c": rs.NewRPCFunc(func(s string, i int) (string, error) { return "foo", nil }, "s,i"),
  26. }
  27. cdc := amino.NewCodec()
  28. mux := http.NewServeMux()
  29. buf := new(bytes.Buffer)
  30. logger := log.NewTMLogger(buf)
  31. rs.RegisterRPCFuncs(mux, funcMap, cdc, logger)
  32. return mux
  33. }
  34. func statusOK(code int) bool { return code >= 200 && code <= 299 }
  35. // Ensure that nefarious/unintended inputs to `params`
  36. // do not crash our RPC handlers.
  37. // See Issue https://github.com/tendermint/tendermint/issues/708.
  38. func TestRPCParams(t *testing.T) {
  39. mux := testMux()
  40. tests := []struct {
  41. payload string
  42. wantErr string
  43. expectedId interface{}
  44. }{
  45. // bad
  46. {`{"jsonrpc": "2.0", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")},
  47. {`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")},
  48. {`{"method": "c", "id": "0", "params": a}`, "invalid character", types.JSONRPCStringID("")}, // id not captured in JSON parsing failures
  49. {`{"method": "c", "id": "0", "params": ["a"]}`, "got 1", types.JSONRPCStringID("0")},
  50. {`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid character", types.JSONRPCStringID("0")},
  51. {`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string", types.JSONRPCStringID("0")},
  52. // good
  53. {`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}`, "", types.JSONRPCStringID("0")},
  54. {`{"method": "c", "id": "0", "params": {}}`, "", types.JSONRPCStringID("0")},
  55. {`{"method": "c", "id": "0", "params": ["a", "10"]}`, "", types.JSONRPCStringID("0")},
  56. }
  57. for i, tt := range tests {
  58. req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload))
  59. rec := httptest.NewRecorder()
  60. mux.ServeHTTP(rec, req)
  61. res := rec.Result()
  62. // Always expecting back a JSONRPCResponse
  63. assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i)
  64. blob, err := ioutil.ReadAll(res.Body)
  65. if err != nil {
  66. t.Errorf("#%d: err reading body: %v", i, err)
  67. continue
  68. }
  69. recv := new(types.RPCResponse)
  70. assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob)
  71. assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i)
  72. assert.Equal(t, tt.expectedId, recv.ID, "#%d: expected ID not matched in RPCResponse", i)
  73. if tt.wantErr == "" {
  74. assert.Nil(t, recv.Error, "#%d: not expecting an error", i)
  75. } else {
  76. assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i)
  77. // The wanted error is either in the message or the data
  78. assert.Contains(t, recv.Error.Message+recv.Error.Data, tt.wantErr, "#%d: expected substring", i)
  79. }
  80. }
  81. }
  82. func TestJSONRPCID(t *testing.T) {
  83. mux := testMux()
  84. tests := []struct {
  85. payload string
  86. wantErr bool
  87. expectedId interface{}
  88. }{
  89. // good id
  90. {`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": ["a", "10"]}`, false, types.JSONRPCStringID("0")},
  91. {`{"jsonrpc": "2.0", "method": "c", "id": "abc", "params": ["a", "10"]}`, false, types.JSONRPCStringID("abc")},
  92. {`{"jsonrpc": "2.0", "method": "c", "id": 0, "params": ["a", "10"]}`, false, types.JSONRPCIntID(0)},
  93. {`{"jsonrpc": "2.0", "method": "c", "id": 1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)},
  94. {`{"jsonrpc": "2.0", "method": "c", "id": 1.3, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)},
  95. {`{"jsonrpc": "2.0", "method": "c", "id": -1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(-1)},
  96. {`{"jsonrpc": "2.0", "method": "c", "id": null, "params": ["a", "10"]}`, false, nil},
  97. // bad id
  98. {`{"jsonrpc": "2.0", "method": "c", "id": {}, "params": ["a", "10"]}`, true, nil},
  99. {`{"jsonrpc": "2.0", "method": "c", "id": [], "params": ["a", "10"]}`, true, nil},
  100. }
  101. for i, tt := range tests {
  102. req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload))
  103. rec := httptest.NewRecorder()
  104. mux.ServeHTTP(rec, req)
  105. res := rec.Result()
  106. // Always expecting back a JSONRPCResponse
  107. assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i)
  108. blob, err := ioutil.ReadAll(res.Body)
  109. if err != nil {
  110. t.Errorf("#%d: err reading body: %v", i, err)
  111. continue
  112. }
  113. recv := new(types.RPCResponse)
  114. err = json.Unmarshal(blob, recv)
  115. assert.Nil(t, err, "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob)
  116. if !tt.wantErr {
  117. assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i)
  118. assert.Equal(t, tt.expectedId, recv.ID, "#%d: expected ID not matched in RPCResponse", i)
  119. assert.Nil(t, recv.Error, "#%d: not expecting an error", i)
  120. } else {
  121. assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i)
  122. }
  123. }
  124. }
  125. func TestRPCNotification(t *testing.T) {
  126. mux := testMux()
  127. body := strings.NewReader(`{"jsonrpc": "2.0", "id": ""}`)
  128. req, _ := http.NewRequest("POST", "http://localhost/", body)
  129. rec := httptest.NewRecorder()
  130. mux.ServeHTTP(rec, req)
  131. res := rec.Result()
  132. // Always expecting back a JSONRPCResponse
  133. require.True(t, statusOK(res.StatusCode), "should always return 2XX")
  134. blob, err := ioutil.ReadAll(res.Body)
  135. require.Nil(t, err, "reading from the body should not give back an error")
  136. require.Equal(t, len(blob), 0, "a notification SHOULD NOT be responded to by the server")
  137. }
  138. func TestUnknownRPCPath(t *testing.T) {
  139. mux := testMux()
  140. req, _ := http.NewRequest("GET", "http://localhost/unknownrpcpath", nil)
  141. rec := httptest.NewRecorder()
  142. mux.ServeHTTP(rec, req)
  143. res := rec.Result()
  144. // Always expecting back a 404 error
  145. require.Equal(t, http.StatusNotFound, res.StatusCode, "should always return 404")
  146. }
  147. //////////////////////////////////////////////////////////////////////////////
  148. // JSON-RPC over WEBSOCKETS
  149. func TestWebsocketManagerHandler(t *testing.T) {
  150. s := newWSServer()
  151. defer s.Close()
  152. // check upgrader works
  153. d := websocket.Dialer{}
  154. c, dialResp, err := d.Dial("ws://"+s.Listener.Addr().String()+"/websocket", nil)
  155. require.NoError(t, err)
  156. if got, want := dialResp.StatusCode, http.StatusSwitchingProtocols; got != want {
  157. t.Errorf("dialResp.StatusCode = %q, want %q", got, want)
  158. }
  159. // check basic functionality works
  160. req, err := types.MapToRequest(amino.NewCodec(), types.JSONRPCStringID("TestWebsocketManager"), "c", map[string]interface{}{"s": "a", "i": 10})
  161. require.NoError(t, err)
  162. err = c.WriteJSON(req)
  163. require.NoError(t, err)
  164. var resp types.RPCResponse
  165. err = c.ReadJSON(&resp)
  166. require.NoError(t, err)
  167. require.Nil(t, resp.Error)
  168. }
  169. func newWSServer() *httptest.Server {
  170. funcMap := map[string]*rs.RPCFunc{
  171. "c": rs.NewWSRPCFunc(func(wsCtx types.WSRPCContext, s string, i int) (string, error) { return "foo", nil }, "s,i"),
  172. }
  173. wm := rs.NewWebsocketManager(funcMap, amino.NewCodec())
  174. wm.SetLogger(log.TestingLogger())
  175. mux := http.NewServeMux()
  176. mux.HandleFunc("/websocket", wm.WebsocketHandler)
  177. return httptest.NewServer(mux)
  178. }