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:
Josh Rickmar 2013-11-18 13:31:58 -05:00
parent 37109bfe0d
commit 28087af90b
3 changed files with 98 additions and 26 deletions

13
cmd.go
View file

@ -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.

View file

@ -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

View file

@ -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