HTTP.WebSocket (http_web_socket v0.21.18)

Copy Markdown View Source

Browser-like WebSocket client API for Elixir.

Events are delivered as messages to the owner process:

{HTTP.WebSocket, socket, %HTTP.WebSocket.Event.Open{}}
{HTTP.WebSocket, socket, %HTTP.WebSocket.Event.Message{}}
{HTTP.WebSocket, socket, %HTTP.WebSocket.Event.Error{}}
{HTTP.WebSocket, socket, %HTTP.WebSocket.Event.Close{}}

HTTP/1 is the default. Select http_version: :http2, :h2c, or :auto explicitly; http_version/1 reports the negotiated HTTP version, while protocol/1 continues to report the selected WebSocket subprotocol.

With delivery: :ack, message events include a delivery reference:

{HTTP.WebSocket, socket, %HTTP.WebSocket.Event.Message{}, ref}

Call acknowledge/2 after consuming each message. Open, Error, and Close retain their three-element event envelopes. Receive queues, pending sends, and fragmented messages have finite configurable limits. Established sessions default to idle_timeout: :infinity; opening and close deadlines are separate.

Telemetry URLs omit query, userinfo, authority, and fragment by default. Pass telemetry_url: nil to omit all URL-derived telemetry metadata, or a URI/string containing only caller-approved fields to replace the telemetry URL (for example, telemetry_url: "ws://127.0.0.1:8080" omits a private path). Invalid replacements reject construction; replacement credentials are always stripped. This option applies to every lifecycle/message event and never changes the transport URL or handshake.

HTTP/1 WebSocket proxy boundary

For frame forwarding, select mode: :proxy, http_version: :http1, delivery: :ack and automatic_pong: false. Proxy mode delivers %HTTP.WebSocket.Event.Frame{opcode: opcode, data: binary} for text, binary, ping and pong, with the same delivery reference/acknowledge/2 contract. binary_type does not alter proxy frame data. Close retains its lifecycle event and automatic reply. send_frame/2 and send_frame_ack/2 accept {opcode, binary} for those four opcodes. Automatic pong defaults to true; disabling it requires proxy mode. Proxy close/3 accepts nil with an empty reason, 1000-1014 except 1004/1005/1006, and 3000-4999. Reserved/non-wire codes, invalid UTF-8 and reasons over 123 bytes remain rejected.

Proxy HTTP/2, h2c and auto selection return {:error, :proxy_requires_http1}. Wait for successful completion of every send_frame_ack/2 reference before initiating Close: local close discards queued application frames in both HTTP versions. This provides ordered local write completion over HTTP/1; proxy mode supplies no HTTP/2 drain contract. Control sends share the bounded application send FIFO, including zero-length payloads. Control delivery uses the receive limits too; ACK capacity must accommodate at least 132 bytes.

Use this public client for the upstream half of a WebSocket proxy. Select http_version: :http1 and delivery: :ack; acknowledge each Message only after downstream consumption. Set finite opening_timeout, idle_timeout, write_timeout and close_timeout, and size/count limits for messages, receive queues and pending sends. send_ack/2 reports local transport completion for each admitted frame. send/2 reports admission only.

The client validates status 101, Upgrade/Connection tokens, exactly one matching Sec-WebSocket-Accept and the selected subprotocol. Bytes coalesced after the response head enter the frame parser. Redirects and uncertain operations are never replayed. Ping is answered with Pong automatically; Close is exchanged with the peer. This interface proxies WebSocket messages, not raw TCP bytes or exact frame fragmentation. Forward binary messages with array_buffer/1 and configure the upstream protocols explicitly.

WSS retains the selected TLS backend's CA, hostname and SNI verification; supply trusted ssl: [cacertfile: ...] for private CAs. Owner death closes the connection. close/1 signals cancellation; use completion/1 with HTTP.ClientCompletion.await/2 to confirm cleanup, including a cancelled opening. The receipt remains readable after the socket process exits. A Close event describes the handshake result; it does not prove peer application receipt of earlier writes. Terminal cancellation discards pending transport bytes, so a failed write may have reached the peer partly.

Plain Elixir binaries are sent as text frames. Use array_buffer/1 or HTTP.Blob for binary frames.

For wss connections, pass tls_backend: :ssl | :ex_ssl (or the equivalent string in a map). When omitted, the shared :http_core TLS backend setting is captured when the socket is created.

