gluegun/websocket

WebSocket helpers for Gun connections.

Protocol limitations

Gun supports WebSocket over HTTP/1.1 only. WebSocket over HTTP/2 (RFC 8441) is not supported by Gun. Call upgrade with the protocol returned by connection.await_up to reject HTTP/2 before calling Gun.

Once an HTTP/1.1 connection is upgraded to WebSocket the underlying TCP connection is exclusively used for WebSocket frames. You cannot send concurrent HTTP requests on that same connection after upgrading.

Typical usage

import gleam/result
import gluegun/connection
import gluegun/error
import gluegun/message
import gluegun/websocket

websocket.with_socket(
  host: "echo.example.com",
  port: 80,
  path: "/ws",
  options: websocket.options(),
  callback: fn(socket) {
    use _ <- result.try(websocket.send_frame(socket, message.Text("hello")))
    case websocket.receive_app_frame(socket) {
      Ok(message.Text(reply)) -> Ok(reply)
      Ok(message.Binary(_))
      | Ok(message.Ping(_))
      | Ok(message.Pong(_))
      | Ok(message.Close)
      | Ok(message.CloseWithReason(_, _)) ->
        Error(error.InvalidMessage("expected a text frame"))
      Error(error) -> Error(error)
    }
  },
)

Types

Completed HTTP/1.1 WebSocket handshake, including the response headers and the server’s selected subprotocol (if any).

pub opaque type Handshake

High-level options for opening and upgrading a WebSocket connection.

Build with options() then chain with_headers, with_connect_options, with_upgrade_options, and with_timeout. Defaults to HTTP/1.1; Gun’s HTTP/2 protocol is rejected before upgrade.

pub opaque type Options

A reusable WebSocket handle.

Wraps the upgraded Gun connection, WebSocket stream, and receive timeout so higher-level helpers can send and receive frames without repeating them.

pub opaque type Socket

Typed options for Gun WebSocket upgrades.

Build with upgrade_options() then chain with_closing_timeout, with_compress, with_flow, with_keepalive, with_subprotocols, and with_silence_pings. The fallible with_flow builder rejects nonpositive credit.

pub opaque type UpgradeOptions

Values

pub fn await_upgrade(
  connection connection: connection.Connection,
  stream stream: request.Stream,
  timeout timeout: connection.Timeout,
) -> Result(Handshake, error.GluegunError)

Wait for the WebSocket upgrade confirmation (101 Switching Protocols).

Call this after upgrade. Informational responses are skipped under one absolute deadline; a zero timeout returns Timeout without polling. Successful handshakes retain the response headers and selected subprotocol.

pub fn close(
  socket socket: Socket,
) -> Result(Nil, error.GluegunError)

Send a close frame, await the peer, then release the Gun process.

Uses one deadline: the socket’s finite timeout, or five seconds for Infinity. Other frames do not extend it. A missing peer close returns Timeout; connection and stream failures return their typed errors. Teardown still terminates the process and flushes its queued Gun messages from this caller after a handshake error. The handshake error takes precedence over a termination error.

An already-exited connection returns Ok(Nil). Repeated close is safe. Call from the socket’s receiver; other receivers and external forwarders need separate mailbox cleanup. Do not reuse the socket after this call.

pub fn connect(
  host host: String,
  port port: Int,
  path path: String,
  options options: Options,
) -> Result(Socket, error.GluegunError)

Open a Gun connection, perform a WebSocket upgrade, and return a reusable socket.

The connection is opened with the configured connect options, awaited up to readiness, then upgraded. If any step fails the underlying Gun connection is closed automatically. On success the caller owns the returned Socket and must eventually call close (or use with_socket for scoped cleanup).

Defaults to HTTP/1.1; HTTP/2 is rejected with UnsupportedFeature because Gun does not support WebSocket over HTTP/2.

pub fn from_upgrade(
  connection connection: connection.Connection,
  stream stream: request.Stream,
  handshake handshake: Handshake,
  timeout timeout: connection.Timeout,
) -> Socket

Adopt a completed upgrade. Call immediately after await_upgrade, in the process receiving this stream’s Gun messages. This process owns receives and close; do not consume frames through another API or receiver, since close tracks delivered frames to restore finite flow credit during teardown.

pub fn handshake(socket: Socket) -> Handshake

Inspect the completed handshake retained by a socket.

