Skip to main content
Version: 0.4.1

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

  1. HTTP Upgrade: A client makes a standard HTTP request with upgrade headers
  2. Connection Upgrade: The server upgrades the HTTP connection to WebSocket protocol
  3. Persistent Connection: The connection remains open for bidirectional communication
  4. Message Exchange: Both client and server can send/receive messages at any time
  5. 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:

go.mod
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:

app/controller.go
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:

  1. Upgrader Configuration: Creates a websocket.Upgrader with custom settings

    • CheckOrigin: Validates the request origin (security feature)
  2. HTTP Upgrade: upgrader.Upgrade() converts the HTTP connection to WebSocket

    • Returns a *websocket.Conn for sending/receiving messages
    • Automatically handles the WebSocket handshake
  3. 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
  4. Echo Response: Sends received messages back to the client

    • conn.WriteMessage(): Sends a message to the client
    • Prefixes the original message with "Echo: "
  5. 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:

app/controller.go
// 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
tip

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:

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:

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:

websocket-test.html
<!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

  1. Open Postman
  2. Create a new WebSocket request
  3. Enter: ws://localhost:3000/api/v1/sample/ws
  4. Click "Connect"
  5. 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:

app/controller.go
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:

app/controller.go
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:

app/controller.go
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:

app/controller.go
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 (or wss:// 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:

  1. Build a real-time chat application
  2. Implement live notifications system
  3. Create collaborative features
  4. Explore the CORS Setup tutorial for advanced CORS configuration
  5. Check out the Multiple HTTP Servers tutorial for scaling strategies

Additional Resources