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 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
|
||||
// backend. Try reconnection if connection failed.
|
||||
for {
|
||||
if err := FrontendListenAndServe(); err != nil {
|
||||
log.Info("Unable to start frontend HTTP server: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
s.Start()
|
||||
}()
|
||||
|
||||
// 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)
|
||||
|
||||
var handlers = map[string]cmdHandler{
|
||||
var rpcHandlers = map[string]cmdHandler{
|
||||
// Standard bitcoind methods
|
||||
"getaddressesbyaccount": GetAddressesByAccount,
|
||||
"getbalance": GetBalance,
|
||||
|
@ -49,16 +49,20 @@ var handlers = map[string]cmdHandler{
|
|||
"walletlock": WalletLock,
|
||||
"walletpassphrase": WalletPassphrase,
|
||||
|
||||
// btcwallet extensions
|
||||
// Extensions not exclusive to websocket connections.
|
||||
"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
|
||||
// message method is one that must be handled by btcwallet, the request
|
||||
// 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
|
||||
cmd, err := btcjson.ParseMarshaledCmd(msg)
|
||||
if err != nil {
|
||||
|
@ -71,13 +75,14 @@ func ProcessFrontendMsg(frontend chan []byte, msg []byte) {
|
|||
|
||||
// btcwallet cannot handle this command, so defer handling
|
||||
// to btcd.
|
||||
fmt.Printf("deferring %v with error %v\n", string(msg), err)
|
||||
DeferToBTCD(frontend, msg)
|
||||
return
|
||||
}
|
||||
|
||||
// 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)
|
||||
} else {
|
||||
// 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
|
||||
// and duplicates messages sent to frontendNotificationMaster to all
|
||||
// connected listeners.
|
||||
|
@ -158,12 +211,12 @@ func NotifyBtcdConnected(reply chan []byte, conn bool) {
|
|||
frontendNotificationMaster <- ntfn
|
||||
}
|
||||
|
||||
// frontendReqsNotifications is the handler function for websocket
|
||||
// connections from a btcwallet instance. It reads messages from wallet and
|
||||
// sends back replies, as well as notififying wallets of chain updates.
|
||||
// There can possibly be many of these running, one for each currently
|
||||
// connected frontend.
|
||||
func frontendReqsNotifications(ws *websocket.Conn) {
|
||||
// frontendSendRecv is the handler function for websocket connections from
|
||||
// a btcwallet instance. It reads requests and sends responses to a
|
||||
// frontend, as well as notififying wallets of chain updates. There can
|
||||
// possibly be many of these running, one for each currently connected
|
||||
// frontend.
|
||||
func frontendSendRecv(ws *websocket.Conn) {
|
||||
// Add frontend notification channel to set so this handler receives
|
||||
// updates.
|
||||
frontendNotification := make(chan []byte)
|
||||
|
@ -195,8 +248,8 @@ func frontendReqsNotifications(ws *websocket.Conn) {
|
|||
// frontend disconnected.
|
||||
return
|
||||
}
|
||||
// Handle JSON message here.
|
||||
go ProcessFrontendMsg(frontendNotification, m)
|
||||
// Handle request here.
|
||||
go ProcessFrontendMsg(frontendNotification, m, true)
|
||||
case ntfn, _ := <-frontendNotification:
|
||||
if err := websocket.Message.Send(ws, ntfn); err != nil {
|
||||
// Frontend disconnected.
|
||||
|
@ -484,9 +537,9 @@ func NtfnTxMined(n btcws.Notification) {
|
|||
|
||||
var duplicateOnce sync.Once
|
||||
|
||||
// FrontendListenAndServe starts a HTTP server to provide websocket
|
||||
// connections for any number of btcwallet frontends.
|
||||
func FrontendListenAndServe() error {
|
||||
// Start starts a HTTP server to provide standard RPC and extension
|
||||
// websocket connections for any number of btcwallet frontends.
|
||||
func (s *server) Start() {
|
||||
// We'll need to duplicate replies to frontends to each frontend.
|
||||
// Replies are sent to frontendReplyMaster, and duplicated to each valid
|
||||
// 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
|
||||
// connections are allowed, and perhaps TLS on the server as well.
|
||||
http.Handle("/frontend", websocket.Handler(frontendReqsNotifications))
|
||||
return http.ListenAndServe(net.JoinHostPort("", cfg.SvrPort), nil)
|
||||
serveMux := http.NewServeMux()
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue