Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions node/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ const (
DefaultAuthPort = 8551 // Default port for the authenticated apis
)

const (
// Engine API batch limits: these are not configurable by users, and should cover the
// needs of all CLs.
engineAPIBatchItemLimit = 2000
engineAPIBatchResponseSizeLimit = 250 * 1000 * 1000
engineAPIBodyLimit = 128 * 1024 * 1024
)

var (
DefaultAuthCors = []string{"localhost"} // Default cors domain for the authenticated apis
DefaultAuthVhosts = []string{"localhost"} // Default virtual hosts for the authenticated apis
Expand Down
13 changes: 9 additions & 4 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,15 +458,20 @@ func (n *Node) startRPC() error {
if err := server.setListenAddr(n.config.AuthHost, port); err != nil {
return err
}
sharedConfig := rpcConfig
sharedConfig.jwtSecret = secret
if err := server.enableRPC(apis, httpConfig{
sharedConfig := rpcEndpointConfig{
jwtSecret: secret,
batchItemLimit: engineAPIBatchItemLimit,
batchResponseSizeLimit: engineAPIBatchResponseSizeLimit,
httpBodyLimit: engineAPIBodyLimit,
}
err := server.enableRPC(apis, httpConfig{
CorsAllowedOrigins: DefaultAuthCors,
Vhosts: DefaultAuthVhosts,
Modules: DefaultAuthModules,
prefix: DefaultAuthPrefix,
rpcEndpointConfig: sharedConfig,
}); err != nil {
})
if err != nil {
return err
}
servers = append(servers, server)
Expand Down
7 changes: 7 additions & 0 deletions node/rpcstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type rpcEndpointConfig struct {
jwtSecret []byte // optional JWT secret
batchItemLimit int
batchResponseSizeLimit int
httpBodyLimit int
}

type rpcHandler struct {
Expand Down Expand Up @@ -289,6 +290,9 @@ func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error {
// Create RPC server and handler.
srv := rpc.NewServer()
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
if config.httpBodyLimit > 0 {
srv.SetHTTPBodyLimit(config.httpBodyLimit)
}
if err := RegisterApisFromWhitelist(apis, config.Modules, srv); err != nil {
return err
}
Expand Down Expand Up @@ -322,6 +326,9 @@ func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error {
// Create RPC server and handler.
srv := rpc.NewServer()
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
if config.httpBodyLimit > 0 {
srv.SetHTTPBodyLimit(config.httpBodyLimit)
}
if err := RegisterApisFromWhitelist(apis, config.Modules, srv); err != nil {
return err
}
Expand Down
18 changes: 9 additions & 9 deletions rpc/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ import (
)

const (
maxRequestContentLength = 1024 * 1024 * 5
contentType = "application/json"
defaultBodyLimit = 5 * 1024 * 1024
contentType = "application/json"
)

// https://www.jsonrpc.org/historical/json-rpc-over-http.html#id13
Expand Down Expand Up @@ -252,8 +252,8 @@ type httpServerConn struct {
r *http.Request
}

func newHTTPServerConn(r *http.Request, w http.ResponseWriter) ServerCodec {
body := io.LimitReader(r.Body, maxRequestContentLength)
func (s *Server) newHTTPServerConn(r *http.Request, w http.ResponseWriter) ServerCodec {
body := io.LimitReader(r.Body, int64(s.httpBodyLimit))
conn := &httpServerConn{Reader: body, Writer: w, r: r}

encoder := func(v any, isErrorResponse bool) error {
Expand Down Expand Up @@ -311,7 +311,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
return
}
if code, err := validateRequest(r); err != nil {
if code, err := s.validateRequest(r); err != nil {
http.Error(w, err.Error(), code)
return
}
Expand All @@ -329,19 +329,19 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// until EOF, writes the response to w, and orders the server to process a
// single request.
w.Header().Set("content-type", contentType)
codec := newHTTPServerConn(r, w)
codec := s.newHTTPServerConn(r, w)
defer codec.close()
s.serveSingleRequest(ctx, codec)
}

// validateRequest returns a non-zero response code and error message if the
// request is invalid.
func validateRequest(r *http.Request) (int, error) {
func (s *Server) validateRequest(r *http.Request) (int, error) {
if r.Method == http.MethodPut || r.Method == http.MethodDelete {
return http.StatusMethodNotAllowed, errors.New("method not allowed")
}
if r.ContentLength > maxRequestContentLength {
err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength)
if r.ContentLength > int64(s.httpBodyLimit) {
err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, s.httpBodyLimit)
return http.StatusRequestEntityTooLarge, err
}
// Allow OPTIONS (regardless of content-type)
Expand Down
8 changes: 5 additions & 3 deletions rpc/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ func confirmStatusCode(t *testing.T, got, want int) {

func confirmRequestValidationCode(t *testing.T, method, contentType, body string, expectedStatusCode int) {
t.Helper()

s := NewServer()
request := httptest.NewRequest(method, "http://url.com", strings.NewReader(body))
if len(contentType) > 0 {
request.Header.Set("Content-Type", contentType)
}
code, err := validateRequest(request)
code, err := s.validateRequest(request)
if code == 0 {
if err != nil {
t.Errorf("validation: got error %v, expected nil", err)
Expand All @@ -64,7 +66,7 @@ func TestHTTPErrorResponseWithPut(t *testing.T) {
}

func TestHTTPErrorResponseWithMaxContentLength(t *testing.T) {
body := make([]rune, maxRequestContentLength+1)
body := make([]rune, defaultBodyLimit+1)
confirmRequestValidationCode(t,
http.MethodPost, contentType, string(body), http.StatusRequestEntityTooLarge)
}
Expand Down Expand Up @@ -104,7 +106,7 @@ func TestHTTPResponseWithEmptyGet(t *testing.T) {

// This checks that maxRequestContentLength is not applied to the response of a request.
func TestHTTPRespBodyUnlimited(t *testing.T) {
const respLength = maxRequestContentLength * 3
const respLength = defaultBodyLimit * 3

s := NewServer()
defer s.Stop()
Expand Down
13 changes: 11 additions & 2 deletions rpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@ type Server struct {
run atomic.Bool
batchItemLimit int
batchResponseLimit int
httpBodyLimit int
}

// NewServer creates a new server instance with no registered handlers.
func NewServer() *Server {
server := &Server{
idgen: randomIDGenerator(),
codecs: make(map[ServerCodec]struct{}),
idgen: randomIDGenerator(),
codecs: make(map[ServerCodec]struct{}),
httpBodyLimit: defaultBodyLimit,
}
server.run.Store(true)
// Register the default service providing meta information about the RPC service such
Expand All @@ -78,6 +80,13 @@ func (s *Server) SetBatchLimits(itemLimit, maxResponseSize int) {
s.batchResponseLimit = maxResponseSize
}

// SetHTTPBodyLimit sets the size limit for HTTP requests.
//
// This method should be called before processing any requests via ServeHTTP.
func (s *Server) SetHTTPBodyLimit(limit int) {
s.httpBodyLimit = limit
}

// RegisterName creates a service for the given receiver type under the given name. When no
// methods on the given receiver match the criteria to be either a RPC method or a
// subscription an error is returned. Otherwise a new service is created and added to the
Expand Down
4 changes: 2 additions & 2 deletions rpc/websocket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func TestWebsocketLargeCall(t *testing.T) {

// This call sends slightly less than the limit and should work.
var result echoResult
arg := strings.Repeat("x", maxRequestContentLength-200)
arg := strings.Repeat("x", defaultBodyLimit-200)
if err := client.Call(&result, "test_echo", arg, 1); err != nil {
t.Fatalf("valid call didn't work: %v", err)
}
Expand All @@ -106,7 +106,7 @@ func TestWebsocketLargeCall(t *testing.T) {
}

// This call sends twice the allowed size and shouldn't work.
arg = strings.Repeat("x", maxRequestContentLength*2)
arg = strings.Repeat("x", defaultBodyLimit*2)
err = client.Call(&result, "test_echo", arg)
if err == nil {
t.Fatal("no error for too large call")
Expand Down