Enable h2c for http handler. add https handler for http2 (#8610)

new flag examples.

--https.enabled
--https.addr="0.0.0.0"
--https.port=443
--https.url="unix:///file.wow"
--https.cert="keyfile.cert" 
--https.key="certfile.cert"

also adds support for h2c to the http handler - http2 protocol without tls.
This commit is contained in:
a 2023-10-31 04:14:20 -05:00 committed by GitHub
parent c90bff7e22
commit d8d7d8d5df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 165 additions and 29 deletions

View File

@ -194,6 +194,20 @@ If the `--http.url` flag is set, then `--http.addr` and `--http.port` with both
note that this is NOT geth-style IPC. for that, read the next section, IPC endpoint(geth-compatible)
### HTTPS, HTTP2, and H2C
Erigon supports HTTPS, HTTP2, and H2C out of the box. H2C is served by the default HTTP handler.
To enable the HTTPS+HTTP2 server, add flag `--https.enabled`, along with providing flags `-https.cert="/path/to.cert"` and `--https.key=/path/to.key`
By default, the HTTPS server will run on the HTTP port + 363. use flag `--https.port` to set the port
The HTTPS server will inherit all other configuration parameters from http, for instance, enabling the websocket server, cors domains, or enabled namespaces
If the `--https.url` flag is set, then `--https.addr` and `--https.port` with both be ignored.
### IPC endpoint (geth compatible)
erigon supports the geth-style unix socket IPC. you can enable this with `--socket.enabled` flag,
@ -266,7 +280,7 @@ The following table shows the current implementation status of Erigon's RPC daem
| eth_getFilterChanges | Yes | |
| eth_uninstallFilter | Yes | |
| eth_getLogs | Yes | |
| | | |
| interned spe | | |
| eth_accounts | No | deprecated |
| eth_sendRawTransaction | Yes | `remote`. |
| eth_sendTransaction | - | not yet implemented |

View File

@ -89,10 +89,6 @@ func RootCommand() (*cobra.Command, *httpcfg.HttpCfg) {
rootCmd.PersistentFlags().StringVar(&cfg.PrivateApiAddr, "private.api.addr", "127.0.0.1:9090", "Erigon's components (txpool, rpcdaemon, sentry, downloader, ...) can be deployed as independent Processes on same/another server. Then components will connect to erigon by this internal grpc API. Example: 127.0.0.1:9090")
rootCmd.PersistentFlags().StringVar(&cfg.DataDir, "datadir", "", "path to Erigon working directory")
rootCmd.PersistentFlags().BoolVar(&cfg.GraphQLEnabled, "graphql", false, "enables graphql endpoint (disabled by default)")
rootCmd.PersistentFlags().StringVar(&cfg.HttpListenAddress, "http.addr", nodecfg.DefaultHTTPHost, "HTTP-RPC server listening interface")
rootCmd.PersistentFlags().StringVar(&cfg.TLSCertfile, "tls.cert", "", "certificate for client side TLS handshake")
rootCmd.PersistentFlags().StringVar(&cfg.TLSKeyFile, "tls.key", "", "key file for client side TLS handshake")
rootCmd.PersistentFlags().StringVar(&cfg.TLSCACert, "tls.cacert", "", "CA certificate for client side TLS handshake")
rootCmd.PersistentFlags().Uint64Var(&cfg.Gascap, "rpc.gascap", 50_000_000, "Sets a cap on gas that can be used in eth_call/estimateGas")
rootCmd.PersistentFlags().Uint64Var(&cfg.MaxTraces, "trace.maxtraces", 200, "Sets a limit on traces that can be returned in trace_filter")
@ -110,17 +106,29 @@ func RootCommand() (*cobra.Command, *httpcfg.HttpCfg) {
rootCmd.PersistentFlags().IntVar(&cfg.GRPCPort, "grpc.port", nodecfg.DefaultGRPCPort, "GRPC server listening port")
rootCmd.PersistentFlags().BoolVar(&cfg.GRPCHealthCheckEnabled, "grpc.healthcheck", false, "Enable GRPC health check")
rootCmd.PersistentFlags().Float64Var(&ethconfig.Defaults.RPCTxFeeCap, utils.RPCGlobalTxFeeCapFlag.Name, utils.RPCGlobalTxFeeCapFlag.Value, utils.RPCGlobalTxFeeCapFlag.Usage)
rootCmd.PersistentFlags().StringVar(&cfg.TLSCertfile, "tls.cert", "", "certificate for client side TLS handshake for GRPC")
rootCmd.PersistentFlags().StringVar(&cfg.TLSKeyFile, "tls.key", "", "key file for client side TLS handshake for GRPC")
rootCmd.PersistentFlags().StringVar(&cfg.TLSCACert, "tls.cacert", "", "CA certificate for client side TLS handshake for GRPC")
rootCmd.PersistentFlags().StringSliceVar(&cfg.API, "http.api", []string{"eth", "erigon"}, "API's offered over the RPC interface: eth,erigon,web3,net,debug,trace,txpool,db. Supported methods: https://github.com/ledgerwatch/erigon/tree/devel/cmd/rpcdaemon")
rootCmd.PersistentFlags().BoolVar(&cfg.HttpServerEnabled, "http.enabled", true, "enable http server")
rootCmd.PersistentFlags().StringVar(&cfg.HttpListenAddress, "http.addr", nodecfg.DefaultHTTPHost, "HTTP server listening interface")
rootCmd.PersistentFlags().IntVar(&cfg.HttpPort, "http.port", nodecfg.DefaultHTTPPort, "HTTP server listening port")
rootCmd.PersistentFlags().StringVar(&cfg.HttpURL, "http.url", "", "HTTP server listening url. will OVERRIDE http.addr and http.port. will NOT respect http paths. prefix supported are tcp, unix")
rootCmd.PersistentFlags().IntVar(&cfg.HttpPort, "http.port", nodecfg.DefaultHTTPPort, "HTTP-RPC server listening port")
rootCmd.PersistentFlags().StringSliceVar(&cfg.HttpCORSDomain, "http.corsdomain", []string{}, "Comma separated list of domains from which to accept cross origin requests (browser enforced)")
rootCmd.PersistentFlags().StringSliceVar(&cfg.HttpVirtualHost, "http.vhosts", nodecfg.DefaultConfig.HTTPVirtualHosts, "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.")
rootCmd.PersistentFlags().BoolVar(&cfg.HttpCompression, "http.compression", true, "Disable http compression")
rootCmd.PersistentFlags().StringSliceVar(&cfg.API, "http.api", []string{"eth", "erigon"}, "API's offered over the HTTP-RPC interface: eth,erigon,web3,net,debug,trace,txpool,db. Supported methods: https://github.com/ledgerwatch/erigon/tree/devel/cmd/rpcdaemon")
rootCmd.PersistentFlags().BoolVar(&cfg.WebsocketEnabled, "ws", false, "Enable Websockets - Same port as HTTP")
rootCmd.PersistentFlags().BoolVar(&cfg.WebsocketEnabled, "ws", false, "Enable Websockets - Same port as HTTP[S]")
rootCmd.PersistentFlags().BoolVar(&cfg.WebsocketCompression, "ws.compression", false, "Enable Websocket compression (RFC 7692)")
rootCmd.PersistentFlags().BoolVar(&cfg.HttpsServerEnabled, "https.enabled", false, "enable http server")
rootCmd.PersistentFlags().StringVar(&cfg.HttpsListenAddress, "https.addr", nodecfg.DefaultHTTPHost, "rpc HTTPS server listening interface")
rootCmd.PersistentFlags().IntVar(&cfg.HttpsPort, "https.port", 0, "rpc HTTPS server listening port. default to http+363 if not set")
rootCmd.PersistentFlags().StringVar(&cfg.HttpsURL, "https.url", "", "rpc HTTPS server listening url. will OVERRIDE https.addr and https.port. will NOT respect paths. prefix supported are tcp, unix")
rootCmd.PersistentFlags().StringVar(&cfg.HttpsCertfile, "https.cert", "", "certificate for rpc HTTPS server")
rootCmd.PersistentFlags().StringVar(&cfg.HttpsKeyFile, "https.key", "", "key file for rpc HTTPS server")
rootCmd.PersistentFlags().BoolVar(&cfg.SocketServerEnabled, "socket.enabled", false, "Enable IPC server")
rootCmd.PersistentFlags().StringVar(&cfg.SocketListenUrl, "socket.url", "unix:///var/run/erigon.sock", "IPC server listening url. prefix supported are tcp, unix")
@ -610,24 +618,25 @@ func startRegularRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rp
logger.Info("Socket Endpoint opened", "url", socketUrl)
}
httpHandler := node.NewHTTPHandlerStack(srv, cfg.HttpCORSDomain, cfg.HttpVirtualHost, cfg.HttpCompression)
var wsHandler http.Handler
if cfg.WebsocketEnabled {
wsHandler = srv.WebsocketHandler([]string{"*"}, nil, cfg.WebsocketCompression, logger)
}
graphQLHandler := graphql.CreateHandler(defaultAPIList)
apiHandler, err := createHandler(cfg, defaultAPIList, httpHandler, wsHandler, graphQLHandler, nil)
if err != nil {
return err
}
if cfg.HttpServerEnabled {
httpHandler := node.NewHTTPHandlerStack(srv, cfg.HttpCORSDomain, cfg.HttpVirtualHost, cfg.HttpCompression)
var wsHandler http.Handler
if cfg.WebsocketEnabled {
wsHandler = srv.WebsocketHandler([]string{"*"}, nil, cfg.WebsocketCompression, logger)
}
graphQLHandler := graphql.CreateHandler(defaultAPIList)
apiHandler, err := createHandler(cfg, defaultAPIList, httpHandler, wsHandler, graphQLHandler, nil)
if err != nil {
return err
}
httpEndpoint := fmt.Sprintf("tcp://%s:%d", cfg.HttpListenAddress, cfg.HttpPort)
if cfg.HttpURL != "" {
httpEndpoint = cfg.HttpURL
}
listener, httpAddr, err := node.StartHTTPEndpoint(httpEndpoint, cfg.HTTPTimeouts, apiHandler)
listener, httpAddr, err := node.StartHTTPEndpoint(httpEndpoint, &node.HttpEndpointConfig{
Timeouts: cfg.HTTPTimeouts,
}, apiHandler)
if err != nil {
return fmt.Errorf("could not start RPC api: %w", err)
}
@ -639,6 +648,34 @@ func startRegularRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rp
logger.Info("HTTP endpoint closed", "url", httpAddr)
}()
}
if cfg.HttpsURL != "" {
cfg.HttpsServerEnabled = true
}
if cfg.HttpsServerEnabled {
if cfg.HttpsPort == 0 {
cfg.HttpsPort = cfg.HttpPort + 363
}
httpsEndpoint := fmt.Sprintf("tcp://%s:%d", cfg.HttpsListenAddress, cfg.HttpsPort)
if cfg.HttpsURL != "" {
httpsEndpoint = cfg.HttpsURL
}
listener, httpAddr, err := node.StartHTTPEndpoint(httpsEndpoint, &node.HttpEndpointConfig{
Timeouts: cfg.HTTPTimeouts,
HTTPS: true,
CertFile: cfg.HttpsCertfile,
KeyFile: cfg.HttpsKeyFile,
}, apiHandler)
if err != nil {
return fmt.Errorf("could not start RPC api: %w", err)
}
info = append(info, "https.url", httpAddr)
defer func() {
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = listener.Shutdown(shutdownCtx)
logger.Info("HTTPS endpoint closed", "url", httpAddr)
}()
}
var (
healthServer *grpcHealth.Server
@ -797,7 +834,9 @@ func createEngineListener(cfg httpcfg.HttpCfg, engineApi []rpc.API, logger log.L
return nil, nil, "", err
}
engineListener, engineAddr, err := node.StartHTTPEndpoint(engineHttpEndpoint, cfg.AuthRpcTimeouts, engineApiHandler)
engineListener, engineAddr, err := node.StartHTTPEndpoint(engineHttpEndpoint, &node.HttpEndpointConfig{
Timeouts: cfg.AuthRpcTimeouts,
}, engineApiHandler)
if err != nil {
return nil, nil, "", fmt.Errorf("could not start RPC api: %w", err)
}

View File

@ -30,6 +30,13 @@ type HttpCfg struct {
AuthRpcVirtualHost []string
HttpCompression bool
HttpsServerEnabled bool
HttpsURL string
HttpsListenAddress string
HttpsPort int
HttpsCertfile string
HttpsKeyFile string
AuthRpcPort int
PrivateApiAddr string

View File

@ -305,7 +305,12 @@ var (
}
HTTPEnabledFlag = cli.BoolFlag{
Name: "http",
Usage: "HTTP-RPC server (enabled by default). Use --http=false to disable it",
Usage: "JSON-RPC server (enabled by default). Use --http=false to disable it",
Value: true,
}
HTTPServerEnabledFlag = cli.BoolFlag{
Name: "http.enabled",
Usage: "JSON-RPC HTTP server (enabled by default). Use --http.enabled=false to disable it",
Value: true,
}
HTTPListenAddrFlag = cli.StringFlag{

View File

@ -29,10 +29,73 @@ import (
"github.com/ledgerwatch/erigon/rpc"
"github.com/ledgerwatch/erigon/rpc/rpccfg"
"github.com/ledgerwatch/log/v3"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
type HttpEndpointConfig struct {
Timeouts rpccfg.HTTPTimeouts
HTTPS bool
CertFile string
KeyFile string
}
// StartHTTPEndpoint starts the HTTP RPC endpoint.
func StartHTTPEndpoint(urlEndpoint string, timeouts rpccfg.HTTPTimeouts, handler http.Handler) (*http.Server, net.Addr, error) {
func StartHTTPEndpoint(urlEndpoint string, cfg *HttpEndpointConfig, handler http.Handler) (*http.Server, net.Addr, error) {
// start the HTTP listener
var (
listener net.Listener
err error
)
socketUrl, err := url.Parse(urlEndpoint)
if err != nil {
return nil, nil, fmt.Errorf("malformatted http listen url %s: %w", urlEndpoint, err)
}
if listener, err = net.Listen(socketUrl.Scheme, socketUrl.Host+socketUrl.EscapedPath()); err != nil {
return nil, nil, err
}
// make sure timeout values are meaningful
CheckTimeouts(&cfg.Timeouts)
// create the http2 server for handling h2c
h2 := &http2.Server{}
// enable h2c support
handler = h2c.NewHandler(handler, h2)
// Bundle the http server
httpSrv := &http.Server{
Handler: handler,
ReadTimeout: cfg.Timeouts.ReadTimeout,
WriteTimeout: cfg.Timeouts.WriteTimeout,
IdleTimeout: cfg.Timeouts.IdleTimeout,
ReadHeaderTimeout: cfg.Timeouts.ReadTimeout,
}
// start the HTTP server
go func() {
var serveErr error
if cfg.HTTPS {
serveErr = httpSrv.ServeTLS(listener, cfg.CertFile, cfg.KeyFile)
if serveErr != nil && !isIgnoredHttpServerError(serveErr) {
log.Warn("Failed to serve https endpoint", "err", serveErr)
}
} else {
serveErr = httpSrv.Serve(listener)
if serveErr != nil && !isIgnoredHttpServerError(serveErr) {
log.Warn("Failed to serve http endpoint", "err", serveErr)
}
}
}()
return httpSrv, listener.Addr(), err
}
func isIgnoredHttpServerError(serveErr error) bool {
return (errors.Is(serveErr, context.Canceled) || errors.Is(serveErr, libcommon.ErrStopped) || errors.Is(serveErr, http.ErrServerClosed))
}
// StartHTTPEndpoint starts the HTTP RPC endpoint.
func StartHTTPSEndpoint(urlEndpoint string,
keyFile string, certFile string,
timeouts rpccfg.HTTPTimeouts, handler http.Handler,
) (*http.Server, net.Addr, error) {
// start the HTTP listener
var (
listener net.Listener
@ -47,7 +110,11 @@ func StartHTTPEndpoint(urlEndpoint string, timeouts rpccfg.HTTPTimeouts, handler
}
// make sure timeout values are meaningful
CheckTimeouts(&timeouts)
// Bundle and start the HTTP server
// create the http2 server for handling h2c
h2 := &http2.Server{}
// enable h2c support
handler = h2c.NewHandler(handler, h2)
// Bundle the http server
httpSrv := &http.Server{
Handler: handler,
ReadTimeout: timeouts.ReadTimeout,
@ -55,8 +122,9 @@ func StartHTTPEndpoint(urlEndpoint string, timeouts rpccfg.HTTPTimeouts, handler
IdleTimeout: timeouts.IdleTimeout,
ReadHeaderTimeout: timeouts.ReadTimeout,
}
// start the HTTP server
go func() {
serveErr := httpSrv.Serve(listener)
serveErr := httpSrv.ServeTLS(listener, certFile, keyFile)
if serveErr != nil &&
!(errors.Is(serveErr, context.Canceled) || errors.Is(serveErr, libcommon.ErrStopped) || errors.Is(serveErr, http.ErrServerClosed)) {
log.Warn("Failed to serve http endpoint", "err", serveErr)

View File

@ -51,6 +51,7 @@ var DefaultFlags = []cli.Flag{
&BadBlockFlag,
&utils.HTTPEnabledFlag,
&utils.HTTPServerEnabledFlag,
&utils.GraphQLEnabledFlag,
&utils.HTTPListenAddrFlag,
&utils.HTTPPortFlag,

View File

@ -2,9 +2,10 @@ package cli
import (
"fmt"
"github.com/ledgerwatch/erigon-lib/common/hexutil"
"time"
"github.com/ledgerwatch/erigon-lib/common/hexutil"
"github.com/ledgerwatch/erigon-lib/txpool/txpoolcfg"
libcommon "github.com/ledgerwatch/erigon-lib/common"
@ -356,8 +357,9 @@ func setEmbeddedRpcDaemon(ctx *cli.Context, cfg *nodecfg.Config, logger log.Logg
logger.Info("starting HTTP APIs", "APIs", apis)
c := &httpcfg.HttpCfg{
Enabled: ctx.Bool(utils.HTTPEnabledFlag.Name),
Dirs: cfg.Dirs,
Enabled: ctx.Bool(utils.HTTPEnabledFlag.Name),
HttpServerEnabled: ctx.Bool(utils.HTTPServerEnabledFlag.Name),
Dirs: cfg.Dirs,
TLSKeyFile: cfg.TLSKeyFile,
TLSCACert: cfg.TLSCACert,