github.com/number571/tendermint@v0.34.11-gost/rpc/core/events.go (about) 1 package core 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 tmpubsub "github.com/number571/tendermint/libs/pubsub" 9 tmquery "github.com/number571/tendermint/libs/pubsub/query" 10 ctypes "github.com/number571/tendermint/rpc/core/types" 11 rpctypes "github.com/number571/tendermint/rpc/jsonrpc/types" 12 ) 13 14 const ( 15 // Buffer on the Tendermint (server) side to allow some slowness in clients. 16 subBufferSize = 100 17 ) 18 19 // Subscribe for events via WebSocket. 20 // More: https://docs.tendermint.com/master/rpc/#/Websocket/subscribe 21 func (env *Environment) Subscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultSubscribe, error) { 22 addr := ctx.RemoteAddr() 23 24 if env.EventBus.NumClients() >= env.Config.MaxSubscriptionClients { 25 return nil, fmt.Errorf("max_subscription_clients %d reached", env.Config.MaxSubscriptionClients) 26 } else if env.EventBus.NumClientSubscriptions(addr) >= env.Config.MaxSubscriptionsPerClient { 27 return nil, fmt.Errorf("max_subscriptions_per_client %d reached", env.Config.MaxSubscriptionsPerClient) 28 } 29 30 env.Logger.Info("Subscribe to query", "remote", addr, "query", query) 31 32 q, err := tmquery.New(query) 33 if err != nil { 34 return nil, fmt.Errorf("failed to parse query: %w", err) 35 } 36 37 subCtx, cancel := context.WithTimeout(ctx.Context(), SubscribeTimeout) 38 defer cancel() 39 40 sub, err := env.EventBus.Subscribe(subCtx, addr, q, subBufferSize) 41 if err != nil { 42 return nil, err 43 } 44 45 // Capture the current ID, since it can change in the future. 46 subscriptionID := ctx.JSONReq.ID 47 go func() { 48 for { 49 select { 50 case msg := <-sub.Out(): 51 var ( 52 resultEvent = &ctypes.ResultEvent{Query: query, Data: msg.Data(), Events: msg.Events()} 53 resp = rpctypes.NewRPCSuccessResponse(subscriptionID, resultEvent) 54 ) 55 writeCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 56 defer cancel() 57 if err := ctx.WSConn.WriteRPCResponse(writeCtx, resp); err != nil { 58 env.Logger.Info("Can't write response (slow client)", 59 "to", addr, "subscriptionID", subscriptionID, "err", err) 60 } 61 case <-sub.Canceled(): 62 if sub.Err() != tmpubsub.ErrUnsubscribed { 63 var reason string 64 if sub.Err() == nil { 65 reason = "Tendermint exited" 66 } else { 67 reason = sub.Err().Error() 68 } 69 var ( 70 err = fmt.Errorf("subscription was canceled (reason: %s)", reason) 71 resp = rpctypes.RPCServerError(subscriptionID, err) 72 ) 73 if ok := ctx.WSConn.TryWriteRPCResponse(resp); !ok { 74 env.Logger.Info("Can't write response (slow client)", 75 "to", addr, "subscriptionID", subscriptionID, "err", err) 76 } 77 } 78 return 79 } 80 } 81 }() 82 83 return &ctypes.ResultSubscribe{}, nil 84 } 85 86 // Unsubscribe from events via WebSocket. 87 // More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe 88 func (env *Environment) Unsubscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) { 89 args := tmpubsub.UnsubscribeArgs{Subscriber: ctx.RemoteAddr()} 90 env.Logger.Info("Unsubscribe from query", "remote", args.Subscriber, "subscription", query) 91 92 var err error 93 args.Query, err = tmquery.New(query) 94 95 if err != nil { 96 args.ID = query 97 } 98 99 err = env.EventBus.Unsubscribe(ctx.Context(), args) 100 if err != nil { 101 return nil, err 102 } 103 return &ctypes.ResultUnsubscribe{}, nil 104 } 105 106 // UnsubscribeAll from all events via WebSocket. 107 // More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe_all 108 func (env *Environment) UnsubscribeAll(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) { 109 addr := ctx.RemoteAddr() 110 env.Logger.Info("Unsubscribe from all", "remote", addr) 111 err := env.EventBus.UnsubscribeAll(ctx.Context(), addr) 112 if err != nil { 113 return nil, err 114 } 115 return &ctypes.ResultUnsubscribe{}, nil 116 }