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.

269 lines
6.5 KiB

cleanup: Reduce and normalize import path aliasing. (#6975) The code in the Tendermint repository makes heavy use of import aliasing. This is made necessary by our extensive reuse of common base package names, and by repetition of similar names across different subdirectories. Unfortunately we have not been very consistent about which packages we alias in various circumstances, and the aliases we use vary. In the spirit of the advice in the style guide and https://github.com/golang/go/wiki/CodeReviewComments#imports, his change makes an effort to clean up and normalize import aliasing. This change makes no API or behavioral changes. It is a pure cleanup intended o help make the code more readable to developers (including myself) trying to understand what is being imported where. Only unexported names have been modified, and the changes were generated and applied mechanically with gofmt -r and comby, respecting the lexical and syntactic rules of Go. Even so, I did not fix every inconsistency. Where the changes would be too disruptive, I left it alone. The principles I followed in this cleanup are: - Remove aliases that restate the package name. - Remove aliases where the base package name is unambiguous. - Move overly-terse abbreviations from the import to the usage site. - Fix lexical issues (remove underscores, remove capitalization). - Fix import groupings to more closely match the style guide. - Group blank (side-effecting) imports and ensure they are commented. - Add aliases to multiple imports with the same base package name.
3 years ago
cleanup: Reduce and normalize import path aliasing. (#6975) The code in the Tendermint repository makes heavy use of import aliasing. This is made necessary by our extensive reuse of common base package names, and by repetition of similar names across different subdirectories. Unfortunately we have not been very consistent about which packages we alias in various circumstances, and the aliases we use vary. In the spirit of the advice in the style guide and https://github.com/golang/go/wiki/CodeReviewComments#imports, his change makes an effort to clean up and normalize import aliasing. This change makes no API or behavioral changes. It is a pure cleanup intended o help make the code more readable to developers (including myself) trying to understand what is being imported where. Only unexported names have been modified, and the changes were generated and applied mechanically with gofmt -r and comby, respecting the lexical and syntactic rules of Go. Even so, I did not fix every inconsistency. Where the changes would be too disruptive, I left it alone. The principles I followed in this cleanup are: - Remove aliases that restate the package name. - Remove aliases where the base package name is unambiguous. - Move overly-terse abbreviations from the import to the usage site. - Fix lexical issues (remove underscores, remove capitalization). - Fix import groupings to more closely match the style guide. - Group blank (side-effecting) imports and ensure they are commented. - Add aliases to multiple imports with the same base package name.
3 years ago
cleanup: Reduce and normalize import path aliasing. (#6975) The code in the Tendermint repository makes heavy use of import aliasing. This is made necessary by our extensive reuse of common base package names, and by repetition of similar names across different subdirectories. Unfortunately we have not been very consistent about which packages we alias in various circumstances, and the aliases we use vary. In the spirit of the advice in the style guide and https://github.com/golang/go/wiki/CodeReviewComments#imports, his change makes an effort to clean up and normalize import aliasing. This change makes no API or behavioral changes. It is a pure cleanup intended o help make the code more readable to developers (including myself) trying to understand what is being imported where. Only unexported names have been modified, and the changes were generated and applied mechanically with gofmt -r and comby, respecting the lexical and syntactic rules of Go. Even so, I did not fix every inconsistency. Where the changes would be too disruptive, I left it alone. The principles I followed in this cleanup are: - Remove aliases that restate the package name. - Remove aliases where the base package name is unambiguous. - Move overly-terse abbreviations from the import to the usage site. - Fix lexical issues (remove underscores, remove capitalization). - Fix import groupings to more closely match the style guide. - Group blank (side-effecting) imports and ensure they are commented. - Add aliases to multiple imports with the same base package name.
3 years ago
cleanup: Reduce and normalize import path aliasing. (#6975) The code in the Tendermint repository makes heavy use of import aliasing. This is made necessary by our extensive reuse of common base package names, and by repetition of similar names across different subdirectories. Unfortunately we have not been very consistent about which packages we alias in various circumstances, and the aliases we use vary. In the spirit of the advice in the style guide and https://github.com/golang/go/wiki/CodeReviewComments#imports, his change makes an effort to clean up and normalize import aliasing. This change makes no API or behavioral changes. It is a pure cleanup intended o help make the code more readable to developers (including myself) trying to understand what is being imported where. Only unexported names have been modified, and the changes were generated and applied mechanically with gofmt -r and comby, respecting the lexical and syntactic rules of Go. Even so, I did not fix every inconsistency. Where the changes would be too disruptive, I left it alone. The principles I followed in this cleanup are: - Remove aliases that restate the package name. - Remove aliases where the base package name is unambiguous. - Move overly-terse abbreviations from the import to the usage site. - Fix lexical issues (remove underscores, remove capitalization). - Fix import groupings to more closely match the style guide. - Group blank (side-effecting) imports and ensure they are commented. - Add aliases to multiple imports with the same base package name.
3 years ago
cleanup: Reduce and normalize import path aliasing. (#6975) The code in the Tendermint repository makes heavy use of import aliasing. This is made necessary by our extensive reuse of common base package names, and by repetition of similar names across different subdirectories. Unfortunately we have not been very consistent about which packages we alias in various circumstances, and the aliases we use vary. In the spirit of the advice in the style guide and https://github.com/golang/go/wiki/CodeReviewComments#imports, his change makes an effort to clean up and normalize import aliasing. This change makes no API or behavioral changes. It is a pure cleanup intended o help make the code more readable to developers (including myself) trying to understand what is being imported where. Only unexported names have been modified, and the changes were generated and applied mechanically with gofmt -r and comby, respecting the lexical and syntactic rules of Go. Even so, I did not fix every inconsistency. Where the changes would be too disruptive, I left it alone. The principles I followed in this cleanup are: - Remove aliases that restate the package name. - Remove aliases where the base package name is unambiguous. - Move overly-terse abbreviations from the import to the usage site. - Fix lexical issues (remove underscores, remove capitalization). - Fix import groupings to more closely match the style guide. - Group blank (side-effecting) imports and ensure they are commented. - Add aliases to multiple imports with the same base package name.
3 years ago
cleanup: Reduce and normalize import path aliasing. (#6975) The code in the Tendermint repository makes heavy use of import aliasing. This is made necessary by our extensive reuse of common base package names, and by repetition of similar names across different subdirectories. Unfortunately we have not been very consistent about which packages we alias in various circumstances, and the aliases we use vary. In the spirit of the advice in the style guide and https://github.com/golang/go/wiki/CodeReviewComments#imports, his change makes an effort to clean up and normalize import aliasing. This change makes no API or behavioral changes. It is a pure cleanup intended o help make the code more readable to developers (including myself) trying to understand what is being imported where. Only unexported names have been modified, and the changes were generated and applied mechanically with gofmt -r and comby, respecting the lexical and syntactic rules of Go. Even so, I did not fix every inconsistency. Where the changes would be too disruptive, I left it alone. The principles I followed in this cleanup are: - Remove aliases that restate the package name. - Remove aliases where the base package name is unambiguous. - Move overly-terse abbreviations from the import to the usage site. - Fix lexical issues (remove underscores, remove capitalization). - Fix import groupings to more closely match the style guide. - Group blank (side-effecting) imports and ensure they are commented. - Add aliases to multiple imports with the same base package name.
3 years ago
  1. package http
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "strings"
  7. "sync"
  8. "time"
  9. "github.com/tendermint/tendermint/internal/pubsub"
  10. tmjson "github.com/tendermint/tendermint/libs/json"
  11. rpcclient "github.com/tendermint/tendermint/rpc/client"
  12. "github.com/tendermint/tendermint/rpc/coretypes"
  13. jsonrpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
  14. )
  15. // WSOptions for the WS part of the HTTP client.
  16. type WSOptions struct {
  17. Path string // path (e.g. "/ws")
  18. jsonrpcclient.WSOptions // WSClient options
  19. }
  20. // DefaultWSOptions returns default WS options.
  21. // See jsonrpcclient.DefaultWSOptions.
  22. func DefaultWSOptions() WSOptions {
  23. return WSOptions{
  24. Path: "/websocket",
  25. WSOptions: jsonrpcclient.DefaultWSOptions(),
  26. }
  27. }
  28. // Validate performs a basic validation of WSOptions.
  29. func (wso WSOptions) Validate() error {
  30. if len(wso.Path) <= 1 {
  31. return errors.New("empty Path")
  32. }
  33. if wso.Path[0] != '/' {
  34. return errors.New("leading slash is missing in Path")
  35. }
  36. return nil
  37. }
  38. // wsEvents is a wrapper around WSClient, which implements EventsClient.
  39. type wsEvents struct {
  40. *rpcclient.RunState
  41. ws *jsonrpcclient.WSClient
  42. mtx sync.RWMutex
  43. subscriptions map[string]*wsSubscription
  44. }
  45. type wsSubscription struct {
  46. res chan coretypes.ResultEvent
  47. id string
  48. query string
  49. }
  50. var _ rpcclient.EventsClient = (*wsEvents)(nil)
  51. func newWsEvents(remote string, wso WSOptions) (*wsEvents, error) {
  52. // validate options
  53. if err := wso.Validate(); err != nil {
  54. return nil, fmt.Errorf("invalid WSOptions: %w", err)
  55. }
  56. // remove the trailing / from the remote else the websocket endpoint
  57. // won't parse correctly
  58. if remote[len(remote)-1] == '/' {
  59. remote = remote[:len(remote)-1]
  60. }
  61. w := &wsEvents{
  62. subscriptions: make(map[string]*wsSubscription),
  63. }
  64. w.RunState = rpcclient.NewRunState("wsEvents", nil)
  65. var err error
  66. w.ws, err = jsonrpcclient.NewWSWithOptions(remote, wso.Path, wso.WSOptions)
  67. if err != nil {
  68. return nil, fmt.Errorf("can't create WS client: %w", err)
  69. }
  70. w.ws.OnReconnect(func() {
  71. // resubscribe immediately
  72. w.redoSubscriptionsAfter(0 * time.Second)
  73. })
  74. w.ws.Logger = w.Logger
  75. return w, nil
  76. }
  77. // Start starts the websocket client and the event loop.
  78. func (w *wsEvents) Start(ctx context.Context) error {
  79. if err := w.ws.Start(ctx); err != nil {
  80. return err
  81. }
  82. go w.eventListener(ctx)
  83. return nil
  84. }
  85. // IsRunning reports whether the websocket client is running.
  86. func (w *wsEvents) IsRunning() bool { return w.ws.IsRunning() }
  87. // Stop shuts down the websocket client.
  88. func (w *wsEvents) Stop() error { return w.ws.Stop() }
  89. // Subscribe implements EventsClient by using WSClient to subscribe given
  90. // subscriber to query. By default, it returns a channel with cap=1. Error is
  91. // returned if it fails to subscribe.
  92. //
  93. // When reading from the channel, keep in mind there's a single events loop, so
  94. // if you don't read events for this subscription fast enough, other
  95. // subscriptions will slow down in effect.
  96. //
  97. // The channel is never closed to prevent clients from seeing an erroneous
  98. // event.
  99. //
  100. // It returns an error if wsEvents is not running.
  101. func (w *wsEvents) Subscribe(ctx context.Context, subscriber, query string,
  102. outCapacity ...int) (out <-chan coretypes.ResultEvent, err error) {
  103. if !w.IsRunning() {
  104. return nil, rpcclient.ErrClientNotRunning
  105. }
  106. if err := w.ws.Subscribe(ctx, query); err != nil {
  107. return nil, err
  108. }
  109. outCap := 1
  110. if len(outCapacity) > 0 {
  111. outCap = outCapacity[0]
  112. }
  113. outc := make(chan coretypes.ResultEvent, outCap)
  114. w.mtx.Lock()
  115. defer w.mtx.Unlock()
  116. // subscriber param is ignored because Tendermint will override it with
  117. // remote IP anyway.
  118. w.subscriptions[query] = &wsSubscription{res: outc, query: query}
  119. return outc, nil
  120. }
  121. // Unsubscribe implements EventsClient by using WSClient to unsubscribe given
  122. // subscriber from query.
  123. //
  124. // It returns an error if wsEvents is not running.
  125. func (w *wsEvents) Unsubscribe(ctx context.Context, subscriber, query string) error {
  126. if !w.IsRunning() {
  127. return rpcclient.ErrClientNotRunning
  128. }
  129. if err := w.ws.Unsubscribe(ctx, query); err != nil {
  130. return err
  131. }
  132. w.mtx.Lock()
  133. info, ok := w.subscriptions[query]
  134. if ok {
  135. if info.id != "" {
  136. delete(w.subscriptions, info.id)
  137. }
  138. delete(w.subscriptions, info.query)
  139. }
  140. w.mtx.Unlock()
  141. return nil
  142. }
  143. // UnsubscribeAll implements EventsClient by using WSClient to unsubscribe
  144. // given subscriber from all the queries.
  145. //
  146. // It returns an error if wsEvents is not running.
  147. func (w *wsEvents) UnsubscribeAll(ctx context.Context, subscriber string) error {
  148. if !w.IsRunning() {
  149. return rpcclient.ErrClientNotRunning
  150. }
  151. if err := w.ws.UnsubscribeAll(ctx); err != nil {
  152. return err
  153. }
  154. w.mtx.Lock()
  155. w.subscriptions = make(map[string]*wsSubscription)
  156. w.mtx.Unlock()
  157. return nil
  158. }
  159. // After being reconnected, it is necessary to redo subscription to server
  160. // otherwise no data will be automatically received.
  161. func (w *wsEvents) redoSubscriptionsAfter(d time.Duration) {
  162. time.Sleep(d)
  163. ctx := context.Background()
  164. w.mtx.Lock()
  165. defer w.mtx.Unlock()
  166. for q, info := range w.subscriptions {
  167. if q != "" && q == info.id {
  168. continue
  169. }
  170. err := w.ws.Subscribe(ctx, q)
  171. if err != nil {
  172. w.Logger.Error("failed to resubscribe", "query", q, "err", err)
  173. delete(w.subscriptions, q)
  174. }
  175. }
  176. }
  177. func isErrAlreadySubscribed(err error) bool {
  178. return strings.Contains(err.Error(), pubsub.ErrAlreadySubscribed.Error())
  179. }
  180. func (w *wsEvents) eventListener(ctx context.Context) {
  181. for {
  182. select {
  183. case resp, ok := <-w.ws.ResponsesCh:
  184. if !ok {
  185. return
  186. }
  187. if resp.Error != nil {
  188. w.Logger.Error("WS error", "err", resp.Error.Error())
  189. // Error can be ErrAlreadySubscribed or max client (subscriptions per
  190. // client) reached or Tendermint exited.
  191. // We can ignore ErrAlreadySubscribed, but need to retry in other
  192. // cases.
  193. if !isErrAlreadySubscribed(resp.Error) {
  194. // Resubscribe after 1 second to give Tendermint time to restart (if
  195. // crashed).
  196. w.redoSubscriptionsAfter(1 * time.Second)
  197. }
  198. continue
  199. }
  200. result := new(coretypes.ResultEvent)
  201. err := tmjson.Unmarshal(resp.Result, result)
  202. if err != nil {
  203. w.Logger.Error("failed to unmarshal response", "err", err)
  204. continue
  205. }
  206. w.mtx.RLock()
  207. out, ok := w.subscriptions[result.Query]
  208. if ok {
  209. if _, idOk := w.subscriptions[result.SubscriptionID]; !idOk {
  210. out.id = result.SubscriptionID
  211. w.subscriptions[result.SubscriptionID] = out
  212. }
  213. }
  214. w.mtx.RUnlock()
  215. if ok {
  216. select {
  217. case out.res <- *result:
  218. case <-ctx.Done():
  219. return
  220. }
  221. }
  222. case <-ctx.Done():
  223. return
  224. }
  225. }
  226. }