github.com/Finschia/ostracon@v1.1.5/rpc/jsonrpc/server/http_json_handler_test.go (about) 1 package server 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "net/http" 8 "net/http/httptest" 9 "strconv" 10 "strings" 11 "testing" 12 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 16 "github.com/Finschia/ostracon/libs/log" 17 types "github.com/Finschia/ostracon/rpc/jsonrpc/types" 18 ) 19 20 func testMux() *http.ServeMux { 21 funcMap := map[string]*RPCFunc{ 22 "c": NewRPCFunc(func(ctx *types.Context, s string, i int) (string, error) { return "foo", nil }, "s,i"), 23 "block": NewRPCFunc(func(ctx *types.Context, h int) (string, error) { return "block", nil }, "height", Cacheable("height")), 24 } 25 mux := http.NewServeMux() 26 buf := new(bytes.Buffer) 27 logger := log.NewOCLogger(buf) 28 RegisterRPCFuncs(mux, funcMap, logger) 29 30 return mux 31 } 32 33 func statusOK(code int) bool { return code >= 200 && code <= 299 } 34 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 // id not captured in JSON parsing failures 49 {`{"method": "c", "id": "0", "params": a}`, "invalid character", nil}, 50 {`{"method": "c", "id": "0", "params": ["a"]}`, "got 1", types.JSONRPCStringID("0")}, 51 {`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid character", types.JSONRPCStringID("0")}, 52 {`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string", types.JSONRPCStringID("0")}, 53 54 // no ID - notification 55 // {`{"jsonrpc": "2.0", "method": "c", "params": ["a", "10"]}`, false, nil}, 56 57 // good 58 {`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}`, "", types.JSONRPCStringID("0")}, 59 {`{"method": "c", "id": "0", "params": {}}`, "", types.JSONRPCStringID("0")}, 60 {`{"method": "c", "id": "0", "params": ["a", "10"]}`, "", types.JSONRPCStringID("0")}, 61 } 62 63 for i, tt := range tests { 64 req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload)) 65 req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum) 66 rec := httptest.NewRecorder() 67 mux.ServeHTTP(rec, req) 68 res := rec.Result() 69 defer res.Body.Close() 70 // Always expecting back a JSONRPCResponse 71 assert.NotZero(t, res.StatusCode, "#%d: should always return code", i) 72 blob, err := io.ReadAll(res.Body) 73 if err != nil { 74 t.Errorf("#%d: err reading body: %v", i, err) 75 continue 76 } 77 78 recv := new(types.RPCResponse) 79 assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob) 80 assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i) 81 assert.Equal(t, tt.expectedID, recv.ID, "#%d: expected ID not matched in RPCResponse", i) 82 if tt.wantErr == "" { 83 assert.Nil(t, recv.Error, "#%d: not expecting an error", i) 84 } else { 85 assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i) 86 // The wanted error is either in the message or the data 87 assert.Contains(t, recv.Error.Message+recv.Error.Data, tt.wantErr, "#%d: expected substring", i) 88 } 89 } 90 } 91 92 func TestJSONRPCID(t *testing.T) { 93 mux := testMux() 94 tests := []struct { 95 payload string 96 wantErr bool 97 expectedID interface{} 98 }{ 99 // good id 100 {`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": ["a", "10"]}`, false, types.JSONRPCStringID("0")}, 101 {`{"jsonrpc": "2.0", "method": "c", "id": "abc", "params": ["a", "10"]}`, false, types.JSONRPCStringID("abc")}, 102 {`{"jsonrpc": "2.0", "method": "c", "id": 0, "params": ["a", "10"]}`, false, types.JSONRPCIntID(0)}, 103 {`{"jsonrpc": "2.0", "method": "c", "id": 1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)}, 104 {`{"jsonrpc": "2.0", "method": "c", "id": 1.3, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)}, 105 {`{"jsonrpc": "2.0", "method": "c", "id": -1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(-1)}, 106 107 // bad id 108 {`{"jsonrpc": "2.0", "method": "c", "id": {}, "params": ["a", "10"]}`, true, nil}, 109 {`{"jsonrpc": "2.0", "method": "c", "id": [], "params": ["a", "10"]}`, true, nil}, 110 } 111 112 for i, tt := range tests { 113 req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload)) 114 req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum) 115 rec := httptest.NewRecorder() 116 mux.ServeHTTP(rec, req) 117 res := rec.Result() 118 // Always expecting back a JSONRPCResponse 119 assert.NotZero(t, res.StatusCode, "#%d: should always return code", i) 120 blob, err := io.ReadAll(res.Body) 121 if err != nil { 122 t.Errorf("#%d: err reading body: %v", i, err) 123 continue 124 } 125 res.Body.Close() 126 127 recv := new(types.RPCResponse) 128 err = json.Unmarshal(blob, recv) 129 assert.Nil(t, err, "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob) 130 if !tt.wantErr { 131 assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i) 132 assert.Equal(t, tt.expectedID, recv.ID, "#%d: expected ID not matched in RPCResponse", i) 133 assert.Nil(t, recv.Error, "#%d: not expecting an error", i) 134 } else { 135 assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i) 136 } 137 } 138 } 139 140 func TestRPCNotification(t *testing.T) { 141 mux := testMux() 142 body := strings.NewReader(`{"jsonrpc": "2.0"}`) 143 req, _ := http.NewRequest("POST", "http://localhost/", body) 144 req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum) 145 rec := httptest.NewRecorder() 146 mux.ServeHTTP(rec, req) 147 res := rec.Result() 148 149 // Always expecting back a JSONRPCResponse 150 require.True(t, statusOK(res.StatusCode), "should always return 2XX") 151 blob, err := io.ReadAll(res.Body) 152 res.Body.Close() 153 require.Nil(t, err, "reading from the body should not give back an error") 154 require.Equal(t, 0, len(blob), "a notification SHOULD NOT be responded to by the server") 155 } 156 157 func TestRPCNotificationInBatch(t *testing.T) { 158 mux := testMux() 159 tests := []struct { 160 payload string 161 expectCount int 162 }{ 163 { 164 `[ 165 {"jsonrpc": "2.0"}, 166 {"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]} 167 ]`, 168 1, 169 }, 170 { 171 `[ 172 {"jsonrpc": "2.0"}, 173 {"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]}, 174 {"jsonrpc": "2.0"}, 175 {"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]} 176 ]`, 177 2, 178 }, 179 } 180 for i, tt := range tests { 181 req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload)) 182 req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum) 183 rec := httptest.NewRecorder() 184 mux.ServeHTTP(rec, req) 185 res := rec.Result() 186 // Always expecting back a JSONRPCResponse 187 assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i) 188 blob, err := io.ReadAll(res.Body) 189 if err != nil { 190 t.Errorf("#%d: err reading body: %v", i, err) 191 continue 192 } 193 res.Body.Close() 194 195 var responses []types.RPCResponse 196 // try to unmarshal an array first 197 err = json.Unmarshal(blob, &responses) 198 if err != nil { 199 // if we were actually expecting an array, but got an error 200 if tt.expectCount > 1 { 201 t.Errorf("#%d: expected an array, couldn't unmarshal it\nblob: %s", i, blob) 202 continue 203 } else { 204 // we were expecting an error here, so let's unmarshal a single response 205 var response types.RPCResponse 206 err = json.Unmarshal(blob, &response) 207 if err != nil { 208 t.Errorf("#%d: expected successful parsing of an RPCResponse\nblob: %s", i, blob) 209 continue 210 } 211 // have a single-element result 212 responses = []types.RPCResponse{response} 213 } 214 } 215 if tt.expectCount != len(responses) { 216 t.Errorf("#%d: expected %d response(s), but got %d\nblob: %s", i, tt.expectCount, len(responses), blob) 217 continue 218 } 219 for _, response := range responses { 220 assert.NotEqual(t, response, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i) 221 } 222 } 223 } 224 225 func TestTooManyRPCNotificationInBatch_error(t *testing.T) { 226 // prepare the mock batch request 227 var jsonArray []json.RawMessage 228 for i := 0; i < 11; i++ { 229 jsonArray = append(jsonArray, json.RawMessage(TestGoodBody)) 230 } 231 jsonData, err := json.Marshal(jsonArray) 232 if err != nil { 233 t.Errorf("expected an array, couldn't marshal it") 234 } 235 // execute the batch request 236 mux := testMux() 237 req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(string(jsonData))) 238 req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum) 239 rec := httptest.NewRecorder() 240 mux.ServeHTTP(rec, req) 241 res := rec.Result() 242 res.Body.Close() 243 // always expecting back a 400 error 244 assert.Equal(t, http.StatusBadRequest, res.StatusCode, "should always return 400") 245 } 246 247 func TestNoMaxBatchRequestNumField_error(t *testing.T) { 248 // execute the batch request 249 mux := testMux() 250 req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(TestGoodBody)) 251 rec := httptest.NewRecorder() 252 mux.ServeHTTP(rec, req) 253 res := rec.Result() 254 res.Body.Close() 255 // always expecting back a 500 error 256 assert.Equal(t, http.StatusInternalServerError, res.StatusCode, "should always return 500") 257 } 258 259 func TestUnknownRPCPath(t *testing.T) { 260 mux := testMux() 261 req, _ := http.NewRequest("GET", "http://localhost/unknownrpcpath", nil) 262 rec := httptest.NewRecorder() 263 mux.ServeHTTP(rec, req) 264 res := rec.Result() 265 266 // Always expecting back a 404 error 267 require.Equal(t, http.StatusNotFound, res.StatusCode, "should always return 404") 268 res.Body.Close() 269 } 270 271 func TestRPCResponseCache(t *testing.T) { 272 mux := testMux() 273 body := strings.NewReader(`{"jsonrpc": "2.0","method":"block","id": 0, "params": ["1"]}`) 274 req, _ := http.NewRequest("Get", "http://localhost/", body) 275 req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum) 276 rec := httptest.NewRecorder() 277 mux.ServeHTTP(rec, req) 278 res := rec.Result() 279 280 // Always expecting back a JSONRPCResponse 281 require.True(t, statusOK(res.StatusCode), "should always return 2XX") 282 require.Equal(t, "public, max-age=86400", res.Header.Get("Cache-control")) 283 284 _, err := io.ReadAll(res.Body) 285 res.Body.Close() 286 require.Nil(t, err, "reading from the body should not give back an error") 287 288 // send a request with default height. 289 body = strings.NewReader(`{"jsonrpc": "2.0","method":"block","id": 0, "params": ["0"]}`) 290 req, _ = http.NewRequest("Get", "http://localhost/", body) 291 req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum) 292 rec = httptest.NewRecorder() 293 mux.ServeHTTP(rec, req) 294 res = rec.Result() 295 296 // Always expecting back a JSONRPCResponse 297 require.True(t, statusOK(res.StatusCode), "should always return 2XX") 298 require.Equal(t, "", res.Header.Get("Cache-control")) 299 300 _, err = io.ReadAll(res.Body) 301 302 res.Body.Close() 303 require.Nil(t, err, "reading from the body should not give back an error") 304 305 // send a request with default height, but as empty set of parameters. 306 body = strings.NewReader(`{"jsonrpc": "2.0","method":"block","id": 0, "params": []}`) 307 req, _ = http.NewRequest("Get", "http://localhost/", body) 308 req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum) 309 rec = httptest.NewRecorder() 310 mux.ServeHTTP(rec, req) 311 res = rec.Result() 312 313 // Always expecting back a JSONRPCResponse 314 require.True(t, statusOK(res.StatusCode), "should always return 2XX") 315 require.Equal(t, "", res.Header.Get("Cache-control")) 316 317 _, err = io.ReadAll(res.Body) 318 319 res.Body.Close() 320 require.Nil(t, err, "reading from the body should not give back an error") 321 } 322 323 func TestMakeJSONRPCHandler_Unmarshal_WriteRPCResponseHTTPError_error(t *testing.T) { 324 handlerFunc := makeJSONRPCHandler(nil, log.TestingLogger()) 325 // json.Unmarshal error 326 req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader("hoge")) 327 // WriteRPCResponseHTTPError error 328 rec := NewFailedWriteResponseWriter() 329 handlerFunc.ServeHTTP(rec, req) 330 assert.Equal(t, 331 strconv.Itoa(http.StatusInternalServerError), 332 rec.Header().Get(http.StatusText(http.StatusInternalServerError))) 333 } 334 335 func TestMakeJSONRPCHandler_last_WriteRPCResponseHTTP_error(t *testing.T) { 336 handlerFunc := makeJSONRPCHandler(TestFuncMap, log.TestingLogger()) 337 req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody)) 338 req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum) 339 // WriteRPCResponseHTTP error 340 rec := NewFailedWriteResponseWriter() 341 handlerFunc.ServeHTTP(rec, req) 342 assert.Equal(t, 343 strconv.Itoa(http.StatusOK), 344 rec.Header().Get(http.StatusText(http.StatusOK))) 345 }