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  }