Skip to content

WebSockets

WebSockets are HTTP upgrade routes declared with sdk.WS.

Routes struct {
Socket sdk.WSWith[ReadPolicy] `path:"/:projectId/socket"`
}

The handler receives sdk.Ctx and sdk.WebSocket:

func (p *Projects) Socket(ctx sdk.Ctx, socket sdk.WebSocket) error {
for {
message, err := socket.Read()
if err != nil {
return ctx.Errors().Wrap(err, "reading websocket message")
}
if err := socket.Write(sdk.WebSocketMessage{
Type: sdk.WebSocketText,
Data: message.Data,
}); err != nil {
return ctx.Errors().Wrap(err, "writing websocket message")
}
}
}

sdk.WebSocket exposes:

  • Context() context.Context
  • Native() any
  • Subprotocol() string
  • Read() (sdk.WebSocketMessage, error)
  • Write(sdk.WebSocketMessage) error
  • Close(sdk.WebSocketCloseCode, string) error

Native() returns the driver-owned socket for deliberate driver-specific behavior.

Official HTTP drivers support writing text and binary messages through socket.Write. Use socket.Close(code, reason) for close frames.

Portable socket handlers treat text and binary as the normal application message types. The Fiber driver can surface close, ping, and pong message types from its underlying socket. The standard driver maps non-text and non-binary reads to WebSocketMessageUnknown. Writing ping, pong, close, or unknown messages through socket.Write returns an error in the official drivers; use socket.Close for close frames or socket.Native() when the handler deliberately opts into driver-specific control-frame behavior.

HTTP middleware policies can run before the upgrade. Put auth, tenant selection, and rate-limit decisions there.

For HandleHTTP middleware, ctx.Next() is the point where Anvil continues to the upgrade and socket handler. Returning without calling ctx.Next() stops the upgrade path. If the middleware returned a body before the socket was accepted, the driver writes a normal HTTP response. AfterHTTP and OnHTTPError run after the upgrade attempt or socket handler returns.

Read HTTP Middleware for the method shapes and policy placement rules.

WebSocket errors go through the same global error pipeline.

Before the upgrade succeeds, the HTTP driver can still write a normal mapped HTTP error response. After the upgrade succeeds, there is no HTTP response left to write. The driver maps the error, publishes the error event, and closes the socket. External reporting belongs in global error observers or protocol middleware.

Drivers recover panics and publish error events so one failed socket does not stop the server.

The standard-library driver uses github.com/coder/websocket underneath. socket.Native() returns *websocket.Conn. The Fiber driver uses github.com/gofiber/contrib/websocket; socket.Native() returns *websocket.Conn from that package.

Driver-specific upgrade behavior:

  • The standard driver only checks WebSocket routes when the request contains WebSocket upgrade headers. The socket library validates the handshake during accept. A normal HTTP request to a WebSocket-only path returns 404 unless another HTTP route matches that path.
  • The Fiber driver registers WebSocket routes as GET routes. A request that reaches that path without a WebSocket upgrade receives 426 Upgrade Required.