dream/servers/mist/websocket
WebSocket support for Dream (Mist server adapter)
This module provides Dream’s WebSocket abstraction for the Mist server adapter. It upgrades an HTTP request to a WebSocket connection and runs a typed message loop driven by your handler functions.
Most applications will import this module as:
import dream/servers/mist/websocket
Concepts
Connection– opaque handle to the WebSocket. Use it to send messages back to the client.Message(custom)– messages received from the client or from your application via aSelector(custom)(text, binary, custom, closed).Action(state, custom)– the next step in the WebSocket state machine, returned from youron_messagehandler.
The typical lifecycle is:
- Router sends a request to a controller.
- Controller calls
upgrade_websocket. on_initruns once when the WebSocket is established.on_messageruns for each incoming or custom message.on_closeruns after the connection closes.
Handlers follow Dream’s “no closures” rule: instead of capturing
dependencies, you define a Dependencies type and pass it explicitly
into upgrade_websocket so every handler receives what it needs.
Example (echo chat)
import dream/http/request.{type Request}
import dream/http/response.{type Response, text_response}
import dream/http/status
import dream/servers/mist/websocket
import gleam/erlang/process
import gleam/option
pub type Dependencies {
Dependencies(user_name: String)
}
pub fn handle_upgrade(request: Request, context, services) -> Response {
let user = request.get_query_param(request.query, "user")
let user = option.unwrap(user, "Anonymous")
let deps = Dependencies(user_name: user)
websocket.upgrade_websocket(
request,
dependencies: deps,
on_init: handle_init,
on_message: handle_message,
on_close: handle_close,
)
}
fn handle_init(
_connection: websocket.Connection,
_deps: Dependencies,
) -> #(String, option.Option(process.Selector(String))) {
// Initial state is the username, no extra messages yet
#("", option.None)
}
fn handle_message(
state: String,
message: websocket.Message(String),
connection: websocket.Connection,
deps: Dependencies,
) -> websocket.Action(String, String) {
case message {
websocket.TextMessage(text) -> {
let reply = deps.user_name <> ": " <> text
let _ = websocket.send_text(connection, reply)
websocket.continue_connection(state)
}
websocket.ConnectionClosed -> websocket.stop_connection()
_ -> websocket.continue_connection(state)
}
}
fn handle_close(_state: String, _deps: Dependencies) -> Nil {
Nil
}
Types
The next action to take in the WebSocket loop.
pub opaque type Action(state, custom)
A WebSocket connection.
This is an opaque type that wraps the underlying server’s connection.
pub opaque type Connection
Messages that can be received over a WebSocket connection.
pub type Message(custom) {
TextMessage(String)
BinaryMessage(BitArray)
CustomMessage(custom)
ConnectionClosed
}
Constructors
-
TextMessage(String)A text frame received from the client
-
BinaryMessage(BitArray)A binary frame received from the client
-
CustomMessage(custom)A custom message from the application (via selector)
-
ConnectionClosedThe connection was closed
Values
pub fn continue_connection(state: state) -> Action(state, custom)
Continue the WebSocket loop with the current state.
pub fn continue_connection_with_selector(
state: state,
selector: process.Selector(custom),
) -> Action(state, custom)
Continue the WebSocket loop with a selector for receiving custom messages.
pub fn send_binary(
connection: Connection,
message: BitArray,
) -> Result(Nil, SendError)
Send a binary frame to the client.
Example
case websocket.send_binary(connection, data) {
Ok(Nil) -> // Message sent successfully
Error(reason) -> // Handle send error
}
pub fn send_text(
connection: Connection,
message: String,
) -> Result(Nil, SendError)
Send a text frame to the client.
Example
case websocket.send_text(connection, "Hello!") {
Ok(Nil) -> // Message sent successfully
Error(reason) -> // Handle send error
}
pub fn stop_connection() -> Action(state, custom)
Stop the WebSocket loop normally.
pub fn upgrade_websocket(
request request: request.Request,
dependencies dependencies: deps,
on_init on_init: fn(Connection, deps) -> #(
state,
option.Option(process.Selector(custom)),
),
on_message on_message: fn(
state,
Message(custom),
Connection,
deps,
) -> Action(state, custom),
on_close on_close: fn(state, deps) -> Nil,
) -> response.Response
Upgrade an HTTP request to a WebSocket connection.
This function must be called from within a Dream controller. It handles the protocol upgrade and sets up the WebSocket message loop.
Parameters
request- The incoming HTTP requestdependencies- Application dependencies (services, context, etc.) passed to all handlerson_init- Called when the WebSocket connects. Returns initial state and optional selector.on_message- Called for each WebSocket message. Returns the next action.on_close- Called when the connection closes.
Example
websocket.upgrade_websocket(
request,
dependencies: services,
on_init: init_handler,
on_message: message_handler,
on_close: close_handler,
)
fn init_handler(connection, services) {
#(initial_state, None)
}
fn message_handler(state, message, connection, services) {
case message {
websocket.TextMessage(text) -> {
// Handle text message
websocket.continue_connection(state)
}
websocket.CustomMessage(msg) -> {
// Handle custom message from selector
websocket.continue_connection(state)
}
websocket.ConnectionClosed -> websocket.stop_connection()
_ -> websocket.continue_connection(state)
}
}
fn close_handler(state, services) {
Nil
}