Introduction
In this article, I will explore the backend implementation of my real-time WebSocket application. Built using Gin and Go, the backend efficiently manages WebSocket connections, stores messages, and broadcasts updates to all connected clients.
Project Structure
https://github.com/tom-takeru/web-socket-demo
My backend project is organized to ensure modularity and reusability. Below is the updated directory structure:
./backend
├── go.mod
├── go.sum
├── main.go
└── stores
└── messages.go
Key Directories and Files
-
go.mod
: Defines module dependencies and versions. -
main.go
: Entry point of the application that initializes the WebSocket server and routes. -
stores/messages.go
: Manages message storage with thread-safe operations.
Core Component: main.go
main.go
is the main entry point for my WebSocket server application. It sets up the Gin router, defines the WebSocket route, and handles the WebSocket lifecycle.
Code Walkthrough
package main
import (
"encoding/json"
"net/http"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/tom-takeru/web-socket-demo/backend/stores"
)
var (
upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
origin := r.Header.Get("Origin")
// NOTE: This project is for local development only.
return origin == "http://localhost:3000"
},
}
messageStore = stores.NewMessageStore()
clients = make(map[*websocket.Conn]bool)
clientsMu sync.Mutex
)
func handleWebSocket(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to upgrade to WebSocket"})
return
}
defer conn.Close()
clientsMu.Lock()
clients[conn] = true
clientsMu.Unlock()
// Send existing messages to the new connection
for _, msg := range messageStore.MarshalMessages() {
conn.WriteMessage(websocket.TextMessage, msg)
}
for {
_, message, err := conn.ReadMessage()
if err != nil {
break
}
var msgData map[string]string
if err := json.Unmarshal(message, &msgData); err != nil {
break
}
timestamp := time.Now().Format(time.RFC3339)
msgData["timestamp"] = timestamp
messageStore.AddMessage(msgData)
modifiedMessage, err := json.Marshal(msgData)
if err != nil {
break
}
clientsMu.Lock()
for client := range clients {
if err := client.WriteMessage(websocket.TextMessage, modifiedMessage); err != nil {
client.Close()
delete(clients, client)
}
}
clientsMu.Unlock()
}
clientsMu.Lock()
delete(clients, conn)
clientsMu.Unlock()
}
func main() {
r := gin.Default()
r.GET("/ws", handleWebSocket)
r.Run("localhost:8080")
}
Key Functionalities
- State Management: Tracks connected clients and ensures thread-safe access using a mutex.
- WebSocket Lifecycle: Handles connection setup, message broadcasting, and cleanup on disconnection.
Conclusion
The backend implementation of my WebSocket demo application demonstrates how to manage real-time communication effectively using Gin and Go. By leveraging WebSocket for persistent connections and a thread-safe message store, this application serves as a robust foundation for building real-time web applications.
In the next article, I will discuss deployment strategies and optimizing WebSocket performance.
Links to the Series
Source link
lol