WebSocket Integration
This tutorial will guide you through implementing WebSocket connections in your Gonyx application. You'll learn how to set up bidirectional real-time communication between your server and clients, handle WebSocket connections in controllers, and build practical WebSocket features.
Prerequisites
Before starting this tutorial, make sure you have:
- Completed the Quick Start tutorial
- Basic understanding of HTTP and REST APIs
- Gonyx Framework v0.4.1 or later installed
- Familiarity with WebSocket concepts
- Go 1.23.0 or later installed
What is WebSocket?
WebSocket is a communication protocol that provides full-duplex (bidirectional) communication channels over a single TCP connection. Unlike traditional HTTP requests, WebSocket connections remain open, allowing real-time data exchange between client and server.
Why Use WebSocket?
WebSocket is ideal when you need:
- Real-time updates: Push notifications, live chat, collaborative editing
- Bidirectional communication: Both client and server can initiate messages
- Low latency: Persistent connection eliminates HTTP request/response overhead
- Efficient data transfer: Reduced bandwidth compared to polling
Common Use Cases
- Chat Applications: Real-time messaging between users
- Live Notifications: Instant alerts and updates
- Gaming: Multiplayer game state synchronization
- Collaborative Tools: Real-time document editing, whiteboards
- Financial Applications: Live stock prices, trading updates
- IoT Dashboards: Real-time sensor data visualization
Understanding WebSocket in Gonyx
Gonyx makes it easy to integrate WebSocket functionality into your HTTP controllers. Under the hood, Gonyx uses the gorilla/websocket package, which is the industry-standard WebSocket implementation for Go.
How WebSocket Works with Gonyx
- HTTP Upgrade: A client makes a standard HTTP request with upgrade headers
- Connection Upgrade: The server upgrades the HTTP connection to WebSocket protocol
- Persistent Connection: The connection remains open for bidirectional communication
- Message Exchange: Both client and server can send/receive messages at any time
- Connection Close: Either side can close the connection when done
Project Setup
First, create a new Gonyx project using the CLI:
gonyx init gonyx-websocket-demo --path .
cd gonyx-websocket-demo
This creates the standard Gonyx project structure.
Step 1: WebSocket Dependency
Good news! The gorilla/websocket package is already included by default when you initialize a Gonyx project. You can verify this by checking your go.mod file:
module gonyx-websocket-demo
go 1.23.0
require (
github.com/Blocktunium/gonyx v0.4.1
github.com/gin-gonic/gin v1.10.0
github.com/gorilla/websocket v1.5.3
// ... other dependencies
)
The dependency is automatically configured during project initialization, so you can start using WebSocket functionality right away without any additional setup.
Step 2: Create WebSocket Controller
Now let's implement a WebSocket endpoint in your controller. Open app/controller.go and add the following:
package app
import (
netHttp "net/http"
"fmt"
"github.com/Blocktunium/gonyx/pkg/http"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
// SampleController - a sample controller with WebSocket support
type SampleController struct{}
// GetName - return the name of the controller to be used as part of the route
func (ctrl *SampleController) GetName() string {
return "Sample"
}
// Routes - returning controller specific routes to be registered
func (ctrl *SampleController) Routes() []http.HttpRoute {
return []http.HttpRoute{
{
Method: http.MethodGet,
Path: "/hello",
RouteName: "hello",
F: ctrl.GetHello,
},
{
Method: http.MethodGet,
Path: "/ws",
RouteName: "ws",
F: ctrl.WebSocketHandler,
},
}
}
// GetHello - just return the 'Hello World' string to user
// @Summary Get Hello World
// @Description Returns a simple "Hello World" message
// @Tags Sample
// @Produce plain
// @Success 200 {string} string "Hello World"
// @Router /api/v1/sample/hello [get]
func (ctrl *SampleController) GetHello(c *gin.Context) {
c.String(200, "Hello World")
}
// WebSocketHandler - establishes a WebSocket connection for real-time bidirectional communication
// @Summary WebSocket Connection Endpoint
// @Description Upgrades HTTP connection to WebSocket protocol for real-time bidirectional communication. Echoes back any received message.
// @Tags Sample
// @Success 101 {string} string "Switching Protocols - WebSocket connection established"
// @Failure 400 {string} string "Bad Request - WebSocket upgrade failed"
// @Router /api/v1/sample/ws [get]
func (ctrl *SampleController) WebSocketHandler(c *gin.Context) {
// Upgrader specifies parameters for upgrading an HTTP connection to a
// WebSocket connection.
//
// It is safe to call Upgrader's methods concurrently.
upgrader := websocket.Upgrader{
CheckOrigin: func(r *netHttp.Request) bool {
// You can add origin checks here for security
// For development, allow all origins
return true
},
}
// Upgrade the HTTP server connection to the WebSocket protocol
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
fmt.Println("WebSocket upgrade error:", err)
return
}
defer conn.Close()
// Actual send/receive logic
for {
messageType, msg, err := conn.ReadMessage()
if err != nil {
fmt.Println("Read error:", err)
break
}
fmt.Printf("Received message: %s\n", msg)
// Echo message back to client
err = conn.WriteMessage(messageType, []byte("Echo: "+string(msg)))
if err != nil {
fmt.Println("Write error:", err)
break
}
}
}
Understanding the Code
Let's break down the WebSocket handler:
-
Upgrader Configuration: Creates a
websocket.Upgraderwith custom settingsCheckOrigin: Validates the request origin (security feature)
-
HTTP Upgrade:
upgrader.Upgrade()converts the HTTP connection to WebSocket- Returns a
*websocket.Connfor sending/receiving messages - Automatically handles the WebSocket handshake
- Returns a
-
Message Loop: Continuously reads messages from the client
conn.ReadMessage(): Blocks until a message is received- Returns message type (text/binary) and the message data
-
Echo Response: Sends received messages back to the client
conn.WriteMessage(): Sends a message to the client- Prefixes the original message with "Echo: "
-
Connection Cleanup:
defer conn.Close()ensures proper cleanup
Documenting with Swagger Annotations
To document your WebSocket endpoint in Swagger/OpenAPI, add annotation comments above the handler function. This helps generate API documentation automatically:
// WebSocketHandler - establishes a WebSocket connection for real-time bidirectional communication
// @Summary WebSocket Connection Endpoint
// @Description Upgrades HTTP connection to WebSocket protocol for real-time bidirectional communication. Echoes back any received message.
// @Tags Sample
// @Success 101 {string} string "Switching Protocols - WebSocket connection established"
// @Failure 400 {string} string "Bad Request - WebSocket upgrade failed"
// @Router /api/v1/sample/ws [get]
func (ctrl *SampleController) WebSocketHandler(c *gin.Context) {
// Implementation here...
}
Swagger Annotation Breakdown:
@Summary: Short description of the endpoint@Description: Detailed explanation of what the endpoint does@Tags: Groups the endpoint under a category in Swagger UI@Success 101: HTTP 101 status code indicates protocol switch (WebSocket upgrade)@Failure 400: Indicates possible failure when upgrade fails@Router: Specifies the full API path and HTTP method
WebSocket endpoints use HTTP GET requests initially, then upgrade to the WebSocket protocol. The status code 101 (Switching Protocols) indicates a successful upgrade.
For more details on Swagger integration, see the Swagger Integration tutorial.
Step 3: Register the Controller
Make sure your controller is registered in app/app.go:
package app
import (
"github.com/Blocktunium/gonyx/pkg/engine"
)
type App struct {}
func (app *App) Init() {
engine.RegisterRestfulController(&SampleController{})
}
Step 4: Configure CORS for WebSocket (Optional)
If you're accessing the WebSocket from a web browser on a different origin, you need to configure CORS. Update your configs/dev/http.json:
{
"default": "s1",
"servers": [
{
"name": "s1",
"addr": ":3000",
"versions": ["v1"],
"support_static": false,
"conf": {
"read_timeout": -1,
"write_timeout": -1,
"request_methods": ["ALL"]
},
"middlewares": {
"order": ["logger", "cors"],
"cors": {
"allow_all_origins": true,
"allow_origins": ["*"],
"allow_methods": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
"allow_websockets": true,
"allow_credentials": false,
"max_age": 0
}
}
}
]
}
Step 5: Run and Test Your WebSocket Server
Start your Gonyx server:
go run main.go runserver
You should see output like:
...
2025/10/07 17:45:38 New Http Server have been created...
[GIN-debug] GET /sample/hello --> gonyx-websocket-test2/app.(*SampleController).GetHello-fm (4 handlers)
[GIN-debug] GET /sample/ws --> gonyx-websocket-test2/app.(*SampleController).WebSocketHandler-fm (4 handlers)
...
Testing Your WebSocket Connection
Option 1: Using wscat (Command Line)
Install wscat globally:
npm install -g wscat
Connect to your WebSocket endpoint:
wscat -c ws://localhost:3000/api/v1/sample/ws
Type messages and see the echo response:
Connected (press CTRL+C to quit)
> Hello WebSocket
< Echo: Hello WebSocket
> This is a test
< Echo: This is a test
Option 2: Using JavaScript in Browser
Create an HTML file to test the WebSocket connection:
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Test</title>
</head>
<body>
<h1>WebSocket Test</h1>
<div>
<input type="text" id="messageInput" placeholder="Enter message">
<button onclick="sendMessage()">Send</button>
</div>
<div>
<h3>Messages:</h3>
<div id="messages"></div>
</div>
<script>
const ws = new WebSocket('ws://localhost:3000/api/v1/sample/ws');
ws.onopen = function() {
console.log('Connected to WebSocket');
addMessage('Connected to server', 'system');
};
ws.onmessage = function(event) {
console.log('Received:', event.data);
addMessage(event.data, 'received');
};
ws.onerror = function(error) {
console.error('WebSocket error:', error);
addMessage('Error: ' + error, 'error');
};
ws.onclose = function() {
console.log('Disconnected from WebSocket');
addMessage('Disconnected from server', 'system');
};
function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value;
if (message) {
ws.send(message);
addMessage(message, 'sent');
input.value = '';
}
}
function addMessage(text, type) {
const messagesDiv = document.getElementById('messages');
const messageDiv = document.createElement('div');
messageDiv.style.padding = '5px';
messageDiv.style.margin = '5px 0';
messageDiv.style.borderRadius = '3px';
if (type === 'sent') {
messageDiv.style.backgroundColor = '#e3f2fd';
messageDiv.textContent = 'Sent: ' + text;
} else if (type === 'received') {
messageDiv.style.backgroundColor = '#f1f8e9';
messageDiv.textContent = text;
} else if (type === 'system') {
messageDiv.style.backgroundColor = '#fff3e0';
messageDiv.textContent = text;
} else {
messageDiv.style.backgroundColor = '#ffebee';
messageDiv.textContent = text;
}
messagesDiv.appendChild(messageDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
</script>
</body>
</html>
Open this file in your browser and test the WebSocket connection.
Option 3: Using Postman
- Open Postman
- Create a new WebSocket request
- Enter:
ws://localhost:3000/api/v1/sample/ws - Click "Connect"
- Send messages and observe the responses
Advanced WebSocket Features
1. Broadcasting to Multiple Clients
To send messages to all connected clients, maintain a list of active connections:
package app
import (
"sync"
"github.com/gorilla/websocket"
)
// ConnectionManager manages all active WebSocket connections
type ConnectionManager struct {
connections map[*websocket.Conn]bool
mu sync.RWMutex
broadcast chan []byte
}
var manager = &ConnectionManager{
connections: make(map[*websocket.Conn]bool),
broadcast: make(chan []byte),
}
// AddConnection adds a new connection to the manager
func (m *ConnectionManager) AddConnection(conn *websocket.Conn) {
m.mu.Lock()
defer m.mu.Unlock()
m.connections[conn] = true
}
// RemoveConnection removes a connection from the manager
func (m *ConnectionManager) RemoveConnection(conn *websocket.Conn) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.connections, conn)
conn.Close()
}
// Start starts the broadcast handler
func (m *ConnectionManager) Start() {
for {
message := <-m.broadcast
m.mu.RLock()
for conn := range m.connections {
err := conn.WriteMessage(websocket.TextMessage, message)
if err != nil {
conn.Close()
delete(m.connections, conn)
}
}
m.mu.RUnlock()
}
}
// BroadcastHandler handles WebSocket connections with broadcasting
func (ctrl *SampleController) BroadcastHandler(c *gin.Context) {
upgrader := websocket.Upgrader{
CheckOrigin: func(r *netHttp.Request) bool {
return true
},
}
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
manager.AddConnection(conn)
defer manager.RemoveConnection(conn)
for {
_, msg, err := conn.ReadMessage()
if err != nil {
break
}
// Broadcast message to all clients
manager.broadcast <- msg
}
}
2. Message Types and JSON Communication
Handle structured JSON messages:
import (
"encoding/json"
)
type Message struct {
Type string `json:"type"`
Payload interface{} `json:"payload"`
From string `json:"from"`
}
func (ctrl *SampleController) JSONWebSocketHandler(c *gin.Context) {
upgrader := websocket.Upgrader{
CheckOrigin: func(r *netHttp.Request) bool {
return true
},
}
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
defer conn.Close()
for {
_, msgBytes, err := conn.ReadMessage()
if err != nil {
break
}
var msg Message
if err := json.Unmarshal(msgBytes, &msg); err != nil {
continue
}
// Process message based on type
switch msg.Type {
case "ping":
response := Message{
Type: "pong",
Payload: "Server is alive",
}
responseBytes, _ := json.Marshal(response)
conn.WriteMessage(websocket.TextMessage, responseBytes)
case "chat":
// Handle chat message
fmt.Printf("Chat from %s: %v\n", msg.From, msg.Payload)
default:
fmt.Println("Unknown message type:", msg.Type)
}
}
}
3. Connection Authentication
Authenticate WebSocket connections using query parameters or headers:
func (ctrl *SampleController) AuthenticatedWebSocketHandler(c *gin.Context) {
// Get authentication token from query parameter
token := c.Query("token")
if token == "" {
c.JSON(401, gin.H{"error": "Authentication required"})
return
}
// Validate token (implement your own validation logic)
if !isValidToken(token) {
c.JSON(401, gin.H{"error": "Invalid token"})
return
}
upgrader := websocket.Upgrader{
CheckOrigin: func(r *netHttp.Request) bool {
return true
},
}
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
defer conn.Close()
// WebSocket logic here...
}
func isValidToken(token string) bool {
// Implement your token validation logic
return token == "valid-token-123"
}
4. Ping/Pong for Connection Health
Implement ping/pong to detect broken connections:
import (
"time"
)
func (ctrl *SampleController) HealthCheckWebSocketHandler(c *gin.Context) {
upgrader := websocket.Upgrader{
CheckOrigin: func(r *netHttp.Request) bool {
return true
},
}
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
defer conn.Close()
// Set up ping/pong handlers
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
conn.SetPongHandler(func(string) error {
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
return nil
})
// Start ping ticker
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
go func() {
for range ticker.C {
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
}()
// Message handling loop
for {
_, msg, err := conn.ReadMessage()
if err != nil {
break
}
// Process message
conn.WriteMessage(websocket.TextMessage, msg)
}
}
Security Best Practices
1. Origin Validation
In production, always validate the origin:
upgrader := websocket.Upgrader{
CheckOrigin: func(r *netHttp.Request) bool {
origin := r.Header.Get("Origin")
allowedOrigins := []string{
"https://yourdomain.com",
"https://app.yourdomain.com",
}
for _, allowed := range allowedOrigins {
if origin == allowed {
return true
}
}
return false
},
}
2. Rate Limiting
Implement rate limiting to prevent abuse:
type Client struct {
conn *websocket.Conn
lastMsg time.Time
msgCount int
}
const (
maxMessagesPerSecond = 10
rateLimitWindow = time.Second
)
func (c *Client) checkRateLimit() bool {
now := time.Now()
if now.Sub(c.lastMsg) > rateLimitWindow {
c.msgCount = 0
c.lastMsg = now
}
c.msgCount++
return c.msgCount <= maxMessagesPerSecond
}
3. Message Size Limits
Set maximum message size to prevent memory exhaustion:
upgrader := websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *netHttp.Request) bool {
return true
},
}
// Set message size limit (1MB)
conn.SetReadLimit(1024 * 1024)
4. TLS/SSL for Production
Always use WSS (WebSocket Secure) in production:
wss://yourdomain.com/api/v1/sample/ws
Configure your HTTP server with TLS certificates in your Gonyx configuration.
Troubleshooting
Connection Fails to Upgrade
Problem: WebSocket upgrade fails with 400 Bad Request
Solutions:
- Ensure you're using
ws://protocol (orwss://for secure) - Check that CORS is properly configured
- Verify the CheckOrigin function returns true for your origin
Messages Not Receiving
Problem: Client sends messages but server doesn't receive them
Solutions:
- Check server logs for errors
- Ensure the message loop is running (not blocked)
- Verify message format matches expected type (text/binary)
Connection Drops Unexpectedly
Problem: WebSocket connection closes after a few seconds
Solutions:
- Implement ping/pong handlers
- Check server timeout settings
- Look for errors in the message handling loop
High Memory Usage
Problem: Server memory grows with each connection
Solutions:
- Ensure connections are properly closed with
defer conn.Close() - Remove closed connections from your connection manager
- Set appropriate read/write buffer sizes
Summary
In this tutorial, you learned:
- ✅ What WebSocket is and when to use it
- ✅ How to set up WebSocket in Gonyx with gorilla/websocket
- ✅ Creating WebSocket endpoints in controllers
- ✅ Handling bidirectional communication
- ✅ Testing WebSocket connections
- ✅ Advanced features: broadcasting, JSON messages, authentication
- ✅ Security best practices for production
Next Steps
Now that you understand WebSocket integration, you can:
- Build a real-time chat application
- Implement live notifications system
- Create collaborative features
- Explore the CORS Setup tutorial for advanced CORS configuration
- Check out the Multiple HTTP Servers tutorial for scaling strategies