You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

432 lines
13 KiB

rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
  1. package server
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "net/http"
  7. "reflect"
  8. "runtime/debug"
  9. "time"
  10. "github.com/gorilla/websocket"
  11. "github.com/tendermint/tendermint/libs/log"
  12. "github.com/tendermint/tendermint/libs/service"
  13. types "github.com/tendermint/tendermint/rpc/jsonrpc/types"
  14. )
  15. ///////////////////////////////////////////////////////////////////////////////
  16. // WebSocket handler
  17. ///////////////////////////////////////////////////////////////////////////////
  18. const (
  19. defaultWSWriteChanCapacity = 1000
  20. defaultWSWriteWait = 10 * time.Second
  21. defaultWSReadWait = 30 * time.Second
  22. defaultWSPingPeriod = (defaultWSReadWait * 9) / 10
  23. )
  24. // WebsocketManager provides a WS handler for incoming connections and passes a
  25. // map of functions along with any additional params to new connections.
  26. // NOTE: The websocket path is defined externally, e.g. in node/node.go
  27. type WebsocketManager struct {
  28. websocket.Upgrader
  29. funcMap map[string]*RPCFunc
  30. logger log.Logger
  31. wsConnOptions []func(*wsConnection)
  32. }
  33. // NewWebsocketManager returns a new WebsocketManager that passes a map of
  34. // functions, connection options and logger to new WS connections.
  35. func NewWebsocketManager(
  36. funcMap map[string]*RPCFunc,
  37. wsConnOptions ...func(*wsConnection),
  38. ) *WebsocketManager {
  39. return &WebsocketManager{
  40. funcMap: funcMap,
  41. Upgrader: websocket.Upgrader{
  42. CheckOrigin: func(r *http.Request) bool {
  43. // TODO ???
  44. //
  45. // The default behaviour would be relevant to browser-based clients,
  46. // afaik. I suppose having a pass-through is a workaround for allowing
  47. // for more complex security schemes, shifting the burden of
  48. // AuthN/AuthZ outside the Tendermint RPC.
  49. // I can't think of other uses right now that would warrant a TODO
  50. // though. The real backstory of this TODO shall remain shrouded in
  51. // mystery
  52. return true
  53. },
  54. },
  55. logger: log.NewNopLogger(),
  56. wsConnOptions: wsConnOptions,
  57. }
  58. }
  59. // SetLogger sets the logger.
  60. func (wm *WebsocketManager) SetLogger(l log.Logger) {
  61. wm.logger = l
  62. }
  63. // WebsocketHandler upgrades the request/response (via http.Hijack) and starts
  64. // the wsConnection.
  65. func (wm *WebsocketManager) WebsocketHandler(w http.ResponseWriter, r *http.Request) {
  66. wsConn, err := wm.Upgrade(w, r, nil)
  67. if err != nil {
  68. // TODO - return http error
  69. wm.logger.Error("Failed to upgrade connection", "err", err)
  70. return
  71. }
  72. defer func() {
  73. if err := wsConn.Close(); err != nil {
  74. wm.logger.Error("Failed to close connection", "err", err)
  75. }
  76. }()
  77. // register connection
  78. con := newWSConnection(wsConn, wm.funcMap, wm.wsConnOptions...)
  79. con.SetLogger(wm.logger.With("remote", wsConn.RemoteAddr()))
  80. wm.logger.Info("New websocket connection", "remote", con.remoteAddr)
  81. err = con.Start() // BLOCKING
  82. if err != nil {
  83. wm.logger.Error("Failed to start connection", "err", err)
  84. return
  85. }
  86. con.Stop()
  87. }
  88. ///////////////////////////////////////////////////////////////////////////////
  89. // WebSocket connection
  90. ///////////////////////////////////////////////////////////////////////////////
  91. // A single websocket connection contains listener id, underlying ws
  92. // connection, and the event switch for subscribing to events.
  93. //
  94. // In case of an error, the connection is stopped.
  95. type wsConnection struct {
  96. service.BaseService
  97. remoteAddr string
  98. baseConn *websocket.Conn
  99. // writeChan is never closed, to allow WriteRPCResponse() to fail.
  100. writeChan chan types.RPCResponse
  101. // chan, which is closed when/if readRoutine errors
  102. // used to abort writeRoutine
  103. readRoutineQuit chan struct{}
  104. funcMap map[string]*RPCFunc
  105. // write channel capacity
  106. writeChanCapacity int
  107. // each write times out after this.
  108. writeWait time.Duration
  109. // Connection times out if we haven't received *anything* in this long, not even pings.
  110. readWait time.Duration
  111. // Send pings to server with this period. Must be less than readWait, but greater than zero.
  112. pingPeriod time.Duration
  113. // Maximum message size.
  114. readLimit int64
  115. // callback which is called upon disconnect
  116. onDisconnect func(remoteAddr string)
  117. ctx context.Context
  118. cancel context.CancelFunc
  119. }
  120. // NewWSConnection wraps websocket.Conn.
  121. //
  122. // See the commentary on the func(*wsConnection) functions for a detailed
  123. // description of how to configure ping period and pong wait time. NOTE: if the
  124. // write buffer is full, pongs may be dropped, which may cause clients to
  125. // disconnect. see https://github.com/gorilla/websocket/issues/97
  126. func newWSConnection(
  127. baseConn *websocket.Conn,
  128. funcMap map[string]*RPCFunc,
  129. options ...func(*wsConnection),
  130. ) *wsConnection {
  131. wsc := &wsConnection{
  132. remoteAddr: baseConn.RemoteAddr().String(),
  133. baseConn: baseConn,
  134. funcMap: funcMap,
  135. writeWait: defaultWSWriteWait,
  136. writeChanCapacity: defaultWSWriteChanCapacity,
  137. readWait: defaultWSReadWait,
  138. pingPeriod: defaultWSPingPeriod,
  139. readRoutineQuit: make(chan struct{}),
  140. }
  141. for _, option := range options {
  142. option(wsc)
  143. }
  144. wsc.baseConn.SetReadLimit(wsc.readLimit)
  145. wsc.BaseService = *service.NewBaseService(nil, "wsConnection", wsc)
  146. return wsc
  147. }
  148. // OnDisconnect sets a callback which is used upon disconnect - not
  149. // Goroutine-safe. Nop by default.
  150. func OnDisconnect(onDisconnect func(remoteAddr string)) func(*wsConnection) {
  151. return func(wsc *wsConnection) {
  152. wsc.onDisconnect = onDisconnect
  153. }
  154. }
  155. // WriteWait sets the amount of time to wait before a websocket write times out.
  156. // It should only be used in the constructor - not Goroutine-safe.
  157. func WriteWait(writeWait time.Duration) func(*wsConnection) {
  158. return func(wsc *wsConnection) {
  159. wsc.writeWait = writeWait
  160. }
  161. }
  162. // WriteChanCapacity sets the capacity of the websocket write channel.
  163. // It should only be used in the constructor - not Goroutine-safe.
  164. func WriteChanCapacity(cap int) func(*wsConnection) {
  165. return func(wsc *wsConnection) {
  166. wsc.writeChanCapacity = cap
  167. }
  168. }
  169. // ReadWait sets the amount of time to wait before a websocket read times out.
  170. // It should only be used in the constructor - not Goroutine-safe.
  171. func ReadWait(readWait time.Duration) func(*wsConnection) {
  172. return func(wsc *wsConnection) {
  173. wsc.readWait = readWait
  174. }
  175. }
  176. // PingPeriod sets the duration for sending websocket pings.
  177. // It should only be used in the constructor - not Goroutine-safe.
  178. func PingPeriod(pingPeriod time.Duration) func(*wsConnection) {
  179. return func(wsc *wsConnection) {
  180. wsc.pingPeriod = pingPeriod
  181. }
  182. }
  183. // ReadLimit sets the maximum size for reading message.
  184. // It should only be used in the constructor - not Goroutine-safe.
  185. func ReadLimit(readLimit int64) func(*wsConnection) {
  186. return func(wsc *wsConnection) {
  187. wsc.readLimit = readLimit
  188. }
  189. }
  190. // OnStart implements service.Service by starting the read and write routines. It
  191. // blocks until there's some error.
  192. func (wsc *wsConnection) OnStart() error {
  193. wsc.writeChan = make(chan types.RPCResponse, wsc.writeChanCapacity)
  194. // Read subscriptions/unsubscriptions to events
  195. go wsc.readRoutine()
  196. // Write responses, BLOCKING.
  197. wsc.writeRoutine()
  198. return nil
  199. }
  200. // OnStop implements service.Service by unsubscribing remoteAddr from all
  201. // subscriptions.
  202. func (wsc *wsConnection) OnStop() {
  203. if wsc.onDisconnect != nil {
  204. wsc.onDisconnect(wsc.remoteAddr)
  205. }
  206. if wsc.ctx != nil {
  207. wsc.cancel()
  208. }
  209. }
  210. // GetRemoteAddr returns the remote address of the underlying connection.
  211. // It implements WSRPCConnection
  212. func (wsc *wsConnection) GetRemoteAddr() string {
  213. return wsc.remoteAddr
  214. }
  215. // WriteRPCResponse pushes a response to the writeChan, and blocks until it is accepted.
  216. // It implements WSRPCConnection. It is Goroutine-safe.
  217. func (wsc *wsConnection) WriteRPCResponse(resp types.RPCResponse) {
  218. select {
  219. case <-wsc.Quit():
  220. return
  221. case wsc.writeChan <- resp:
  222. }
  223. }
  224. // TryWriteRPCResponse attempts to push a response to the writeChan, but does not block.
  225. // It implements WSRPCConnection. It is Goroutine-safe
  226. func (wsc *wsConnection) TryWriteRPCResponse(resp types.RPCResponse) bool {
  227. select {
  228. case <-wsc.Quit():
  229. return false
  230. case wsc.writeChan <- resp:
  231. return true
  232. default:
  233. return false
  234. }
  235. }
  236. // Context returns the connection's context.
  237. // The context is canceled when the client's connection closes.
  238. func (wsc *wsConnection) Context() context.Context {
  239. if wsc.ctx != nil {
  240. return wsc.ctx
  241. }
  242. wsc.ctx, wsc.cancel = context.WithCancel(context.Background())
  243. return wsc.ctx
  244. }
  245. // Read from the socket and subscribe to or unsubscribe from events
  246. func (wsc *wsConnection) readRoutine() {
  247. defer func() {
  248. if r := recover(); r != nil {
  249. err, ok := r.(error)
  250. if !ok {
  251. err = fmt.Errorf("WSJSONRPC: %v", r)
  252. }
  253. wsc.Logger.Error("Panic in WSJSONRPC handler", "err", err, "stack", string(debug.Stack()))
  254. wsc.WriteRPCResponse(types.RPCInternalError(types.JSONRPCIntID(-1), err))
  255. go wsc.readRoutine()
  256. }
  257. }()
  258. wsc.baseConn.SetPongHandler(func(m string) error {
  259. return wsc.baseConn.SetReadDeadline(time.Now().Add(wsc.readWait))
  260. })
  261. for {
  262. select {
  263. case <-wsc.Quit():
  264. return
  265. default:
  266. // reset deadline for every type of message (control or data)
  267. if err := wsc.baseConn.SetReadDeadline(time.Now().Add(wsc.readWait)); err != nil {
  268. wsc.Logger.Error("failed to set read deadline", "err", err)
  269. }
  270. var in []byte
  271. _, in, err := wsc.baseConn.ReadMessage()
  272. if err != nil {
  273. if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
  274. wsc.Logger.Info("Client closed the connection")
  275. } else {
  276. wsc.Logger.Error("Failed to read request", "err", err)
  277. }
  278. wsc.Stop()
  279. close(wsc.readRoutineQuit)
  280. return
  281. }
  282. var request types.RPCRequest
  283. err = json.Unmarshal(in, &request)
  284. if err != nil {
  285. wsc.WriteRPCResponse(types.RPCParseError(fmt.Errorf("error unmarshaling request: %w", err)))
  286. continue
  287. }
  288. // A Notification is a Request object without an "id" member.
  289. // The Server MUST NOT reply to a Notification, including those that are within a batch request.
  290. if request.ID == nil {
  291. wsc.Logger.Debug(
  292. "WSJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)",
  293. "req", request,
  294. )
  295. continue
  296. }
  297. // Now, fetch the RPCFunc and execute it.
  298. rpcFunc := wsc.funcMap[request.Method]
  299. if rpcFunc == nil {
  300. wsc.WriteRPCResponse(types.RPCMethodNotFoundError(request.ID))
  301. continue
  302. }
  303. ctx := &types.Context{JSONReq: &request, WSConn: wsc}
  304. args := []reflect.Value{reflect.ValueOf(ctx)}
  305. if len(request.Params) > 0 {
  306. fnArgs, err := jsonParamsToArgs(rpcFunc, request.Params)
  307. if err != nil {
  308. wsc.WriteRPCResponse(
  309. types.RPCInternalError(request.ID, fmt.Errorf("error converting json params to arguments: %w", err)),
  310. )
  311. continue
  312. }
  313. args = append(args, fnArgs...)
  314. }
  315. returns := rpcFunc.f.Call(args)
  316. // TODO: Need to encode args/returns to string if we want to log them
  317. wsc.Logger.Info("WSJSONRPC", "method", request.Method)
  318. result, err := unreflectResult(returns)
  319. if err != nil {
  320. wsc.WriteRPCResponse(types.RPCInternalError(request.ID, err))
  321. continue
  322. }
  323. wsc.WriteRPCResponse(types.NewRPCSuccessResponse(request.ID, result))
  324. }
  325. }
  326. }
  327. // receives on a write channel and writes out on the socket
  328. func (wsc *wsConnection) writeRoutine() {
  329. pingTicker := time.NewTicker(wsc.pingPeriod)
  330. defer func() {
  331. pingTicker.Stop()
  332. }()
  333. // https://github.com/gorilla/websocket/issues/97
  334. pongs := make(chan string, 1)
  335. wsc.baseConn.SetPingHandler(func(m string) error {
  336. select {
  337. case pongs <- m:
  338. default:
  339. }
  340. return nil
  341. })
  342. for {
  343. select {
  344. case <-wsc.Quit():
  345. return
  346. case <-wsc.readRoutineQuit: // error in readRoutine
  347. return
  348. case m := <-pongs:
  349. err := wsc.writeMessageWithDeadline(websocket.PongMessage, []byte(m))
  350. if err != nil {
  351. wsc.Logger.Info("Failed to write pong (client may disconnect)", "err", err)
  352. }
  353. case <-pingTicker.C:
  354. err := wsc.writeMessageWithDeadline(websocket.PingMessage, []byte{})
  355. if err != nil {
  356. wsc.Logger.Error("Failed to write ping", "err", err)
  357. return
  358. }
  359. case msg := <-wsc.writeChan:
  360. jsonBytes, err := json.MarshalIndent(msg, "", " ")
  361. if err != nil {
  362. wsc.Logger.Error("Failed to marshal RPCResponse to JSON", "err", err)
  363. } else if err = wsc.writeMessageWithDeadline(websocket.TextMessage, jsonBytes); err != nil {
  364. wsc.Logger.Error("Failed to write response", "msg", msg, "err", err)
  365. return
  366. }
  367. }
  368. }
  369. }
  370. // All writes to the websocket must (re)set the write deadline.
  371. // If some writes don't set it while others do, they may timeout incorrectly
  372. // (https://github.com/tendermint/tendermint/issues/553)
  373. func (wsc *wsConnection) writeMessageWithDeadline(msgType int, msg []byte) error {
  374. if err := wsc.baseConn.SetWriteDeadline(time.Now().Add(wsc.writeWait)); err != nil {
  375. return err
  376. }
  377. return wsc.baseConn.WriteMessage(msgType, msg)
  378. }