github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/rpc/lib/server/http_server_test.go (about) 1 package rpcserver 2 3 import ( 4 "crypto/tls" 5 "fmt" 6 "io" 7 "net" 8 "net/http" 9 "net/http/httptest" 10 "sync" 11 "sync/atomic" 12 "testing" 13 "time" 14 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 18 types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" 19 "github.com/gnolang/gno/tm2/pkg/log" 20 ) 21 22 func TestMaxOpenConnections(t *testing.T) { 23 t.Parallel() 24 25 const max = 5 // max simultaneous connections 26 27 // Start the server. 28 var open int32 29 mux := http.NewServeMux() 30 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 31 if n := atomic.AddInt32(&open, 1); n > int32(max) { 32 t.Errorf("%d open connections, want <= %d", n, max) 33 } 34 defer atomic.AddInt32(&open, -1) 35 time.Sleep(10 * time.Millisecond) 36 fmt.Fprint(w, "some body") 37 }) 38 config := DefaultConfig() 39 config.MaxOpenConnections = max 40 l, err := Listen("tcp://127.0.0.1:0", config) 41 require.NoError(t, err) 42 defer l.Close() 43 go StartHTTPServer(l, mux, log.NewTestingLogger(t), config) 44 45 // Make N GET calls to the server. 46 attempts := max * 2 47 var wg sync.WaitGroup 48 var failed int32 49 for i := 0; i < attempts; i++ { 50 wg.Add(1) 51 go func() { 52 defer wg.Done() 53 c := http.Client{Timeout: 3 * time.Second} 54 r, err := c.Get("http://" + l.Addr().String()) 55 if err != nil { 56 t.Log(err) 57 atomic.AddInt32(&failed, 1) 58 return 59 } 60 defer r.Body.Close() 61 io.Copy(io.Discard, r.Body) 62 }() 63 } 64 wg.Wait() 65 66 // We expect some Gets to fail as the server's accept queue is filled, 67 // but most should succeed. 68 if int(failed) >= attempts/2 { 69 t.Errorf("%d requests failed within %d attempts", failed, attempts) 70 } 71 } 72 73 func TestStartHTTPAndTLSServer(t *testing.T) { 74 t.Parallel() 75 76 ln, err := net.Listen("tcp", "localhost:0") 77 require.NoError(t, err) 78 defer ln.Close() 79 80 mux := http.NewServeMux() 81 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 82 fmt.Fprint(w, "some body") 83 }) 84 85 go StartHTTPAndTLSServer(ln, mux, "test.crt", "test.key", log.NewTestingLogger(t), DefaultConfig()) 86 87 tr := &http.Transport{ 88 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 89 } 90 c := &http.Client{Transport: tr} 91 res, err := c.Get("https://" + ln.Addr().String()) 92 require.NoError(t, err) 93 defer res.Body.Close() 94 assert.Equal(t, http.StatusOK, res.StatusCode) 95 96 body, err := io.ReadAll(res.Body) 97 require.NoError(t, err) 98 assert.Equal(t, []byte("some body"), body) 99 } 100 101 func TestRecoverAndLogHandler(t *testing.T) { 102 t.Parallel() 103 104 tests := []struct { 105 name string 106 panicArg any 107 expectedResponse string 108 }{ 109 { 110 name: "panic with types.RPCResponse", 111 panicArg: types.NewRPCErrorResponse(types.JSONRPCStringID("id"), 42, "msg", "data"), 112 expectedResponse: `{ 113 "jsonrpc": "2.0", 114 "id": "id", 115 "error": { 116 "code": 42, 117 "message": "msg", 118 "data": "data" 119 } 120 }`, 121 }, 122 { 123 name: "panic with error", 124 panicArg: fmt.Errorf("I'm an error"), 125 expectedResponse: `{ 126 "jsonrpc": "2.0", 127 "id": "", 128 "error": { 129 "code": -32603, 130 "message": "Internal error", 131 "data": "I'm an error" 132 } 133 }`, 134 }, 135 { 136 name: "panic with string", 137 panicArg: "I'm an string", 138 expectedResponse: `{ 139 "jsonrpc": "2.0", 140 "id": "", 141 "error": { 142 "code": -32603, 143 "message": "Internal error", 144 "data": "I'm an string" 145 } 146 }`, 147 }, 148 { 149 name: "panic with random struct", 150 panicArg: struct { 151 f int 152 }{f: 1}, 153 expectedResponse: `{ 154 "jsonrpc": "2.0", 155 "id": "", 156 "error": { 157 "code": -32603, 158 "message": "Internal error", 159 "data": "{1}" 160 } 161 }`, 162 }, 163 } 164 for _, tt := range tests { 165 t.Run(tt.name, func(t *testing.T) { 166 t.Parallel() 167 168 var ( 169 req, _ = http.NewRequest(http.MethodGet, "", nil) 170 resp = httptest.NewRecorder() 171 logger = log.NewNoopLogger() 172 // Create a handler that will always panic with argument tt.panicArg 173 handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 174 panic(tt.panicArg) 175 }) 176 ) 177 178 RecoverAndLogHandler(handler, logger).ServeHTTP(resp, req) 179 180 require.Equal(t, tt.expectedResponse, resp.Body.String()) 181 }) 182 } 183 }