github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/net/websocket/wsclient.go (about)

     1  package websocket
     2  
     3  import (
     4  	"container/list"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/gorilla/websocket"
    13  	log "github.com/sirupsen/logrus"
    14  
    15  	"github.com/bytom/bytom/errors"
    16  )
    17  
    18  // websocketSendBufferSize is the number of elements the send channel
    19  // can queue before blocking.  Note that this only applies to requests
    20  // handled directly in the websocket client input handler or the async
    21  // handler since notifications have their own queuing mechanism
    22  // independent of the send channel buffer.
    23  const (
    24  	logModule               = "websocket"
    25  	websocketSendBufferSize = 50
    26  )
    27  
    28  var (
    29  	// ErrWSParse means a request parsing error
    30  	ErrWSParse = errors.New("Websocket request parsing error")
    31  	// ErrWSInternal means service handling errors
    32  	ErrWSInternal = errors.New("Websocket Internal error")
    33  	// ErrWSClientQuit means the websocket client is disconnected
    34  	ErrWSClientQuit = errors.New("Websocket client quit")
    35  
    36  	// timeZeroVal is simply the zero value for a time.Time and is used to avoid creating multiple instances.
    37  	timeZeroVal time.Time
    38  )
    39  
    40  type semaphore chan struct{}
    41  
    42  func makeSemaphore(n int) semaphore {
    43  	return make(chan struct{}, n)
    44  }
    45  
    46  func (s semaphore) acquire() { s <- struct{}{} }
    47  func (s semaphore) release() { <-s }
    48  
    49  // wsTopicHandler describes a callback function used to handle a specific topic.
    50  type wsTopicHandler func(*WSClient)
    51  
    52  // wsHandlers maps websocket topic strings to appropriate websocket handler
    53  // functions.  This is set by init because help references wsHandlers and thus
    54  // causes a dependency loop.
    55  var wsHandlers = map[string]wsTopicHandler{
    56  	"notify_raw_blocks":            handleNotifyBlocks,
    57  	"notify_new_transactions":      handleNotifyNewTransactions,
    58  	"stop_notify_raw_blocks":       handleStopNotifyBlocks,
    59  	"stop_notify_new_transactions": handleStopNotifyNewTransactions,
    60  }
    61  
    62  // responseMessage houses a message to send to a connected websocket client as
    63  // well as a channel to reply on when the message is sent.
    64  type responseMessage struct {
    65  	msg      []byte
    66  	doneChan chan bool
    67  }
    68  
    69  // WSClient provides an abstraction for handling a websocket client.  The
    70  // overall data flow is split into 3 main goroutines, a possible 4th goroutine
    71  // for long-running operations (only started if request is made), and a
    72  // websocket manager which is used to allow things such as broadcasting
    73  // requested notifications to all connected websocket clients.   Inbound
    74  // messages are read via the inHandler goroutine and generally dispatched to
    75  // their own handler.  However, certain potentially long-running operations such
    76  // as rescans, are sent to the asyncHander goroutine and are limited to one at a
    77  // time.  There are two outbound message types - one for responding to client
    78  // requests and another for async notifications.  Responses to client requests
    79  // use SendMessage which employs a buffered channel thereby limiting the number
    80  // of outstanding requests that can be made.  Notifications are sent via
    81  // QueueNotification which implements a queue via notificationQueueHandler to
    82  // ensure sending notifications from other subsystems can't block.  Ultimately,
    83  // all messages are sent via the outHandler.
    84  type WSClient struct {
    85  	sync.Mutex
    86  	conn *websocket.Conn
    87  	// disconnected indicated whether or not the websocket client is disconnected.
    88  	disconnected bool
    89  	// addr is the remote address of the client.
    90  	addr              string
    91  	serviceRequestSem semaphore
    92  	ntfnChan          chan []byte
    93  	sendChan          chan responseMessage
    94  	quit              chan struct{}
    95  	wg                sync.WaitGroup
    96  	notificationMgr   *WSNotificationManager
    97  }
    98  
    99  // NewWebsocketClient means to create a new object to the connected websocket client
   100  func NewWebsocketClient(w http.ResponseWriter, r *http.Request, notificationMgr *WSNotificationManager) (*WSClient, error) {
   101  	// Limit max number of websocket clients.
   102  	if notificationMgr.IsMaxConnect() {
   103  		return nil, fmt.Errorf("numOfMaxWS: %d, disconnecting: %s", notificationMgr.MaxNumWebsockets, r.RemoteAddr)
   104  	}
   105  
   106  	// Attempt to upgrade the connection to a websocket connection using the default size for read/write buffers.
   107  	conn, err := websocket.Upgrade(w, r, nil, 0, 0)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	conn.SetReadDeadline(timeZeroVal)
   113  
   114  	client := &WSClient{
   115  		conn:              conn,
   116  		addr:              r.RemoteAddr,
   117  		serviceRequestSem: makeSemaphore(notificationMgr.maxNumConcurrentReqs),
   118  		ntfnChan:          make(chan []byte, 1), // nonblocking sync
   119  		sendChan:          make(chan responseMessage, websocketSendBufferSize),
   120  		quit:              make(chan struct{}),
   121  		notificationMgr:   notificationMgr,
   122  	}
   123  	return client, nil
   124  }
   125  
   126  // inHandler handles all incoming messages for the websocket connection.
   127  func (c *WSClient) inHandler() {
   128  out:
   129  	for {
   130  		// Break out of the loop once the quit channel has been closed.
   131  		// Use a non-blocking select here so we fall through otherwise.
   132  		select {
   133  		case <-c.quit:
   134  			break out
   135  		default:
   136  		}
   137  
   138  		_, msg, err := c.conn.ReadMessage()
   139  		if err != nil {
   140  			if err != io.EOF {
   141  				log.WithFields(log.Fields{"module": logModule, "remoteAddress": c.addr, "error": err}).Error("Websocket receive error")
   142  			}
   143  			break out
   144  		}
   145  
   146  		var request WSRequest
   147  		if err = json.Unmarshal(msg, &request); err != nil {
   148  			respError := errors.Wrap(err, ErrWSParse)
   149  			resp := NewWSResponse(NTRequestStatus.String(), nil, respError)
   150  			reply, err := json.Marshal(resp)
   151  			if err != nil {
   152  				log.WithFields(log.Fields{"module": logModule, "error": err}).Error("Failed to marshal parse failure reply")
   153  				continue
   154  			}
   155  
   156  			c.SendMessage(reply, nil)
   157  			continue
   158  		}
   159  
   160  		c.serviceRequestSem.acquire()
   161  		go func() {
   162  			c.serviceRequest(request.Topic)
   163  			c.serviceRequestSem.release()
   164  		}()
   165  	}
   166  
   167  	// Ensure the connection is closed.
   168  	c.Disconnect()
   169  	c.wg.Done()
   170  	log.WithFields(log.Fields{"module": logModule, "remoteAddress": c.addr}).Debug("Websocket client input handler done")
   171  }
   172  
   173  func (c *WSClient) serviceRequest(topic string) {
   174  	var respErr error
   175  
   176  	if wsHandler, ok := wsHandlers[topic]; ok {
   177  		wsHandler(c)
   178  	} else {
   179  		err := fmt.Errorf("There is not this topic: %s", topic)
   180  		respErr = errors.Wrap(err, ErrWSInternal)
   181  		log.WithFields(log.Fields{"module": logModule, "topic": topic}).Debug("There is not this topic")
   182  	}
   183  
   184  	resp := NewWSResponse(NTRequestStatus.String(), nil, respErr)
   185  	reply, err := json.Marshal(resp)
   186  	if err != nil {
   187  		log.WithFields(log.Fields{"module": logModule, "error": err}).Debug("Failed to marshal parse failure reply")
   188  		return
   189  	}
   190  
   191  	c.SendMessage(reply, nil)
   192  }
   193  
   194  // notificationQueueHandler handles the queuing of outgoing notifications for  the websocket client.
   195  func (c *WSClient) notificationQueueHandler() {
   196  	ntfnSentChan := make(chan bool, 1) // nonblocking sync
   197  
   198  	// pendingNtfns is used as a queue for notifications that are ready to
   199  	// be sent once there are no outstanding notifications currently being
   200  	// sent.
   201  	pendingNtfns := list.New()
   202  	waiting := false
   203  out:
   204  	for {
   205  		select {
   206  		// This channel is notified when a message is being queued to
   207  		// be sent across the network socket.  It will either send the
   208  		// message immediately if a send is not already in progress, or
   209  		// queue the message to be sent once the other pending messages
   210  		// are sent.
   211  		case msg := <-c.ntfnChan:
   212  			if !waiting {
   213  				c.SendMessage(msg, ntfnSentChan)
   214  			} else {
   215  				pendingNtfns.PushBack(msg)
   216  			}
   217  			waiting = true
   218  		// This channel is notified when a notification has been sent across the network socket.
   219  		case <-ntfnSentChan:
   220  			// No longer waiting if there are no more messages in the pending messages queue.
   221  			next := pendingNtfns.Front()
   222  			if next == nil {
   223  				waiting = false
   224  				continue
   225  			}
   226  
   227  			// Notify the outHandler about the next item to asynchronously send.
   228  			msg := pendingNtfns.Remove(next).([]byte)
   229  			c.SendMessage(msg, ntfnSentChan)
   230  		case <-c.quit:
   231  			break out
   232  		}
   233  	}
   234  
   235  	// Drain any wait channels before exiting so nothing is left waiting around to send.
   236  cleanup:
   237  	for {
   238  		select {
   239  		case <-c.ntfnChan:
   240  		case <-ntfnSentChan:
   241  		default:
   242  			break cleanup
   243  		}
   244  	}
   245  	c.wg.Done()
   246  	log.WithFields(log.Fields{"module": logModule, "remoteAddress": c.addr}).Debug("Websocket client notification queue handler done")
   247  }
   248  
   249  // outHandler handles all outgoing messages for the websocket connection.
   250  func (c *WSClient) outHandler() {
   251  out:
   252  	for {
   253  		// Send any messages ready for send until the quit channel is closed.
   254  		select {
   255  		case r := <-c.sendChan:
   256  			if err := c.conn.WriteMessage(websocket.TextMessage, r.msg); err != nil {
   257  				log.WithFields(log.Fields{"module": logModule, "error": err}).Error("Failed to send message to wesocket client")
   258  				c.Disconnect()
   259  				break out
   260  			}
   261  			if r.doneChan != nil {
   262  				r.doneChan <- true
   263  			}
   264  		case <-c.quit:
   265  			break out
   266  		}
   267  	}
   268  
   269  	// Drain any wait channels before exiting so nothing is left waiting around to send.
   270  cleanup:
   271  	for {
   272  		select {
   273  		case r := <-c.sendChan:
   274  			if r.doneChan != nil {
   275  				r.doneChan <- false
   276  			}
   277  		default:
   278  			break cleanup
   279  		}
   280  	}
   281  	c.wg.Done()
   282  	log.WithFields(log.Fields{"module": logModule, "remoteAddress": c.addr}).Debug("Websocket client output handler done")
   283  }
   284  
   285  // SendMessage sends the passed json to the websocket client.  It is backed
   286  // by a buffered channel, so it will not block until the send channel is full.
   287  // Note however that QueueNotification must be used for sending async
   288  // notifications instead of the this function.  This approach allows a limit to
   289  // the number of outstanding requests a client can make without preventing or
   290  // blocking on async notifications.
   291  func (c *WSClient) SendMessage(marshalledJSON []byte, doneChan chan bool) {
   292  	// Don't send the message if disconnected.
   293  	if c.Disconnected() {
   294  		if doneChan != nil {
   295  			doneChan <- false
   296  		}
   297  		return
   298  	}
   299  
   300  	c.sendChan <- responseMessage{msg: marshalledJSON, doneChan: doneChan}
   301  }
   302  
   303  // QueueNotification queues the passed notification to be sent to the websocket client.
   304  func (c *WSClient) QueueNotification(marshalledJSON []byte) error {
   305  	// Don't queue the message if disconnected.
   306  	if c.Disconnected() {
   307  		return ErrWSClientQuit
   308  	}
   309  
   310  	c.ntfnChan <- marshalledJSON
   311  	return nil
   312  }
   313  
   314  // Disconnected returns whether or not the websocket client is disconnected.
   315  func (c *WSClient) Disconnected() bool {
   316  	c.Lock()
   317  	defer c.Unlock()
   318  
   319  	return c.disconnected
   320  }
   321  
   322  // Disconnect disconnects the websocket client.
   323  func (c *WSClient) Disconnect() {
   324  	c.Lock()
   325  	defer c.Unlock()
   326  
   327  	// Nothing to do if already disconnected.
   328  	if c.disconnected {
   329  		return
   330  	}
   331  
   332  	log.WithFields(log.Fields{"module": logModule, "remoteAddress": c.addr}).Info("Disconnecting websocket client")
   333  
   334  	close(c.quit)
   335  	c.conn.Close()
   336  	c.disconnected = true
   337  }
   338  
   339  // Start begins processing input and output messages.
   340  func (c *WSClient) Start() {
   341  	log.WithFields(log.Fields{"module": logModule, "remoteAddress": c.addr}).Info("Starting websocket client")
   342  
   343  	c.wg.Add(3)
   344  	go c.inHandler()
   345  	go c.notificationQueueHandler()
   346  	go c.outHandler()
   347  }
   348  
   349  // WaitForShutdown blocks until the websocket client goroutines are stopped and the connection is closed.
   350  func (c *WSClient) WaitForShutdown() {
   351  	c.wg.Wait()
   352  }
   353  
   354  // handleNotifyBlocks implements the notifyblocks topic extension for websocket connections.
   355  func handleNotifyBlocks(wsc *WSClient) {
   356  	wsc.notificationMgr.RegisterBlockUpdates(wsc)
   357  }
   358  
   359  // handleStopNotifyBlocks implements the stopnotifyblocks topic extension for websocket connections.
   360  func handleStopNotifyBlocks(wsc *WSClient) {
   361  	wsc.notificationMgr.UnregisterBlockUpdates(wsc)
   362  }
   363  
   364  // handleNotifyNewTransations implements the notifynewtransactions topic extension for websocket connections.
   365  func handleNotifyNewTransactions(wsc *WSClient) {
   366  	wsc.notificationMgr.RegisterNewMempoolTxsUpdates(wsc)
   367  }
   368  
   369  // handleStopNotifyNewTransations implements the stopnotifynewtransactions topic extension for websocket connections.
   370  func handleStopNotifyNewTransactions(wsc *WSClient) {
   371  	wsc.notificationMgr.UnregisterNewMempoolTxsUpdates(wsc)
   372  }