Summary

Functions

Acknowledges an ACK-mode message delivery reference.

Returns the durable cleanup receipt without contacting the connection.

Returns the negotiated HTTP version, or nil before establishment.

Admits a bounded send and returns its completion reference.

Admits an explicit text, binary, ping or pong frame in mode: :proxy.

Admits an explicit proxy frame and returns a local write completion reference.

Returns connection state and bounded delivery and send queue usage.

Types

t()

@type t() :: %HTTP.WebSocket{
  completion: HTTP.ClientCompletion.t() | nil,
  pid: pid() | nil,
  ref: reference() | nil,
  url: String.t() | nil
}

Functions

acknowledge(socket, ref)

@spec acknowledge(t(), reference()) :: :ok | {:error, term()}

Acknowledges an ACK-mode message delivery reference.

array_buffer(data)

@spec array_buffer(binary()) ::
  HTTP.WebSocket.ArrayBuffer.t() | {:error, :invalid_array_buffer}

binary_type(socket)

@spec binary_type(t()) :: :blob | :array_buffer

buffered_amount(socket)

@spec buffered_amount(t()) :: non_neg_integer()

close(socket)

@spec close(t()) :: :ok | {:error, term()}

close(socket, code)

@spec close(t(), non_neg_integer()) :: :ok | {:error, term()}

close(socket, code, reason)

@spec close(t(), non_neg_integer() | nil, String.t()) :: :ok | {:error, term()}

closed()

@spec closed() :: 3

closing()

@spec closing() :: 2

completion(web_socket)

@spec completion(t()) :: HTTP.ClientCompletion.t() | nil

Returns the durable cleanup receipt without contacting the connection.

connecting()

@spec connecting() :: 0

extensions(socket)

@spec extensions(t()) :: String.t()

http_version(socket)

@spec http_version(t()) :: :http1 | :http2 | nil

Returns the negotiated HTTP version, or nil before establishment.

new(url, protocols \\ [], init \\ [])

@spec new(String.t() | URI.t(), String.t() | [String.t()], keyword() | map()) ::
  t() | {:error, term()}

open()

@spec open() :: 1

protocol(socket)

@spec protocol(t()) :: String.t()

ready_state(socket)

@spec ready_state(t()) :: 0 | 1 | 2 | 3

send(socket, data)

@spec send(t(), String.t() | HTTP.Blob.t() | HTTP.WebSocket.ArrayBuffer.t()) ::
  :ok | {:error, term()}

send_ack(socket, data)

@spec send_ack(t(), String.t() | HTTP.Blob.t() | HTTP.WebSocket.ArrayBuffer.t()) ::
  {:ok, reference()} | {:error, term()}

Admits a bounded send and returns its completion reference.

The owner receives {HTTP.WebSocket, socket, {:send_result, ref, result}}, where result is :ok only after the complete frame was accepted by the transport, or {:error, reason} on cancellation/failure. This confirms local write completion, not peer application receipt. Failed/uncertain writes are never replayed. Unlike send/2, a closed connection rejects admission. Queuing and writing share the finite write_timeout (default 5 seconds).

send_frame(socket, frame)

@spec send_frame(t(), {:text | :binary | :ping | :pong, binary()}) ::
  :ok | {:error, term()}

Admits an explicit text, binary, ping or pong frame in mode: :proxy.

Control payloads are limited to 125 bytes; text must be valid UTF-8. Frames share the bounded FIFO send queue and write deadline with send/2. This reports admission only; use send_frame_ack/2 before forwarding Close. Send Close using close/3, preserving the connection's close deadline.

send_frame_ack(socket, frame)

@spec send_frame_ack(t(), {:text | :binary | :ping | :pong, binary()}) ::
  {:ok, reference()} | {:error, term()}

Admits an explicit proxy frame and returns a local write completion reference.

The owner receives the same {:send_result, ref, result} event as send_ack/2. Wait for :ok for every admitted frame before calling close/3 to preserve prior writes. Completion does not prove peer application receipt.

set_binary_type(socket, binary_type)

@spec set_binary_type(t(), :blob | :array_buffer) :: :ok | {:error, term()}

status(socket)

@spec status(t()) :: map()

Returns connection state and bounded delivery and send queue usage.

url(web_socket)

@spec url(t()) :: String.t() | nil