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  }