Skip to content

Commit 5d7e185

Browse files
ryanschneiderkaralabe
authored andcommitted
rpc: make HTTP RPC timeouts configurable, raise defaults (#17240)
* rpc: Make HTTP server timeout values configurable * rpc: Remove flags for setting HTTP Timeouts, configuring via .toml is sufficient. * rpc: Replace separate constants with a single default struct. * rpc: Update HTTP Server Read and Write Timeouts to 30s. * rpc: Remove redundant NewDefaultHTTPTimeouts function. * rpc: document HTTPTimeouts. * rpc: sanitize timeout values for library use
1 parent c4a1d4f commit 5d7e185

File tree

7 files changed

+66
-11
lines changed

7 files changed

+66
-11
lines changed

cmd/clef/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ func signer(c *cli.Context) error {
415415

416416
// start http server
417417
httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
418-
listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts)
418+
listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts)
419419
if err != nil {
420420
utils.Fatalf("Could not start RPC api: %v", err)
421421
}

node/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis
157157
}
158158
}
159159

160-
if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts); err != nil {
160+
if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts, api.node.config.HTTPTimeouts); err != nil {
161161
return false, err
162162
}
163163
return true, nil

node/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/ethereum/go-ethereum/log"
3434
"github.com/ethereum/go-ethereum/p2p"
3535
"github.com/ethereum/go-ethereum/p2p/discover"
36+
"github.com/ethereum/go-ethereum/rpc"
3637
)
3738

