github.com/martinohmann/rfoutlet@v1.2.1-0.20220707195255-8a66aa411105/internal/websocket/client.go (about)

     1  package websocket
     2  
     3  import (
     4  	"errors"
     5  	"time"
     6  
     7  	"github.com/gorilla/websocket"
     8  	"github.com/martinohmann/rfoutlet/internal/command"
     9  	uuid "github.com/satori/go.uuid"
    10  	"github.com/sirupsen/logrus"
    11  )
    12  
    13  var log = logrus.WithField("component", "websocket")
    14  
    15  const (
    16  	// Time allowed to write a message to the peer.
    17  	writeWait = 10 * time.Second
    18  
    19  	// Time allowed to read the next pong message from the peer.
    20  	pongWait = 60 * time.Second
    21  
    22  	// Send pings to peer with this period. Must be less than pongWait.
    23  	pingPeriod = (pongWait * 9) / 10
    24  
    25  	// Maximum message size allowed from peer.
    26  	maxMessageSize = 512
    27  
    28  	sendBufSize = 256
    29  )
    30  
    31  // client is a connected websocket client.
    32  type client struct {
    33  	uuid         string
    34  	hub          *Hub
    35  	conn         *websocket.Conn
    36  	send         chan []byte
    37  	commandQueue chan<- command.Command
    38  }
    39  
    40  // newClient creates a new *client to handle a websocket connection.
    41  func newClient(hub *Hub, conn *websocket.Conn, queue chan<- command.Command) *client {
    42  	return &client{
    43  		uuid:         uuid.NewV4().String(),
    44  		hub:          hub,
    45  		conn:         conn,
    46  		send:         make(chan []byte, sendBufSize),
    47  		commandQueue: queue,
    48  	}
    49  }
    50  
    51  // listen registers the client to the websocket hub and starts listening for
    52  // incoming data from and data that should be written to the websocket.
    53  func (c *client) listen() {
    54  	c.hub.register <- c
    55  
    56  	go c.listenWrite()
    57  	go c.listenRead()
    58  }
    59  
    60  // listenRead reads messages from the websocket and processes them.
    61  func (c *client) listenRead() {
    62  	defer func() {
    63  		c.hub.unregister <- c
    64  		c.conn.Close()
    65  	}()
    66  
    67  	c.conn.SetReadLimit(maxMessageSize)
    68  	c.conn.SetReadDeadline(time.Now().Add(pongWait))
    69  	c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
    70  
    71  	for {
    72  		envelope := command.Envelope{}
    73  
    74  		if err := c.conn.ReadJSON(&envelope); err != nil {
    75  			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure, websocket.CloseNormalClosure) {
    76  				log.Errorf("websocket read error: %v", err)
    77  			}
    78  			return
    79  		}
    80  
    81  		cmd, err := command.Unpack(envelope)
    82  		if err != nil {
    83  			log.Errorf("failed to decode command: %v", err)
    84  			continue
    85  		}
    86  
    87  		if clientAwareCmd, ok := cmd.(command.SenderAwareCommand); ok {
    88  			clientAwareCmd.SetSender(c)
    89  		}
    90  
    91  		c.commandQueue <- cmd
    92  	}
    93  }
    94  
    95  // listenWrite writes messages received from the hub back to the websocket
    96  // connection.
    97  func (c *client) listenWrite() {
    98  	ticker := time.NewTicker(pingPeriod)
    99  	defer func() {
   100  		ticker.Stop()
   101  		c.conn.Close()
   102  	}()
   103  
   104  	for {
   105  		select {
   106  		case message, ok := <-c.send:
   107  			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
   108  			if !ok {
   109  				err := c.conn.WriteMessage(websocket.CloseMessage, []byte{})
   110  				if err != nil && !errors.Is(err, websocket.ErrCloseSent) {
   111  					log.Errorf("failed to send websocket close message: %v", err)
   112  				}
   113  				return
   114  			}
   115  
   116  			log.WithFields(logrus.Fields{
   117  				"length": len(message),
   118  				"uuid":   c.uuid,
   119  			}).Debug("sending message")
   120  
   121  			if err := c.conn.WriteMessage(websocket.TextMessage, message); err != nil {
   122  				log.Errorf("websocket write error: %v", err)
   123  				return
   124  			}
   125  		case <-ticker.C:
   126  			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
   127  			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
   128  				log.Errorf("websocket write error: %v", err)
   129  				return
   130  			}
   131  		}
   132  	}
   133  }
   134  
   135  // Send implements command.Sender.
   136  func (c *client) Send(msg []byte) {
   137  	c.hub.Send(c, msg)
   138  }