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