bgammon/pkg/server/client_websocket.go
2023-12-21 12:52:56 -08:00

151 lines
2.5 KiB
Go

package server
import (
"bytes"
"log"
"net"
"net/http"
"sync"
"time"
"code.rocket9labs.com/tslocum/bgammon"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
)
var _ bgammon.Client = &webSocketClient{}
type webSocketClient struct {
conn net.Conn
events chan []byte
commands chan<- []byte
terminated bool
wgEvents sync.WaitGroup
verbose bool
}
func newWebSocketClient(r *http.Request, w http.ResponseWriter, commands chan<- []byte, events chan []byte, verbose bool) *webSocketClient {
conn, _, _, err := ws.UpgradeHTTP(r, w)
if err != nil {
return nil
}
return &webSocketClient{
conn: conn,
events: events,
commands: commands,
verbose: verbose,
}
}
func (c *webSocketClient) HandleReadWrite() {
if c.terminated {
return
}
closeWrite := make(chan struct{}, 1)
go c.writeEvents(closeWrite)
c.readCommands()
closeWrite <- struct{}{}
}
func (c *webSocketClient) Write(message []byte) {
if c.terminated {
return
}
c.wgEvents.Add(1)
c.events <- message
}
func (c *webSocketClient) readCommands() {
setTimeout := func() {
err := c.conn.SetReadDeadline(time.Now().Add(clientTimeout))
if err != nil {
c.Terminate(err.Error())
return
}
}
for {
if c.terminated {
return
}
setTimeout()
msg, op, err := wsutil.ReadClientData(c.conn)
if err != nil {
c.Terminate(err.Error())
return
} else if op != ws.OpText {
continue
}
buf := make([]byte, len(msg))
copy(buf, msg)
c.commands <- buf
if c.verbose {
logClientRead(msg)
}
}
}
func (c *webSocketClient) writeEvents(closeWrite chan struct{}) {
setTimeout := func() {
err := c.conn.SetWriteDeadline(time.Now().Add(clientTimeout))
if err != nil {
c.Terminate(err.Error())
return
}
}
setTimeout()
var event []byte
for {
select {
case <-closeWrite:
for {
select {
case <-c.events:
c.wgEvents.Done()
default:
return
}
}
case event = <-c.events:
}
if c.terminated {
c.wgEvents.Done()
continue
}
setTimeout()
err := wsutil.WriteServerMessage(c.conn, ws.OpText, event)
if err != nil {
c.Terminate(err.Error())
c.wgEvents.Done()
continue
}
if c.verbose && !bytes.HasPrefix(event, []byte(`{"Type":"ping"`)) && !bytes.HasPrefix(event, []byte(`{"Type":"list"`)) {
log.Printf("-> %s", event)
}
c.wgEvents.Done()
}
}
func (c *webSocketClient) Terminate(reason string) {
if c.terminated {
return
}
c.terminated = true
c.conn.Close()
}
func (c *webSocketClient) Terminated() bool {
return c.terminated
}