Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
139: WebSockets in Go with gorilla/websocket
Think about the difference between sending a series of letters through the mail and having a phone call. With a letter (which is basically how standard HTTP works), you send a request, wait for it to travel, and then wait for the other person to write back and mail a response. If you want updated information, you have to send another letter. It's slow, and the other person can't just shout "Hey, something changed!" unless you've already asked them for an update.
A WebSocket is that phone call. You go through a brief "handshake" to establish the connection, but once that call is active, the line stays open. Either side can speak whenever they want, and the other side hears it instantly. In Go, the gorilla/websocket package is the industry standard for managing this "open line."
Turning an HTTP request into a persistent connection
WebSockets actually start their life as a standard HTTP request. The client asks the server, "Hey, can we upgrade this connection to a WebSocket?" To handle this in Go, we use an Upgrader. I like to think of the Upgrader as the receptionist who verifies your credentials before transferring you to a private, direct line.
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
// In production, you'd check the origin for security.
// For now, we'll just allow everything.
return true
},
}
func handleConnections(w http.ResponseWriter, r *http.Request) {
// Upgrade initial GET request to a websocket
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
defer ws.Close()
// Now 'ws' is our open phone line.
}
Notice that CheckOrigin function. If you don't define it, Gorilla defaults to a strict check that might block your frontend if it's running on a different port (like a React app on 3000 talking to a Go API on 8080). I've set it to return true here just to keep us moving, but be careful with that in a real app.
The "Listen and Speak" loop
Once the connection is upgraded, you're no longer dealing with http.ResponseWriter. You're dealing with a *websocket.Conn. Because this connection is persistent, we usually wrap the logic in a for loop. If the loop breaks (because the user closed their browser or the network dropped), the connection is dead.
Let's look at a real example: a server that monitors system memory and pushes updates to the client every second without the client ever asking for them.
func streamMemoryUsage(ws *websocket.Conn) {
for {
// Imagine getMemUsage() is a helper that returns a string
stats := getMemUsage()
// WriteMessage takes the message type (TextMessage or BinaryMessage)
// and the payload as a byte slice.
err := ws.WriteMessage(websocket.TextMessage, []byte(stats))
if err != nil {
log.Printf("Connection lost: %v", err)
break
}
time.Sleep(1 * time.Second)
}
}
Managing bidirectional traffic with Goroutines
Here is where a lot of developers trip up. If you have a for loop that is blocking while waiting to ReadMessage from the client, you can't simultaneously WriteMessage to them on the same goroutine. It's like trying to listen to someone and talk over them at the exact same time on a single-track brain.
The fix is simple: use Go's concurrency. I typically spin up a separate goroutine for reading and keep the main handler for writing, or vice versa. This allows the server to push data (like our memory stats) while still listening for "stop" or "configure" commands from the user.
func handleConnections(w http.ResponseWriter, r *http.Request) {
ws, _ := upgrader.Upgrade(w, r, nil)
defer ws.Close()
// Start a goroutine to handle incoming messages from the client
go func() {
for {
_, msg, err := ws.ReadMessage()
if err != nil {
break
}
log.Printf("Client says: %s", msg)
}
}()
// The main loop handles pushing data TO the client
for {
err := ws.WriteMessage(websocket.TextMessage, []byte("Server Heartbeat"))
if err != nil {
break
}
time.Sleep(5 * time.Second)
}
}
I'll leave you with one tip: always remember to defer ws.Close(). Leaking WebSocket connections is a fast track to crashing your server because each open connection consumes a file descriptor and some memory. Be diligent about cleaning up.
📋 Practical Task
Build a Live System Log Streamer
Your task is to create a Go server that simulates a "Live Log Streamer." Instead of a user requesting logs via a GET request, the server should push a new "log line" to the client every 500 milliseconds.
- Implement a
/logsendpoint that upgrades the connection to a WebSocket usinggorilla/websocket. - Create a slice of strings containing fake log messages (e.g.,
"User logged in","Database query took 20ms","Cache miss on key: user_123"). - Use a
forloop and atime.Ticker(ortime.Sleep) to pick a random log message from your slice and push it to the client. - Ensure that if the client disconnects, the server stops the loop for that specific connection to prevent goroutine leaks.
- Bonus: Implement a "Ping" mechanism where if the client sends the message
"PING", the server responds immediately with"PONG"without interrupting the log stream.
There are no comments for now.