Skip to content

WebSockets

ws.Module(ws.Options{Path: "/ws"}) // served on the app's HTTP server
func (c *Chat) Gateway(g ws.Router) {
g.On("chat.send", ws.Handle(c.send)) // func(ctx, *ws.Client, In) (Out, error)
}
func (c *Chat) OnConnection(ctx context.Context, cl *ws.Client) error { ... } // an error refuses the client (1008)
func (c *Chat) OnDisconnect(cl *ws.Client, r ws.DisconnectReason) { ... } // close code and reason

Clients send {"event": "chat.send", "data": {...}, "id": 1}. The result comes back with the same event and id; an error comes as {"event": "exception", "data": {"statusCode": 400, ...}, "id": 1}. client.Join(room), server.To(room).Emit(...), .Except(ctx, sender, ...) and server.Broadcast(...) send to groups. client.Request() is the handshake request (read a token there in OnConnection), and client.Set/Get keep values such as the user for guards, which get the client from ec.WS().Client(). Shutdown closes clients with 1001.

Gateways are singletons. By default a request scoped provider that a handler resolves with dygo.Scoped[T].Get(ctx) belongs to the handshake request, so it lives as long as the connection. g.Use(ws.PerMessageScope()) (or the option on one On) gives every message its own scope: instances are built for that message, their OnRequestEnd runs after the ack is sent, *http.Request is the handshake request, and durable providers resolve for the client’s context ID. Use it for per-message state such as a transaction; it costs a scope per message that resolves one. Other transports can do the same with app.WithRequestScope(ctx, req).