An extension-based WebSocket gateway for Go, over RFC 6455. The sibling of rex.
Alpha. The gateway works: it serves, dispatches, broadcasts and shuts down, at 87.8% coverage with
-racethroughout. Nothing is published, sogo getwill not resolve it until the first tag.
wsx is the gateway: address routing, message dispatch, connection pumps, and
a listener. It mirrors rex’s module split,
its lifecycle and its decision culture — over RFC 6455 instead of HTTP.
Two topologies, one Gateway:
- Standalone —
wsxbinds its ownnet/httplistener, so a bind failure is returned fromRunrather than surfacing in a goroutine. - Mounted —
rextension-wsxregisters a rex route that delegates toGateway.ServeHTTP.
A channel is an address pattern — /chat/{roomId} — matched by a
parameter trie and resolved once, at the handshake. Within a channel, dispatch
is by message name read from an envelope, plus a direction.
Authorization therefore happens once, over HTTP, using the schemes rex already has — rather than through a second, WebSocket-specific path, which is how two security implementations drift apart. And the channel table stays static, which is what lets an AsyncAPI document be generated at startup instead of lazily.
wsxtension.Codec decodes a frame to a name, a correlation id and a payload.
wsx ships three as subpackages — wsx/codec/wsxjson, wsx/codec/jsonrpc,
wsx/codec/raw — and dead-code elimination means an unimported one costs
nothing in a binary.
A codec also describes itself, which is what lets
wsxtension-asyncapi
document the format an application is actually speaking instead of a
hardcoded envelope.
Gorilla sets no limits and its writes are not concurrency-safe, so the gateway owns all of it rather than leaving it to handlers: exactly one reader and one writer goroutine per connection, a bounded outbound queue that closes a slow connection rather than silently dropping a message from an ordered conversation, a server-initiated ping with a pong deadline so half-open connections cannot accumulate, and a required origin allowlist — because WebSocket is not subject to CORS and the browser sends the request regardless.
go get github.com/kryovyx/wsxwsx/wsxtest connects a real gorilla client to a real net/http.Server over
net.Pipe — no listener, no port, no kernel socket. The handshake, the
framing, the origin check, the caps and the close handshake are all production
code paths.
c, err := wsxtest.Dial(gateway, wsxtest.Options{Path: "/chat/42"})
defer c.Close()
c.SendJSON(map[string]any{"type": "chat.send", "id": "1", "payload": msg})
var reply Envelope
c.ReceiveJSON(&reply)Without it, every test of a handler, a middleware or a codec in every downstream module would bind a port — which makes them slow, makes them fail in parallel, and makes them fail in a sandbox with no network, so the tests that matter most become the ones people skip.
New in the REX v0.3.0 release, at v0.1.0 — there is nothing to migrate from. MIGRATION.md says what this module is, what it costs to adopt, and links to the guide for every other module in that release.
The gateway is complete and tested: 87.8% coverage, -race throughout, and
the pumps and the send queue were tested before the dispatcher existed —
which caught a time.NewTicker(0) panic, a read deadline of now+0 that
dropped every connection instantly, and a select whose random case choice
made Send-after-close succeed half the time.
What is uncovered is the double-fault paths: a write that fails while a close is already in progress, an encode that fails while answering an encode failure. Reaching them needs a socket that fails on command in the middle of a real handshake.
Not published. External contributions open at v1.0.0 — see
CONTRIBUTING.md. Issues are welcome now.
MIT — see LICENSE.