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  }