Bastion.ChannelServer
lib/channel_server.march — WebSocket server for Bastion Channels.
Bastion.ChannelServer multiplexes multiple topics over a single WebSocket connection, routing joins, incoming events, and leaves to user-defined channel handler modules. It integrates with Bastion.PubSub so that broadcasts from any handler are delivered to all subscribed connections.
── Wire protocol (JSON) ───────────────────────────────────────────────────────
Client → Server (incoming frames): {"topic":"room:42","event":"join", "payload":{}, "ref":"1"} {"topic":"room:42","event":"leave", "payload":{}, "ref":"2"} {"topic":"room:42","event":"msg:new","payload":{"body":"hi"},"ref":"3"} {"topic":"phoenix", "event":"heartbeat","payload":{}, "ref":"4"}
Server → Client (reply to join/leave/heartbeat): {"topic":"room:42","event":"reply","payload":{"status":"ok","response":{}},"ref":"1"} {"topic":"room:42","event":"reply","payload":{"status":"error","response":{"reason":"unauthorized"}},"ref":"1"}
Server → Client (broadcast / push): {"topic":"room:42","event":"msg:new","payload":{...},"ref":null}
── Usage ──────────────────────────────────────────────────────────────────────
let routes =
Bastion.ChannelServer.new_routes()
|> Bastion.ChannelServer.add_route("room:", MyApp.RoomChannel.join, MyApp.RoomChannel.handle_in)
-- In the server pipeline:
HttpServer.new(4000)
|> HttpServer.plug(Bastion.ChannelServer.plug(routes))
|> HttpServer.listen()
-- Or with a custom path:
|> HttpServer.plug(Bastion.ChannelServer.plug_at("/socket/websocket", routes))Types
Functions
Register a topic prefix with its channel handler functions.
prefix — topic must start with this string (e.g. "room:", "")
join_fn — fn(conn, topic, params_json) → Result(ChannelConn, String)
handle_in_fn — fn(conn, event, payload_json) → ChannelConn
The empty string prefix "\"\"" matches every topic — use it as a catch-all
after more specific prefixes.
routes
|> ChannelServer.add_route("room:", MyApp.RoomChannel.join, MyApp.RoomChannel.handle_in)
|> ChannelServer.add_route("system:", MyApp.SysChannel.join, MyApp.SysChannel.handle_in)Create an empty route table.
Build a Conn → Conn plug that serves the channel WebSocket at the default path /_bastion/channel.
Requests to any other path pass through unchanged.Build a Conn → Conn plug that serves the channel WebSocket at a custom path.
HttpServer.plug(ChannelServer.plug_at("/socket/websocket", routes))