Skip to content

[BUG] Socket Disconnects Unexpectdly (ON SERVER ONLY) After Receiving an event successfully and handling all logic (everything works fine locally) #979

@moath2elmarmori

Description

@moath2elmarmori

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

No response

Expected Behavior

No response

Steps To Reproduce

No response

Anything else?

I am experiencing an issue with WebSocket connections when deploying my application to a VPS. The WebSocket connection works perfectly on my local machine, but when deployed to a VPS, the frontend connect to the socket without any problems
and it even can last minutes on stale connection
the problem is that when i send an event to the socket and it receives it and handles it well and saves some data in the db and even emits events to other connected user to the same room
it closes the socket connection and says socket closed => status code 1005 (no status)
what is bugging me is that sometimes it doesn't close the connection even after 4 or 5 events of send message and everything works fine you think
but then after the fifth event it well handle it well
and then close the socket

i suspected that there is something wrong with buffer size
and i'm printing it and it's fine for the messages that i'm receiving and the messages that i'm writing

i even double and triple checked nginc configuration
and everything is ok
i disabled the firewall also
and still the problem persistes

can anyone give me an insight so i can know if this is a problem with my code (and why does it work fine on my local computer with multiple clients)
or is it a problem with network and something else or something with package

my code:


socketUtils.go

`package utils

import (
"encoding/json"
"fmt"
"log"
"sync"
"time"

"github.com/fatih/color"
"github.com/gorilla/websocket"
"github.com/moath2elmarmori/noorbeam_go_backend/config"
"github.com/moath2elmarmori/noorbeam_go_backend/dto"
"github.com/moath2elmarmori/noorbeam_go_backend/models"

)

var rooms = make(map[string]*dto.SocketRoomDTO)
var roomsMutex sync.Mutex

func SocketJoinRoom(roomName string, client *dto.SocketClientDTO) {
roomsMutex.Lock()
room, exists := rooms[roomName]
if !exists {
room = &dto.SocketRoomDTO{
Clients: make(map[*dto.SocketClientDTO]bool),
Broadcast: make(chan []byte),
Register: make(chan *dto.SocketClientDTO),
Unregister: make(chan *dto.SocketClientDTO),
}
rooms[roomName] = room
go room.Run()
}
roomsMutex.Unlock()

room.Register <- client
color.Green("Client joined room %s", roomName)

}

func SocketBroadcastToRoom(roomName string, eventName string, message any) {

// color.Red("Broadcasting message to room %s", roomName)
// color.Red("Broadcasting message to event %s", eventName)
// color.Red("Broadcasting message %s", message)
roomsMutex.Lock()
room, exists := rooms[roomName]
roomsMutex.Unlock()

if exists {
	event := dto.SocketEventDTO{
		Name: eventName,
		Data: message,
	}
	messageBytes, err := json.Marshal(event)
	msgSize := len(messageBytes)
	color.Yellow("Size of event %s: message size bytes %d", eventName, msgSize)
	// messageBytes, err := json.Marshal(message)
	if err != nil {
		log.Println("Failed to marshal message:", err)
		return
	}
	room.Broadcast <- messageBytes
	// color.Green("Message broadcasted to room: %s", roomName)
}

}`



socketController.go

`
func (sc SocketController) ChatSocket(context *gin.Context) {
userId := context.Query("userId")
userSlug := context.Query("userSlug")
accessToken := context.Query("accessToken")
userIdAsUInt, _, err := utils.VerifiyToken(accessToken)
if err != nil {
context.AbortWithStatusJSON(401, gin.H{"message": "Unauthenticated"})
return
}
// update user to be online
var foundUser models.User
db.GormDB.Where("id = ?", userId).First(&foundUser)
if foundUser.ID == 0 {
context.AbortWithStatusJSON(404, gin.H{"message": "User not found"})
return
}
if foundUser.ID != userIdAsUInt || foundUser.Slug != userSlug {
context.AbortWithStatusJSON(401, gin.H{"message": "Unauthenticated"})
return
}
foundUser.IsOnline = true
now := time.Now()
foundUser.LastOnlineAt = &now
db.GormDB.Save(&foundUser)

// the logic for websocket
ws, err := upgrader.Upgrade(context.Writer, context.Request, nil)
if err != nil {
	color.Red("Failed to upgrade to websocket: %s", err.Error())
	return
}
defer func() {
	color.Cyan("Closing the connection chat")
	ws.Close()
}()

client := &dto.SocketClientDTO{Conn: ws, Send: make(chan []byte)}
go client.WritePump()

// start heartbeat
go utils.StartWebSocketHeartbeat(ws)

for {
	_, msg, err := ws.ReadMessage()
	if err != nil {
		if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
			color.Red("Unexpected close error: %v", err)
		} else {
			color.Red("chat socket read: %v", err)
		}
		break
	}
	color.Green("chat socket recv: %s", msg)

	var event dto.SocketEventDTO
	if err := json.Unmarshal(msg, &event); err != nil {

		color.Red("unmarshal:", err)
		continue
	}

	switch event.Name {
	case "join-room":

		services.ChatServiceInstance.SocketJoinRoomHandler(event.Room, userSlug, client)
	case "join-chat":
		services.ChatServiceInstance.SocketJoinChatHandler(event.ChatID, foundUser.ID, client)

	case "send-message":
		type IncomingEvent struct {
			Message string `json:"message"`
		}
		var incomingEvent IncomingEvent
		err := json.Unmarshal(msg, &incomingEvent)
		if err != nil {
			log.Fatalf("Error unmarshaling JSON: %v", err)
			return
		}
		services.ChatServiceInstance.SocketSendMessageHandler(incomingEvent.Message, userSlug, foundUser.ID, event.ChatID)

	case "seen-new-message":
		services.ChatServiceInstance.SocketSeenNewMessageHandler(event.ChatID, foundUser.ID, userSlug)
	case "disconnect-me":
		services.ChatServiceInstance.SocketDisconnectMeHandler(foundUser.ID, foundUser.Slug)

	}
}

}`



