Docs WebSocket

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:

  1. JWT middleware authenticates the request
  2. Handler upgrades the connection
  3. 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.