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.

125 lines
2.8 KiB

  1. package rpcclient
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "time"
  7. "github.com/gorilla/websocket"
  8. . "github.com/tendermint/go-common"
  9. "github.com/tendermint/go-rpc/types"
  10. )
  11. const (
  12. wsResultsChannelCapacity = 10
  13. wsErrorsChannelCapacity = 1
  14. wsWriteTimeoutSeconds = 10
  15. )
  16. type WSClient struct {
  17. QuitService
  18. Address string
  19. *websocket.Conn
  20. ResultsCh chan json.RawMessage // closes upon WSClient.Stop()
  21. ErrorsCh chan error // closes upon WSClient.Stop()
  22. }
  23. // create a new connection
  24. func NewWSClient(addr string) *WSClient {
  25. wsClient := &WSClient{
  26. Address: addr,
  27. Conn: nil,
  28. ResultsCh: make(chan json.RawMessage, wsResultsChannelCapacity),
  29. ErrorsCh: make(chan error, wsErrorsChannelCapacity),
  30. }
  31. wsClient.QuitService = *NewQuitService(log, "WSClient", wsClient)
  32. return wsClient
  33. }
  34. func (wsc *WSClient) OnStart() error {
  35. wsc.QuitService.OnStart()
  36. err := wsc.dial()
  37. if err != nil {
  38. return err
  39. }
  40. go wsc.receiveEventsRoutine()
  41. return nil
  42. }
  43. func (wsc *WSClient) dial() error {
  44. // Dial
  45. dialer := websocket.DefaultDialer
  46. rHeader := http.Header{}
  47. con, _, err := dialer.Dial(wsc.Address, rHeader)
  48. if err != nil {
  49. return err
  50. }
  51. // Set the ping/pong handlers
  52. con.SetPingHandler(func(m string) error {
  53. // NOTE: https://github.com/gorilla/websocket/issues/97
  54. go con.WriteControl(websocket.PongMessage, []byte(m), time.Now().Add(time.Second*wsWriteTimeoutSeconds))
  55. return nil
  56. })
  57. con.SetPongHandler(func(m string) error {
  58. // NOTE: https://github.com/gorilla/websocket/issues/97
  59. return nil
  60. })
  61. wsc.Conn = con
  62. return nil
  63. }
  64. func (wsc *WSClient) OnStop() {
  65. wsc.QuitService.OnStop()
  66. // ResultsCh/ErrorsCh is closed in receiveEventsRoutine.
  67. }
  68. func (wsc *WSClient) receiveEventsRoutine() {
  69. for {
  70. _, data, err := wsc.ReadMessage()
  71. if err != nil {
  72. log.Info("WSClient failed to read message", "error", err, "data", string(data))
  73. wsc.Stop()
  74. break
  75. } else {
  76. var response rpctypes.RPCResponse
  77. err := json.Unmarshal(data, &response)
  78. if err != nil {
  79. log.Info("WSClient failed to parse message", "error", err, "data", string(data))
  80. wsc.ErrorsCh <- err
  81. continue
  82. }
  83. if response.Error != "" {
  84. wsc.ErrorsCh <- fmt.Errorf(err.Error())
  85. continue
  86. }
  87. wsc.ResultsCh <- *response.Result
  88. }
  89. }
  90. // Cleanup
  91. close(wsc.ResultsCh)
  92. close(wsc.ErrorsCh)
  93. }
  94. // subscribe to an event
  95. func (wsc *WSClient) Subscribe(eventid string) error {
  96. err := wsc.WriteJSON(rpctypes.RPCRequest{
  97. JSONRPC: "2.0",
  98. ID: "",
  99. Method: "subscribe",
  100. Params: []interface{}{eventid},
  101. })
  102. return err
  103. }
  104. // unsubscribe from an event
  105. func (wsc *WSClient) Unsubscribe(eventid string) error {
  106. err := wsc.WriteJSON(rpctypes.RPCRequest{
  107. JSONRPC: "2.0",
  108. ID: "",
  109. Method: "unsubscribe",
  110. Params: []interface{}{eventid},
  111. })
  112. return err
  113. }