mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-09 14:43:19 +00:00
Add writeRPCResponse and writeHTTPResponse helpers, that handle the way RPC responses are written to HTTP replies. These replace the exported helpers. Visible effects: - JSON results are now marshaled without indentation. - HTTP status codes are now normalized. - Cache control headers are no longer set. Details: - When writing a response to a URL (GET) request, do not marshal the whole JSON-RPC object into the body, only encode the result or the error object. This is a user-visible change. - Do not change the HTTP status code for RPC errors. The RPC error already reports what went wrong, the HTTP status should only report problems with the HTTP transaction itself. This is a user-visible change. - Encode JSON without indentation in POST response bodies. This is mainly cosmetic but saves quite a bit of response data. Indent is still applied to GET responses to make life easier for code examples. - Remove an obsolete TODO about reporting an HTTP error on websocket upgrade. Nothing needed to change; the upgrader already reports an error. - Report an HTTP error when starting the server loop fails. - Improve logging for encoding errors. - Log less aggressively.
167 lines
4.3 KiB
Go
167 lines
4.3 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
|
|
)
|
|
|
|
type sampleResult struct {
|
|
Value string `json:"value"`
|
|
}
|
|
|
|
func TestMaxOpenConnections(t *testing.T) {
|
|
const max = 5 // max simultaneous connections
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
logger := log.NewTestingLogger(t)
|
|
|
|
// Start the server.
|
|
var open int32
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
if n := atomic.AddInt32(&open, 1); n > int32(max) {
|
|
t.Errorf("%d open connections, want <= %d", n, max)
|
|
}
|
|
defer atomic.AddInt32(&open, -1)
|
|
time.Sleep(10 * time.Millisecond)
|
|
fmt.Fprint(w, "some body")
|
|
})
|
|
config := DefaultConfig()
|
|
l, err := Listen("tcp://127.0.0.1:0", max)
|
|
require.NoError(t, err)
|
|
defer l.Close()
|
|
|
|
go Serve(ctx, l, mux, logger, config) //nolint:errcheck // ignore for tests
|
|
|
|
// Make N GET calls to the server.
|
|
attempts := max * 2
|
|
var wg sync.WaitGroup
|
|
var failed int32
|
|
for i := 0; i < attempts; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
c := http.Client{Timeout: 3 * time.Second}
|
|
r, err := c.Get("http://" + l.Addr().String())
|
|
if err != nil {
|
|
atomic.AddInt32(&failed, 1)
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
|
|
// We expect some Gets to fail as the server's accept queue is filled,
|
|
// but most should succeed.
|
|
if int(failed) >= attempts/2 {
|
|
t.Errorf("%d requests failed within %d attempts", failed, attempts)
|
|
}
|
|
}
|
|
|
|
func TestServeTLS(t *testing.T) {
|
|
ln, err := net.Listen("tcp", "localhost:0")
|
|
require.NoError(t, err)
|
|
defer ln.Close()
|
|
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprint(w, "some body")
|
|
})
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
logger := log.NewTestingLogger(t)
|
|
|
|
chErr := make(chan error, 1)
|
|
go func() {
|
|
chErr <- ServeTLS(ctx, ln, mux, "test.crt", "test.key", logger, DefaultConfig())
|
|
}()
|
|
|
|
select {
|
|
case err := <-chErr:
|
|
require.NoError(t, err)
|
|
case <-time.After(100 * time.Millisecond):
|
|
}
|
|
|
|
tr := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
}
|
|
c := &http.Client{Transport: tr}
|
|
res, err := c.Get("https://" + ln.Addr().String())
|
|
require.NoError(t, err)
|
|
defer res.Body.Close()
|
|
assert.Equal(t, http.StatusOK, res.StatusCode)
|
|
|
|
body, err := io.ReadAll(res.Body)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("some body"), body)
|
|
}
|
|
|
|
func TestWriteRPCResponse(t *testing.T) {
|
|
id := rpctypes.JSONRPCIntID(-1)
|
|
|
|
// one argument
|
|
w := httptest.NewRecorder()
|
|
logger := log.NewTestingLogger(t)
|
|
writeRPCResponse(w, logger,
|
|
rpctypes.NewRPCSuccessResponse(id, &sampleResult{"hello"}))
|
|
resp := w.Result()
|
|
body, err := io.ReadAll(resp.Body)
|
|
_ = resp.Body.Close()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
|
|
assert.Equal(t, "", resp.Header.Get("Cache-control"))
|
|
assert.Equal(t, `{"jsonrpc":"2.0","id":-1,"result":{"value":"hello"}}`, string(body))
|
|
|
|
// multiple arguments
|
|
w = httptest.NewRecorder()
|
|
writeRPCResponse(w, logger,
|
|
rpctypes.NewRPCSuccessResponse(id, &sampleResult{"hello"}),
|
|
rpctypes.NewRPCSuccessResponse(id, &sampleResult{"world"}),
|
|
)
|
|
resp = w.Result()
|
|
body, err = io.ReadAll(resp.Body)
|
|
_ = resp.Body.Close()
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
|
|
assert.Equal(t, `[{"jsonrpc":"2.0","id":-1,"result":{"value":"hello"}},`+
|
|
`{"jsonrpc":"2.0","id":-1,"result":{"value":"world"}}]`, string(body))
|
|
}
|
|
|
|
func TestWriteHTTPResponse(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
logger := log.NewTestingLogger(t)
|
|
writeHTTPResponse(w, logger,
|
|
rpctypes.RPCInternalError(rpctypes.JSONRPCIntID(-1), errors.New("foo")))
|
|
resp := w.Result()
|
|
body, err := io.ReadAll(resp.Body)
|
|
_ = resp.Body.Close()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
|
|
assert.Equal(t, `{"code":-32603,"message":"Internal error","data":"foo"}`, string(body))
|
|
}
|