I am working on the application with chat module which implemented using Angular in Frontend and Golang in Backend. We used RxJS package for websocket in the frontend, Fiber (framework) websocket (Includes Gorilla websocket) in backend. I have few queries here.
The websocket connections are getting closed some time randomly. Everything seems to be working fine in localhost, but when we move the code to Dev/QA server, it works fine for sometime, later it stops working, messages send will not reach to server / will not reach to recipients. We found that the websocket is getting closed due to some reasons. We thought that timeout in Load balancer cause the issue, we increased the timeout upto 3600 seconds. Also made some config changes in ngnix. But still we have the same issue.
To keep the websocket connection alive we implemented the PING/PONG mechanism. The server will send the PING frame, clients will respond as PONG. When I googled, there are some articles suggested to send the PING frame from clients and server will respond to it as PONG. I am bit confused which way would be feasible and better way to implement the PING/PONG mechanism, Is it from clients or server side?
When I debugged the code more, I got to know that the connections are being randomly changed in the Hub.Clients map which we use it for keeping the connections. So when we send a message, only few will receive it and few will not receive the messages. But we tried client side when we see that connection seems to be active. So bit confused where could be the issue?
Hub is passed as param to all the supporting functions wherever needed. Can we keep this Hub.Clients as global map so that it can be accessed from everywhere? Is there any restrictions on this? ALso can we store the connections in the DB and access it?
Also, Is it okay to use non-80 port for websockets? Will there be any advantage over port 80 than other ports?
Also any specific things to be handled in the infrastructure level?
Here the code what I use:
Angular Code
connectWebsocket() {
closeSubject.subscribe(error => {
this.connectWebsocket();
});
var websocketSubjectObj = {
url: this.websocketUrl,
closeObserver: closeSubject,
openObserver: {
next: () => {
console.log("Connection established");
}
}
};
this.wsSubject = webSocket(websocketSubjectObj);
var thisTemp = this;
this.wsSubject.asObservable().pipe(
retryWhen(
genericRetryStrategy({
scalingDuration: 5000,
maxRetryAttempts: 10
})
),
catchError(error => of(error))
).subscribe(dataFromServer => {
thisTemp.dataObserver.next(dataFromServer);
}, err => {
console.log("Error",err)
}, () => {
console.log("Web socket disconnected")
});
}
Go Code
// Routes/route.go
// ---------------
Hub := chatHandler.NewConnHub()
go chatHandler.RunConnHub(Hub)
app.Use("/ws", func(c *fiber.Ctx) error {
if websocket.IsWebSocketUpgrade(c) {
return c.Next()
}
log.Println("Error on websocket connection", fiber.StatusUpgradeRequired)
return c.SendStatus(fiber.StatusUpgradeRequired)
})
app.Get("/ws/:uniq_id", websocket.New(func(socket *websocket.Conn) {
chatHandler.ChannelWebSocketConnRedirector(Hub, socket) // chatHandler is another package where we have written the core logic
}))
// Chat/chat_handler.go
// --------------------
type ClientModel struct {
Socket *websocket.Conn
id string
Mu sync.Mutex
}
var clientsMutex ClientModel
func NewConnHub() *config.ConnHub {
return &config.ConnHub{
Notification: make(chan *config.Notification),
Register: make(chan *config.ClientModel),
Unregister: make(chan *config.ClientModel),
Clients: make(map[string]*config.ClientModel),
}
}
func ChannelWebSocketConn(hub *model.ConnHub, user *model.Client) {
// When the function returns, unregister the client and close the connection
defer func() {
hub.Unregister <- user
user.Socket.Close()
}()
t := time.Time{}
user.Socket.SetReadDeadline(t)
user.Socket.SetPongHandler(func(string) error {
log.Println("Got the pong")
user.Socket.SetReadDeadline(t)
return nil
})
// Register the client
hub.Register <- user
for {
messageType, message, err := user.Socket.ReadMessage()
if err != nil {
log.Println("this is unexpected close error", err)
return // Calls the deferred function, i.e. closes the connection on error
}
if messageType == websocket.TextMessage {
log.Println("Received some message", string(message))
hub.Notification <- &model.Notification{Client: user, Message: string(message)}
} else {
log.Println("Received the messages in other formats")
}
}
}
func RunConnHub(hub *config.ConnHub) {
for {
select {
case client := <-hub.Register:
// Add the user to the connection list
clientsMutex.Lock()
Hub.Clients[client.id] = client
clientsMutex.Unlock()
case client := <-hub.Unregister:
// Remove the user from the connection list
delete(Hub.Clients, client.id)
case notification := <-hub.Notification:
// Store the message
parsedMessage, err := gabs.ParseJSON([]byte(notification.Message))
message = parsedMessage.Path("message").Data().(string)
room = parsedMessage.Path("room_id").Data().(primitive.ObjectID)
activeUsers = getActiveUsersFromConnections(room,Hub.Clients) // Here will fetch the users from room and match with Hub.clients and get the active users
support.BroadcastMessage(activeUsers,message) // Called from support functions
}
}
}
// Support/support.functions.go
// -----------------------------
BroadcastMessage(activeUsers,message){
for _, ec := range activeUsers {
ec.Mu.Lock()
defer ec.Mu.Unlock()
t := time.Time{}
ec.Socket.SetWriteDeadline(t)
err = ec.Socket.WriteMessage(messageType, []byte(message))
if err != nil {
ec.Socket.Close()
}
}
}