brazier/transport

Types

The allowed_hosts of a listener, parsed once when it starts (Django’s ALLOWED_HOSTS, SEC-16).

pub opaque type AllowedHosts

The ws_allowed_origins of a listener, parsed once when it starts.

pub opaque type AllowedOrigins

Messages sent from the client (browser) to the server. This is Brazier’s wire protocol — not Lustre’s internal format.

pub type ClientMessage {
  ClientEvent(
    name: String,
    handler_id: String,
    data: String,
    target_path: String,
    clock: Int,
    ops: List(ClientOp),
  )
  ClientHeartbeat
  ClientJoin(token: String, path: String)
  ClientNavigate(path: String, clock: Int)
  ClientRequestSync
  ClientEventBatch(events: List(ClientMessage))
}

Constructors

  • ClientEvent(
      name: String,
      handler_id: String,
      data: String,
      target_path: String,
      clock: Int,
      ops: List(ClientOp),
    )

    A DOM event was fired (click, input, submit, etc).

    Arguments

    name

    The event name (e.g. “click”, “input”).

    handler_id

    The handler identifier from the data-brazier-event-* attribute value. This is the semantic identifier set by the view (e.g. “increment”, “decrement”).

    data

    JSON-encoded event data.

    target_path

    Path to the element in the VDOM tree that fired the event.

    clock

    Monotonic event clock value for ordering and acknowledgment. Reference: LiveView 1.1 event clocking.

    ops

    Model operations from a client-side model diff: the frame’s ops, a JSON array embedded raw ("ops":[...]), each op checked by client_op_decoder. [] when the frame has no ops. The server treats them as hints only and never applies them: the server’s update is authoritative. A frame whose ops is not such an array is refused (error frame "Invalid event ops"). The size is bounded by max_message_bytes.

  • ClientHeartbeat

    Client sends a heartbeat to keep the connection alive.

  • ClientJoin(token: String, path: String)

    Client is requesting initial state after connecting. Includes an optional session token for state recovery and the current URL path for route-aware apps.

  • ClientNavigate(path: String, clock: Int)

    Client navigated to a new URL path (SPA navigation). clock is stamped from the same counter as the events’ clocks, and the frame that carries the navigation’s state acknowledges it (ack_clock), so the client knows which render shows the new page (it scrolls to the URL’s fragment then). Absent on the wire is 0, as for an event.

  • ClientRequestSync

    Client requests a full authoritative model sync without remounting HTML.

  • ClientEventBatch(events: List(ClientMessage))

    Batch of events: LOCAL events replayed + MODEL event at the end. Sent when a MODEL event fires after LOCAL events accumulated state.

One model operation a client attached to an event: an RFC 6902 op (add, remove, replace, move, copy, test) or Brazier’s append (see brazier/patch). path and from are JSON Pointers (RFC 6901) that pass through no unsafe key (patch.unsafe_keys). from is set for move and copy only, value for add, replace, test and append only (an array for append). Members RFC 6902 does not define for an op are ignored, as the RFC says.

pub type ClientOp {
  ClientOp(
    op: String,
    path: String,
    from: option.Option(String),
    value: option.Option(dynamic.Dynamic),
  )
}

Constructors

Unique identifier for a WebSocket connection. Used for logging and session tracking.

pub type ConnectionId =
  String

State held by each WebSocket connection process.

pub type ConnectionState {
  ConnectionState(
    id: String,
    socket: server.Socket,
    on_event: fn(String, ClientMessage) -> Nil,
    on_close: fn(String) -> Nil,
    event_count: Int,
    rate_window_start: Int,
    security_limits: SecurityLimits,
    heartbeat_count: Int,
    heartbeat_window_start: Int,
    buffer: BitArray,
    reassembly: @internal Reassembly,
    peer_ip: String,
    request: request.Request(server.Connection),
    ws_auth: option.Option(
      fn(request.Request(server.Connection)) -> Result(
        Nil,
        String,
      ),
    ),
    middlewares: List(
      fn(
        request.Request(server.Connection),
        fn(request.Request(server.Connection)) -> response.Response(
          server.ResponseBody,
        ),
      ) -> response.Response(server.ResponseBody),
    ),
    page: LivePage,
    last_activity_ms: Int,
    ping_seq: Int,
    pending_ping: option.Option(Int),
    decode_errors: Int,
    rate_violations: Int,
    timer_subject: process.Subject(WsTimer),
    ping_timer: option.Option(process.Timer),
    pong_timer: option.Option(process.Timer),
    idle_timer: option.Option(process.Timer),
    auth_timer: option.Option(process.Timer),
    opened_at: Int,
    frames_in: Int,
    frames_out: Int,
    bytes_in: Int,
    bytes_out: Int,
    warned_no_build: Bool,
  )
}

