Skip to content

Instantly share code, notes, and snippets.

@percybolmer
Last active September 11, 2022 05:37
Show Gist options
  • Save percybolmer/30afe06964f954161c6eb74dc6077b0f to your computer and use it in GitHub Desktop.
Save percybolmer/30afe06964f954161c6eb74dc6077b0f to your computer and use it in GitHub Desktop.
package main
import (
"encoding/json"
"log"
"github.com/gorilla/websocket"
)
// ClientList is a map used to help manage a map of clients
type ClientList map[*Client]bool
// Client is a websocket client, basically a frontend visitor
type Client struct {
// the websocket connection
connection *websocket.Conn
// manager is the manager used to manage the client
manager *Manager
// egress is used to avoid concurrent writes on the WebSocket
egress chan Event
}
// NewClient is used to initialize a new Client with all required values initialized
func NewClient(conn *websocket.Conn, manager *Manager) *Client {
return &Client{
connection: conn,
manager: manager,
egress: make(chan Event),
}
}
// readMessages will start the client to read messages and handle them
// appropriatly.
// This is suppose to be ran as a goroutine
func (c *Client) readMessages() {
defer func() {
// Graceful Close the Connection once this
// function is done
c.manager.removeClient(c)
}()
// Loop Forever
for {
// ReadMessage is used to read the next message in queue
// in the connection
_, payload, err := c.connection.ReadMessage()
if err != nil {
// If Connection is closed, we will Recieve an error here
// We only want to log Strange errors, but simple Disconnection
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("error reading message: %v", err)
}
break // Break the loop to close conn & Cleanup
}
// Marshal incoming data into a Event struct
var request Event
if err := json.Unmarshal(payload, &request); err != nil {
log.Printf("error marshalling message: %v", err)
break // Breaking the connection here might be harsh xD
}
// Route the Event
if err := c.manager.routeEvent(request, c); err != nil {
log.Println("Error handeling Message: ", err)
}
}
}
// writeMessages is a process that listens for new messages to output to the Client
func (c *Client) writeMessages() {
defer func() {
// Graceful close if this triggers a closing
c.manager.removeClient(c)
}()
for {
select {
case message, ok := <-c.egress:
// Ok will be false Incase the egress channel is closed
if !ok {
// Manager has closed this connection channel, so communicate that to frontend
if err := c.connection.WriteMessage(websocket.CloseMessage, nil); err != nil {
// Log that the connection is closed and the reason
log.Println("connection closed: ", err)
}
// Return to close the goroutine
return
}
data, err := json.Marshal(message)
if err != nil {
log.Println(err)
return // closes the connection, should we really
}
// Write a Regular text message to the connection
if err := c.connection.WriteMessage(websocket.TextMessage, data); err != nil {
log.Println(err)
}
log.Println("sent message")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment