2fas-server/internal/websocket/common/client.go

164 lines
3.4 KiB
Go
Raw Normal View History

2022-12-31 10:22:38 +01:00
package common
import (
"bytes"
2024-04-09 09:08:48 +02:00
"sync"
"time"
2022-12-31 10:22:38 +01:00
"github.com/gorilla/websocket"
2024-04-09 09:08:48 +02:00
2023-01-30 19:59:42 +01:00
"github.com/twofas/2fas-server/internal/common/logging"
2022-12-31 10:22:38 +01:00
)
var (
newline = []byte{'\n'}
space = []byte{' '}
acceptedCloseStatus = []int{
websocket.CloseNormalClosure,
websocket.CloseGoingAway,
websocket.CloseNoStatusReceived,
websocket.CloseAbnormalClosure,
}
)
const (
// Time allowed to write a message to the peer.
writeWait = 10 * time.Second
// Time allowed to read the next pong message from the peer.
pongWait = 60 * time.Second
// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
// Maximum message size allowed from peer.
maxMessageSize = 4 * 1048
)
// Client is a middleman between the websocket connection and the hub.
type Client struct {
hub *Hub
// The websocket connection.
conn *websocket.Conn
// Buffered channel of outbound messages.
send chan []byte
2024-04-09 09:08:48 +02:00
sendMtx *sync.Mutex
2022-12-31 10:22:38 +01:00
}
// readPump pumps messages from the websocket connection to the hub.
//
// The application runs readPump in a per-connection goroutine. The application
// ensures that there is at most one reader on a connection by executing all
// reads from this goroutine.
2024-03-16 19:05:21 +01:00
func (c *Client) readPump(log logging.FieldLogger) {
2022-12-31 10:22:38 +01:00
defer func() {
c.hub.unregisterClient(c)
2022-12-31 10:22:38 +01:00
c.conn.Close()
}()
c.conn.SetReadLimit(maxMessageSize)
c.conn.SetReadDeadline(time.Now().Add(pongWait))
2023-04-11 23:38:12 +02:00
c.conn.SetPongHandler(func(string) error {
c.conn.SetReadDeadline(time.Now().Add(pongWait))
return nil
})
2022-12-31 10:22:38 +01:00
for {
_, message, err := c.conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, acceptedCloseStatus...) {
2024-03-16 19:05:21 +01:00
log.WithFields(logging.Fields{
2022-12-31 10:22:38 +01:00
"reason": err.Error(),
}).Error("Websocket connection closed unexpected")
2023-02-01 13:05:12 +01:00
} else {
2024-03-16 19:05:21 +01:00
log.WithFields(logging.Fields{
2023-02-01 13:05:12 +01:00
"reason": err.Error(),
}).Info("Connection closed")
2022-12-31 10:22:38 +01:00
}
break
}
message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
go c.hub.broadcastMsg(message)
2022-12-31 10:22:38 +01:00
}
}
// writePump pumps messages from the hub to the websocket connection.
//
// A goroutine running writePump is started for each connection. The
// application ensures that there is at most one writer to a connection by
// executing all writes from this goroutine.
func (c *Client) writePump() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
c.conn.Close()
}()
for {
select {
case message, ok := <-c.send:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if !ok {
// The hub closed the channel.
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
w, err := c.conn.NextWriter(websocket.TextMessage)
if err != nil {
return
}
w.Write(message)
// Add queued chat messages to the current websocket message.
n := len(c.send)
for i := 0; i < n; i++ {
w.Write(newline)
w.Write(<-c.send)
}
if err := w.Close(); err != nil {
return
}
case <-ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
}
}
2024-04-09 09:08:48 +02:00
func (c *Client) sendMsg(bb []byte) bool {
c.sendMtx.Lock()
defer c.sendMtx.Unlock()
if c.send == nil {
return false
}
c.send <- bb
return true
}
func (c *Client) close() {
c.sendMtx.Lock()
defer c.sendMtx.Unlock()
if c.send == nil {
return
}
close(c.send)
c.send = nil
}