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  }