rpc: add CORS workaround for RPC

This one enables our RPC to be called from the browser if there is a
need. It's insecure and not standards-compliant, thus this behaviour is
configurable is not enabled by default. It makes our node with this workaround
enabled compatible with neo-mon monitoring.

Originally debugged by @anatoly-bogatyrev in #464.
This commit is contained in:
Roman Khimov 2019-11-01 13:23:46 +03:00
parent e5205d26a3
commit b46dd295bc
14 changed files with 72 additions and 22 deletions

View file

@ -247,7 +247,7 @@ func startServer(ctx *cli.Context) error {
} }
server := network.NewServer(serverConfig, chain) server := network.NewServer(serverConfig, chain)
rpcServer := rpc.NewServer(chain, cfg.ApplicationConfiguration.RPCPort, server) rpcServer := rpc.NewServer(chain, cfg.ApplicationConfiguration.RPC, server)
errChan := make(chan error) errChan := make(chan error)
monitoring := metrics.NewMetricsService(cfg.ApplicationConfiguration.Monitoring) monitoring := metrics.NewMetricsService(cfg.ApplicationConfiguration.Monitoring)

View file

@ -67,7 +67,6 @@ type (
// ApplicationConfiguration config specific to the node. // ApplicationConfiguration config specific to the node.
ApplicationConfiguration struct { ApplicationConfiguration struct {
DBConfiguration storage.DBConfiguration `yaml:"DBConfiguration"` DBConfiguration storage.DBConfiguration `yaml:"DBConfiguration"`
RPCPort uint16 `yaml:"RPCPort"`
NodePort uint16 `yaml:"NodePort"` NodePort uint16 `yaml:"NodePort"`
Relay bool `yaml:"Relay"` Relay bool `yaml:"Relay"`
DialTimeout time.Duration `yaml:"DialTimeout"` DialTimeout time.Duration `yaml:"DialTimeout"`
@ -75,6 +74,15 @@ type (
MaxPeers int `yaml:"MaxPeers"` MaxPeers int `yaml:"MaxPeers"`
MinPeers int `yaml:"MinPeers"` MinPeers int `yaml:"MinPeers"`
Monitoring metrics.PrometheusConfig `yaml:"Monitoring"` Monitoring metrics.PrometheusConfig `yaml:"Monitoring"`
RPC RPCConfig `yaml:"RPC"`
}
// RPCConfig is an RPC service configuration information (to be moved to the rpc package, see #423).
RPCConfig struct {
Enabled bool `yaml:"Enabled"`
EnableCORSWorkaround bool `yaml:"EnableCORSWorkaround"`
Address string `yaml:"Address"`
Port uint16 `yaml:"Port"`
} }
// NetMode describes the mode the blockchain will operate on. // NetMode describes the mode the blockchain will operate on.

View file

@ -42,13 +42,16 @@ ApplicationConfiguration:
# DB: 0 # DB: 0
# BoltDBOptions: # BoltDBOptions:
# FilePath: "./chains/mainnet.bolt" # FilePath: "./chains/mainnet.bolt"
RPCPort: 20332
NodePort: 20333 NodePort: 20333
Relay: true Relay: true
DialTimeout: 3 DialTimeout: 3
ProtoTickInterval: 2 ProtoTickInterval: 2
MaxPeers: 50 MaxPeers: 50
MinPeers: 5 MinPeers: 5
RPC:
Enabled: true
EnableCORSWorkaround: false
Port: 20332
Monitoring: Monitoring:
Enabled: true Enabled: true
Port: 2112 Port: 2112

View file

@ -30,13 +30,16 @@ ApplicationConfiguration:
# DB: 0 # DB: 0
# BoltDBOptions: # BoltDBOptions:
# FilePath: "./chains/privnet.bolt" # FilePath: "./chains/privnet.bolt"
RPCPort: 20336
NodePort: 20337 NodePort: 20337
Relay: true Relay: true
DialTimeout: 3 DialTimeout: 3
ProtoTickInterval: 2 ProtoTickInterval: 2
MaxPeers: 50 MaxPeers: 50
MinPeers: 3 MinPeers: 3
RPC:
Enabled: true
EnableCORSWorkaround: false
Port: 20336
Monitoring: Monitoring:
Enabled: true Enabled: true
Port: 2112 Port: 2112

View file

@ -27,13 +27,16 @@ ApplicationConfiguration:
# DB: 0 # DB: 0
# BoltDBOptions: # BoltDBOptions:
# FilePath: "./chains/privnet.bolt" # FilePath: "./chains/privnet.bolt"
RPCPort: 20333
NodePort: 20334 NodePort: 20334
Relay: true Relay: true
DialTimeout: 3 DialTimeout: 3
ProtoTickInterval: 2 ProtoTickInterval: 2
MaxPeers: 50 MaxPeers: 50
MinPeers: 3 MinPeers: 3
RPC:
Enabled: true
EnableCORSWorkaround: false
Port: 20333
Monitoring: Monitoring:
Enabled: true Enabled: true
Port: 2112 Port: 2112

View file

@ -27,13 +27,16 @@ ApplicationConfiguration:
# DB: 0 # DB: 0
# BoltDBOptions: # BoltDBOptions:
# FilePath: "./chains/privnet.bolt" # FilePath: "./chains/privnet.bolt"
RPCPort: 20335
NodePort: 20336 NodePort: 20336
Relay: true Relay: true
DialTimeout: 3 DialTimeout: 3
ProtoTickInterval: 2 ProtoTickInterval: 2
MaxPeers: 50 MaxPeers: 50
MinPeers: 3 MinPeers: 3
RPC:
Enabled: true
EnableCORSWorkaround: false
Port: 20335
Monitoring: Monitoring:
Enabled: true Enabled: true
Port: 2112 Port: 2112

View file

@ -27,13 +27,16 @@ ApplicationConfiguration:
# DB: 0 # DB: 0
# BoltDBOptions: # BoltDBOptions:
# FilePath: "./chains/privnet.bolt" # FilePath: "./chains/privnet.bolt"
RPCPort: 20334
NodePort: 20335 NodePort: 20335
Relay: true Relay: true
DialTimeout: 3 DialTimeout: 3
ProtoTickInterval: 2 ProtoTickInterval: 2
MaxPeers: 50 MaxPeers: 50
MinPeers: 3 MinPeers: 3
RPC:
Enabled: true
EnableCORSWorkaround: false
Port: 20334
Monitoring: Monitoring:
Enabled: true Enabled: true
Port: 2112 Port: 2112

View file

@ -33,13 +33,16 @@ ApplicationConfiguration:
# DB: 0 # DB: 0
# BoltDBOptions: # BoltDBOptions:
# FilePath: "./chains/privnet.bolt" # FilePath: "./chains/privnet.bolt"
RPCPort: 20331
NodePort: 20332 NodePort: 20332
Relay: true Relay: true
DialTimeout: 3 DialTimeout: 3
ProtoTickInterval: 2 ProtoTickInterval: 2
MaxPeers: 50 MaxPeers: 50
MinPeers: 3 MinPeers: 3
RPC:
Enabled: true
EnableCORSWorkaround: false
Port: 20331
Monitoring: Monitoring:
Enabled: true Enabled: true
Port: 2112 Port: 2112

View file

@ -42,13 +42,16 @@ ApplicationConfiguration:
# DB: 0 # DB: 0
# BoltDBOptions: # BoltDBOptions:
# FilePath: "./chains/testnet.bolt" # FilePath: "./chains/testnet.bolt"
RPCPort: 20332
NodePort: 20333 NodePort: 20333
Relay: true Relay: true
DialTimeout: 3 DialTimeout: 3
ProtoTickInterval: 2 ProtoTickInterval: 2
MaxPeers: 50 MaxPeers: 50
MinPeers: 5 MinPeers: 5
RPC:
Enabled: true
EnableCORSWorkaround: false
Port: 20332
Monitoring: Monitoring:
Enabled: true Enabled: true
Port: 2112 Port: 2112

View file

@ -32,13 +32,16 @@ ApplicationConfiguration:
# DB: 0 # DB: 0
# BoltDBOptions: # BoltDBOptions:
# FilePath: "./chains/unit_testnet.bolt" # FilePath: "./chains/unit_testnet.bolt"
RPCPort: 20332
NodePort: 20333 NodePort: 20333
Relay: true Relay: true
DialTimeout: 3 DialTimeout: 3
ProtoTickInterval: 2 ProtoTickInterval: 2
MaxPeers: 50 MaxPeers: 50
MinPeers: 1 MinPeers: 1
RPC:
Enabled: true
EnableCORSWorkaround: false
Port: 20332
Monitoring: Monitoring:
Enabled: false #since it's not useful for unit tests. Enabled: false #since it's not useful for unit tests.
Port: 2112 Port: 2112

2
go.mod
View file

@ -15,6 +15,8 @@ require (
github.com/gomodule/redigo v2.0.0+incompatible // indirect github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/mattn/go-colorable v0.1.2 // indirect github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.9 // indirect github.com/mattn/go-isatty v0.0.9 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/mr-tron/base58 v1.1.2 github.com/mr-tron/base58 v1.1.2
github.com/nspcc-dev/rfc6979 v0.1.0 github.com/nspcc-dev/rfc6979 v0.1.0
github.com/onsi/gomega v1.4.2 // indirect github.com/onsi/gomega v1.4.2 // indirect

View file

@ -21,6 +21,7 @@ type (
Method string `json:"method"` Method string `json:"method"`
RawParams json.RawMessage `json:"params,omitempty"` RawParams json.RawMessage `json:"params,omitempty"`
RawID json.RawMessage `json:"id,omitempty"` RawID json.RawMessage `json:"id,omitempty"`
enableCORSWorkaround bool
} }
// Response represents a standard JSON-RPC 2.0 // Response represents a standard JSON-RPC 2.0
@ -34,9 +35,10 @@ type (
) )
// NewRequest creates a new Request struct. // NewRequest creates a new Request struct.
func NewRequest() *Request { func NewRequest(corsWorkaround bool) *Request {
return &Request{ return &Request{
JSONRPC: jsonRPCVersion, JSONRPC: jsonRPCVersion,
enableCORSWorkaround: corsWorkaround,
} }
} }
@ -110,6 +112,11 @@ func (r Request) WriteResponse(w http.ResponseWriter, result interface{}) {
func (r Request) writeServerResponse(w http.ResponseWriter, response Response) { func (r Request) writeServerResponse(w http.ResponseWriter, response Response) {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
if r.enableCORSWorkaround {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With")
}
encoder := json.NewEncoder(w) encoder := json.NewEncoder(w)
err := encoder.Encode(response) err := encoder.Encode(response)

View file

@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto" "github.com/CityOfZion/neo-go/pkg/crypto"
@ -24,6 +25,7 @@ type (
Server struct { Server struct {
*http.Server *http.Server
chain core.Blockchainer chain core.Blockchainer
config config.RPCConfig
coreServer *network.Server coreServer *network.Server
} }
) )
@ -35,12 +37,15 @@ var (
) )
// NewServer creates a new Server struct. // NewServer creates a new Server struct.
func NewServer(chain core.Blockchainer, port uint16, coreServer *network.Server) Server { func NewServer(chain core.Blockchainer, conf config.RPCConfig, coreServer *network.Server) Server {
httpServer := &http.Server{
Addr: conf.Address + ":" + strconv.FormatUint(uint64(conf.Port), 10),
}
return Server{ return Server{
Server: &http.Server{ Server: httpServer,
Addr: ":" + strconv.FormatUint(uint64(port), 10),
},
chain: chain, chain: chain,
config: conf,
coreServer: coreServer, coreServer: coreServer,
} }
} }
@ -48,6 +53,10 @@ func NewServer(chain core.Blockchainer, port uint16, coreServer *network.Server)
// Start creates a new JSON-RPC server // Start creates a new JSON-RPC server
// listening on the configured port. // listening on the configured port.
func (s *Server) Start(errChan chan error) { func (s *Server) Start(errChan chan error) {
if !s.config.Enabled {
log.Info("RPC server is not enabled")
return
}
s.Handler = http.HandlerFunc(s.requestHandler) s.Handler = http.HandlerFunc(s.requestHandler)
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"endpoint": s.Addr, "endpoint": s.Addr,
@ -66,7 +75,7 @@ func (s *Server) Shutdown() error {
} }
func (s *Server) requestHandler(w http.ResponseWriter, httpRequest *http.Request) { func (s *Server) requestHandler(w http.ResponseWriter, httpRequest *http.Request) {
req := NewRequest() req := NewRequest(s.config.EnableCORSWorkaround)
if httpRequest.Method != "POST" { if httpRequest.Method != "POST" {
req.WriteErrorResponse( req.WriteErrorResponse(

View file

@ -167,7 +167,7 @@ func initServerWithInMemoryChain(ctx context.Context, t *testing.T) (*core.Block
serverConfig := network.NewServerConfig(cfg) serverConfig := network.NewServerConfig(cfg)
server := network.NewServer(serverConfig, chain) server := network.NewServer(serverConfig, chain)
rpcServer := NewServer(chain, cfg.ApplicationConfiguration.RPCPort, server) rpcServer := NewServer(chain, cfg.ApplicationConfiguration.RPC, server)
handler := http.HandlerFunc(rpcServer.requestHandler) handler := http.HandlerFunc(rpcServer.requestHandler)
return chain, handler return chain, handler