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

The typical lifecycle is:

  1. Router sends a request to a controller.
  2. Controller calls upgrade_websocket.
  3. on_init runs once when the WebSocket is established.
  4. on_message runs for each incoming or custom message.
  5. on_close runs 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)

  • ConnectionClosed

    The connection was closed

Errors that can occur when sending WebSocket messages.

pub type SendError {
  SocketClosed
  Timeout
  UnknownError
}

Constructors

  • SocketClosed

    The socket is closed or unavailable

  • Timeout

    The send operation timed out

  • UnknownError

    An unknown error occurred

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 request
  • dependencies - Application dependencies (services, context, etc.) passed to all handlers
  • on_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
}
Search Document