github.com/Finschia/ostracon@v1.1.5/rpc/jsonrpc/server/http_server_test.go (about) 1 package server 2 3 import ( 4 "crypto/tls" 5 "errors" 6 "fmt" 7 "io" 8 "net" 9 "net/http" 10 "net/http/httptest" 11 "strconv" 12 "strings" 13 "sync" 14 "sync/atomic" 15 "testing" 16 "time" 17 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 21 "github.com/Finschia/ostracon/libs/log" 22 types "github.com/Finschia/ostracon/rpc/jsonrpc/types" 23 ) 24 25 type sampleResult struct { 26 Value string `json:"value"` 27 } 28 29 func TestMaxOpenConnections(t *testing.T) { 30 const max = 5 // max simultaneous connections 31 32 // Start the server. 33 var open int32 34 mux := http.NewServeMux() 35 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 36 if n := atomic.AddInt32(&open, 1); n > int32(max) { 37 t.Errorf("%d open connections, want <= %d", n, max) 38 } 39 defer atomic.AddInt32(&open, -1) 40 time.Sleep(10 * time.Millisecond) 41 fmt.Fprint(w, "some body") 42 }) 43 config := DefaultConfig() 44 config.MaxOpenConnections = max 45 l, err := Listen("tcp://127.0.0.1:0", config) 46 require.NoError(t, err) 47 defer l.Close() 48 go Serve(l, mux, log.TestingLogger(), config) //nolint:errcheck // ignore for tests 49 50 // Make N GET calls to the server. 51 attempts := max * 2 52 var wg sync.WaitGroup 53 var failed int32 54 for i := 0; i < attempts; i++ { 55 wg.Add(1) 56 go func() { 57 defer wg.Done() 58 c := http.Client{Timeout: 3 * time.Second} 59 r, err := c.Get("http://" + l.Addr().String()) 60 if err != nil { 61 atomic.AddInt32(&failed, 1) 62 return 63 } 64 defer r.Body.Close() 65 }() 66 } 67 wg.Wait() 68 69 // We expect some Gets to fail as the server's accept queue is filled, 70 // but most should succeed. 71 if int(failed) >= attempts/2 { 72 t.Errorf("%d requests failed within %d attempts", failed, attempts) 73 } 74 } 75 76 func TestServeTLS(t *testing.T) { 77 ln, err := net.Listen("tcp", "localhost:0") 78 require.NoError(t, err) 79 defer ln.Close() 80 81 mux := http.NewServeMux() 82 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 83 fmt.Fprint(w, "some body") 84 }) 85 86 chErr := make(chan error, 1) 87 go func() { 88 // FIXME This goroutine leaks 89 chErr <- ServeTLS(ln, mux, "test.crt", "test.key", log.TestingLogger(), DefaultConfig()) 90 }() 91 92 select { 93 case err := <-chErr: 94 require.NoError(t, err) 95 case <-time.After(100 * time.Millisecond): 96 } 97 98 tr := &http.Transport{ 99 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 100 } 101 c := &http.Client{Transport: tr} 102 res, err := c.Get("https://" + ln.Addr().String()) 103 require.NoError(t, err) 104 defer res.Body.Close() 105 assert.Equal(t, http.StatusOK, res.StatusCode) 106 107 body, err := io.ReadAll(res.Body) 108 require.NoError(t, err) 109 assert.Equal(t, []byte("some body"), body) 110 } 111 112 func TestWriteRPCResponseHTTP(t *testing.T) { 113 id := types.JSONRPCIntID(-1) 114 115 // one argument 116 w := httptest.NewRecorder() 117 err := WriteCacheableRPCResponseHTTP(w, types.NewRPCSuccessResponse(id, &sampleResult{"hello"})) 118 require.NoError(t, err) 119 resp := w.Result() 120 body, err := io.ReadAll(resp.Body) 121 _ = resp.Body.Close() 122 require.NoError(t, err) 123 assert.Equal(t, 200, resp.StatusCode) 124 assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) 125 assert.Equal(t, "public, max-age=86400", resp.Header.Get("Cache-control")) 126 assert.Equal(t, `{"jsonrpc":"2.0","id":-1,"result":{"value":"hello"}}`, string(body)) 127 128 // multiple arguments 129 w = httptest.NewRecorder() 130 err = WriteRPCResponseHTTP(w, 131 types.NewRPCSuccessResponse(id, &sampleResult{"hello"}), 132 types.NewRPCSuccessResponse(id, &sampleResult{"world"})) 133 require.NoError(t, err) 134 resp = w.Result() 135 body, err = io.ReadAll(resp.Body) 136 _ = resp.Body.Close() 137 require.NoError(t, err) 138 139 assert.Equal(t, 200, resp.StatusCode) 140 assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) 141 assert.Equal(t, `[{"jsonrpc":"2.0","id":-1,"result":{"value":"hello"}},{"jsonrpc":"2.0","id":-1,"result":{"value":"world"}}]`, string(body)) 142 } 143 144 // This function is to test whether the max_requeset_batch_request is work in HTTP and TLS 145 func TestMaxBatchRequestHandler(t *testing.T) { 146 config := DefaultConfig() 147 config.MaxBatchRequestNum = 20 148 149 mux := http.NewServeMux() 150 var capturedRequest *http.Request 151 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 152 capturedRequest = r 153 }) 154 155 tests := []struct { 156 ListenFunc func() (net.Listener, error) 157 Server func(l net.Listener) error 158 PostCall func(l net.Listener) (*http.Response, error) 159 }{ 160 { 161 ListenFunc: func() (net.Listener, error) { return Listen("tcp://127.0.0.1:0", config) }, 162 Server: func(l net.Listener) error { return Serve(l, mux, log.TestingLogger(), config) }, 163 PostCall: func(l net.Listener) (*http.Response, error) { 164 c := http.Client{Timeout: 3 * time.Second} 165 res, err := c.Post("http://"+l.Addr().String(), "application/json", strings.NewReader(TestGoodBody)) 166 return res, err 167 }, 168 }, 169 { 170 ListenFunc: func() (net.Listener, error) { return net.Listen("tcp", "localhost:0") }, 171 Server: func(l net.Listener) error { 172 return ServeTLS(l, mux, "test.crt", "test.key", log.TestingLogger(), config) 173 }, 174 PostCall: func(l net.Listener) (*http.Response, error) { 175 tr := &http.Transport{ 176 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 177 } 178 c := &http.Client{Transport: tr} 179 res, err := c.Post("https://"+l.Addr().String(), "application/json", strings.NewReader(TestGoodBody)) 180 return res, err 181 }, 182 }, 183 } 184 185 // defer in a loop 186 // https://stackoverflow.com/questions/45617758/proper-way-to-release-resources-with-defer-in-a-loop 187 for _, tt := range tests { 188 func() { 189 // start the listener 190 l, err := tt.ListenFunc() 191 require.NoError(t, err) 192 defer l.Close() 193 194 // start the server 195 chErr := make(chan error, 1) 196 go func() { 197 defer close(chErr) 198 chErr <- tt.Server(l) 199 }() 200 select { 201 case err := <-chErr: 202 require.NoError(t, err) 203 case <-time.After(100 * time.Millisecond): 204 } 205 206 // make a post call to the server 207 res, err := tt.PostCall(l) 208 require.NoError(t, err) 209 defer res.Body.Close() 210 211 // check the request 212 assert.Equal(t, "20", capturedRequest.Header.Get("Max-Batch-Request-Num")) 213 214 }() 215 } 216 } 217 218 func TestWriteRPCResponseHTTPError(t *testing.T) { 219 w := httptest.NewRecorder() 220 err := WriteRPCResponseHTTPError( 221 w, 222 http.StatusInternalServerError, 223 types.RPCInternalError(types.JSONRPCIntID(-1), errors.New("foo"))) 224 require.NoError(t, err) 225 resp := w.Result() 226 body, err := io.ReadAll(resp.Body) 227 _ = resp.Body.Close() 228 require.NoError(t, err) 229 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) 230 assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) 231 assert.Equal(t, `{"jsonrpc":"2.0","id":-1,"error":{"code":-32603,"message":"Internal error","data":"foo"}}`, string(body)) 232 } 233 func TestWriteRPCResponseHTTP_MarshalIndent_error(t *testing.T) { 234 w := NewFailedWriteResponseWriter() 235 result, _ := TestRawMSG.MarshalJSON() 236 // json.MarshalIndent error 237 err := WriteRPCResponseHTTP(w, 238 types.RPCResponse{Result: result[1:]}) // slice json for error 239 require.Error(t, err) 240 assert.NotEqual(t, w.error, err) 241 } 242 243 func TestWriteRPCResponseHTTP_Write_error(t *testing.T) { 244 w := NewFailedWriteResponseWriter() 245 result, _ := TestRawMSG.MarshalJSON() 246 // w.Write error 247 err := WriteRPCResponseHTTP(w, 248 types.NewRPCSuccessResponse(TestJSONIntID, result)) 249 require.Error(t, err) 250 assert.Equal(t, w.error, err) 251 } 252 253 func TestWriteRPCResponseHTTPError_MarshallIndent_error(t *testing.T) { 254 w := NewFailedWriteResponseWriter() 255 // json.MarshalIndent error 256 result, _ := TestRawMSG.MarshalJSON() 257 err := WriteRPCResponseHTTPError(w, http.StatusInternalServerError, 258 types.RPCResponse{Result: result[1:], Error: TestRPCError}) // slice json for error 259 require.Error(t, err) 260 assert.NotEqual(t, w.error, err) 261 } 262 263 func TestWriteRPCResponseHTTPError_Write_error(t *testing.T) { 264 w := NewFailedWriteResponseWriter() 265 // w.Write error 266 err := WriteRPCResponseHTTPError(w, http.StatusInternalServerError, 267 types.RPCInternalError(TestJSONIntID, ErrFoo)) 268 require.Error(t, err) 269 assert.Equal(t, w.error, err) 270 } 271 272 func TestWriteRPCResponseHTTPError_rerErrorIsNil_panic(t *testing.T) { 273 w := NewFailedWriteResponseWriter() 274 // panic: res.Error == nil 275 defer func() { 276 e := recover() 277 assert.True(t, e != nil) 278 }() 279 result, _ := TestRawMSG.MarshalJSON() 280 WriteRPCResponseHTTPError(w, http.StatusInternalServerError, types.RPCResponse{Result: result}) // nolint: errcheck 281 } 282 283 func TestRecoverAndLogHandler_RPCResponseOK_WriteRPCResponseHTTPError_error(t *testing.T) { 284 // RPCResponse == ok and WriteRPCResponseHTTPError is error 285 handlerFunc := makeJSONRPCHandler(TestFuncMap, log.TestingLogger()) 286 handler := RecoverAndLogHandler(handlerFunc, log.TestingLogger()) 287 assert.NotNil(t, handler) 288 req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody)) 289 // throwing and encounter 290 rec := NewFailedWriteResponseWriter() 291 rec.fm.throwPanic = true 292 rec.fm.failedCounter = 1 293 handler.ServeHTTP(rec, req) 294 assert.Equal(t, 295 strconv.Itoa(http.StatusOK), 296 rec.Header().Get(http.StatusText(http.StatusOK))) 297 } 298 299 func TestRecoverAndLogHandler_RPCResponseNG_WriteRPCResponseHTTPError_error(t *testing.T) { 300 // RPCResponse != ok and WriteRPCResponseHTTPError is error 301 handler := RecoverAndLogHandler(nil, log.TestingLogger()) 302 assert.NotNil(t, handler) 303 req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody)) 304 // encounter 305 rec := NewFailedWriteResponseWriter() 306 handler.ServeHTTP(rec, req) 307 assert.Equal(t, 308 strconv.Itoa(http.StatusInternalServerError), 309 rec.Header().Get(http.StatusText(http.StatusInternalServerError))) 310 } 311 312 func TestRecoverAndLogHandler_RPCResponseNG_WriteRPCResponseHTTPError_error_panic(t *testing.T) { 313 // RPCResponse != ok and WriteRPCResponseHTTPError is error and logger.Error is panic on 2nd times 314 // encounter 315 logger := NewFailedLogger() 316 logger.fm.failedCounter = 1 317 handler := RecoverAndLogHandler(nil, &logger) 318 assert.NotNil(t, handler) 319 req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody)) 320 // encounter 321 rec := NewFailedWriteResponseWriter() 322 handler.ServeHTTP(rec, req) 323 assert.Equal(t, 324 strconv.Itoa(http.StatusInternalServerError), 325 rec.Header().Get(http.StatusText(http.StatusInternalServerError))) 326 }