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 }