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  }