chatService.go

`
func (cs ChatService) SocketSendMessageHandler(message, userSlug string, userId, chatId uint) {
color.Green("before sending message")
if chatId == 0 {
return
}

var chat models.Chat
err := db.GormDB.Preload("Users").Where("id = ?", chatId).First(&chat).Error
if err != nil {
	return
}

// check if user is in the chat
var chatUser models.ChatUser
err = db.GormDB.Where("user_id = ? AND chat_id = ?", userId, chatId).First(&chatUser).Error
if err != nil {
	return
}

var senderUser models.User
err = db.GormDB.Where("id = ?", userId).First(&senderUser).Error
if err != nil {
	return
}

// save the message in the db
newMessage := models.Message{
	ChatId:   chatId,
	Text:     &message,
	SenderId: userId,
}
err = db.GormDB.Create(&newMessage).Error
if err != nil {
	return
}

// update the chat last message
err = db.GormDB.Model(&models.Chat{}).Where("id = ?", chatId).Update("last_message_id", newMessage.ID).Error
// err = db.GormDB.Save(&chat).Error
if err != nil {
	color.Red("Error updating chat last message id: %v", err)
	return
}

chatIdStr := strconv.Itoa(int(chatId))

messageHasBeenSentEvent := fmt.Sprintf("your-message-sent-successfully-%s", userSlug)

// emit an event to the user that sent the message that the message was sent
utils.SocketBroadcastToRoom(chatIdStr, messageHasBeenSentEvent, true)

utils.SocketBroadcastToRoom(chatIdStr, "receive-message", newMessage)
// update last message id

foundChatWithRelationships, err := cs.FindByIdWithRelationships(chatId)
if err != nil {
	return
}

utils.SocketBroadcastToRoom(userSlug, "receive-chat", foundChatWithRelationships)

// emitting the chat for the other users
for _, iteratedUser := range chat.Users {

	color.Blue("user name %s, userId %d, userSlug %s", iteratedUser.Username, iteratedUser.ID, iteratedUser.Slug)
	if iteratedUser.ID == userId {
		continue
	}
	if iteratedUser.ID != userId {
		utils.SocketBroadcastToRoom(iteratedUser.Slug, "receive-chat", foundChatWithRelationships)
	}

	newUnreadCount, err := cs.GetUnreadCountForChats(iteratedUser.ID)
	if err != nil {
		color.Red("Error getting unread count for user %d: %v", iteratedUser.ID, err)
		continue
	}

	// make an object to send the unread count
	unreadCountObj := map[string]int{"unread_count": newUnreadCount}

	utils.SocketBroadcastToRoom(iteratedUser.Slug, "unread-count", unreadCountObj)
	var foundUser models.User
	db.GormDB.Preload("FCMTokens").First(&foundUser, iteratedUser.ID)
	if foundUser.ID == 0 {
		color.Red("User not found with id %d", iteratedUser.ID)
		continue
	}
	// send push notification
	if len(foundUser.FCMTokens) > 0 {
		for _, fcmTokenRecord := range foundUser.FCMTokens {
			title := fmt.Sprintf("New message from %s", senderUser.Username)
			body := newMessage.Text
			utils.SendFirebaseNotificaiton(constants.FirebaseApp, title, *body, fcmTokenRecord.TheToken)
		}
	}
}

color.Green("after sending message")

}
`



socketRoomDTO.go

`package dto

import (
"github.com/fatih/color"
)

type SocketRoomDTO struct {
Clients map[*SocketClientDTO]bool
Broadcast chan []byte
Register chan *SocketClientDTO
Unregister chan *SocketClientDTO
}

func (r *SocketRoomDTO) Run() {
color.Red("Room is running")
for {
select {
case client := <-r.Register:
color.Green("Client registered")
r.Clients[client] = true
case client := <-r.Unregister:
color.Red("Client unregistered")
if _, ok := r.Clients[client]; ok {
delete(r.Clients, client)
close(client.Send)
}
case message := <-r.Broadcast:
color.Yellow("Broadcasting message")
for client := range r.Clients {
select {
case client.Send <- message:
default:
close(client.Send)
delete(r.Clients, client)
}
}
}
}
}
`


thank in advance
best regards

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions