github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/rpc/jsonrpc/server/http_server_test.go (about) 1 package server 2 3 import ( 4 "crypto/tls" 5 "errors" 6 "fmt" 7 "io/ioutil" 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/line/ostracon/libs/log" 22 types "github.com/line/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 := ioutil.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 := WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(id, &sampleResult{"hello"})) 118 require.NoError(t, err) 119 resp := w.Result() 120 body, err := ioutil.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, `{ 126 "jsonrpc": "2.0", 127 "id": -1, 128 "result": { 129 "value": "hello" 130 } 131 }`, string(body)) 132 133 // multiple arguments 134 w = httptest.NewRecorder() 135 err = WriteRPCResponseHTTP(w, 136 types.NewRPCSuccessResponse(id, &sampleResult{"hello"}), 137 types.NewRPCSuccessResponse(id, &sampleResult{"world"})) 138 require.NoError(t, err) 139 resp = w.Result() 140 body, err = ioutil.ReadAll(resp.Body) 141 _ = resp.Body.Close() 142 require.NoError(t, err) 143 144 assert.Equal(t, 200, resp.StatusCode) 145 assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) 146 assert.Equal(t, `[ 147 { 148 "jsonrpc": "2.0", 149 "id": -1, 150 "result": { 151 "value": "hello" 152 } 153 }, 154 { 155 "jsonrpc": "2.0", 156 "id": -1, 157 "result": { 158 "value": "world" 159 } 160 } 161 ]`, string(body)) 162 } 163 164 func TestWriteRPCResponseHTTPError(t *testing.T) { 165 w := httptest.NewRecorder() 166 err := WriteRPCResponseHTTPError( 167 w, 168 http.StatusInternalServerError, 169 types.RPCInternalError(types.JSONRPCIntID(-1), errors.New("foo"))) 170 require.NoError(t, err) 171 resp := w.Result() 172 body, err := ioutil.ReadAll(resp.Body) 173 _ = resp.Body.Close() 174 require.NoError(t, err) 175 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) 176 assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) 177 assert.Equal(t, `{ 178 "jsonrpc": "2.0", 179 "id": -1, 180 "error": { 181 "code": -32603, 182 "message": "Internal error", 183 "data": "foo" 184 } 185 }`, string(body)) 186 } 187 func TestWriteRPCResponseHTTP_MarshalIndent_error(t *testing.T) { 188 w := NewFailedWriteResponseWriter() 189 result, _ := TestRawMSG.MarshalJSON() 190 // json.MarshalIndent error 191 err := WriteRPCResponseHTTP(w, 192 types.RPCResponse{Result: result[1:]}) // slice json for error 193 require.Error(t, err) 194 assert.NotEqual(t, w.error, err) 195 } 196 197 func TestWriteRPCResponseHTTP_Write_error(t *testing.T) { 198 w := NewFailedWriteResponseWriter() 199 result, _ := TestRawMSG.MarshalJSON() 200 // w.Write error 201 err := WriteRPCResponseHTTP(w, 202 types.NewRPCSuccessResponse(TestJSONIntID, result)) 203 require.Error(t, err) 204 assert.Equal(t, w.error, err) 205 } 206 207 func TestWriteRPCResponseHTTPError_MarshallIndent_error(t *testing.T) { 208 w := NewFailedWriteResponseWriter() 209 // json.MarshalIndent error 210 result, _ := TestRawMSG.MarshalJSON() 211 err := WriteRPCResponseHTTPError(w, http.StatusInternalServerError, 212 types.RPCResponse{Result: result[1:], Error: TestRPCError}) // slice json for error 213 require.Error(t, err) 214 assert.NotEqual(t, w.error, err) 215 } 216 217 func TestWriteRPCResponseHTTPError_Write_error(t *testing.T) { 218 w := NewFailedWriteResponseWriter() 219 // w.Write error 220 err := WriteRPCResponseHTTPError(w, http.StatusInternalServerError, 221 types.RPCInternalError(TestJSONIntID, ErrFoo)) 222 require.Error(t, err) 223 assert.Equal(t, w.error, err) 224 } 225 226 func TestWriteRPCResponseHTTPError_rerErrorIsNil_panic(t *testing.T) { 227 w := NewFailedWriteResponseWriter() 228 // panic: res.Error == nil 229 defer func() { 230 e := recover() 231 assert.True(t, e != nil) 232 }() 233 result, _ := TestRawMSG.MarshalJSON() 234 WriteRPCResponseHTTPError(w, http.StatusInternalServerError, types.RPCResponse{Result: result}) // nolint: errcheck 235 } 236 237 func TestRecoverAndLogHandler_RPCResponseOK_WriteRPCResponseHTTPError_error(t *testing.T) { 238 // RPCResponse == ok and WriteRPCResponseHTTPError is error 239 handlerFunc := makeJSONRPCHandler(TestFuncMap, log.TestingLogger()) 240 handler := RecoverAndLogHandler(handlerFunc, log.TestingLogger()) 241 assert.NotNil(t, handler) 242 req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody)) 243 // throwing and encounter 244 rec := NewFailedWriteResponseWriter() 245 rec.fm.throwPanic = true 246 rec.fm.failedCounter = 1 247 handler.ServeHTTP(rec, req) 248 assert.Equal(t, 249 strconv.Itoa(http.StatusOK), 250 rec.Header().Get(http.StatusText(http.StatusOK))) 251 } 252 253 func TestRecoverAndLogHandler_RPCResponseNG_WriteRPCResponseHTTPError_error(t *testing.T) { 254 // RPCResponse != ok and WriteRPCResponseHTTPError is error 255 handler := RecoverAndLogHandler(nil, log.TestingLogger()) 256 assert.NotNil(t, handler) 257 req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody)) 258 // encounter 259 rec := NewFailedWriteResponseWriter() 260 handler.ServeHTTP(rec, req) 261 assert.Equal(t, 262 strconv.Itoa(http.StatusInternalServerError), 263 rec.Header().Get(http.StatusText(http.StatusInternalServerError))) 264 } 265 266 func TestRecoverAndLogHandler_RPCResponseNG_WriteRPCResponseHTTPError_error_panic(t *testing.T) { 267 // RPCResponse != ok and WriteRPCResponseHTTPError is error and logger.Error is panic on 2nd times 268 // encounter 269 logger := NewFailedLogger() 270 logger.fm.failedCounter = 1 271 handler := RecoverAndLogHandler(nil, &logger) 272 assert.NotNil(t, handler) 273 req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody)) 274 // encounter 275 rec := NewFailedWriteResponseWriter() 276 handler.ServeHTTP(rec, req) 277 assert.Equal(t, 278 strconv.Itoa(http.StatusInternalServerError), 279 rec.Header().Get(http.StatusText(http.StatusInternalServerError))) 280 }