Jelajahi Sumber

cmd, common, node, rpc: move HTTP RPC into node, drop singletone aspect

Péter Szilágyi 9 tahun lalu
induk
melakukan
a13bc9d7a1
10 mengubah file dengan 274 tambahan dan 215 penghapusan
  1. 0 5
      cmd/geth/main.go
  2. 8 29
      cmd/gethrpctest/main.go
  3. 20 28
      cmd/utils/flags.go
  4. 48 0
      common/defaults.go
  5. 0 22
      common/path.go
  6. 16 30
      node/api.go
  7. 36 1
      node/config.go
  8. 138 44
      node/node.go
  9. 7 54
      rpc/http.go
  10. 1 2
      rpc/utils.go

+ 0 - 5
cmd/geth/main.go

@@ -503,11 +503,6 @@ func startNode(ctx *cli.Context, stack *node.Node) {
 		}
 	}
 	// Start auxiliary services if enabled
-	if ctx.GlobalBool(utils.RPCEnabledFlag.Name) {
-		if err := utils.StartRPC(stack, ctx); err != nil {
-			utils.Fatalf("Failed to start RPC: %v", err)
-		}
-	}
 	if ctx.GlobalBool(utils.WSEnabledFlag.Name) {
 		if err := utils.StartWS(stack, ctx); err != nil {
 			utils.Fatalf("Failed to start WS: %v", err)

+ 8 - 29
cmd/gethrpctest/main.go

@@ -18,7 +18,6 @@
 package main
 
 import (
-	"errors"
 	"flag"
 	"io/ioutil"
 	"log"
@@ -26,10 +25,10 @@ import (
 	"os/signal"
 
 	"github.com/ethereum/go-ethereum/accounts"
+	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/eth"
 	"github.com/ethereum/go-ethereum/ethdb"
-	"github.com/ethereum/go-ethereum/logger"
 	"github.com/ethereum/go-ethereum/logger/glog"
 	"github.com/ethereum/go-ethereum/node"
 	"github.com/ethereum/go-ethereum/tests"
@@ -84,12 +83,6 @@ func main() {
 	}
 	log.Println("Initial test suite passed...")
 
-	// Start the RPC interface and wait until terminated
-	if err := StartRPC(stack); err != nil {
-		log.Fatalf("Failed to start RPC interface: %v", err)
-	}
-	log.Println("RPC Interface started, accepting requests...")
-
 	quit := make(chan os.Signal, 1)
 	signal.Notify(quit, os.Interrupt)
 	<-quit
@@ -99,7 +92,13 @@ func main() {
 // keystore path and initial pre-state.
 func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node.Node, error) {
 	// Create a networkless protocol stack
-	stack, err := node.New(&node.Config{IpcPath: node.DefaultIpcEndpoint(), NoDiscovery: true})
+	stack, err := node.New(&node.Config{
+		IpcPath:     node.DefaultIpcEndpoint(),
+		HttpHost:    common.DefaultHttpHost,
+		HttpPort:    common.DefaultHttpPort,
+		HttpModules: []string{"admin", "db", "eth", "debug", "miner", "net", "shh", "txpool", "personal", "web3"},
+		NoDiscovery: true,
+	})
 	if err != nil {
 		return nil, err
 	}
@@ -164,23 +163,3 @@ func RunTest(stack *node.Node, test *tests.BlockTest) error {
 	}
 	return nil
 }
-
-// StartRPC initializes an RPC interface to the given protocol stack.
-func StartRPC(stack *node.Node) error {
-	/*
-		web3 := NewPublicWeb3API(stack)
-		server.RegisterName("web3", web3)
-		net := NewPublicNetAPI(stack.Server(), ethereum.NetVersion())
-		server.RegisterName("net", net)
-	*/
-
-	for _, api := range stack.APIs() {
-		if adminApi, ok := api.Service.(*node.PrivateAdminAPI); ok {
-			_, err := adminApi.StartRPC("127.0.0.1", 8545, "", "admin,db,eth,debug,miner,net,shh,txpool,personal,web3")
-			return err
-		}
-	}
-
-	glog.V(logger.Error).Infof("Unable to start RPC-HTTP interface, could not find admin API")
-	return errors.New("Unable to start RPC-HTTP interface")
-}

+ 20 - 28
cmd/utils/flags.go

@@ -233,12 +233,12 @@ var (
 	RPCListenAddrFlag = cli.StringFlag{
 		Name:  "rpcaddr",
 		Usage: "HTTP-RPC server listening interface",
-		Value: "127.0.0.1",
+		Value: common.DefaultHttpHost,
 	}
 	RPCPortFlag = cli.IntFlag{
 		Name:  "rpcport",
 		Usage: "HTTP-RPC server listening port",
-		Value: 8545,
+		Value: common.DefaultHttpPort,
 	}
 	RPCCORSDomainFlag = cli.StringFlag{
 		Name:  "rpccorsdomain",
@@ -262,7 +262,7 @@ var (
 	IPCPathFlag = DirectoryFlag{
 		Name:  "ipcpath",
 		Usage: "Filename for IPC socket/pipe within the datadir (explicit paths escape it)",
-		Value: DirectoryString{common.DefaultIpcSocket()},
+		Value: DirectoryString{common.DefaultIpcSocket},
 	}
 	WSEnabledFlag = cli.BoolFlag{
 		Name:  "ws",
@@ -271,12 +271,12 @@ var (
 	WSListenAddrFlag = cli.StringFlag{
 		Name:  "wsaddr",
 		Usage: "WS-RPC server listening interface",
-		Value: "127.0.0.1",
+		Value: common.DefaultWsHost,
 	}
 	WSPortFlag = cli.IntFlag{
 		Name:  "wsport",
 		Usage: "WS-RPC server listening port",
-		Value: 8546,
+		Value: common.DefaultWsPort,
 	}
 	WSApiFlag = cli.StringFlag{
 		Name:  "wsapi",
@@ -284,7 +284,7 @@ var (
 		Value: rpc.DefaultHttpRpcApis,
 	}
 	WSAllowedDomainsFlag = cli.StringFlag{
-		Name:  "wsdomains",
+		Name:  "wscors",
 		Usage: "Domains from which to accept websockets requests",
 		Value: "",
 	}
@@ -482,6 +482,15 @@ func MakeNAT(ctx *cli.Context) nat.Interface {
 	return natif
 }
 
+// MakeHttpRpcHost creates the HTTP RPC listener interface string from the set
+// command line flags, returning empty if the HTTP endpoint is disabled.
+func MakeHttpRpcHost(ctx *cli.Context) string {
+	if !ctx.GlobalBool(RPCEnabledFlag.Name) {
+		return ""
+	}
+	return ctx.GlobalString(RPCListenAddrFlag.Name)
+}
+
 // MakeGenesisBlock loads up a genesis block from an input file specified in the
 // command line, or returns the empty string if none set.
 func MakeGenesisBlock(ctx *cli.Context) string {
@@ -591,7 +600,6 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.
 	// Configure the node's service container
 	stackConf := &node.Config{
 		DataDir:         MustMakeDataDir(ctx),
-		IpcPath:         MakeIpcPath(ctx),
 		PrivateKey:      MakeNodeKey(ctx),
 		Name:            MakeNodeName(name, version, ctx),
 		NoDiscovery:     ctx.GlobalBool(NoDiscoverFlag.Name),
@@ -600,6 +608,11 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.
 		NAT:             MakeNAT(ctx),
 		MaxPeers:        ctx.GlobalInt(MaxPeersFlag.Name),
 		MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name),
+		IpcPath:         MakeIpcPath(ctx),
+		HttpHost:        MakeHttpRpcHost(ctx),
+		HttpPort:        ctx.GlobalInt(RPCPortFlag.Name),
+		HttpCors:        ctx.GlobalString(RPCCORSDomainFlag.Name),
+		HttpModules:     strings.Split(ctx.GlobalString(RPCApiFlag.Name), ","),
 	}
 	// Configure the Ethereum service
 	accman := MakeAccountManager(ctx)
@@ -744,27 +757,6 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database
 	return chain, chainDb
 }
 
-// StartRPC starts a HTTP JSON-RPC API server.
-func StartRPC(stack *node.Node, ctx *cli.Context) error {
-	for _, api := range stack.APIs() {
-		if adminApi, ok := api.Service.(*node.PrivateAdminAPI); ok {
-			address := ctx.GlobalString(RPCListenAddrFlag.Name)
-			port := ctx.GlobalInt(RPCPortFlag.Name)
-			cors := ctx.GlobalString(RPCCORSDomainFlag.Name)
-			apiStr := ""
-			if ctx.GlobalIsSet(RPCApiFlag.Name) {
-				apiStr = ctx.GlobalString(RPCApiFlag.Name)
-			}
-
-			_, err := adminApi.StartRPC(address, port, cors, apiStr)
-			return err
-		}
-	}
-
-	glog.V(logger.Error).Infof("Unable to start RPC-HTTP interface, could not find admin API")
-	return errors.New("Unable to start RPC-HTTP interface")
-}
-
 // StartWS starts a websocket JSON-RPC API server.
 func StartWS(stack *node.Node, ctx *cli.Context) error {
 	for _, api := range stack.APIs() {

+ 48 - 0
common/defaults.go

@@ -0,0 +1,48 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package common
+
+import (
+	"path/filepath"
+	"runtime"
+)
+
+const (
+	DefaultIpcSocket = "geth.ipc"  // Default (relative) name of the IPC RPC socket
+	DefaultHttpHost  = "localhost" // Default host interface for the HTTP RPC server
+	DefaultHttpPort  = 8545        // Default TCP port for the HTTP RPC server
+	DefaultWsHost    = "localhost" // Default host interface for the websocket RPC server
+	DefaultWsPort    = 8546        // Default TCP port for the websocket RPC server
+)
+
+// DefaultDataDir is the default data directory to use for the databases and other
+// persistence requirements.
+func DefaultDataDir() string {
+	// Try to place the data folder in the user's home dir
+	home := HomeDir()
+	if home != "" {
+		if runtime.GOOS == "darwin" {
+			return filepath.Join(home, "Library", "Ethereum")
+		} else if runtime.GOOS == "windows" {
+			return filepath.Join(home, "AppData", "Roaming", "Ethereum")
+		} else {
+			return filepath.Join(home, ".ethereum")
+		}
+	}
+	// As we cannot guess a stable location, return empty and handle later
+	return ""
+}

+ 0 - 22
common/path.go

@@ -72,25 +72,3 @@ func HomeDir() string {
 	}
 	return ""
 }
-
-func DefaultDataDir() string {
-	// Try to place the data folder in the user's home dir
-	home := HomeDir()
-	if home != "" {
-		if runtime.GOOS == "darwin" {
-			return filepath.Join(home, "Library", "Ethereum")
-		} else if runtime.GOOS == "windows" {
-			return filepath.Join(home, "AppData", "Roaming", "Ethereum")
-		} else {
-			return filepath.Join(home, ".ethereum")
-		}
-	}
-	// As we cannot guess a stable location, return empty and handle later
-	return ""
-}
-
-// DefaultIpcSocket returns the relative name of the default IPC socket. The path
-// resolution is done by a node with other contextual infos.
-func DefaultIpcSocket() string {
-	return "geth.ipc"
-}

+ 16 - 30
node/api.go

@@ -27,7 +27,6 @@ import (
 	"github.com/ethereum/go-ethereum/p2p/discover"
 	"github.com/ethereum/go-ethereum/rpc"
 	"github.com/rcrowley/go-metrics"
-
 	"gopkg.in/fatih/set.v0"
 )
 
@@ -61,42 +60,29 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) {
 }
 
 // StartRPC starts the HTTP RPC API server.
-func (api *PrivateAdminAPI) StartRPC(address string, port int, cors string, apis string) (bool, error) {
-	var offeredAPIs []rpc.API
-	if len(apis) > 0 {
-		namespaces := set.New()
-		for _, a := range strings.Split(apis, ",") {
-			namespaces.Add(strings.TrimSpace(a))
-		}
-		for _, api := range api.node.APIs() {
-			if namespaces.Has(api.Namespace) {
-				offeredAPIs = append(offeredAPIs, api)
-			}
-		}
-	} else { // use by default all public API's
-		for _, api := range api.node.APIs() {
-			if api.Public {
-				offeredAPIs = append(offeredAPIs, api)
-			}
-		}
-	}
+func (api *PrivateAdminAPI) StartRPC(host string, port int, cors string, apis string) (bool, error) {
+	api.node.lock.Lock()
+	defer api.node.lock.Unlock()
 
-	if address == "" {
-		address = "127.0.0.1"
+	if api.node.httpHandler != nil {
+		return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpEndpoint)
 	}
-	if port == 0 {
-		port = 8545
+	if err := api.node.startHTTP(fmt.Sprintf("%s:%d", host, port), api.node.rpcAPIs, strings.Split(apis, ","), cors); err != nil {
+		return false, err
 	}
-
-	corsDomains := strings.Split(cors, " ")
-	err := rpc.StartHTTP(address, port, corsDomains, offeredAPIs)
-	return err == nil, err
+	return true, nil
 }
 
 // StopRPC terminates an already running HTTP RPC API endpoint.
 func (api *PrivateAdminAPI) StopRPC() (bool, error) {
-	err := rpc.StopHTTP()
-	return err == nil, err
+	api.node.lock.Lock()
+	defer api.node.lock.Unlock()
+
+	if api.node.httpHandler == nil {
+		return false, fmt.Errorf("HTTP RPC not running")
+	}
+	api.node.stopHTTP()
+	return true, nil
 }
 
 // StartWS starts the websocket RPC API server.

+ 36 - 1
node/config.go

@@ -19,6 +19,7 @@ package node
 import (
 	"crypto/ecdsa"
 	"encoding/json"
+	"fmt"
 	"io/ioutil"
 	"net"
 	"os"
@@ -97,6 +98,25 @@ type Config struct {
 	// handshake phase, counted separately for inbound and outbound connections.
 	// Zero defaults to preset values.
 	MaxPendingPeers int
+
+	// HttpHost is the host interface on which to start the HTTP RPC server. If this
+	// field is empty, no HTTP API endpoint will be started.
+	HttpHost string
+
+	// HttpPort is the TCP port number on which to start the HTTP RPC server. The
+	// default zero value is/ valid and will pick a port number randomly (useful
+	// for ephemeral nodes).
+	HttpPort int
+
+	// HttpCors is the Cross-Origin Resource Sharing header to send to requesting
+	// clients. Please be aware that CORS is a browser enforced security, it's fully
+	// useless for custom HTTP clients.
+	HttpCors string
+
+	// HttpModules is a list of API modules to expose via the HTTP RPC interface.
+	// If the module list is empty, all RPC API endpoints designated public will be
+	// exposed.
+	HttpModules []string
 }
 
 // IpcEndpoint resolves an IPC endpoint based on a configured value, taking into
@@ -126,10 +146,25 @@ func (c *Config) IpcEndpoint() string {
 
 // DefaultIpcEndpoint returns the IPC path used by default.
 func DefaultIpcEndpoint() string {
-	config := &Config{DataDir: common.DefaultDataDir(), IpcPath: common.DefaultIpcSocket()}
+	config := &Config{DataDir: common.DefaultDataDir(), IpcPath: common.DefaultIpcSocket}
 	return config.IpcEndpoint()
 }
 
+// HttpEndpoint resolves an HTTP endpoint based on the configured host interface
+// and port parameters.
+func (c *Config) HttpEndpoint() string {
+	if c.HttpHost == "" {
+		return ""
+	}
+	return fmt.Sprintf("%s:%d", c.HttpHost, c.HttpPort)
+}
+
+// DefaultHttpEndpoint returns the HTTP endpoint used by default.
+func DefaultHttpEndpoint() string {
+	config := &Config{HttpHost: common.DefaultHttpHost, HttpPort: common.DefaultHttpPort}
+	return config.HttpEndpoint()
+}
+
 // NodeKey retrieves the currently configured private key of the node, checking
 // first any manually set key, falling back to the one found in the configured
 // data folder. If no key can be found, a new one is generated.

+ 138 - 44
node/node.go

@@ -55,10 +55,17 @@ type Node struct {
 	serviceFuncs []ServiceConstructor     // Service constructors (in dependency order)
 	services     map[reflect.Type]Service // Currently running services
 
+	rpcAPIs     []rpc.API    // List of APIs currently provided by the node
 	ipcEndpoint string       // IPC endpoint to listen at (empty = IPC disabled)
 	ipcListener net.Listener // IPC RPC listener socket to serve API requests
 	ipcHandler  *rpc.Server  // IPC RPC request handler to process the API requests
 
+	httpEndpoint  string       // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled)
+	httpWhitelist []string     // HTTP RPC modules to allow through this endpoint
+	httpCors      string       // HTTP RPC Cross-Origin Resource Sharing header
+	httpListener  net.Listener // HTTP RPC listener socket to server API requests
+	httpHandler   *rpc.Server  // HTTP RPC request handler to process the API requests
+
 	stop chan struct{} // Channel to wait for termination notifications
 	lock sync.RWMutex
 }
@@ -93,9 +100,12 @@ func New(conf *Config) (*Node, error) {
 			MaxPeers:        conf.MaxPeers,
 			MaxPendingPeers: conf.MaxPendingPeers,
 		},
-		serviceFuncs: []ServiceConstructor{},
-		ipcEndpoint:  conf.IpcEndpoint(),
-		eventmux:     new(event.TypeMux),
+		serviceFuncs:  []ServiceConstructor{},
+		ipcEndpoint:   conf.IpcEndpoint(),
+		httpEndpoint:  conf.HttpEndpoint(),
+		httpWhitelist: conf.HttpModules,
+		httpCors:      conf.HttpCors,
+		eventmux:      new(event.TypeMux),
 	}, nil
 }
 
@@ -188,58 +198,146 @@ func (n *Node) Start() error {
 	return nil
 }
 
-// startRPC initializes and starts the IPC RPC endpoints.
+// startRPC is a helper method to start all the various RPC endpoint during node
+// startup. It's not meant to be called at any time afterwards as it makes certain
+// assumptions about the state of the node.
 func (n *Node) startRPC(services map[reflect.Type]Service) error {
-	// Gather and register all the APIs exposed by the services
+	// Gather all the possible APIs to surface
 	apis := n.apis()
 	for _, service := range services {
 		apis = append(apis, service.APIs()...)
 	}
-	ipcHandler := rpc.NewServer()
+	// Start the various API endpoints, terminating all in case of errors
+	if err := n.startIPC(apis); err != nil {
+		return err
+	}
+	if err := n.startHTTP(n.httpEndpoint, apis, n.httpWhitelist, n.httpCors); err != nil {
+		n.stopIPC()
+		return err
+	}
+	// All API endpoints started successfully
+	n.rpcAPIs = apis
+	return nil
+}
+
+// startIPC initializes and starts the IPC RPC endpoint.
+func (n *Node) startIPC(apis []rpc.API) error {
+	// Short circuit if the IPC endpoint isn't being exposed
+	if n.ipcEndpoint == "" {
+		return nil
+	}
+	// Register all the APIs exposed by the services
+	handler := rpc.NewServer()
 	for _, api := range apis {
-		if err := ipcHandler.RegisterName(api.Namespace, api.Service); err != nil {
+		if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
 			return err
 		}
-		glog.V(logger.Debug).Infof("Register %T under namespace '%s'", api.Service, api.Namespace)
+		glog.V(logger.Debug).Infof("IPC registered %T under '%s'", api.Service, api.Namespace)
 	}
-	// All APIs registered, start the IPC and HTTP listeners
+	// All APIs registered, start the IPC listener
 	var (
-		ipcListener net.Listener
-		err         error
+		listener net.Listener
+		err      error
 	)
-	if n.ipcEndpoint != "" {
-		if ipcListener, err = rpc.CreateIPCListener(n.ipcEndpoint); err != nil {
-			return err
-		}
-		go func() {
-			glog.V(logger.Info).Infof("IPC endpoint opened: %s", n.ipcEndpoint)
-			defer glog.V(logger.Info).Infof("IPC endpoint closed: %s", n.ipcEndpoint)
-
-			for {
-				conn, err := ipcListener.Accept()
-				if err != nil {
-					// Terminate if the listener was closed
-					n.lock.RLock()
-					closed := n.ipcListener == nil
-					n.lock.RUnlock()
-					if closed {
-						return
-					}
-					// Not closed, just some error; report and continue
-					glog.V(logger.Error).Infof("IPC accept failed: %v", err)
-					continue
+	if listener, err = rpc.CreateIPCListener(n.ipcEndpoint); err != nil {
+		return err
+	}
+	go func() {
+		glog.V(logger.Info).Infof("IPC endpoint opened: %s", n.ipcEndpoint)
+
+		for {
+			conn, err := listener.Accept()
+			if err != nil {
+				// Terminate if the listener was closed
+				n.lock.RLock()
+				closed := n.ipcListener == nil
+				n.lock.RUnlock()
+				if closed {
+					return
 				}
-				go ipcHandler.ServeCodec(rpc.NewJSONCodec(conn))
+				// Not closed, just some error; report and continue
+				glog.V(logger.Error).Infof("IPC accept failed: %v", err)
+				continue
+			}
+			go handler.ServeCodec(rpc.NewJSONCodec(conn))
+		}
+	}()
+	// All listeners booted successfully
+	n.ipcListener = listener
+	n.ipcHandler = handler
+
+	return nil
+}
+
+// stopIPC terminates the IPC RPC endpoint.
+func (n *Node) stopIPC() {
+	if n.ipcListener != nil {
+		n.ipcListener.Close()
+		n.ipcListener = nil
+
+		glog.V(logger.Info).Infof("IPC endpoint closed: %s", n.ipcEndpoint)
+	}
+	if n.ipcHandler != nil {
+		n.ipcHandler.Stop()
+		n.ipcHandler = nil
+	}
+}
+
+// startHTTP initializes and starts the HTTP RPC endpoint.
+func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors string) error {
+	// Short circuit if the IPC endpoint isn't being exposed
+	if endpoint == "" {
+		return nil
+	}
+	// Generate the whitelist based on the allowed modules
+	whitelist := make(map[string]bool)
+	for _, module := range modules {
+		whitelist[module] = true
+	}
+	// Register all the APIs exposed by the services
+	handler := rpc.NewServer()
+	for _, api := range apis {
+		if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
+			if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
+				return err
 			}
-		}()
+			glog.V(logger.Debug).Infof("HTTP registered %T under '%s'", api.Service, api.Namespace)
+		}
 	}
+	// All APIs registered, start the HTTP listener
+	var (
+		listener net.Listener
+		err      error
+	)
+	if listener, err = net.Listen("tcp", endpoint); err != nil {
+		return err
+	}
+	go rpc.NewHTTPServer(cors, handler).Serve(listener)
+	glog.V(logger.Info).Infof("HTTP endpoint opened: http://%s", endpoint)
+
 	// All listeners booted successfully
-	n.ipcListener = ipcListener
-	n.ipcHandler = ipcHandler
+	n.httpEndpoint = endpoint
+	n.httpListener = listener
+	n.httpHandler = handler
+	n.httpCors = cors
 
 	return nil
 }
 
+// stopHTTP terminates the HTTP RPC endpoint.
+func (n *Node) stopHTTP() {
+	if n.httpListener != nil {
+		n.httpListener.Close()
+		n.httpListener = nil
+
+		glog.V(logger.Info).Infof("HTTP endpoint closed: http://%s", n.httpEndpoint)
+	}
+	if n.httpHandler != nil {
+		n.httpHandler.Stop()
+		n.httpHandler = nil
+	}
+}
+
 // Stop terminates a running node along with all it's services. In the node was
 // not started, an error is returned.
 func (n *Node) Stop() error {
@@ -251,14 +349,10 @@ func (n *Node) Stop() error {
 		return ErrNodeStopped
 	}
 	// Otherwise terminate the API, all services and the P2P server too
-	if n.ipcListener != nil {
-		n.ipcListener.Close()
-		n.ipcListener = nil
-	}
-	if n.ipcHandler != nil {
-		n.ipcHandler.Stop()
-		n.ipcHandler = nil
-	}
+	n.stopIPC()
+	n.stopHTTP()
+	n.rpcAPIs = nil
+
 	failure := &StopError{
 		Services: make(map[reflect.Type]error),
 	}

+ 7 - 54
rpc/http.go

@@ -20,7 +20,6 @@ import (
 	"bufio"
 	"bytes"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -29,7 +28,6 @@ import (
 	"net/url"
 	"strconv"
 	"strings"
-	"sync"
 	"time"
 
 	"github.com/ethereum/go-ethereum/logger"
@@ -41,12 +39,6 @@ const (
 	httpReadDeadLine = 60 * time.Second // wait max httpReadDeadeline for next request
 )
 
-var (
-	httpServerMu  sync.Mutex   // prevent concurrent access to the httpListener and httpServer
-	httpListener  net.Listener // listener for the http server
-	httpRPCServer *Server      // the node can only start 1 HTTP RPC server instance
-)
-
 // httpMessageStream is the glue between a HTTP connection which is message based
 // and the RPC codecs that expect json requests to be read from a stream. It will
 // parse HTTP messages and offer the bodies of these requests as a stream through
@@ -249,53 +241,14 @@ func (h *httpConnHijacker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	go h.rpcServer.ServeCodec(codec)
 }
 
-// StartHTTP will start the JSONRPC HTTP RPC interface when its not yet running.
-func StartHTTP(address string, port int, corsdomains []string, apis []API) error {
-	httpServerMu.Lock()
-	defer httpServerMu.Unlock()
-
-	if httpRPCServer != nil {
-		return fmt.Errorf("HTTP RPC interface already started on %s", httpListener.Addr())
-	}
-
-	rpcServer := NewServer()
-
-	for _, api := range apis {
-		if err := rpcServer.RegisterName(api.Namespace, api.Service); err != nil {
-			return err
-		}
-	}
-
-	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", address, port))
-	if err != nil {
-		return err
+// NewHTTPServer creates a new HTTP RPC server around an API provider.
+func NewHTTPServer(cors string, handler *Server) *http.Server {
+	return &http.Server{
+		Handler: &httpConnHijacker{
+			corsdomains: strings.Split(cors, ","),
+			rpcServer:   handler,
+		},
 	}
-
-	httpServer := http.Server{Handler: &httpConnHijacker{corsdomains, rpcServer}}
-	go httpServer.Serve(listener)
-
-	httpListener = listener
-	httpRPCServer = rpcServer
-
-	return nil
-}
-
-// StopHTTP will stop the running HTTP interface. If it is not running an error will be returned.
-func StopHTTP() error {
-	httpServerMu.Lock()
-	defer httpServerMu.Unlock()
-
-	if httpRPCServer == nil {
-		return errors.New("HTTP RPC interface not started")
-	}
-
-	httpListener.Close()
-	httpRPCServer.Stop()
-
-	httpRPCServer = nil
-	httpListener = nil
-
-	return nil
 }
 
 // httpClient connects to a geth RPC server over HTTP.

+ 1 - 2
rpc/utils.go

@@ -20,13 +20,12 @@ import (
 	"crypto/rand"
 	"encoding/hex"
 	"errors"
+	"fmt"
 	"math/big"
 	"reflect"
 	"unicode"
 	"unicode/utf8"
 
-	"fmt"
-
 	"golang.org/x/net/context"
 )