Constructors

  • ConnectionState(
      id: String,
      socket: server.Socket,
      on_event: fn(String, ClientMessage) -> Nil,
      on_close: fn(String) -> Nil,
      event_count: Int,
      rate_window_start: Int,
      security_limits: SecurityLimits,
      heartbeat_count: Int,
      heartbeat_window_start: Int,
      buffer: BitArray,
      reassembly: @internal Reassembly,
      peer_ip: String,
      request: request.Request(server.Connection),
      ws_auth: option.Option(
        fn(request.Request(server.Connection)) -> Result(Nil, String),
      ),
      middlewares: List(
        fn(
          request.Request(server.Connection),
          fn(request.Request(server.Connection)) -> response.Response(
            server.ResponseBody,
          ),
        ) -> response.Response(server.ResponseBody),
      ),
      page: LivePage,
      last_activity_ms: Int,
      ping_seq: Int,
      pending_ping: option.Option(Int),
      decode_errors: Int,
      rate_violations: Int,
      timer_subject: process.Subject(WsTimer),
      ping_timer: option.Option(process.Timer),
      pong_timer: option.Option(process.Timer),
      idle_timer: option.Option(process.Timer),
      auth_timer: option.Option(process.Timer),
      opened_at: Int,
      frames_in: Int,
      frames_out: Int,
      bytes_in: Int,
      bytes_out: Int,
      warned_no_build: Bool,
    )

    Arguments

    id

    Unique ID for this connection, used in logging.

    socket

    The gen_tcp socket for this connection.

    on_event

    Callback invoked when a client event is received (per-connection).

    on_close

    Callback invoked when this connection closes (per-connection).

    event_count

    Rate limiting: count of events in current 1-second window.

    rate_window_start

    Rate limiting: start of current window (monotonic milliseconds).

    security_limits

    Security limits for this connection (copied from TransportConfig at init).

    heartbeat_count

    Heartbeat rate limiting: count in current 1-second window.

    heartbeat_window_start

    Heartbeat rate limiting: start of current window (monotonic milliseconds).

    buffer

    Receive buffer holding at most one partial frame between reads.

    reassembly

    Fragmented message reassembly state.

    peer_ip

    Peer IP address, for logs.

    request

    The original upgrade request (for periodic ws_auth re-checks).

    ws_auth

    The configured ws_auth, re-run every ws_auth_recheck_interval_ms.

    middlewares

    The listener’s middleware pipeline: the gate every navigation and join of this connection must pass (gate_live_page).

    page

    The page this connection last passed the middleware gate for: the upgrade’s page, then each admitted navigation.

    last_activity_ms

    Monotonic ms of the last application message from the client.

    ping_seq

    Sequence number of the last ping sent.

    pending_ping

    Sequence number of the ping awaiting a pong, if any.

    decode_errors

    Undecodable messages received so far.

    rate_violations

    Rate limit violations in the current rate window.

    timer_subject

    Subject the connection’s own timers deliver to.

    ping_timer

    Next server ping.

    pong_timer

    Deadline for the outstanding pong.

    idle_timer

    Next idle check.

    auth_timer

    Next ws_auth re-check (only when ws_auth is configured).

    opened_at

    Native monotonic time the connection started (after the 101).

    frames_in

    WebSocket frames decoded from the client.

    frames_out

    WebSocket frames written to the client (including the close frame).

    bytes_in

    TCP bytes received from the client after the upgrade.

    bytes_out

    Bytes written to the client after the upgrade.

    warned_no_build

    Whether a join without a build id was already warned about on this connection (check_build): later ones are logged at debug.

