Go WebSocket Communication
WebSocket communication in Go enables real-time bidirectional communication between clients and servers. Unlike HTTP, which is request-response based, WebSockets provide persistent connections that allow both the client and server to send data at any time. Understanding WebSocket communication is essential for building real-time applications like chat systems, live updates, and collaborative tools. This comprehensive guide will teach you everything you need to know about WebSocket communication in Go.
Understanding WebSockets
What Are WebSockets?
WebSockets are a communication protocol that provides full-duplex communication over a single TCP connection. They provide:
- Persistent Connections - Long-lived connections between client and server
- Bidirectional Communication - Both client and server can send data
- Real-time Data Exchange - Low latency for real-time applications
- Protocol Upgrade - Upgrade from HTTP to WebSocket protocol
- Connection Management - Handle connection lifecycle and cleanup
WebSocket vs HTTP
HTTP Characteristics
- Request-response pattern
- Stateless connections
- Higher latency for real-time applications
- Good for traditional web applications
WebSocket Characteristics
- Persistent bidirectional connection
- Stateful communication
- Low latency for real-time applications
- Ideal for real-time features
Basic WebSocket Implementation
WebSocket Server Setup
The golang.org/x/net/websocket
Package
Using the WebSocket package for WebSocket implementation.
Connection Handling
Managing WebSocket connections and upgrades.
package main
import (
"fmt"
"log"
"net/http"
"time"
"golang.org/x/net/websocket"
)
func main() {
// Basic WebSocket implementation examples
fmt.Println("Basic WebSocket implementation examples:")
// Simple WebSocket server
func simpleWebSocketServer() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `
<!DOCTYPE html>
<html>
<head><title>WebSocket Test</title></head>
<body>
<h1>WebSocket Test</h1>
<div id="messages"></div>
<input type="text" id="messageInput" placeholder="Enter message">
<button onclick="sendMessage()">Send</button>
<script>
const ws = new WebSocket("ws://localhost:8080/ws");
const messages = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
ws.onopen = function() {
messages.innerHTML += '<p>Connected to WebSocket server</p>';
};
ws.onmessage = function(event) {
messages.innerHTML += '<p>Received: ' + event.data + '</p>';
};
ws.onclose = function() {
messages.innerHTML += '<p>Connection closed</p>';
};
function sendMessage() {
const message = messageInput.value;
if (message) {
ws.send(message);
messageInput.value = '';
}
}
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
</body>
</html>
`)
})
http.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) {
defer ws.Close()
fmt.Println("WebSocket connection established")
for {
var message string
err := websocket.Message.Receive(ws, &message)
if err != nil {
fmt.Printf("Error receiving message: %v\n", err)
break
}
fmt.Printf("Received message: %s\n", message)
// Echo the message back
err = websocket.Message.Send(ws, "Echo: "+message)
if err != nil {
fmt.Printf("Error sending message: %v\n", err)
break
}
}
fmt.Println("WebSocket connection closed")
}))
fmt.Println("Simple WebSocket server starting on :8080")
http.ListenAndServe(":8080", nil)
}
// Start simple WebSocket server
go simpleWebSocketServer()
time.Sleep(2 * time.Second)
// WebSocket with JSON messages
func websocketWithJSONMessages() {
type Message struct {
Type string `json:"type"`
Content string `json:"content"`
Time string `json:"time"`
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `
<!DOCTYPE html>
<html>
<head><title>WebSocket JSON Test</title></head>
<body>
<h1>WebSocket JSON Test</h1>
<div id="messages"></div>
<input type="text" id="messageInput" placeholder="Enter message">
<button onclick="sendMessage()">Send</button>
<script>
const ws = new WebSocket("ws://localhost:8081/ws");
const messages = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
ws.onopen = function() {
messages.innerHTML += '<p>Connected to WebSocket server</p>';
};
ws.onmessage = function(event) {
const message = JSON.parse(event.data);
messages.innerHTML += '<p>[' + message.time + '] ' + message.type + ': ' + message.content + '</p>';
};
ws.onclose = function() {
messages.innerHTML += '<p>Connection closed</p>';
};
function sendMessage() {
const content = messageInput.value;
if (content) {
const message = {
type: 'user_message',
content: content,
time: new Date().toLocaleTimeString()
};
ws.send(JSON.stringify(message));
messageInput.value = '';
}
}
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
</body>
</html>
`)
})
http.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) {
defer ws.Close()
fmt.Println("WebSocket JSON connection established")
// Send welcome message
welcomeMsg := Message{
Type: "system",
Content: "Welcome to the WebSocket server!",
Time: time.Now().Format("15:04:05"),
}
err := websocket.JSON.Send(ws, welcomeMsg)
if err != nil {
fmt.Printf("Error sending welcome message: %v\n", err)
return
}
for {
var message Message
err := websocket.JSON.Receive(ws, &message)
if err != nil {
fmt.Printf("Error receiving message: %v\n", err)
break
}
fmt.Printf("Received JSON message: %+v\n", message)
// Echo the message back with server timestamp
response := Message{
Type: "server_response",
Content: "Server received: " + message.Content,
Time: time.Now().Format("15:04:05"),
}
err = websocket.JSON.Send(ws, response)
if err != nil {
fmt.Printf("Error sending response: %v\n", err)
break
}
}
fmt.Println("WebSocket JSON connection closed")
}))
fmt.Println("WebSocket with JSON messages server starting on :8081")
http.ListenAndServe(":8081", nil)
}
// Start WebSocket with JSON messages server
go websocketWithJSONMessages()
time.Sleep(2 * time.Second)
fmt.Println("Basic WebSocket implementation examples completed!")
}
Connection Management
Connection Pool Management
Managing multiple WebSocket connections.
Connection Lifecycle
Handling connection establishment, maintenance, and cleanup.
package main
import (
"fmt"
"log"
"net/http"
"sync"
"time"
"golang.org/x/net/websocket"
)
func main() {
// Connection management examples
fmt.Println("Connection management examples:")
// Connection pool
type ConnectionPool struct {
connections map[*websocket.Conn]bool
mutex sync.RWMutex
register chan *websocket.Conn
unregister chan *websocket.Conn
broadcast chan []byte
}
func NewConnectionPool() *ConnectionPool {
return &ConnectionPool{
connections: make(map[*websocket.Conn]bool),
register: make(chan *websocket.Conn),
unregister: make(chan *websocket.Conn),
broadcast: make(chan []byte),
}
}
func (pool *ConnectionPool) Run() {
for {
select {
case conn := <-pool.register:
pool.mutex.Lock()
pool.connections[conn] = true
pool.mutex.Unlock()
fmt.Printf("Connection registered. Total connections: %d\n", len(pool.connections))
case conn := <-pool.unregister:
pool.mutex.Lock()
if _, exists := pool.connections[conn]; exists {
delete(pool.connections, conn)
conn.Close()
}
pool.mutex.Unlock()
fmt.Printf("Connection unregistered. Total connections: %d\n", len(pool.connections))
case message := <-pool.broadcast:
pool.mutex.RLock()
for conn := range pool.connections {
select {
case <-time.After(5 * time.Second):
fmt.Printf("Timeout sending message to connection\n")
delete(pool.connections, conn)
conn.Close()
default:
err := websocket.Message.Send(conn, string(message))
if err != nil {
fmt.Printf("Error sending message: %v\n", err)
delete(pool.connections, conn)
conn.Close()
}
}
}
pool.mutex.RUnlock()
}
}
}
// Connection management server
func connectionManagementServer() {
pool := NewConnectionPool()
go pool.Run()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `
<!DOCTYPE html>
<html>
<head><title>Connection Management Test</title></head>
<body>
<h1>Connection Management Test</h1>
<div id="messages"></div>
<input type="text" id="messageInput" placeholder="Enter message">
<button onclick="sendMessage()">Send</button>
<button onclick="sendBroadcast()">Broadcast</button>
<script>
const ws = new WebSocket("ws://localhost:8082/ws");
const messages = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
ws.onopen = function() {
messages.innerHTML += '<p>Connected to WebSocket server</p>';
};
ws.onmessage = function(event) {
messages.innerHTML += '<p>Received: ' + event.data + '</p>';
};
ws.onclose = function() {
messages.innerHTML += '<p>Connection closed</p>';
};
function sendMessage() {
const message = messageInput.value;
if (message) {
ws.send(message);
messageInput.value = '';
}
}
function sendBroadcast() {
const message = messageInput.value;
if (message) {
ws.send('BROADCAST:' + message);
messageInput.value = '';
}
}
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
</body>
</html>
`)
})
http.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) {
pool.register <- ws
defer func() {
pool.unregister <- ws
}()
fmt.Println("WebSocket connection established")
for {
var message string
err := websocket.Message.Receive(ws, &message)
if err != nil {
fmt.Printf("Error receiving message: %v\n", err)
break
}
fmt.Printf("Received message: %s\n", message)
if message == "ping" {
err = websocket.Message.Send(ws, "pong")
if err != nil {
fmt.Printf("Error sending pong: %v\n", err)
break
}
} else if message[:10] == "BROADCAST:" {
broadcastMessage := message[10:]
pool.broadcast <- []byte("Broadcast: " + broadcastMessage)
} else {
err = websocket.Message.Send(ws, "Echo: "+message)
if err != nil {
fmt.Printf("Error sending echo: %v\n", err)
break
}
}
}
fmt.Println("WebSocket connection closed")
}))
fmt.Println("Connection management server starting on :8082")
http.ListenAndServe(":8082", nil)
}
// Start connection management server
go connectionManagementServer()
time.Sleep(2 * time.Second)
fmt.Println("Connection management examples completed!")
}
Broadcasting and Real-time Features
Message Broadcasting
Broadcasting to All Connections
Sending messages to all connected clients.
Selective Broadcasting
Sending messages to specific groups of clients.
package main
import (
"fmt"
"log"
"net/http"
"sync"
"time"
"golang.org/x/net/websocket"
)
func main() {
// Broadcasting and real-time features examples
fmt.Println("Broadcasting and real-time features examples:")
// Chat room with broadcasting
type ChatRoom struct {
connections map[*websocket.Conn]bool
mutex sync.RWMutex
register chan *websocket.Conn
unregister chan *websocket.Conn
broadcast chan []byte
}
func NewChatRoom() *ChatRoom {
return &ChatRoom{
connections: make(map[*websocket.Conn]bool),
register: make(chan *websocket.Conn),
unregister: make(chan *websocket.Conn),
broadcast: make(chan []byte),
}
}
func (room *ChatRoom) Run() {
for {
select {
case conn := <-room.register:
room.mutex.Lock()
room.connections[conn] = true
room.mutex.Unlock()
fmt.Printf("User joined chat. Total users: %d\n", len(room.connections))
// Notify all users about new user
room.broadcast <- []byte("User joined the chat room")
case conn := <-room.unregister:
room.mutex.Lock()
if _, exists := room.connections[conn]; exists {
delete(room.connections, conn)
conn.Close()
}
room.mutex.Unlock()
fmt.Printf("User left chat. Total users: %d\n", len(room.connections))
// Notify all users about user leaving
room.broadcast <- []byte("User left the chat room")
case message := <-room.broadcast:
room.mutex.RLock()
for conn := range room.connections {
select {
case <-time.After(5 * time.Second):
fmt.Printf("Timeout sending message to user\n")
delete(room.connections, conn)
conn.Close()
default:
err := websocket.Message.Send(conn, string(message))
if err != nil {
fmt.Printf("Error sending message: %v\n", err)
delete(room.connections, conn)
conn.Close()
}
}
}
room.mutex.RUnlock()
}
}
}
// Chat room server
func chatRoomServer() {
room := NewChatRoom()
go room.Run()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `
<!DOCTYPE html>
<html>
<head><title>Chat Room</title></head>
<body>
<h1>Chat Room</h1>
<div id="messages" style="height: 300px; overflow-y: scroll; border: 1px solid #ccc; padding: 10px;"></div>
<input type="text" id="messageInput" placeholder="Enter your message">
<button onclick="sendMessage()">Send</button>
<script>
const ws = new WebSocket("ws://localhost:8083/ws");
const messages = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
ws.onopen = function() {
messages.innerHTML += '<p style="color: green;">Connected to chat room</p>';
};
ws.onmessage = function(event) {
messages.innerHTML += '<p>' + event.data + '</p>';
messages.scrollTop = messages.scrollHeight;
};
ws.onclose = function() {
messages.innerHTML += '<p style="color: red;">Connection closed</p>';
};
function sendMessage() {
const message = messageInput.value;
if (message) {
ws.send(message);
messageInput.value = '';
}
}
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
</body>
</html>
`)
})
http.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) {
room.register <- ws
defer func() {
room.unregister <- ws
}()
fmt.Println("User joined chat room")
for {
var message string
err := websocket.Message.Receive(ws, &message)
if err != nil {
fmt.Printf("Error receiving message: %v\n", err)
break
}
fmt.Printf("Chat message: %s\n", message)
// Broadcast message to all users
room.broadcast <- []byte(message)
}
fmt.Println("User left chat room")
}))
fmt.Println("Chat room server starting on :8083")
http.ListenAndServe(":8083", nil)
}
// Start chat room server
go chatRoomServer()
time.Sleep(2 * time.Second)
// Real-time notifications
func realTimeNotificationsServer() {
type NotificationService struct {
connections map[*websocket.Conn]bool
mutex sync.RWMutex
register chan *websocket.Conn
unregister chan *websocket.Conn
notify chan string
}
func NewNotificationService() *NotificationService {
return &NotificationService{
connections: make(map[*websocket.Conn]bool),
register: make(chan *websocket.Conn),
unregister: make(chan *websocket.Conn),
notify: make(chan string),
}
}
func (ns *NotificationService) Run() {
for {
select {
case conn := <-ns.register:
ns.mutex.Lock()
ns.connections[conn] = true
ns.mutex.Unlock()
fmt.Printf("User subscribed to notifications. Total subscribers: %d\n", len(ns.connections))
case conn := <-ns.unregister:
ns.mutex.Lock()
if _, exists := ns.connections[conn]; exists {
delete(ns.connections, conn)
conn.Close()
}
ns.mutex.Unlock()
fmt.Printf("User unsubscribed from notifications. Total subscribers: %d\n", len(ns.connections))
case notification := <-ns.notify:
ns.mutex.RLock()
for conn := range ns.connections {
err := websocket.Message.Send(conn, notification)
if err != nil {
fmt.Printf("Error sending notification: %v\n", err)
delete(ns.connections, conn)
conn.Close()
}
}
ns.mutex.RUnlock()
}
}
}
notificationService := NewNotificationService()
go notificationService.Run()
// Simulate notifications
go func() {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
notification := fmt.Sprintf("System notification: %s", time.Now().Format("15:04:05"))
notificationService.notify <- notification
}
}
}()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `
<!DOCTYPE html>
<html>
<head><title>Real-time Notifications</title></head>
<body>
<h1>Real-time Notifications</h1>
<div id="notifications" style="height: 300px; overflow-y: scroll; border: 1px solid #ccc; padding: 10px;"></div>
<button onclick="sendNotification()">Send Test Notification</button>
<script>
const ws = new WebSocket("ws://localhost:8084/ws");
const notifications = document.getElementById('notifications');
ws.onopen = function() {
notifications.innerHTML += '<p style="color: green;">Connected to notification service</p>';
};
ws.onmessage = function(event) {
notifications.innerHTML += '<p>[' + new Date().toLocaleTimeString() + '] ' + event.data + '</p>';
notifications.scrollTop = notifications.scrollHeight;
};
ws.onclose = function() {
notifications.innerHTML += '<p style="color: red;">Connection closed</p>';
};
function sendNotification() {
ws.send('Test notification from client');
}
</script>
</body>
</html>
`)
})
http.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) {
notificationService.register <- ws
defer func() {
notificationService.unregister <- ws
}()
fmt.Println("User subscribed to notifications")
for {
var message string
err := websocket.Message.Receive(ws, &message)
if err != nil {
fmt.Printf("Error receiving message: %v\n", err)
break
}
fmt.Printf("Received notification: %s\n", message)
// Echo back as notification
notificationService.notify <- "Client notification: " + message
}
fmt.Println("User unsubscribed from notifications")
}))
fmt.Println("Real-time notifications server starting on :8084")
http.ListenAndServe(":8084", nil)
}
// Start real-time notifications server
go realTimeNotificationsServer()
time.Sleep(2 * time.Second)
fmt.Println("Broadcasting and real-time features examples completed!")
}
WebSocket Security
Security Considerations
Authentication and Authorization
Implementing security for WebSocket connections.
Input Validation
Validating WebSocket messages and connections.
package main
import (
"fmt"
"log"
"net/http"
"sync"
"time"
"golang.org/x/net/websocket"
)
func main() {
// WebSocket security examples
fmt.Println("WebSocket security examples:")
// Authenticated WebSocket server
func authenticatedWebSocketServer() {
type AuthenticatedConnection struct {
conn *websocket.Conn
userID string
token string
}
type SecureChatRoom struct {
connections map[*AuthenticatedConnection]bool
mutex sync.RWMutex
register chan *AuthenticatedConnection
unregister chan *AuthenticatedConnection
broadcast chan []byte
}
func NewSecureChatRoom() *SecureChatRoom {
return &SecureChatRoom{
connections: make(map[*AuthenticatedConnection]bool),
register: make(chan *AuthenticatedConnection),
unregister: make(chan *AuthenticatedConnection),
broadcast: make(chan []byte),
}
}
func (room *SecureChatRoom) Run() {
for {
select {
case conn := <-room.register:
room.mutex.Lock()
room.connections[conn] = true
room.mutex.Unlock()
fmt.Printf("Authenticated user %s joined chat. Total users: %d\n", conn.userID, len(room.connections))
case conn := <-room.unregister:
room.mutex.Lock()
if _, exists := room.connections[conn]; exists {
delete(room.connections, conn)
conn.conn.Close()
}
room.mutex.Unlock()
fmt.Printf("User %s left chat. Total users: %d\n", conn.userID, len(room.connections))
case message := <-room.broadcast:
room.mutex.RLock()
for conn := range room.connections {
select {
case <-time.After(5 * time.Second):
fmt.Printf("Timeout sending message to user %s\n", conn.userID)
delete(room.connections, conn)
conn.conn.Close()
default:
err := websocket.Message.Send(conn.conn, string(message))
if err != nil {
fmt.Printf("Error sending message to user %s: %v\n", conn.userID, err)
delete(room.connections, conn)
conn.conn.Close()
}
}
}
room.mutex.RUnlock()
}
}
}
// Simple token validation (in production, use proper JWT validation)
func validateToken(token string) (string, bool) {
// Mock validation - in production, validate against database or JWT
if token == "valid-token-123" {
return "user123", true
}
return "", false
}
room := NewSecureChatRoom()
go room.Run()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `
<!DOCTYPE html>
<html>
<head><title>Secure Chat Room</title></head>
<body>
<h1>Secure Chat Room</h1>
<div id="messages" style="height: 300px; overflow-y: scroll; border: 1px solid #ccc; padding: 10px;"></div>
<input type="text" id="messageInput" placeholder="Enter your message">
<button onclick="sendMessage()">Send</button>
<script>
const ws = new WebSocket("ws://localhost:8085/ws");
const messages = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
ws.onopen = function() {
messages.innerHTML += '<p style="color: green;">Connected to secure chat room</p>';
};
ws.onmessage = function(event) {
messages.innerHTML += '<p>' + event.data + '</p>';
messages.scrollTop = messages.scrollHeight;
};
ws.onclose = function() {
messages.innerHTML += '<p style="color: red;">Connection closed</p>';
};
function sendMessage() {
const message = messageInput.value;
if (message) {
ws.send(message);
messageInput.value = '';
}
}
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
</body>
</html>
`)
})
http.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) {
// Get token from query parameter
token := ws.Request().URL.Query().Get("token")
if token == "" {
fmt.Println("No token provided")
ws.Close()
return
}
// Validate token
userID, valid := validateToken(token)
if !valid {
fmt.Println("Invalid token")
ws.Close()
return
}
// Create authenticated connection
authConn := &AuthenticatedConnection{
conn: ws,
userID: userID,
token: token,
}
room.register <- authConn
defer func() {
room.unregister <- authConn
}()
fmt.Printf("Authenticated user %s joined secure chat room\n", userID)
for {
var message string
err := websocket.Message.Receive(ws, &message)
if err != nil {
fmt.Printf("Error receiving message from user %s: %v\n", userID, err)
break
}
fmt.Printf("Secure chat message from %s: %s\n", userID, message)
// Broadcast message with user ID
broadcastMessage := fmt.Sprintf("[%s]: %s", userID, message)
room.broadcast <- []byte(broadcastMessage)
}
fmt.Printf("Authenticated user %s left secure chat room\n", userID)
}))
fmt.Println("Authenticated WebSocket server starting on :8085")
http.ListenAndServe(":8085", nil)
}
// Start authenticated WebSocket server
go authenticatedWebSocketServer()
time.Sleep(2 * time.Second)
fmt.Println("WebSocket security examples completed!")
}
What You've Learned
Congratulations! You now have a comprehensive understanding of Go's WebSocket communication capabilities:
Basic WebSocket Implementation
- Understanding WebSocket protocol and characteristics
- Creating WebSocket servers with the
golang.org/x/net/websocket
package - Handling WebSocket connections and message exchange
- Working with JSON messages over WebSockets
Connection Management
- Managing multiple WebSocket connections
- Implementing connection pools and lifecycle management
- Handling connection registration and cleanup
- Implementing connection timeouts and error handling
Broadcasting and Real-time Features
- Broadcasting messages to all connected clients
- Creating chat rooms and real-time communication
- Implementing notification services
- Building real-time collaborative features
WebSocket Security
- Implementing authentication for WebSocket connections
- Validating tokens and user credentials
- Securing WebSocket communication
- Protecting against unauthorized access
Key Concepts
- WebSocket Protocol - Full-duplex communication over TCP
- Connection Management - Managing multiple persistent connections
- Broadcasting - Sending messages to multiple clients
- Real-time Communication - Low-latency bidirectional data exchange
- Security - Authentication and authorization for WebSocket connections
Next Steps
You now have a solid foundation in Go's WebSocket communication capabilities. In the next section, we'll explore web security best practices, which will help you secure your web applications and protect against common vulnerabilities.
Understanding WebSocket communication is crucial for building real-time applications and collaborative features. These concepts form the foundation for all the more advanced web development techniques we'll cover in the coming sections.
Ready to learn about web security best practices? Let's explore comprehensive security measures and learn how to protect your web applications against common vulnerabilities and attacks!