Skip to content

Commit

Permalink
serve json rpc on custom path
Browse files Browse the repository at this point in the history
  • Loading branch information
renaynay committed Jan 25, 2021
1 parent adf130d commit 5c6b70b
Show file tree
Hide file tree
Showing 11 changed files with 285 additions and 43 deletions.
2 changes: 2 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ var (
utils.GraphQLCORSDomainFlag,
utils.GraphQLVirtualHostsFlag,
utils.HTTPApiFlag,
utils.HTTPPathFlag,
utils.LegacyRPCApiFlag,
utils.WSEnabledFlag,
utils.WSListenAddrFlag,
Expand All @@ -188,6 +189,7 @@ var (
utils.WSApiFlag,
utils.LegacyWSApiFlag,
utils.WSAllowedOriginsFlag,
utils.WSPathFlag,
utils.LegacyWSAllowedOriginsFlag,
utils.IPCDisabledFlag,
utils.IPCPathFlag,
Expand Down
2 changes: 2 additions & 0 deletions cmd/geth/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,14 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.HTTPListenAddrFlag,
utils.HTTPPortFlag,
utils.HTTPApiFlag,
utils.HTTPPathFlag,
utils.HTTPCORSDomainFlag,
utils.HTTPVirtualHostsFlag,
utils.WSEnabledFlag,
utils.WSListenAddrFlag,
utils.WSPortFlag,
utils.WSApiFlag,
utils.WSPathFlag,
utils.WSAllowedOriginsFlag,
utils.GraphQLEnabledFlag,
utils.GraphQLCORSDomainFlag,
Expand Down
22 changes: 22 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,11 @@ var (
Usage: "API's offered over the HTTP-RPC interface",
Value: "",
}
HTTPPathFlag = cli.StringFlag{
Name: "http.path",
Usage: "Path on which to mount the http server. Default value is root",
Value: "/",
}
GraphQLEnabledFlag = cli.BoolFlag{
Name: "graphql",
Usage: "Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server is started as well.",
Expand Down Expand Up @@ -570,6 +575,11 @@ var (
Usage: "Origins from which to accept websockets requests",
Value: "",
}
WSPathFlag = cli.StringFlag{
Name: "ws.path",
Usage: "Path on which to mount the ws server. Default value is root",
Value: "/",
}
ExecFlag = cli.StringFlag{
Name: "exec",
Usage: "Execute JavaScript statement",
Expand Down Expand Up @@ -955,6 +965,12 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) {
if ctx.GlobalIsSet(HTTPVirtualHostsFlag.Name) {
cfg.HTTPVirtualHosts = SplitAndTrim(ctx.GlobalString(HTTPVirtualHostsFlag.Name))
}

if ctx.GlobalIsSet(HTTPPathFlag.Name) {
cfg.HTTPPath = ctx.GlobalString(HTTPPathFlag.Name)
} else {
cfg.HTTPPath = HTTPPathFlag.Value
}
}

// setGraphQL creates the GraphQL listener interface string from the set
Expand Down Expand Up @@ -1004,6 +1020,12 @@ func setWS(ctx *cli.Context, cfg *node.Config) {
if ctx.GlobalIsSet(WSApiFlag.Name) {
cfg.WSModules = SplitAndTrim(ctx.GlobalString(WSApiFlag.Name))
}

if ctx.GlobalIsSet(WSPathFlag.Name) {
cfg.WSPath = ctx.GlobalString(WSPathFlag.Name)
} else {
cfg.WSPath = WSPathFlag.Value
}
}

// setIPC creates an IPC path configuration from the set command line flags,
Expand Down
5 changes: 0 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,6 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down Expand Up @@ -503,7 +502,6 @@ golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapK
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
Expand Down Expand Up @@ -551,15 +549,12 @@ google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyz
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0=
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
Expand Down
14 changes: 3 additions & 11 deletions graphql/graphql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import (
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"

"github.com/stretchr/testify/assert"
)

func TestBuildSchema(t *testing.T) {
Expand Down Expand Up @@ -166,18 +168,8 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) {
if err != nil {
t.Fatalf("could not post: %v", err)
}
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("could not read from response body: %v", err)
}
resp.Body.Close()
// make sure the request is not handled successfully
if want, have := "404 page not found\n", string(bodyBytes); have != want {
t.Errorf("have:\n%v\nwant:\n%v", have, want)
}
if want, have := 404, resp.StatusCode; want != have {
t.Errorf("wrong statuscode, have:\n%v\nwant:%v", have, want)
}
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
}

func createNode(t *testing.T, gqlEnabled bool) *node.Node {
Expand Down
24 changes: 22 additions & 2 deletions node/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func (api *privateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription,
}

// StartRPC starts the HTTP RPC API server.
func (api *privateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) {
func (api *privateAdminAPI) StartRPC(host *string, port *int, path *string, cors *string, apis *string, vhosts *string) (bool, error) {
api.node.lock.Lock()
defer api.node.lock.Unlock()

Expand All @@ -179,11 +179,21 @@ func (api *privateAdminAPI) StartRPC(host *string, port *int, cors *string, apis
port = &api.node.config.HTTPPort
}

// Check if path set on which to mount http handler
if path == nil {
p := "/"
if api.node.config.HTTPPath != "" {
p = api.node.config.HTTPPath
}
path = &p
}

// Determine config.
config := httpConfig{
CorsAllowedOrigins: api.node.config.HTTPCors,
Vhosts: api.node.config.HTTPVirtualHosts,
Modules: api.node.config.HTTPModules,
path: *path,
}
if cors != nil {
config.CorsAllowedOrigins = nil
Expand Down Expand Up @@ -223,7 +233,7 @@ func (api *privateAdminAPI) StopRPC() (bool, error) {
}

// StartWS starts the websocket RPC API server.
func (api *privateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) {
func (api *privateAdminAPI) StartWS(host *string, port *int, path *string, allowedOrigins *string, apis *string) (bool, error) {
api.node.lock.Lock()
defer api.node.lock.Unlock()

Expand All @@ -239,10 +249,20 @@ func (api *privateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str
port = &api.node.config.WSPort
}

// Check if path set on which to mount ws handler
if path == nil {
p := "/"
if api.node.config.WSPath != "" {
p = api.node.config.WSPath
}
path = &p
}

// Determine config.
config := wsConfig{
Modules: api.node.config.WSModules,
Origins: api.node.config.WSOrigins,
path: *path,
// ExposeAll: api.node.config.WSExposeAll,
}
if apis != nil {
Expand Down
93 changes: 83 additions & 10 deletions node/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"strings"
"testing"

"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -69,7 +70,7 @@ func TestStartRPC(t *testing.T) {
name: "rpc enabled through API",
cfg: Config{},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
_, err := api.StartRPC(sp("127.0.0.1"), ip(0), nil, nil, nil)
_, err := api.StartRPC(sp("127.0.0.1"), ip(0), nil, nil, nil, nil)
assert.NoError(t, err)
},
wantReachable: true,
Expand All @@ -90,14 +91,14 @@ func TestStartRPC(t *testing.T) {
port := listener.Addr().(*net.TCPAddr).Port

// Now try to start RPC on that port. This should fail.
_, err = api.StartRPC(sp("127.0.0.1"), ip(port), nil, nil, nil)
_, err = api.StartRPC(sp("127.0.0.1"), ip(port), nil, nil, nil, nil)
if err == nil {
t.Fatal("StartRPC should have failed on port", port)
}

// Try again after unblocking the port. It should work this time.
listener.Close()
_, err = api.StartRPC(sp("127.0.0.1"), ip(port), nil, nil, nil)
_, err = api.StartRPC(sp("127.0.0.1"), ip(port), nil, nil, nil, nil)
assert.NoError(t, err)
},
wantReachable: true,
Expand Down Expand Up @@ -144,7 +145,7 @@ func TestStartRPC(t *testing.T) {
name: "ws enabled through API",
cfg: Config{},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
_, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil)
_, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil, nil)
assert.NoError(t, err)
},
wantReachable: true,
Expand Down Expand Up @@ -184,7 +185,7 @@ func TestStartRPC(t *testing.T) {
cfg: Config{HTTPHost: "127.0.0.1"},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
wsport := n.http.port
_, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
_, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil, nil)
assert.NoError(t, err)
},
wantReachable: true,
Expand All @@ -197,7 +198,7 @@ func TestStartRPC(t *testing.T) {
cfg: Config{HTTPHost: "127.0.0.1"},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
wsport := n.http.port
_, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
_, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil, nil)
assert.NoError(t, err)

_, err = api.StopWS()
Expand All @@ -211,11 +212,11 @@ func TestStartRPC(t *testing.T) {
{
name: "rpc stopped with ws enabled",
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
_, err := api.StartRPC(sp("127.0.0.1"), ip(0), nil, nil, nil)
_, err := api.StartRPC(sp("127.0.0.1"), ip(0), nil, nil, nil, nil)
assert.NoError(t, err)

wsport := n.http.port
_, err = api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
_, err = api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil, nil)
assert.NoError(t, err)

_, err = api.StopRPC()
Expand All @@ -229,11 +230,11 @@ func TestStartRPC(t *testing.T) {
{
name: "rpc enabled after ws",
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
_, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil)
_, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil, nil)
assert.NoError(t, err)

wsport := n.http.port
_, err = api.StartRPC(sp("127.0.0.1"), ip(wsport), nil, nil, nil)
_, err = api.StartRPC(sp("127.0.0.1"), ip(wsport), nil, nil, nil, nil)
assert.NoError(t, err)
},
wantReachable: true,
Expand Down Expand Up @@ -293,6 +294,78 @@ func TestStartRPC(t *testing.T) {
}
}

// TestRPC_OnPath tests whether RPC will be handled on a custom path.
func TestRPC_OnPath(t *testing.T) {
// Create Node.
stack, err := New(&Config{
NoUSB: true,
P2P: p2p.Config{
NoDiscovery: true,
},
})
if err != nil {
t.Fatal("can't create node:", err)
}
defer stack.Close()

if err := stack.Start(); err != nil {
t.Fatal("can't start node:", err)
}

api := &privateAdminAPI{stack}

paths := []string{"/test", "/testtesttest", "/t", "/"}

for _, path := range paths {
_, err := api.StartRPC(sp("127.0.0.1"), ip(0), sp(path), nil, nil, nil)
assert.NoError(t, err)

baseURL := stack.HTTPEndpoint()
assert.True(t, checkReachable(baseURL+path))
assert.True(t, checkRPC(baseURL+path))
assert.False(t, checkRPC(baseURL+"/fail"))

_, err = api.StopRPC()
assert.NoError(t, err)
}
}

// TestWS_OnPath tests whether RPC will be handled on a custom path.
func TestWS_OnPath(t *testing.T) {
// Create Node.
stack, err := New(&Config{
NoUSB: true,
P2P: p2p.Config{
NoDiscovery: true,
},
})
if err != nil {
t.Fatal("can't create node:", err)
}
defer stack.Close()

if err := stack.Start(); err != nil {
t.Fatal("can't start node:", err)
}

api := &privateAdminAPI{stack}

paths := []string{"/test", "/testtesttest", "/t", "/"}

for _, path := range paths {
_, err := api.StartWS(sp("127.0.0.1"), ip(0), sp(path), nil, nil)
assert.NoError(t, err)

baseURL := stack.WSEndpoint()
assert.True(t, checkReachable(baseURL+path))
assert.True(t, checkRPC(baseURL+path))
assert.False(t, checkRPC(baseURL+"/fail"))

_, err = api.StopWS()
assert.NoError(t, err)
}
}

// checkReachable checks if the TCP endpoint in rawurl is open.
func checkReachable(rawurl string) bool {
u, err := url.Parse(rawurl)
Expand Down
8 changes: 8 additions & 0 deletions node/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ type Config struct {
// interface.
HTTPTimeouts rpc.HTTPTimeouts

// HTTPPath specifies a path on which http-rpc is to be served. The default is the
// root path.
HTTPPath string `toml:",omitempty"`

// WSHost is the host interface on which to start the websocket RPC server. If
// this field is empty, no websocket API endpoint will be started.
WSHost string
Expand All @@ -148,6 +152,10 @@ type Config struct {
// ephemeral nodes).
WSPort int `toml:",omitempty"`

// WSPath specifies a path on which ws-rpc is to be served. The default is the
// root path.
WSPath string `toml:",omitempty"`

// WSOrigins is the list of domain to accept websocket requests from. Please be
// aware that the server can only act upon the HTTP request the client sends and
// cannot verify the validity of the request header.
Expand Down
Loading

0 comments on commit 5c6b70b

Please sign in to comment.