Skip to content

Commit 35a2026

Browse files
gzliudanfjl
andauthored
node, rpc: add configurable HTTP request limit ethereum#28948 (#1010)
Adds a configurable HTTP request limit, and bumps the engine default Co-authored-by: Felix Lange <[email protected]>
1 parent 9373c71 commit 35a2026

File tree

7 files changed

+51
-20
lines changed

7 files changed

+51
-20
lines changed

node/defaults.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ const (
3636
DefaultAuthPort = 8551 // Default port for the authenticated apis
3737
)
3838

39+
const (
40+
// Engine API batch limits: these are not configurable by users, and should cover the
41+
// needs of all CLs.
42+
engineAPIBatchItemLimit = 2000
43+
engineAPIBatchResponseSizeLimit = 250 * 1000 * 1000
44+
engineAPIBodyLimit = 128 * 1024 * 1024
45+
)
46+
3947
var (
4048
DefaultAuthCors = []string{"localhost"} // Default cors domain for the authenticated apis
4149
DefaultAuthVhosts = []string{"localhost"} // Default virtual hosts for the authenticated apis

node/node.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -458,15 +458,20 @@ func (n *Node) startRPC() error {
458458
if err := server.setListenAddr(n.config.AuthHost, port); err != nil {
459459
return err
460460
}
461-
sharedConfig := rpcConfig
462-
sharedConfig.jwtSecret = secret
463-
if err := server.enableRPC(apis, httpConfig{
461+
sharedConfig := rpcEndpointConfig{
462+
jwtSecret: secret,
463+
batchItemLimit: engineAPIBatchItemLimit,
464+
batchResponseSizeLimit: engineAPIBatchResponseSizeLimit,
465+
httpBodyLimit: engineAPIBodyLimit,
466+
}
467+
err := server.enableRPC(apis, httpConfig{
464468
CorsAllowedOrigins: DefaultAuthCors,
465469
Vhosts: DefaultAuthVhosts,
466470
Modules: DefaultAuthModules,
467471
prefix: DefaultAuthPrefix,
468472
rpcEndpointConfig: sharedConfig,
469-
}); err != nil {
473+
})
474+
if err != nil {
470475
return err
471476
}
472477
servers = append(servers, server)

node/rpcstack.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type rpcEndpointConfig struct {
5555
jwtSecret []byte // optional JWT secret
5656
batchItemLimit int
5757
batchResponseSizeLimit int
58+
httpBodyLimit int
5859
}
5960

6061
type rpcHandler struct {
@@ -289,6 +290,9 @@ func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error {
289290
// Create RPC server and handler.
290291
srv := rpc.NewServer()
291292
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
293+
if config.httpBodyLimit > 0 {
294+
srv.SetHTTPBodyLimit(config.httpBodyLimit)
295+
}
292296
if err := RegisterApisFromWhitelist(apis, config.Modules, srv); err != nil {
293297
return err
294298
}
@@ -322,6 +326,9 @@ func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error {
322326
// Create RPC server and handler.
323327
srv := rpc.NewServer()
324328
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
329+
if config.httpBodyLimit > 0 {
330+
srv.SetHTTPBodyLimit(config.httpBodyLimit)
331+
}
325332
if err := RegisterApisFromWhitelist(apis, config.Modules, srv); err != nil {
326333
return err
327334
}

rpc/http.go

Lines changed: 9 additions & 9 deletions
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
@@ -252,8 +252,8 @@ type httpServerConn struct {
252252
r *http.Request
253253
}
254254

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

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

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

rpc/http_test.go

Lines changed: 5 additions & 3 deletions
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()

rpc/server.go

Lines changed: 11 additions & 2 deletions
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

rpc/websocket_test.go

Lines changed: 2 additions & 2 deletions
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)