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