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.

135 lines
3.0 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 // IP:PORT or /path/to/socket
  19. Endpoint string // /websocket/url/endpoint
  20. *websocket.Conn
  21. ResultsCh chan json.RawMessage // closes upon WSClient.Stop()
  22. ErrorsCh chan error // closes upon WSClient.Stop()
  23. }
  24. // create a new connection
  25. func NewWSClient(addr, endpoint string) *WSClient {
  26. wsClient := &WSClient{
  27. Address: addr,
  28. Endpoint: endpoint,
  29. Conn: nil,
  30. ResultsCh: make(chan json.RawMessage, wsResultsChannelCapacity),
  31. ErrorsCh: make(chan error, wsErrorsChannelCapacity),
  32. }
  33. wsClient.QuitService = *NewQuitService(log, "WSClient", wsClient)
  34. return wsClient
  35. }
  36. func (wsc *WSClient) String() string {
  37. return wsc.Address + ", " + wsc.Endpoint
  38. }
  39. func (wsc *WSClient) OnStart() error {
  40. wsc.QuitService.OnStart()
  41. err := wsc.dial()
  42. if err != nil {
  43. return err
  44. }
  45. go wsc.receiveEventsRoutine()
  46. return nil
  47. }
  48. func (wsc *WSClient) dial() error {
  49. // Dial
  50. dialer := &websocket.Dialer{
  51. NetDial: dialer(wsc.Address),
  52. Proxy: http.ProxyFromEnvironment,
  53. }
  54. rHeader := http.Header{}
  55. con, _, err := dialer.Dial("ws://"+dummyDomain+wsc.Endpoint, rHeader)
  56. if err != nil {
  57. return err
  58. }
  59. // Set the ping/pong handlers
  60. con.SetPingHandler(func(m string) error {
  61. // NOTE: https://github.com/gorilla/websocket/issues/97
  62. go con.WriteControl(websocket.PongMessage, []byte(m), time.Now().Add(time.Second*wsWriteTimeoutSeconds))
  63. return nil
  64. })
  65. con.SetPongHandler(func(m string) error {
  66. // NOTE: https://github.com/gorilla/websocket/issues/97
  67. return nil
  68. })
  69. wsc.Conn = con
  70. return nil
  71. }
  72. func (wsc *WSClient) OnStop() {
  73. wsc.QuitService.OnStop()
  74. // ResultsCh/ErrorsCh is closed in receiveEventsRoutine.
  75. }
  76. func (wsc *WSClient) receiveEventsRoutine() {
  77. for {
  78. _, data, err := wsc.ReadMessage()
  79. if err != nil {
  80. log.Info("WSClient failed to read message", "error", err, "data", string(data))
  81. wsc.Stop()
  82. break
  83. } else {
  84. var response rpctypes.RPCResponse
  85. err := json.Unmarshal(data, &response)
  86. if err != nil {
  87. log.Info("WSClient failed to parse message", "error", err, "data", string(data))
  88. wsc.ErrorsCh <- err
  89. continue
  90. }
  91. if response.Error != "" {
  92. wsc.ErrorsCh <- fmt.Errorf(response.Error)
  93. continue
  94. }
  95. wsc.ResultsCh <- *response.Result
  96. }
  97. }
  98. // Cleanup
  99. close(wsc.ResultsCh)
  100. close(wsc.ErrorsCh)
  101. }
  102. // subscribe to an event
  103. func (wsc *WSClient) Subscribe(eventid string) error {
  104. err := wsc.WriteJSON(rpctypes.RPCRequest{
  105. JSONRPC: "2.0",
  106. ID: "",
  107. Method: "subscribe",
  108. Params: []interface{}{eventid},
  109. })
  110. return err
  111. }
  112. // unsubscribe from an event
  113. func (wsc *WSClient) Unsubscribe(eventid string) error {
  114. err := wsc.WriteJSON(rpctypes.RPCRequest{
  115. JSONRPC: "2.0",
  116. ID: "",
  117. Method: "unsubscribe",
  118. Params: []interface{}{eventid},
  119. })
  120. return err
  121. }