Internal messages that the connection process can receive. These come from the runtime (pushing patches) or from TCP events.

pub type InternalMessage {
  SendMount(payload: String)
  SendError(reason: String)
  SendModelSync(
    model_json: String,
    version: Int,
    ack_clock: Int,
    streams_json: String,
  )
  SendPatch(
    ops_json: String,
    version: Int,
    ack_clock: Int,
    streams_json: String,
  )
  SendNavigate(path: String)
  SendReplaceNavigate(path: String)
  SendHardNavigate(path: String)
  SendTitle(title: String)
  CloseForBackpressure(reason: String)
  ReceivedTcpData(data: BitArray)
  TcpConnectionClosed
  TcpConnectionError(reason: String)
}

Constructors

  • SendMount(payload: String)

    A mount payload needs to be sent to the client.

  • SendError(reason: String)

    An error needs to be sent to the client.

  • SendModelSync(
      model_json: String,
      version: Int,
      ack_clock: Int,
      streams_json: String,
    )

    Send authoritative Model state to the client. model_json must be one valid JSON value and streams_json one valid JSON array; see ServerModelSync.

  • SendPatch(
      ops_json: String,
      version: Int,
      ack_clock: Int,
      streams_json: String,
    )

    Send incremental model patch to the client. ops_json and streams_json must each be one valid JSON array; see ServerPatch.

  • SendNavigate(path: String)

    Send navigation redirect to the client (pushState).

  • SendReplaceNavigate(path: String)

    Send navigation to the client that replaces the history entry.

  • SendHardNavigate(path: String)

    Send hard navigation to the client (full page reload).

  • SendTitle(title: String)

    Send the document title to the client.

  • CloseForBackpressure(reason: String)

    Close the connection because the server has detected sustained pressure.

  • ReceivedTcpData(data: BitArray)

    Raw TCP data received — needs WebSocket frame decoding.

  • TcpConnectionClosed

    TCP connection closed by peer.

  • TcpConnectionError(reason: String)

    TCP error occurred.

The page a live connection mounts or navigates to: a same-origin path and its query.

pub type LivePage {
  LivePage(path: String, query: option.Option(String))
}

Constructors

Configurable security limits for the transport layer. All fields have sensible defaults via default_security_limits(). validate_security_limits rejects nonsensical values; the listener refuses to start with invalid limits.

pub type SecurityLimits {
  SecurityLimits(
    max_message_bytes: Int,
    max_events_per_second: Int,
    max_connections: Int,
    max_http_headers: Int,
    max_http_header_bytes: Int,
    pre_upgrade_timeout_ms: Int,
    max_connections_per_ip: Int,
    max_request_line_bytes: Int,
    max_body_bytes: Int,
    body_read_timeout_ms: Int,
    request_timeout_ms: Int,
    keepalive_idle_timeout_ms: Int,
    max_keepalive_requests: Int,
    send_timeout_ms: Int,
    drain_timeout_ms: Int,
    trusted_proxies: List(String),
    forwarding_header: ip.ForwardingHeader,
    max_ws_connections_per_ip: Int,
    ws_ping_interval_ms: Int,
    ws_pong_timeout_ms: Int,
    ws_idle_timeout_ms: Int,
    ws_send_timeout_ms: Int,
    ws_max_outbound_queue: Int,
    ws_auth_recheck_interval_ms: Int,
    ws_max_decode_errors: Int,
    ws_max_rate_violations: Int,
    ws_allowed_origins: List(String),
    allowed_hosts: List(String),
    stream_max_limit: Int,
    stream_item_max_bytes: Int,
    stream_ops_max_per_update: Int,
    stream_frame_max_bytes: Int,
    stream_key_max_bytes: Int,
  )
}