pub fn handshake_headers(
  handshake: Handshake,
) -> List(#(String, String))

Inspect the upgrade response headers, with normalized header names.

pub fn handshake_subprotocol(
  handshake: Handshake,
) -> option.Option(String)

Inspect the selected subprotocol in a completed handshake.

pub fn options() -> Options

Construct default high-level WebSocket connection options.

pub fn ping(
  socket socket: Socket,
  data data: BitArray,
) -> Result(Nil, error.GluegunError)

Send a ping WebSocket frame using a reusable socket.

pub fn pong(
  socket socket: Socket,
  data data: BitArray,
) -> Result(Nil, error.GluegunError)

Send a pong WebSocket frame using a reusable socket.

pub fn receive_app_frame(
  socket socket: Socket,
) -> Result(message.Frame, error.GluegunError)

Receive the next application frame, handling ping/pong control frames.

Gun automatically answers incoming pings with a matching pong. This helper skips visible ping and pong frames, including empty payloads, without sending another reply. Text, binary, close, and close-with-reason frames are returned to the caller. Use pong to send an explicit pong.

Each call uses one total deadline from the socket’s timeout. Skipped control frames do not restart it. Infinity permits an unlimited wait; a zero timeout returns Timeout without consuming queued frames.

pub fn receive_frame(
  socket socket: Socket,
) -> Result(message.Frame, error.GluegunError)

Receive the next WebSocket frame using a reusable socket.

Returns Ok(frame) when a WebSocket frame arrives. Returns Error(InvalidMessage(...)) if a non-WebSocket message arrives (e.g. an HTTP response or upgrade acknowledgement that arrived out of order). Returns DecodeError for malformed incoming values, Timeout when no frame arrives, and ConnectionDown on disconnect (even with Gun retries). Only this socket’s stream and connection messages are consumed.

pub fn selected_subprotocol(
  socket: Socket,
) -> option.Option(String)

Inspect the server-selected subprotocol.

pub fn send_binary(
  socket socket: Socket,
  data data: BitArray,
) -> Result(Nil, error.GluegunError)

Send a binary WebSocket frame using a reusable socket.

pub fn send_close_frame(
  socket socket: Socket,
) -> Result(Nil, error.GluegunError)

Send a close WebSocket frame using a reusable socket.

This only sends the close frame; it does not close the underlying Gun connection. Follow with close(socket) or use with_socket for automatic teardown.

pub fn send_frame(
  socket socket: Socket,
  frame frame: message.Frame,
) -> Result(Nil, error.GluegunError)

Send a single WebSocket frame using a reusable socket.

Supported frame types: Text, Binary, Ping, Pong, Close, CloseWithReason. Invalid control frames return InvalidMessage before calling Gun. Ping and Pong payloads can contain at most 125 bytes. Close reasons must be valid UTF-8 and contain at most 123 bytes. Accepted close codes: 1000-1003, 1007-1014, and 3000-4999.

pub fn send_many(
  socket socket: Socket,
  frames frames: List(message.Frame),
) -> Result(Nil, error.GluegunError)

Send one or more WebSocket frames on the stream.

Validates the complete list before sending. If a frame is invalid, returns InvalidMessage without sending any frame.

Binary, ping, pong, and close-reason payloads must contain whole bytes. Empty payloads are valid. A non-byte-aligned BitArray returns InvalidMessage before Gun receives any frame in the list. Payloads are never padded or truncated, and the connection remains usable.

pub fn send_text(
  socket socket: Socket,
  text text: String,
) -> Result(Nil, error.GluegunError)

Send a text WebSocket frame using a reusable socket.

pub fn upgrade(
  connection connection: connection.Connection,
  protocol protocol: connection.Protocol,
  path path: String,
  headers headers: List(#(String, String)),
  options options: UpgradeOptions,
) -> Result(request.Stream, error.GluegunError)

Initiate a WebSocket upgrade with options when the negotiated protocol is known.

Returns UnsupportedFeature for HTTP/2 because Gun does not support WebSocket over HTTP/2. Path, headers, and all subprotocol tokens are validated before calling Gun; invalid input returns InvalidOptions.

pub fn upgrade_options() -> UpgradeOptions

Construct default WebSocket upgrade options.

pub fn with_closing_timeout(
  options: UpgradeOptions,
  timeout timeout: connection.Timeout,
) -> UpgradeOptions

Set Gun’s WebSocket closing timeout.

pub fn with_compress(
  options: UpgradeOptions,
  enabled enabled: Bool,
) -> UpgradeOptions

Enable or disable WebSocket compression.

pub fn with_connect_options(
  options: Options,
  connect_options connect: connection.ConnectOptions,
) -> Options

Set Gun connection options used when opening the connection.

pub fn with_flow(
  options: UpgradeOptions,
  initial_flow initial_flow: Int,
) -> Result(UpgradeOptions, error.GluegunError)

Set a positive initial WebSocket flow-control allowance.

pub fn with_headers(
  options: Options,
  headers headers: List(#(String, String)),
) -> Options

Set headers sent with the WebSocket upgrade request.

pub fn with_keepalive(
  options: UpgradeOptions,
  timeout timeout: connection.Timeout,
) -> UpgradeOptions

Set Gun’s WebSocket keepalive timeout.

pub fn with_reply_to(
  options: UpgradeOptions,
  receiver receiver: process.Pid,
) -> UpgradeOptions

Deliver upgrade and frame messages to this process. That receiver must await the upgrade, adopt it, receive frames, and close its socket.

pub fn with_silence_pings(
  options: UpgradeOptions,
  enabled enabled: Bool,
) -> UpgradeOptions

Enable or disable silencing automatic ping frames.

pub fn with_socket(
  host host: String,
  port port: Int,
  path path: String,
  options options: Options,
  callback callback: fn(Socket) -> Result(a, error.GluegunError),
) -> Result(a, error.GluegunError)

Open a WebSocket, run a callback, then send the close frame and close the underlying connection.

Scoped lifecycle helper. Use this when the WebSocket session is self-contained. The callback receives a reusable Socket. Errors from the callback take precedence over cleanup errors; cleanup is attempted even when the callback fails. If the callback panics, fails an assertion, or raises an Erlang exception, teardown uses a finite close deadline before re-raising the original exception class, reason, and stack. Cleanup cannot replace it.

pub fn with_subprotocols(
  options: UpgradeOptions,
  protocols protocols: List(String),
) -> UpgradeOptions

Offer subprotocols using Gun’s standard WebSocket handler. Every token is validated before sending; order and case are preserved.

pub fn with_timeout(
  options: Options,
  timeout timeout: connection.Timeout,
) -> Options

Set the timeout used when awaiting connection readiness, upgrade, and frames.

pub fn with_upgrade_options(
  options: Options,
  upgrade_options upgrade: UpgradeOptions,
) -> Options

Set Gun WebSocket upgrade options used for the upgrade request.

✨ Search Document