I'm trying to set up a simple websocket server that should serve the client some content at unknown intervals.
My code currently looks like this:
router.go
func SetupRoutes(app *fiber.App) error {
app.Get("/whop/validate", handler.HandleWhopValidate)
/*Other non-websocket routes*/
/*...*/
app.Get("/ws/monitor", websocket.New(wsHandler.HandleWsMonitor))
app.Use(func(c *fiber.Ctx) error {
c.SendStatus(404)
return c.Next()
})
return nil
}
handler.go
package handlers
import (
"fmt"
"log"
"github.com/gofiber/websocket/v2"
)
var register = make(chan *websocket.Conn)
var unregister = make(chan *websocket.Conn)
func HandleWsMonitor(c *websocket.Conn) {
go SocketListener()
defer func() {
unregister <- c
//may need to check whether connection is already closed before re-closing?
c.Close()
}()
//sends conn into channel
register <- c
for {
messageType, message, err := c.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Println("read error:", err)
}
return
}
if messageType == websocket.TextMessage {
log.Println("got textmessage:", string(message))
} else {
log.Println("received message of type:", messageType)
}
}
}
func SocketListener() {
for {
select {
case c := <-register:
messageType, message, err := c.ReadMessage()
if err != nil {
log.Println(err)
unregister <- c
return
}
fmt.Printf("Got message of type: %d\nMessage:%s\n", messageType, string(message))
fmt.Printf("Connection Params: %s\n", c.Params("id"))
//append to list of co
case c := <-unregister:
//remove conection from list of clients
c.Close()
fmt.Printf("Closed connection\n")
}
}
}
The issue I'm having is that when I connect to the websocket, my select case for register is not hit (I'd want to register the client connection to a map using a uuid that is previously provided to the client).
client.go
package main
import (
"flag"
"log"
"net/url"
"github.com/fasthttp/websocket"
)
type Client struct {
C *websocket.Conn
}
func main() {
addr := flag.String("addr", "localhost:8080", "http service address")
u := url.URL{
Scheme: "ws",
Host: *addr,
Path: "/ws/monitor",
RawQuery: "id=12",
}
wsClient := &Client{}
log.Printf("connecting to %s\n", u.String())
// Connect to the WebSocket server
conn, resp, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("Dial:", err)
}
wsClient.C = conn
if resp != nil {
log.Println("Got response:", resp)
}
defer wsClient.closeConn()
}
func (client *Client) closeConn() {
err := client.C.WriteMessage(
websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""),
)
if err != nil {
log.Println("Write close:", err)
return
}
client.C.Close()
log.Println("Connection closed")
}
Is there something I'm missing in handler.go or should i just be taking a different approach when connecting to the server with my client?
The select case for register did hit according to my test (the code I used is attached to the bottom of this answer).
But I found other issues in the code:
unregisterchan is unbuffered, andunregister <- cinSocketListenerwill be blocked. When the code reachesunregister <- c, there is a deadlock between it andcase c := <-unregister.SocketListenergoroutine for the whole server. If this is the case, it should be moved outside ofHandleWsMonitor.HandleWsMonitorandSocketListenerread from the connection. What's the responsibility ofSocketListener? It seems that it should not read from the connection.Thinking it more, it seems that you can add connections to and delete connections from the map in
HandleWsMonitordirectly.SocketListenercan be removed entirely. Simplicity should be a key goal in design. See the KISS principle.