Constructors

  • SecurityLimits(
      max_message_bytes: Int,
      max_events_per_second: Int,
      max_connections: Int,
      max_http_headers: Int,
      max_http_header_bytes: Int,
      pre_upgrade_timeout_ms: Int,
      max_connections_per_ip: Int,
      max_request_line_bytes: Int,
      max_body_bytes: Int,
      body_read_timeout_ms: Int,
      request_timeout_ms: Int,
      keepalive_idle_timeout_ms: Int,
      max_keepalive_requests: Int,
      send_timeout_ms: Int,
      drain_timeout_ms: Int,
      trusted_proxies: List(String),
      forwarding_header: ip.ForwardingHeader,
      max_ws_connections_per_ip: Int,
      ws_ping_interval_ms: Int,
      ws_pong_timeout_ms: Int,
      ws_idle_timeout_ms: Int,
      ws_send_timeout_ms: Int,
      ws_max_outbound_queue: Int,
      ws_auth_recheck_interval_ms: Int,
      ws_max_decode_errors: Int,
      ws_max_rate_violations: Int,
      ws_allowed_origins: List(String),
      allowed_hosts: List(String),
      stream_max_limit: Int,
      stream_item_max_bytes: Int,
      stream_ops_max_per_update: Int,
      stream_frame_max_bytes: Int,
      stream_key_max_bytes: Int,
    )

    Arguments

    max_message_bytes

    Maximum WebSocket message size in bytes. Default: 65536 (64KB). Bounds both a single frame (checked from the frame header, before the payload is buffered) and a reassembled fragmented message. Exceeding it closes the connection with 1009.

    max_events_per_second

    Maximum events per second per connection. Default: 50. Every event inside an event_batch counts individually.

    max_connections

    Maximum concurrent HTTP/WebSocket connections globally. Default: 10000.

    max_http_headers

    Maximum number of HTTP headers accepted before upgrade. Default: 100.

    max_http_header_bytes

    Maximum total HTTP header bytes accepted before upgrade. Default: 65536.

    pre_upgrade_timeout_ms

    Timeout for request line/header reads before upgrade, in milliseconds. Default: 5000.

    max_connections_per_ip

    Maximum concurrent connections from one peer IP. Peers listed in trusted_proxies are exempt (they multiplex many clients). Default: 256.

    max_request_line_bytes

    Maximum request-line length in bytes; longer lines get 414. Default: 8192.

    max_body_bytes

    Maximum declared request body size in bytes; larger Content-Length gets 413 before the handler runs. Default: 8388608 (8MB).

    body_read_timeout_ms

    Maximum wait for each chunk of request body, in milliseconds. Default: 30000.

    request_timeout_ms

    Absolute deadline for reading a request body, in milliseconds from the moment its head was parsed (the head itself is bounded by pre_upgrade_timeout_ms). Default: 60000.

    keepalive_idle_timeout_ms

    How long an idle keep-alive connection waits for the next request, in milliseconds. Default: 15000.

    max_keepalive_requests

    Maximum requests served on one keep-alive connection. Default: 1000.

    send_timeout_ms

    Maximum time a blocked socket send may take before the connection is closed, in milliseconds. Default: 30000.

    drain_timeout_ms

    How long a stopping listener waits for its open connections to finish, in milliseconds. When the listener stops, an idle keep-alive connection closes at once, a request in progress is answered with connection: close, and a WebSocket is closed with 1001. A connection still open after this long is killed (and logged as an error). 0 kills every connection at once. The environment variable BRAZIER_SHUTDOWN_TIMEOUT overrides it for an application. Default: 5000.

    trusted_proxies

    Reverse proxies (IP addresses or CIDRs) whose forwarding_header is believed. Parsed once, when the listener starts. Empty means the socket peer address is always the client address. Default: [].

    forwarding_header

    The one header the trusted_proxies set to name the client and its scheme: ip.Forwarded (for= and proto=), ip.XForwardedFor (with X-Forwarded-Proto) or ip.NoForwardingHeader (the peer, over http). The other header is never read. A header with no trusted proxies is refused. Default: ip.NoForwardingHeader.

    max_ws_connections_per_ip

    Maximum concurrent WebSocket connections per peer IP (per listener). Upgrades beyond it get 429. Default: 100. Behind a reverse proxy every client shares the proxy’s address — raise this or enforce it upstream.

    ws_ping_interval_ms

    Interval between server pings, in milliseconds. Default: 30000.

    ws_pong_timeout_ms

    Time a client has to answer a ping before the connection is closed (1000, “pong timeout”), in milliseconds. Default: 20000.

    ws_idle_timeout_ms

    Close the connection (1000, “idle timeout”) when no application message — including the client heartbeat, sent every 30s — arrives for this long, in milliseconds. Default: 90000.

    ws_send_timeout_ms

    A socket send that cannot complete within this many milliseconds fails and the socket is closed (slow reader protection). Default: 15000.

    ws_max_outbound_queue

    Maximum messages queued for the connection process. When the runtime produces faster than the client reads, the connection closes with 1013. Default: 1000.

    ws_auth_recheck_interval_ms

    Interval for re-running ws_auth on a live connection, in milliseconds. A failed re-check closes with 4401. Default: 60000.

    ws_max_decode_errors

    Undecodable messages tolerated per connection before it is closed with 1008. Default: 10.

    ws_max_rate_violations

    Rate limit violations tolerated within one rate window before the connection is closed with 1008. Each rejected message is one violation, so the default tolerates a burst of roughly three times max_events_per_second in one window. Default: 100.

    ws_allowed_origins

    Extra origins (e.g. “https://app.example.com”) allowed to open WebSockets in addition to the request’s own scheme/host/port. Default: [].

    allowed_hosts

    Host names this server answers to; every HTTP request and WebSocket upgrade whose Host header names another host gets 400 (DNS rebinding protection, Django’s ALLOWED_HOSTS). The port is not compared. An entry is a host ("example.com", "127.0.0.1", "[::1]"), a domain and all its subdomains (".example.com"), or "*" for every host (which turns the check off, and is logged as a warning). Empty means [".localhost", "127.0.0.1", "[::1]"] in development; in production (BRAZIER_ENV=production) the listener refuses to start with an empty list. Default: [].

    stream_max_limit

    The largest limit a stream may be used with. An op with a larger one is refused (limit_out_of_range). At most 1000, the largest limit the wire and the client accept. Default: 1000.

    stream_item_max_bytes

    The largest encoded stream item, in bytes. An op with a larger item is refused (item_too_large). Default: 65536.

    stream_ops_max_per_update

    The most stream ops one update may carry, all streams together; a reset counts one per item. An update with more has every stream op refused (too_many_ops); its model part is still sent. Default: 1000.

    stream_frame_max_bytes

    The largest streams array of one frame, in bytes. An update whose entries are larger has every stream op refused (frame_too_large). Default: 1048576 (1MB).

    stream_key_max_bytes

    The longest stream key, in bytes. An op with a longer key is refused (key_too_large). Default: 256.

Messages sent from the server to the client (browser).

pub type ServerMessage {
  ServerMount(payload: String)
  ServerHeartbeatAck
  ServerError(reason: String)
  ServerModelSync(
    model_json: String,
    version: Int,
    ack_clock: Int,
    streams_json: String,
  )
  ServerPatch(
    ops_json: String,
    version: Int,
    ack_clock: Int,
    streams_json: String,
  )
  ServerNavigate(path: String)
  ServerReplaceNavigate(path: String)
  ServerHardNavigate(path: String)
  ServerReload
  ServerTitle(title: String)
}

Constructors

  • ServerMount(payload: String)

    Initial mount: SSR HTML for first paint.

  • ServerHeartbeatAck

    Server acknowledges a heartbeat.

  • ServerError(reason: String)

    Server-initiated error message.

  • ServerModelSync(
      model_json: String,
      version: Int,
      ack_clock: Int,
      streams_json: String,
    )

    Authoritative Model state from server. Client renders the view locally from this state. Invariant: model_json is one complete, valid JSON value. It is embedded raw in the frame ("model":{...}), so invalid text would corrupt the whole frame. The runtime only sends text it decoded into a patch.Snapshot. streams_json is the frame’s streams array (docs/design/STREAMS.md 6.1), embedded raw the same way; the runtime only sends stream_window.encode_deltas output or the literal [].

  • ServerPatch(
      ops_json: String,
      version: Int,
      ack_clock: Int,
      streams_json: String,
    )

    Incremental model patch — only changed fields. Client applies ops to its cached model JSON instead of replacing entirely. Invariant: ops_json is one valid JSON array of RFC 6902 operations, embedded raw in the frame ("ops":[...]). The runtime only sends the output of patch.diff_snapshots or the literal []. streams_json is as on ServerModelSync.

  • ServerNavigate(path: String)

    Server-initiated navigation (redirect via pushState).

  • ServerReplaceNavigate(path: String)

    Server-initiated navigation that replaces the current history entry (replaceState). Encoded as navigate with "replace": true.

  • ServerHardNavigate(path: String)

    Server-initiated hard navigation (full page reload via window.location.href). Use when the browser needs to make a real HTTP request (e.g., to receive Set-Cookie).

  • ServerReload

    Dev mode: tell browser to reload.

  • ServerTitle(title: String)

    The document title for the session’s state: sent on every join and whenever an update changes it. The client sets document.title.

Why a per-connection runtime did not start. Either way the socket is closed right after the upgrade, before any other frame. The factory logs the cause where it happens (Brazier’s runtime does, with the connection id); the transport logs the close at info.

pub type StartupError {
  InitRefused(error: error.InitError)
  StartupFailed(reason: String)
}

Constructors

  • InitRefused(error: error.InitError)

    The app’s init refused the session. The socket closes with error.init_close_code (4000 + the status) and the reason "init refused"; the app’s own reason is only logged.

  • StartupFailed(reason: String)

    Anything else: an invalid page URL, a crash in init, a refused presence track. The socket closes with 1011 "startup failure".

Configuration for the transport layer.

pub type TransportConfig {
  TransportConfig(
    port: Int,
    on_connect: fn(String, process.Subject(InternalMessage)) -> Nil,
    on_event: fn(String, ClientMessage) -> Nil,
    on_disconnect: fn(String) -> Nil,
    page_html: option.Option(@internal RenderedPage),
    middlewares: List(
      fn(
        request.Request(server.Connection),
        fn(request.Request(server.Connection)) -> response.Response(
          server.ResponseBody,
        ),
      ) -> response.Response(server.ResponseBody),
    ),
    static_config: option.Option(static.StaticConfig),
    runtime_factory: option.Option(
      fn(
        String,
        process.Subject(InternalMessage),
        request.Request(server.Connection),
      ) -> Result(
        #(fn(String, ClientMessage) -> Nil, fn(String) -> Nil),
        StartupError,
      ),
    ),
    ws_auth: option.Option(
      fn(request.Request(server.Connection)) -> Result(
        Nil,
        String,
      ),
    ),
    ssr_factory: option.Option(
      fn(request.Request(server.Connection), String) -> @internal RenderedPage,
    ),
    security_limits: SecurityLimits,
    api_handler: option.Option(
      fn(request.Request(server.Connection)) -> option.Option(
        response.Response(server.ResponseBody),
      ),
    ),
  )
}

Constructors

  • TransportConfig(
      port: Int,
      on_connect: fn(String, process.Subject(InternalMessage)) -> Nil,
      on_event: fn(String, ClientMessage) -> Nil,
      on_disconnect: fn(String) -> Nil,
      page_html: option.Option(@internal RenderedPage),
      middlewares: List(
        fn(
          request.Request(server.Connection),
          fn(request.Request(server.Connection)) -> response.Response(
            server.ResponseBody,
          ),
        ) -> response.Response(server.ResponseBody),
      ),
      static_config: option.Option(static.StaticConfig),
      runtime_factory: option.Option(
        fn(
          String,
          process.Subject(InternalMessage),
          request.Request(server.Connection),
        ) -> Result(
          #(fn(String, ClientMessage) -> Nil, fn(String) -> Nil),
          StartupError,
        ),
      ),
      ws_auth: option.Option(
        fn(request.Request(server.Connection)) -> Result(Nil, String),
      ),
      ssr_factory: option.Option(
        fn(request.Request(server.Connection), String) -> @internal RenderedPage,
      ),
      security_limits: SecurityLimits,
      api_handler: option.Option(
        fn(request.Request(server.Connection)) -> option.Option(
          response.Response(server.ResponseBody),
        ),
      ),
    )

    Arguments

    port

    Port to listen on.

    on_connect

    Callback invoked when a new WebSocket connection is established.

    on_event

    Callback invoked when a client sends an event.

    on_disconnect

    Callback invoked when a WebSocket connection closes.

    page_html

    Optional: pre-rendered page for SSR. If provided, HTTP requests get this instead of the default page.

    middlewares

    Optional: middleware pipeline applied to all HTTP requests. Middleware runs before routing (SSR, static files). A WebSocket is gated on the page it mounts instead: the upgrade, every navigation and every join run the pipeline on a GET of that page with the upgrade’s headers (gate_live_page), never on /ws itself.

    static_config

    Optional: static file serving configuration.

    runtime_factory

    Optional: factory that creates a per-connection runtime. If set, each WebSocket connection gets its OWN runtime (like LiveView). The factory returns callbacks for this specific connection’s runtime. If None, uses the shared on_connect/on_event/on_disconnect callbacks. The factory receives the upgrade request, from which the runtime builds the connected mount’s Context (cookies, page URL).

    ws_auth

    Optional: WebSocket authentication function. If set, runs before upgrade — returns Ok to allow, Error to reject with 401.

    ssr_factory

    Optional: SSR factory for route-aware server-side rendering. Given the HTTP request and path, returns the rendered page for that route. The request is available for reading cookies, headers, etc. When set, HTTP requests use this instead of page_html.

    security_limits

    Configurable security limits (message size, rate limiting, max connections). Defaults to default_security_limits().

    api_handler

    Optional: API route handler — runs BEFORE SSR/static file routing. If it returns Some(response), that response is sent immediately. If it returns None, the request falls through to normal routing.

Timer events a WebSocket connection process schedules for itself.

pub type WsTimer {
  PingTick
  PongDeadline(seq: Int)
  IdleCheck
  AuthRecheck
}

Constructors

  • PingTick

    Send the next server ping.

  • PongDeadline(seq: Int)

    Deadline for the pong answering ping seq.

  • IdleCheck

    Check whether the connection has been idle too long.

  • AuthRecheck

    Re-run ws_auth against the original upgrade request.

Values

pub fn connection_tracker_init() -> Nil
pub fn create_handler(
  config: TransportConfig,
) -> fn(request.Request(server.Connection)) -> response.Response(
  server.ResponseBody,
)

Create the HTTP handler for non-WebSocket requests. Handles: API routes, static files, client JS, health check, SSR pages. API handler runs first — if it returns Some(response), that’s used. Otherwise falls through to static files, brazier_client.js, health, SSR.

pub fn decode_client_message(
  raw: String,
) -> Result(ClientMessage, error.BrazierError)

Decode a JSON string from the client into a ClientMessage.

pub fn default_security_limits() -> SecurityLimits

Sensible default security limits.

pub const development_allowed_hosts: List(String)

The hosts a development server answers to when allowed_hosts is empty: Django’s development default.

pub fn encode_server_message(msg: ServerMessage) -> String

Encode a ServerMessage to the JSON text sent over the wire.

pub fn encode_server_message_tree(
  msg: ServerMessage,
) -> string_tree.StringTree

Encode a ServerMessage to the JSON text sent over the wire, as a string tree the socket writes without flattening it into one binary.

The model of a model_sync, the ops of a patch and the streams of both are already JSON text, and they are embedded raw: "model":{...}, "ops":[...] and "streams":[...], not re-encoded as JSON strings. That relies on the invariant documented on ServerModelSync and ServerPatch: the text is one valid JSON value. Field order is fixed (type, model or ops, version, ack_clock, streams).

Reference: Phoenix.Socket.V2.JSONSerializer, whose encode! returns iodata with the already-encoded payload spliced in; LiveView sends its rendered diff as a JSON object inside the reply, never as a string.

pub fn get_upgrade_page(
  req: request.Request(server.Connection),
) -> Result(LivePage, String)

The page a WebSocket upgrade request mounts: the path and query in its path query parameter (/ws?path=%2Fusers%2F7%3Ftab%3Dposts), which the client sets to its own location.pathname + location.search. The first path parameter counts. It is refused when it is missing, is not a same-origin path (internal/local_path, the rule router.push uses) or does not parse.

The transport gates the upgrade on this page and the runtime mounts it, so both read the same page.

pub fn origin_allowed(
  origin: String,
  request_scheme: String,
  request_host: String,
  allowed_origins: AllowedOrigins,
) -> Result(Nil, String)

Validate an Origin header against the request’s own origin (its scheme and Host header, default ports applied) and an explicit allow-list of origins. Scheme, host and port must all match.

pub fn origin_allowed_for_host(
  origin: String,
  request_host: String,
) -> Result(Nil, String)

Validate that an Origin header value belongs to the request Host over plain http. Empty origins, empty hosts, scheme-less values and scheme/host/port mismatches reject.

pub const page_route: String

Telemetry route of every page request. Pages are routed by the app’s typed router, which has no patterns, so all pages share one label.

pub fn parse_allowed_origins(
  entries: List(String),
) -> Result(AllowedOrigins, String)

Parse ws_allowed_origins entries; each must be a bare scheme://host[:port] origin (http or https, no path). A refusal is returned for the caller to log once (“Invalid security limits: …”).

pub fn start(
  config: TransportConfig,
) -> Result(process.Pid, error.BrazierError)

Start the transport layer — binds to the given port and begins accepting connections. Returns the listener supervisor’s Pid. Use start_listener to also learn the bound port (for port 0).

pub fn start_listener(
  config: TransportConfig,
) -> Result(server.Listener, error.BrazierError)

Start the transport layer and return the running listener, which reports the bound port (server.get_port) and live connection counts. The listener supervisor is linked to the caller.

pub fn startup_error_to_string(err: StartupError) -> String
pub fn stream_limits(limits: SecurityLimits) -> @internal Limits

The stream bounds of limits, for the runtime and the dead render.

pub const stream_wire_max_limit: Int

The largest stream limit the wire allows (docs/design/STREAMS.md 6.1): the client faults on a larger one.

pub fn supervised(
  config: TransportConfig,
) -> Result(
  supervision.ChildSpecification(server.Listener),
  error.BrazierError,
)

A child specification that starts the listener under a supervisor (the application’s root supervision tree). The configuration is checked when the specification is built, so a bad config fails before any process starts. A restarted listener binds the port of its first start again (with port 0, the port the OS assigned then).

pub fn to_listener_options(
  config: TransportConfig,
) -> server.ListenerOptions

The listener options implied by a transport configuration.

pub fn trusted_proxies(
  limits: SecurityLimits,
) -> Result(ip.TrustedProxies, String)

Parse the trusted_proxies and forwarding_header of limits, for middleware.rate_limit_behind_proxies. Call it once, when building the middleware: it parses the list on every call. The listener parses its own copy once when it starts and refuses to start on a bad entry. validate_security_limits rejects the same entries, so an error here means the limits were never validated; it is logged.

pub fn validate_drain_timeout(ms: Int) -> Result(Nil, String)

A drain timeout is a receive timeout: 0 (kill at once) up to the largest timer. A refusal is returned, not logged: each caller logs it once with its own context (“Invalid security limits: …”, or the application’s “BRAZIER_SHUTDOWN_TIMEOUT: …”), so one refusal makes one log line.

pub fn validate_security_limits(
  limits: SecurityLimits,
) -> Result(Nil, String)

Validate security limits. Every count, size and interval must be positive (the two abuse thresholds may be 0, meaning “first offence closes”, max_body_bytes may be 0, meaning “no request bodies”, and drain_timeout_ms may be 0, meaning “kill connections at once”), timer intervals must fit erlang:send_after/3, every allowed origin must be a bare scheme://host[:port] origin, every allowed host a host name, .domain or *, and in production (BRAZIER_ENV=production) allowed_hosts must not be empty. The listener runs the same checks when it starts.

pub const ws_route: String

Telemetry route of the WebSocket endpoint.

✨ Search Document