Go routes and context¶
Ready objects and connection-owned state¶
Router.Handle("name", service.Method) registers a bound method on an initialized
object. Its dependencies and application state are kept. Only registered methods
are remotely accessible; exported methods are not automatically exposed.
Use Options.Factory to construct state for one connection:
options := wsrpc.Options{
Factory: func(peer *wsrpc.Peer, routes *wsrpc.Router) (func(), error) {
counter := &Counter{DB: database}
if err := routes.Handle("counter.add", counter.Add); err != nil {
return nil, err
}
return counter.Close, nil
},
}
Here Counter.Add has signature func(context.Context, Params) (Result, error)
and Counter.Close has signature func(). The factory runs once per connection,
before remote calls. Cleanup runs once after active handlers finish. Shared
mutable data must use mutexes or atomics: calls execute concurrently in Go.
Router.Mount("counter", methods) adds an explicitly registered method set under
a prefix. An init method additionally accepts the bare route name counter.
Constructing the object does not invoke init. Connection factories are eager;
use sync.Once inside a connection-owned object if construction should be lazy.
Peer.Handle adds or replaces a live connection’s route, and DeleteRoute removes
it. Router registrations are snapshotted for each new connection.
Context and reverse calls¶
Every handler receives context.Context. wsrpc.FromContext(ctx) gives the peer;
wsrpc.Current(ctx) gives its call ID and method as well. Pass the context into
helpers and new goroutines. Go does not have ambient contextvars or
AsyncLocalStorage, and a shared service must not swap its socket field for each
call.
The peer can call the other endpoint while the current handler runs:
peer, ok := wsrpc.FromContext(ctx)
if !ok { return 0, errors.New("missing RPC context") }
return peer.Call[int](ctx, "double", params)
Use the handler’s context to preserve call metadata. peer.Context() refers to
the connection lifetime and has no individual call metadata. Work that calls the
remote endpoint at connection startup belongs in Server.OnConnect; factory
setup has not started the reader yet.
JSON argument objects map to tagged Go structs rather than keyword arguments.
Missing fields get zero values and unknown fields are ignored; validate required
inputs in application code. JSON maps decode numbers as json.Number. Arbitrary
integers sent to ordinary JavaScript JSON consumers should be encoded as strings.