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_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 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.