Skip to content

Commit 69f5d5b

Browse files
authored
node, rpc: add configurable HTTP request limit (#28948)
Adds a configurable HTTP request limit, and bumps the engine default
1 parent 449d3f0 commit 69f5d5b

File tree

7 files changed

+39
-18
lines changed

7 files changed

+39
-18
lines changed

Diff for: node/defaults.go

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const (
4141
// needs of all CLs.
4242
engineAPIBatchItemLimit = 2000
4343
engineAPIBatchResponseSizeLimit = 250 * 1000 * 1000
44+
engineAPIBodyLimit = 128 * 1024 * 1024
4445
)
4546

4647
var (

Diff for: node/node.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -453,14 +453,16 @@ func (n *Node) startRPC() error {
453453
jwtSecret: secret,
454454
batchItemLimit: engineAPIBatchItemLimit,
455455
batchResponseSizeLimit: engineAPIBatchResponseSizeLimit,
456+
httpBodyLimit: engineAPIBodyLimit,
456457
}
457-
if err := server.enableRPC(allAPIs, httpConfig{
458+
err := server.enableRPC(allAPIs, httpConfig{
458459
CorsAllowedOrigins: DefaultAuthCors,
459460
Vhosts: n.config.AuthVirtualHosts,
460461
Modules: DefaultAuthModules,
461462
prefix: DefaultAuthPrefix,
462463
rpcEndpointConfig: sharedConfig,
463-
}); err != nil {
464+
})
465+
if err != nil {
464466
return err
465467
}
466468
servers = append(servers, server)

Diff for: node/rpcstack.go

+7
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type rpcEndpointConfig struct {
5656
jwtSecret []byte // optional JWT secret
5757
batchItemLimit int
5858
batchResponseSizeLimit int
59+
httpBodyLimit int
5960
}
6061

6162
type rpcHandler struct {
@@ -304,6 +305,9 @@ func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error {
304305
// Create RPC server and handler.
305306
srv := rpc.NewServer()
306307
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
308+
if config.httpBodyLimit > 0 {
309+
srv.SetHTTPBodyLimit(config.httpBodyLimit)
310+
}
307311
if err := RegisterApis(apis, config.Modules, srv); err != nil {
308312
return err
309313
}
@@ -336,6 +340,9 @@ func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error {
336340
// Create RPC server and handler.
337341
srv := rpc.NewServer()
338342
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
343+
if config.httpBodyLimit > 0 {
344+
srv.SetHTTPBodyLimit(config.httpBodyLimit)
345+
}
339346
if err := RegisterApis(apis, config.Modules, srv); err != nil {
340347
return err
341348
}

Diff for: rpc/http.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ import (
3333
)
3434

3535
const (
36-
maxRequestContentLength = 1024 * 1024 * 5
37-
contentType = "application/json"
36+
defaultBodyLimit = 5 * 1024 * 1024
37+
contentType = "application/json"
3838
)
3939

4040
// https://www.jsonrpc.org/historical/json-rpc-over-http.html#id13
@@ -253,8 +253,8 @@ type httpServerConn struct {
253253
r *http.Request
254254
}
255255

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

260260
encoder := func(v any, isErrorResponse bool) error {
@@ -312,7 +312,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
312312
w.WriteHeader(http.StatusOK)
313313
return
314314
}
315-
if code, err := validateRequest(r); err != nil {
315+
if code, err := s.validateRequest(r); err != nil {
316316
http.Error(w, err.Error(), code)
317317
return
318318
}
@@ -330,19 +330,19 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
330330
// until EOF, writes the response to w, and orders the server to process a
331331
// single request.
332332
w.Header().Set("content-type", contentType)
333-
codec := newHTTPServerConn(r, w)
333+
codec := s.newHTTPServerConn(r, w)
334334
defer codec.close()
335335
s.serveSingleRequest(ctx, codec)
336336
}
337337

338338
// validateRequest returns a non-zero response code and error message if the
339339
// request is invalid.
340-
func validateRequest(r *http.Request) (int, error) {
340+
func (s *Server) validateRequest(r *http.Request) (int, error) {
341341
if r.Method == http.MethodPut || r.Method == http.MethodDelete {
342342
return http.StatusMethodNotAllowed, errors.New("method not allowed")
343343
}
344-
if r.ContentLength > maxRequestContentLength {
345-
err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength)
344+
if r.ContentLength > int64(s.httpBodyLimit) {
345+
err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, s.httpBodyLimit)
346346
return http.StatusRequestEntityTooLarge, err
347347
}
348348
// Allow OPTIONS (regardless of content-type)

Diff for: rpc/http_test.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@ func confirmStatusCode(t *testing.T, got, want int) {
4040

4141
func confirmRequestValidationCode(t *testing.T, method, contentType, body string, expectedStatusCode int) {
4242
t.Helper()
43+
44+
s := NewServer()
4345
request := httptest.NewRequest(method, "http://url.com", strings.NewReader(body))
4446
if len(contentType) > 0 {
4547
request.Header.Set("Content-Type", contentType)
4648
}
47-
code, err := validateRequest(request)
49+
code, err := s.validateRequest(request)
4850
if code == 0 {
4951
if err != nil {
5052
t.Errorf("validation: got error %v, expected nil", err)
@@ -64,7 +66,7 @@ func TestHTTPErrorResponseWithPut(t *testing.T) {
6466
}
6567

6668
func TestHTTPErrorResponseWithMaxContentLength(t *testing.T) {
67-
body := make([]rune, maxRequestContentLength+1)
69+
body := make([]rune, defaultBodyLimit+1)
6870
confirmRequestValidationCode(t,
6971
http.MethodPost, contentType, string(body), http.StatusRequestEntityTooLarge)
7072
}
@@ -104,7 +106,7 @@ func TestHTTPResponseWithEmptyGet(t *testing.T) {
104106

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

109111
s := NewServer()
110112
defer s.Stop()

Diff for: rpc/server.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@ type Server struct {
5151
run atomic.Bool
5252
batchItemLimit int
5353
batchResponseLimit int
54+
httpBodyLimit int
5455
}
5556

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

83+
// SetHTTPBodyLimit sets the size limit for HTTP requests.
84+
//
85+
// This method should be called before processing any requests via ServeHTTP.
86+
func (s *Server) SetHTTPBodyLimit(limit int) {
87+
s.httpBodyLimit = limit
88+
}
89+
8190
// RegisterName creates a service for the given receiver type under the given name. When no
8291
// methods on the given receiver match the criteria to be either a RPC method or a
8392
// subscription an error is returned. Otherwise a new service is created and added to the

Diff for: rpc/websocket_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func TestWebsocketLargeCall(t *testing.T) {
9797

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

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

0 commit comments

Comments
 (0)