WebSocket
WebSocket introduces a fundamentally different interaction model from HTTP.
Instead of short-lived request/response cycles, you now manage:
- Long-lived connections
- Bidirectional message flow
- Client-controlled lifetimes
- Server-side resource retention
This changes both failure modes and architecture responsibilities.
Plumego does not abstract WebSocket into a high-level framework feature.
This is intentional.
WebSocket support must remain explicit, visible, and controllable.
Design Principles
Before implementing WebSocket support, align on these principles:
- Connection lifecycle must be explicit
- Authentication happens once, at upgrade time
- State must be scoped to the connection
- Concurrency must be controlled deliberately
- Shutdown must be coordinated
WebSocket is not just “HTTP, but faster”.
Where WebSocket Fits in Plumego
WebSocket begins as an HTTP request.
The lifecycle looks like this:
HTTP Request
→ Middleware (Trace, Logging, Auth)
→ WebSocket Upgrade
→ Connection Loop
→ Read / Write Messages
→ Connection Close
Everything before the upgrade follows normal Plumego rules.
After the upgrade, you own the connection.
Step 1: Authenticate Before Upgrade
Authentication must occur before upgrading to WebSocket.
This ensures:
- Unauthorized clients never get a connection
- Identity is established once
- No per-message authentication hacks
Typical pattern:
- JWT middleware authenticates the request
- Handler upgrades the connection
- Identity is bound to the connection
Never authenticate per message unless absolutely required.
Step 2: Perform the WebSocket Upgrade
Conceptual example inside a handler:
func wsHandler(ctx *plumego.Context) {
conn, err := upgrader.Upgrade(
ctx.ResponseWriter(),
ctx.Request(),
nil,
)
if err != nil {
return
}
handleConnection(ctx, conn)
}
Key points:
- Upgrade happens in the handler
- Middleware has already run
- Errors abort the request early
After upgrade, HTTP semantics no longer apply.
Step 3: Bind Connection-Scoped State
Each WebSocket connection should have its own state container.
Example:
type WSConnection struct {
ID string
Identity Identity
Conn *websocket.Conn
Send chan []byte
}
Rules:
- One state object per connection
- No shared mutable state by default
- Explicit ownership of resources
Do not reuse HTTP context after upgrade.
Step 4: Read and Write Loops
A common, safe pattern uses separate goroutines:
- One goroutine reads messages
- One goroutine writes messages
- Channels coordinate message flow
Example (conceptual):
func handleConnection(c *WSConnection) {
go readLoop(c)
go writeLoop(c)
}
This avoids:
- Blocking reads
- Concurrent writes on the same socket
- Race conditions
Never write to a WebSocket connection from multiple goroutines without coordination.
Step 5: Handling Message Types Explicitly
WebSocket messages should be treated as application-level events.
Example:
type IncomingMessage struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
Dispatch explicitly:
switch msg.Type {
case "ping":
handlePing(c)
case "subscribe":
handleSubscribe(c, msg.Data)
default:
handleUnknown(c)
}
Avoid dynamic or reflection-heavy dispatch.
Step 6: Heartbeats and Liveness
Long-lived connections need liveness checks.
Common techniques:
- Periodic ping/pong
- Read deadlines
- Write deadlines
Failure to implement heartbeats leads to:
- Zombie connections
- Resource leaks
- Inaccurate online state
Liveness is a server responsibility.
Step 7: Error Handling and Disconnects
Connections will close for many reasons:
- Client disconnects
- Network issues
- Protocol violations
- Server shutdown
Your code must treat disconnects as normal events, not errors.
Cleanup must be idempotent.
Step 8: Graceful Shutdown with WebSockets
Graceful shutdown becomes more complex with long-lived connections.
Recommended approach:
- Stop accepting new connections
- Notify existing connections (optional)
- Close connections with a timeout
- Release resources
WebSocket connections must respect shutdown signals explicitly.
Avoiding Common WebSocket Mistakes
Using Global Connection Maps Without Control
This leads to:
- Data races
- Memory leaks
- Hard-to-debug state
If you track connections, do so via controlled structures.
Mixing HTTP and WebSocket Logic
Once upgraded, HTTP concepts no longer apply.
Do not reuse request handlers or middleware patterns inside message loops.
Ignoring Backpressure
If clients cannot consume messages fast enough, buffers will grow.
Implement limits and drop strategies deliberately.
Observability for WebSockets
Logging and metrics should include:
- Connection open / close
- Identity
- Message counts
- Error rates
Trace IDs are useful during upgrade, but connection IDs matter afterward.
Testing WebSocket Behavior
You should test:
- Successful upgrade
- Authentication failure
- Message handling
- Disconnect behavior
- Shutdown behavior
WebSocket bugs often appear under concurrency and load.
Summary
In Plumego, WebSocket support is:
- Explicit and handler-driven
- Authenticated at the boundary
- Connection-scoped in state
- Concurrency-safe by design
- Shutdown-aware
WebSocket is powerful —
but only when its lifecycle is treated with respect.
Next
You have completed the Guides section.
From here, you can move on to:
→ Patterns — recommended structural practices
→ Examples — end-to-end sample applications
→ Reference — API-level documentation
All of them build on the foundations you now understand.