diff --git a/btcjson/chainsvrwscmds.go b/btcjson/chainsvrwscmds.go index ba8a5391..007dd83b 100644 --- a/btcjson/chainsvrwscmds.go +++ b/btcjson/chainsvrwscmds.go @@ -1,4 +1,4 @@ -// Copyright (c) 2014 The btcsuite developers +// Copyright (c) 2014-2015 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -56,6 +56,15 @@ func NewNotifyNewTransactionsCmd(verbose *bool) *NotifyNewTransactionsCmd { } } +// SessionCmd defines the session JSON-RPC command. +type SessionCmd struct{} + +// NewSessionCmd returns a new instance which can be used to issue a session +// JSON-RPC command. +func NewSessionCmd() *SessionCmd { + return &SessionCmd{} +} + // StopNotifyNewTransactionsCmd defines the stopnotifynewtransactions JSON-RPC command. type StopNotifyNewTransactionsCmd struct{} @@ -158,6 +167,7 @@ func init() { MustRegisterCmd("notifynewtransactions", (*NotifyNewTransactionsCmd)(nil), flags) MustRegisterCmd("notifyreceived", (*NotifyReceivedCmd)(nil), flags) MustRegisterCmd("notifyspent", (*NotifySpentCmd)(nil), flags) + MustRegisterCmd("session", (*SessionCmd)(nil), flags) MustRegisterCmd("stopnotifyblocks", (*StopNotifyBlocksCmd)(nil), flags) MustRegisterCmd("stopnotifynewtransactions", (*StopNotifyNewTransactionsCmd)(nil), flags) MustRegisterCmd("stopnotifyspent", (*StopNotifySpentCmd)(nil), flags) diff --git a/btcjson/chainsvrwsresults.go b/btcjson/chainsvrwsresults.go new file mode 100644 index 00000000..12968496 --- /dev/null +++ b/btcjson/chainsvrwsresults.go @@ -0,0 +1,10 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcjson + +// SessionResult models the data from the session command. +type SessionResult struct { + SessionID uint64 `json:"sessionid"` +} diff --git a/docs/json_rpc_api.md b/docs/json_rpc_api.md index 56ea1785..f0776cd3 100644 --- a/docs/json_rpc_api.md +++ b/docs/json_rpc_api.md @@ -685,6 +685,7 @@ user. Click the method name for further details such as parameter and return in |8|[rescan](#rescan)|Rescan block chain for transactions to addresses and spent transaction outpoints.|[recvtx](#recvtx), [redeemingtx](#redeemingtx), [rescanprogress](#rescanprogress), and [rescanfinished](#rescanfinished) | |9|[notifynewtransactions](#notifynewtransactions)|Send notifications for all new transactions as they are accepted into the mempool.|[txaccepted](#txaccepted) or [txacceptedverbose](#txacceptedverbose)| |10|[stopnotifynewtransactions](#stopnotifynewtransactions)|Stop sending either a txaccepted or a txacceptedverbose notification when a new transaction is accepted into the mempool.|None| +|11|[session](#session)|Return details regarding a websocket client's current connection.|None| **7.2 Method Details**
@@ -815,6 +816,20 @@ user. Click the method name for further details such as parameter and return in |Returns|Nothing| [Return to Overview](#WSExtMethodOverview)
+*** + +
+ +| | | +|---|---| +|Method|session| +|Notifications|None| +|Parameters|None| +|Description|Return a JSON object with details regarding a websocket client's current connection to the RPC server. This currently only includes the session ID, a random unsigned 64-bit integer that is created for each newly connected client. Session IDs may be used to verify that the current connection was not lost and subsequently reestablished.| +|Returns|`{ (json object)`
  `"sessionid": n (numeric) the session ID`
`}`| +|Example Return|`{`
  `"sessionid": 67089679842`
`}`| +[Return to Overview](#WSExtMethodOverview)
+
### 8. Notifications (Websocket-specific) diff --git a/rpcserverhelp.go b/rpcserverhelp.go index bf8c7519..53256379 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -525,6 +525,10 @@ var helpDescsEnUS = map[string]string{ // -------- Websocket-specific help -------- + // Session help. + "session--synopsis": "Return details regarding a websocket client's current connection session.", + "sessionresult-sessionid": "The unique session ID for a client's websocket connection.", + // NotifyBlocksCmd help. "notifyblocks--synopsis": "Request notifications for whenever a block is connected or disconnected from the main (best) chain.", @@ -616,6 +620,7 @@ var rpcResultTypes = map[string][]interface{}{ "verifymessage": []interface{}{(*bool)(nil)}, // Websocket commands. + "session": []interface{}{(*btcjson.SessionResult)(nil)}, "notifyblocks": nil, "stopnotifyblocks": nil, "notifynewtransactions": nil, diff --git a/rpcwebsocket.go b/rpcwebsocket.go index 725314c7..04c07889 100644 --- a/rpcwebsocket.go +++ b/rpcwebsocket.go @@ -54,6 +54,7 @@ var wsHandlersBeforeInit = map[string]wsCommandHandler{ "notifynewtransactions": handleNotifyNewTransactions, "notifyreceived": handleNotifyReceived, "notifyspent": handleNotifySpent, + "session": handleSession, "stopnotifyblocks": handleStopNotifyBlocks, "stopnotifynewtransactions": handleStopNotifyNewTransactions, "stopnotifyspent": handleStopNotifySpent, @@ -94,7 +95,12 @@ func (s *rpcServer) WebsocketHandler(conn *websocket.Conn, remoteAddr string, // Create a new websocket client to handle the new websocket connection // and wait for it to shutdown. Once it has shutdown (and hence // disconnected), remove it and any notifications it registered for. - client := newWebsocketClient(s, conn, remoteAddr, authenticated, isAdmin) + client, err := newWebsocketClient(s, conn, remoteAddr, authenticated, isAdmin) + if err != nil { + rpcsLog.Errorf("Failed to serve client %s: %v", remoteAddr, err) + conn.Close() + return + } s.ntfnMgr.AddClient(client) client.Start() client.WaitForShutdown() @@ -869,6 +875,11 @@ type wsClient struct { // false means its access is only to the limited set of RPC calls. isAdmin bool + // sessionID is a random ID generated for each client when connected. + // These IDs may be queried by a client using the session RPC. A change + // to the session ID indicates that the client reconnected. + sessionID uint64 + // verboseTxUpdates specifies whether a client has requested verbose // information about all new transactions. verboseTxUpdates bool @@ -1383,13 +1394,19 @@ func (c *wsClient) WaitForShutdown() { // incoming and outgoing messages in separate goroutines complete with queueing // and asynchrous handling for long-running operations. func newWebsocketClient(server *rpcServer, conn *websocket.Conn, - remoteAddr string, authenticated bool, isAdmin bool) *wsClient { + remoteAddr string, authenticated bool, isAdmin bool) (*wsClient, error) { - return &wsClient{ + sessionID, err := wire.RandomUint64() + if err != nil { + return nil, err + } + + client := &wsClient{ conn: conn, addr: remoteAddr, authenticated: authenticated, isAdmin: isAdmin, + sessionID: sessionID, server: server, addrRequests: make(map[string]struct{}), spentRequests: make(map[wire.OutPoint]struct{}), @@ -1398,6 +1415,7 @@ func newWebsocketClient(server *rpcServer, conn *websocket.Conn, sendChan: make(chan wsResponse, websocketSendBufferSize), quit: make(chan struct{}), } + return client, nil } // handleWebsocketHelp implements the help command for websocket connections. @@ -1454,6 +1472,12 @@ func handleNotifyBlocks(wsc *wsClient, icmd interface{}) (interface{}, error) { return nil, nil } +// handleSession implements the session command extension for websocket +// connections. +func handleSession(wsc *wsClient, icmd interface{}) (interface{}, error) { + return &btcjson.SessionResult{SessionID: wsc.sessionID}, nil +} + // handleStopNotifyBlocks implements the stopnotifyblocks command extension for // websocket connections. func handleStopNotifyBlocks(wsc *wsClient, icmd interface{}) (interface{}, error) {