github.com/franono/tendermint@v0.32.2-0.20200527150959-749313264ce9/rpc/core/events.go (about) 1 package core 2 3 import ( 4 "context" 5 "fmt" 6 7 tmpubsub "github.com/franono/tendermint/libs/pubsub" 8 tmquery "github.com/franono/tendermint/libs/pubsub/query" 9 ctypes "github.com/franono/tendermint/rpc/core/types" 10 rpctypes "github.com/franono/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 ctx.WSConn.Codec(), 54 subscriptionID, 55 resultEvent, 56 )) 57 case <-sub.Cancelled(): 58 if sub.Err() != tmpubsub.ErrUnsubscribed { 59 var reason string 60 if sub.Err() == nil { 61 reason = "Tendermint exited" 62 } else { 63 reason = sub.Err().Error() 64 } 65 ctx.WSConn.TryWriteRPCResponse( 66 rpctypes.RPCServerError( 67 subscriptionID, 68 fmt.Errorf("subscription was cancelled (reason: %s)", reason), 69 )) 70 } 71 return 72 } 73 } 74 }() 75 76 return &ctypes.ResultSubscribe{}, nil 77 } 78 79 // Unsubscribe from events via WebSocket. 80 // More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe 81 func Unsubscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) { 82 addr := ctx.RemoteAddr() 83 env.Logger.Info("Unsubscribe from query", "remote", addr, "query", query) 84 q, err := tmquery.New(query) 85 if err != nil { 86 return nil, fmt.Errorf("failed to parse query: %w", err) 87 } 88 err = env.EventBus.Unsubscribe(context.Background(), addr, q) 89 if err != nil { 90 return nil, err 91 } 92 return &ctypes.ResultUnsubscribe{}, nil 93 } 94 95 // UnsubscribeAll from all events via WebSocket. 96 // More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe_all 97 func UnsubscribeAll(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) { 98 addr := ctx.RemoteAddr() 99 env.Logger.Info("Unsubscribe from all", "remote", addr) 100 err := env.EventBus.UnsubscribeAll(context.Background(), addr) 101 if err != nil { 102 return nil, err 103 } 104 return &ctypes.ResultUnsubscribe{}, nil 105 }