Add handling for standard bitcoind-style RPC.
With the exception of the createencryptedwallet extension (which is required to make a wallet), all websocket-specific handlers are now only available from a websocket connection, and standard RPC requests are handled with a normal HTTP request and reply. As an added bonus, listening on IPv6 now works.
This commit is contained in:
parent
37109bfe0d
commit
28087af90b
3 changed files with 98 additions and 26 deletions
13
cmd.go
13
cmd.go
|
@ -316,14 +316,15 @@ func main() {
|
||||||
go DirtyAccountSyncer()
|
go DirtyAccountSyncer()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
s, err := newServer()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to create HTTP server: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
// Start HTTP server to listen and send messages to frontend and btcd
|
// Start HTTP server to listen and send messages to frontend and btcd
|
||||||
// backend. Try reconnection if connection failed.
|
// backend. Try reconnection if connection failed.
|
||||||
for {
|
s.Start()
|
||||||
if err := FrontendListenAndServe(); err != nil {
|
|
||||||
log.Info("Unable to start frontend HTTP server: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Begin generating new IDs for JSON calls.
|
// Begin generating new IDs for JSON calls.
|
||||||
|
|
19
cmdmgr.go
19
cmdmgr.go
|
@ -37,7 +37,7 @@ var (
|
||||||
|
|
||||||
type cmdHandler func(chan []byte, btcjson.Cmd)
|
type cmdHandler func(chan []byte, btcjson.Cmd)
|
||||||
|
|
||||||
var handlers = map[string]cmdHandler{
|
var rpcHandlers = map[string]cmdHandler{
|
||||||
// Standard bitcoind methods
|
// Standard bitcoind methods
|
||||||
"getaddressesbyaccount": GetAddressesByAccount,
|
"getaddressesbyaccount": GetAddressesByAccount,
|
||||||
"getbalance": GetBalance,
|
"getbalance": GetBalance,
|
||||||
|
@ -49,16 +49,20 @@ var handlers = map[string]cmdHandler{
|
||||||
"walletlock": WalletLock,
|
"walletlock": WalletLock,
|
||||||
"walletpassphrase": WalletPassphrase,
|
"walletpassphrase": WalletPassphrase,
|
||||||
|
|
||||||
// btcwallet extensions
|
// Extensions not exclusive to websocket connections.
|
||||||
"createencryptedwallet": CreateEncryptedWallet,
|
"createencryptedwallet": CreateEncryptedWallet,
|
||||||
"getbalances": GetBalances,
|
}
|
||||||
"walletislocked": WalletIsLocked,
|
|
||||||
|
// Extensions exclusive to websocket connections.
|
||||||
|
var wsHandlers = map[string]cmdHandler{
|
||||||
|
"getbalances": GetBalances,
|
||||||
|
"walletislocked": WalletIsLocked,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessFrontendMsg checks the message sent from a frontend. If the
|
// ProcessFrontendMsg checks the message sent from a frontend. If the
|
||||||
// message method is one that must be handled by btcwallet, the request
|
// message method is one that must be handled by btcwallet, the request
|
||||||
// is processed here. Otherwise, the message is sent to btcd.
|
// is processed here. Otherwise, the message is sent to btcd.
|
||||||
func ProcessFrontendMsg(frontend chan []byte, msg []byte) {
|
func ProcessFrontendMsg(frontend chan []byte, msg []byte, ws bool) {
|
||||||
// Parse marshaled command and check
|
// Parse marshaled command and check
|
||||||
cmd, err := btcjson.ParseMarshaledCmd(msg)
|
cmd, err := btcjson.ParseMarshaledCmd(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -71,13 +75,14 @@ func ProcessFrontendMsg(frontend chan []byte, msg []byte) {
|
||||||
|
|
||||||
// btcwallet cannot handle this command, so defer handling
|
// btcwallet cannot handle this command, so defer handling
|
||||||
// to btcd.
|
// to btcd.
|
||||||
fmt.Printf("deferring %v with error %v\n", string(msg), err)
|
|
||||||
DeferToBTCD(frontend, msg)
|
DeferToBTCD(frontend, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a handler to reply to cmd. If none exist, defer to btcd.
|
// Check for a handler to reply to cmd. If none exist, defer to btcd.
|
||||||
if f, ok := handlers[cmd.Method()]; ok {
|
if f, ok := rpcHandlers[cmd.Method()]; ok {
|
||||||
|
f(frontend, cmd)
|
||||||
|
} else if f, ok := wsHandlers[cmd.Method()]; ws && ok {
|
||||||
f(frontend, cmd)
|
f(frontend, cmd)
|
||||||
} else {
|
} else {
|
||||||
// btcwallet does not have a handler for the command. Pass
|
// btcwallet does not have a handler for the command. Pass
|
||||||
|
|
92
sockets.go
92
sockets.go
|
@ -84,6 +84,59 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// server holds the items the RPC server may need to access (auth,
|
||||||
|
// config, shutdown, etc.)
|
||||||
|
type server struct {
|
||||||
|
port string
|
||||||
|
wg sync.WaitGroup
|
||||||
|
listeners []net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
// newServer returns a new instance of the server struct.
|
||||||
|
func newServer() (*server, error) {
|
||||||
|
s := server{
|
||||||
|
port: cfg.SvrPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv4 listener.
|
||||||
|
var listeners []net.Listener
|
||||||
|
listenAddr4 := net.JoinHostPort("127.0.0.1", s.port)
|
||||||
|
listener4, err := net.Listen("tcp4", listenAddr4)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("RPCS: Couldn't create listener: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
listeners = append(listeners, listener4)
|
||||||
|
|
||||||
|
// IPv6 listener.
|
||||||
|
listenAddr6 := net.JoinHostPort("::1", s.port)
|
||||||
|
listener6, err := net.Listen("tcp6", listenAddr6)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("RPCS: Couldn't create listener: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
listeners = append(listeners, listener6)
|
||||||
|
|
||||||
|
s.listeners = listeners
|
||||||
|
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleRPCRequest processes a JSON-RPC request from a frontend.
|
||||||
|
func (s *server) handleRPCRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
frontend := make(chan []byte)
|
||||||
|
|
||||||
|
body, err := btcjson.GetRaw(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("RPCS: Error getting JSON message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessFrontendMsg(frontend, body, false)
|
||||||
|
if _, err := w.Write(<-frontend); err != nil {
|
||||||
|
log.Warnf("RPCS: could not respond to RPC request: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// frontendListenerDuplicator listens for new wallet listener channels
|
// frontendListenerDuplicator listens for new wallet listener channels
|
||||||
// and duplicates messages sent to frontendNotificationMaster to all
|
// and duplicates messages sent to frontendNotificationMaster to all
|
||||||
// connected listeners.
|
// connected listeners.
|
||||||
|
@ -158,12 +211,12 @@ func NotifyBtcdConnected(reply chan []byte, conn bool) {
|
||||||
frontendNotificationMaster <- ntfn
|
frontendNotificationMaster <- ntfn
|
||||||
}
|
}
|
||||||
|
|
||||||
// frontendReqsNotifications is the handler function for websocket
|
// frontendSendRecv is the handler function for websocket connections from
|
||||||
// connections from a btcwallet instance. It reads messages from wallet and
|
// a btcwallet instance. It reads requests and sends responses to a
|
||||||
// sends back replies, as well as notififying wallets of chain updates.
|
// frontend, as well as notififying wallets of chain updates. There can
|
||||||
// There can possibly be many of these running, one for each currently
|
// possibly be many of these running, one for each currently connected
|
||||||
// connected frontend.
|
// frontend.
|
||||||
func frontendReqsNotifications(ws *websocket.Conn) {
|
func frontendSendRecv(ws *websocket.Conn) {
|
||||||
// Add frontend notification channel to set so this handler receives
|
// Add frontend notification channel to set so this handler receives
|
||||||
// updates.
|
// updates.
|
||||||
frontendNotification := make(chan []byte)
|
frontendNotification := make(chan []byte)
|
||||||
|
@ -195,8 +248,8 @@ func frontendReqsNotifications(ws *websocket.Conn) {
|
||||||
// frontend disconnected.
|
// frontend disconnected.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Handle JSON message here.
|
// Handle request here.
|
||||||
go ProcessFrontendMsg(frontendNotification, m)
|
go ProcessFrontendMsg(frontendNotification, m, true)
|
||||||
case ntfn, _ := <-frontendNotification:
|
case ntfn, _ := <-frontendNotification:
|
||||||
if err := websocket.Message.Send(ws, ntfn); err != nil {
|
if err := websocket.Message.Send(ws, ntfn); err != nil {
|
||||||
// Frontend disconnected.
|
// Frontend disconnected.
|
||||||
|
@ -484,9 +537,9 @@ func NtfnTxMined(n btcws.Notification) {
|
||||||
|
|
||||||
var duplicateOnce sync.Once
|
var duplicateOnce sync.Once
|
||||||
|
|
||||||
// FrontendListenAndServe starts a HTTP server to provide websocket
|
// Start starts a HTTP server to provide standard RPC and extension
|
||||||
// connections for any number of btcwallet frontends.
|
// websocket connections for any number of btcwallet frontends.
|
||||||
func FrontendListenAndServe() error {
|
func (s *server) Start() {
|
||||||
// We'll need to duplicate replies to frontends to each frontend.
|
// We'll need to duplicate replies to frontends to each frontend.
|
||||||
// Replies are sent to frontendReplyMaster, and duplicated to each valid
|
// Replies are sent to frontendReplyMaster, and duplicated to each valid
|
||||||
// channel in frontendReplySet. This runs a goroutine to duplicate
|
// channel in frontendReplySet. This runs a goroutine to duplicate
|
||||||
|
@ -497,8 +550,21 @@ func FrontendListenAndServe() error {
|
||||||
|
|
||||||
// TODO(jrick): We need some sort of authentication before websocket
|
// TODO(jrick): We need some sort of authentication before websocket
|
||||||
// connections are allowed, and perhaps TLS on the server as well.
|
// connections are allowed, and perhaps TLS on the server as well.
|
||||||
http.Handle("/frontend", websocket.Handler(frontendReqsNotifications))
|
serveMux := http.NewServeMux()
|
||||||
return http.ListenAndServe(net.JoinHostPort("", cfg.SvrPort), nil)
|
httpServer := &http.Server{Handler: serveMux}
|
||||||
|
serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
s.handleRPCRequest(w, r)
|
||||||
|
})
|
||||||
|
serveMux.Handle("/frontend", websocket.Handler(frontendSendRecv))
|
||||||
|
for _, listener := range s.listeners {
|
||||||
|
s.wg.Add(1)
|
||||||
|
go func(listener net.Listener) {
|
||||||
|
log.Infof("RPCS: RPC server listening on %s", listener.Addr())
|
||||||
|
httpServer.Serve(listener)
|
||||||
|
log.Tracef("RPCS: RPC listener done for %s", listener.Addr())
|
||||||
|
s.wg.Done()
|
||||||
|
}(listener)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BtcdConnect connects to a running btcd instance over a websocket
|
// BtcdConnect connects to a running btcd instance over a websocket
|
||||||
|
|
Loading…
Reference in a new issue