3839
const (
@@ -119,6 +120,10 @@ type Config struct {
119120
// exposed.
120121
HTTPModules []string `toml:",omitempty"`
121122

123+
// HTTPTimeouts allows for customization of the timeout values used by the HTTP RPC
124+
// interface.
125+
HTTPTimeouts rpc.HTTPTimeouts
126+
122127
// WSHost is the host interface on which to start the websocket RPC server. If
123128
// this field is empty, no websocket API endpoint will be started.
124129
WSHost string `toml:",omitempty"`

node/defaults.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
"github.com/ethereum/go-ethereum/p2p"
2626
"github.com/ethereum/go-ethereum/p2p/nat"
27+
"github.com/ethereum/go-ethereum/rpc"
2728
)
2829

2930
const (
@@ -39,6 +40,7 @@ var DefaultConfig = Config{
3940
HTTPPort: DefaultHTTPPort,
4041
HTTPModules: []string{"net", "web3"},
4142
HTTPVirtualHosts: []string{"localhost"},
43+
HTTPTimeouts: rpc.DefaultHTTPTimeouts,
4244
WSPort: DefaultWSPort,
4345
WSModules: []string{"net", "web3"},
4446
P2P: p2p.Config{

node/node.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error {
263263
n.stopInProc()
264264
return err
265265
}
266-
if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts); err != nil {
266+
if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts); err != nil {
267267
n.stopIPC()
268268
n.stopInProc()
269269
return err
@@ -331,12 +331,12 @@ func (n *Node) stopIPC() {
331331
}
332332

333333
// startHTTP initializes and starts the HTTP RPC endpoint.
334-
func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string) error {
334+
func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts) error {
335335
// Short circuit if the HTTP endpoint isn't being exposed
336336
if endpoint == "" {
337337
return nil
338338
}
339-
listener, handler, err := rpc.StartHTTPEndpoint(endpoint, apis, modules, cors, vhosts)
339+
listener, handler, err := rpc.StartHTTPEndpoint(endpoint, apis, modules, cors, vhosts, timeouts)
340340
if err != nil {
341341
return err
342342
}

rpc/endpoints.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
)
2424

2525
// StartHTTPEndpoint starts the HTTP RPC endpoint, configured with cors/vhosts/modules
26-
func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []string, vhosts []string) (net.Listener, *Server, error) {
26+
func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []string, vhosts []string, timeouts HTTPTimeouts) (net.Listener, *Server, error) {
2727
// Generate the whitelist based on the allowed modules
2828
whitelist := make(map[string]bool)
2929
for _, module := range modules {
@@ -47,7 +47,7 @@ func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []str
4747
if listener, err = net.Listen("tcp", endpoint); err != nil {
4848
return nil, nil, err
4949
}
50-
go NewHTTPServer(cors, vhosts, handler).Serve(listener)
50+
go NewHTTPServer(cors, vhosts, timeouts, handler).Serve(listener)
5151
return listener, handler, err
5252
}
5353

rpc/http.go

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"sync"
3232
"time"
3333

34+
"github.com/ethereum/go-ethereum/log"
3435
"github.com/rs/cors"
3536
)
3637

@@ -66,6 +67,38 @@ func (hc *httpConn) Close() error {
6667
return nil
6768
}
6869

70+
// HTTPTimeouts represents the configuration params for the HTTP RPC server.
71+
type HTTPTimeouts struct {
72+
// ReadTimeout is the maximum duration for reading the entire
73+
// request, including the body.
74+
//
75+
// Because ReadTimeout does not let Handlers make per-request
76+
// decisions on each request body's acceptable deadline or
77+
// upload rate, most users will prefer to use
78+
// ReadHeaderTimeout. It is valid to use them both.
79+
ReadTimeout time.Duration
80+
81+
// WriteTimeout is the maximum duration before timing out
82+
// writes of the response. It is reset whenever a new
83+
// request's header is read. Like ReadTimeout, it does not
84+
// let Handlers make decisions on a per-request basis.
85+
WriteTimeout time.Duration
86+
87+
// IdleTimeout is the maximum amount of time to wait for the
88+
// next request when keep-alives are enabled. If IdleTimeout
89+
// is zero, the value of ReadTimeout is used. If both are
90+
// zero, ReadHeaderTimeout is used.
91+
IdleTimeout time.Duration
92+
}
93+
94+
// DefaultHTTPTimeouts represents the default timeout values used if further
95+
// configuration is not provided.
96+
var DefaultHTTPTimeouts = HTTPTimeouts{
97+
ReadTimeout: 30 * time.Second,
98+
WriteTimeout: 30 * time.Second,
99+
IdleTimeout: 120 * time.Second,
100+
}
101+
69102
// DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP
70103
// using the provided HTTP Client.
71104
func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) {
@@ -161,15 +194,30 @@ func (t *httpReadWriteNopCloser) Close() error {
161194
// NewHTTPServer creates a new HTTP RPC server around an API provider.
162195
//
163196
// Deprecated: Server implements http.Handler
164-
func NewHTTPServer(cors []string, vhosts []string, srv *Server) *http.Server {
197+
func NewHTTPServer(cors []string, vhosts []string, timeouts HTTPTimeouts, srv *Server) *http.Server {
165198
// Wrap the CORS-handler within a host-handler
166199
handler := newCorsHandler(srv, cors)
167200
handler = newVHostHandler(vhosts, handler)
201+
202+
// Make sure timeout values are meaningful
203+
if timeouts.ReadTimeout < time.Second {
204+
log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", DefaultHTTPTimeouts.ReadTimeout)
205+
timeouts.ReadTimeout = DefaultHTTPTimeouts.ReadTimeout
206+
}
207+
if timeouts.WriteTimeout < time.Second {
208+
log.Warn("Sanitizing invalid HTTP write timeout", "provided", timeouts.WriteTimeout, "updated", DefaultHTTPTimeouts.WriteTimeout)
209+
timeouts.WriteTimeout = DefaultHTTPTimeouts.WriteTimeout
210+
}
211+
if timeouts.IdleTimeout < time.Second {
212+
log.Warn("Sanitizing invalid HTTP idle timeout", "provided", timeouts.IdleTimeout, "updated", DefaultHTTPTimeouts.IdleTimeout)
213+
timeouts.IdleTimeout = DefaultHTTPTimeouts.IdleTimeout
214+
}
215+
// Bundle and start the HTTP server
168216
return &http.Server{
169217
Handler: handler,
170-
ReadTimeout: 5 * time.Second,
171-
WriteTimeout: 10 * time.Second,
172-
IdleTimeout: 120 * time.Second,
218+
ReadTimeout: timeouts.ReadTimeout,
219+
WriteTimeout: timeouts.WriteTimeout,
220+
IdleTimeout: timeouts.IdleTimeout,
173221
}
174222
}
175223

0 commit comments

Comments
 (0)