Channel
Channel module: Phoenix-style real-time channels for March.
Channels provide multiplexed bidirectional messaging over a single transport (WebSocket, long-poll, …). Each channel join creates a green thread that calls user-defined callbacks:
join/3 — called on join attempt; returns Ok(state) or Err(reason)
handle_in/4 — called when the client sends an event
handle_info/3 — called when the channel process receives an actor message
leave/3 — called on leave / disconnectTypes defined here: Socket — per-connection state (topic, assigns, pids) HandleResult — return type for channel callbacks ChannelMsg — JSON wire message (topic/event/payload/ref/join_ref) ChannelRoute — pattern → handler association used by the router LeaveReason — reason a channel is leaving
Helper functions (usable inside channel handlers): reply/3 — reply to client + update state noreply/1 — no reply, update state stop/2 — stop channel with reason assign/3 — add/update a key in socket.assigns push/3 — push a message to this specific client broadcast/3 — broadcast to all topic subscribers broadcast_from/3 — broadcast to all subscribers except self
Wire format (JSON over WebSocket): { "topic": "room:lobby", "event": "new_msg", "payload": {...}, "ref": "1", "join_ref": "0" }
Topic pattern matching (ChannelRoute): "room:*" matches "room:lobby", "room:42", etc. "notify:" matches exactly "notify:"
See also: channel_server.march (runtime loop), presence.march.
Types
Socket: per-connection state for one channel join. topic — the joined topic, e.g. "room:lobby" channel_pid — the channel process (green thread) pid transport_pid — the underlying transport (WS frame loop) pid assigns — user key-value bag; set with Channel.assign/3 join_ref — the ref string from the original join message
Return type for channel callbacks (handle_in, handle_info, join). Reply(status, payload, state) — reply to client; status is ":ok" or ":error" NoReply(state) — no reply; just update channel state Stop(reason, state) — close this channel with a reason string
Messages that a channel process can receive in its mailbox. Defined here so they are available to push/broadcast helpers above and to ChannelServer/ChannelSocket which reference them.
Wire message sent to subscribers on broadcast.
Reason a channel is leaving. NormalLeave — the client explicitly left Disconnect — the transport closed Kicked(msg) — server-initiated close with a message
A ChannelRoute maps a topic pattern to a handler module name. pattern — e.g. "room:*" or "notify:user" handler — the name of the channel handler module (as a string)
A channel message as it travels over the wire (JSON over WebSocket). Fields mirror the Phoenix channel protocol: join_ref — ref of the join message (correlates replies) ref — per-message ref (for request-reply correlation) topic — the topic string, e.g. "room:lobby" event — event name, e.g. "new_msg" or "phx_join" payload — JSON-encoded payload string
Functions
Return a socket with key set to value (both Strings). The pair is prepended, so it shadows any earlier value for the same key; the old pair stays in socket_assigns.
Examples
march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.get_assign(Channel.assign(sock, "user", "alice"), "user")
Some("alice")Broadcast event+payload to ALL subscribers of the socket's topic (including self). Casts ChannelBroadcast(topic, event, payload) to the socket's own channel pid, whose loop is expected to route it through the PubSub shard. Requires actor runtime.
Channel.broadcast(sock, "new_msg", "{\"body\":\"hi\"}")Broadcast event+payload to all subscribers of the socket's topic EXCEPT the sender (this channel process). Casts ChannelBroadcastFrom(topic, channel_pid, event, payload) to the socket's own channel pid. Requires actor runtime.
Channel.broadcast_from(sock, "typing", "{}")A route sending topics that match pattern to the handler named handler_name. A pattern ending in * matches by prefix at a segment boundary (see PubSub.topic_matches); any other pattern matches exactly.
Examples
march> Channel.route_pattern(Channel.channel_route("room:*", "RoomChannel"))
"room:*"A phx_close message for topic, with an empty ref and payload {} (sent when the channel is closed).
Examples
march> Channel.serialize(Channel.close_msg("0", "room:lobby"))
"{\"join_ref\":\"0\",\"ref\":\"\",\"topic\":\"room:lobby\",\"event\":\"phx_close\",\"payload\":{}}"A phx_error message for the client's ref on topic, with payload {"reason":<reason>} (sent on join rejection or handler Stop).
Examples
march> Channel.msg_payload(Channel.error_msg("0", "1", "room:lobby", "unauthorized"))
"{\"reason\":\"unauthorized\"}"The value most recently assigned to key, as Some(value), or None when the key was never assigned. O(number of assigns).
Examples
march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.get_assign(Channel.assign(sock, "user", "alice"), "user")
Some("alice")
march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.get_assign(Channel.assign(Channel.assign(sock, "user", "alice"), "user", "bob"), "user")
Some("bob")
march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.get_assign(sock, "user")
NoneA wire message from its five fields, in wire order: join_ref, ref, topic, event, payload (a JSON value as text).
Examples
march> let cm = Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}")
march> Channel.msg_event(cm)
"new_msg"The event field of a message, e.g. "phx_join" or an application event.
Examples
march> Channel.msg_event(Channel.close_msg("0", "room:lobby"))
"phx_close"The join_ref field of a message.
Examples
march> let cm = Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}")
march> Channel.msg_join_ref(cm)
"0"The payload of a message: JSON text, kept verbatim.
Examples
march> let cm = Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}")
march> Channel.msg_payload(cm)
"{}"The per-message ref field (the client correlates replies by it).
Examples
march> let cm = Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}")
march> Channel.msg_ref(cm)
"1"The topic field of a message.
Examples
march> let cm = Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}")
march> Channel.msg_topic(cm)
"room:lobby"A socket for one channel join on topic, served by channel_pid over transport_pid, with no assigns and the given join_ref.
Examples
march> Channel.socket_topic(Channel.new_socket("room:42", 1, 2, "7"))
"room:42"NoReply(st): send nothing back to the client and continue with st.
Examples
march> Channel.noreply(5)
NoReply(5)Parse a JSON channel message string into a ChannelMsg. Returns Some(msg) on success, None on parse failure. Performs minimal parsing: extracts the five fields using string search, not a JSON parser, so it only accepts the shape serialize produces:
- each string field must appear as
"key":"value"with no space after the
colon; a `null` field (Phoenix sends `"join_ref":null`) or any spacing
gives `None`;- a string value ends at the first
", so escaped quotes are not
supported and escapes are not decoded;- the payload is everything after
"payload":up to the end of the input,
minus one trailing `}`, so `payload` must be the last field.Examples
march> Channel.parse("{\"join_ref\":\"0\",\"ref\":\"1\",\"topic\":\"room:lobby\",\"event\":\"new_msg\",\"payload\":{\"body\":\"hi\"}}")
Some(ChannelMsg("0", "1", "room:lobby", "new_msg", "{\"body\":\"hi\"}"))
march> let cm = Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}")
march> Channel.parse(Channel.serialize(cm)) == Some(cm)
true
march> Channel.parse("{\"join_ref\": \"0\"}")
NonePush a message directly to this client only: casts ChannelPush(event, payload) to the socket's transport pid. Fire and forget; returns whatever actor_cast returns. Requires actor runtime.
Channel.push(sock, "new_msg", "{\"body\":\"hi\"}")Reply(status, payload, st): reply to the client's ref with status (conventionally "ok" or "error") and the JSON payload, and continue with state st.
Examples
march> Channel.reply("ok", "{}", 5)
Reply("ok", "{}", 5)A phx_reply message answering the client's ref on topic, with payload {"status":<status>,"response":<response_payload>} (response_payload embedded verbatim as JSON).
Examples
march> Channel.msg_payload(Channel.reply_msg("0", "1", "room:lobby", "ok", "{}"))
"{\"status\":\"ok\",\"response\":{}}"The handler module name of a route.
Examples
march> Channel.route_handler(Channel.channel_route("room:*", "RoomChannel"))
"RoomChannel"The topic pattern of a route.
Examples
march> Channel.route_pattern(Channel.channel_route("notify:user", "NotifyChannel"))
"notify:user"Serialize a ChannelMsg to a JSON string conforming to the Phoenix channel wire format: {"join_ref":"0","ref":"1","topic":"room:lobby","event":"new_msg","payload":{}} The four string fields are escaped for " and backslash only (control characters are not escaped); the payload is embedded verbatim and must already be valid JSON.
Examples
march> Channel.serialize(Channel.make_msg("0", "1", "room:lobby", "new_msg", "{}"))
"{\"join_ref\":\"0\",\"ref\":\"1\",\"topic\":\"room:lobby\",\"event\":\"new_msg\",\"payload\":{}}"The socket's assigns as (key, value) pairs, most recently assigned first. A key assigned twice appears twice (the newer pair first).
Examples
march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.socket_assigns(sock)
[]
march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.socket_assigns(Channel.assign(Channel.assign(sock, "user", "alice"), "role", "admin"))
[("role", "admin"), ("user", "alice")]The pid of the channel process (green thread) serving this join.
Examples
march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.socket_channel_pid(sock)
10The ref of the client's original join message, which replies on this channel echo back as join_ref.
Examples
march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.socket_join_ref(sock)
"0"The topic the socket joined, e.g. "room:lobby".
Examples
march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.socket_topic(sock)
"room:lobby"The pid of the transport (WebSocket frame loop) the socket belongs to.
Examples
march> let sock = Channel.new_socket("room:lobby", 10, 11, "0")
march> Channel.socket_transport_pid(sock)
11Stop(reason, st): close this channel with reason; st is the final state handed to the leave callback.
Examples
march> Channel.stop("kicked", 5)
Stop("kicked", 5)