websocks

Types

Status codes that may appear in a close frame.

The codes reserved for local use only, such as 1005, 1006 and 1015, are absent. They must never be sent, and receiving one is a protocol violation.

pub type CloseCode {
  NormalClosure
  GoingAway
  ProtocolError
  UnsupportedData
  InvalidPayloadData
  PolicyViolation
  MessageTooBig
  MandatoryExtension
  InternalError
  ServiceRestart
  TryAgainLater
  BadGateway
  ApplicationCode(code: Int)
}

Constructors

  • NormalClosure

    The connection successfully completed its purpose and is closing normally.

  • GoingAway

    The endpoint is going away, either due to server shutdown or browser navigation.

  • ProtocolError

    A WebSocket protocol violation was detected.

  • UnsupportedData

    The endpoint received data it cannot accept.

  • InvalidPayloadData

    The message data doesn’t match the declared type.

  • PolicyViolation

    Generic status for policy violations when no other code applies.

  • MessageTooBig

    Message exceeds the maximum size the endpoint can handle.

  • MandatoryExtension

    The server encountered an unexpected condition preventing request fulfillment.

  • InternalError

    The server encountered unexpected error.

  • ServiceRestart

    Server is restarting.

  • TryAgainLater

    Temporary server overload.

  • BadGateway

    Gateway/proxy received invalid response.

  • ApplicationCode(code: Int)

    An application-specific code, between 3000 and 4999.

Why the connection is closing. Close frame is allowed to carry neither code nor reason.

pub type CloseReason {
  NoCloseReason
  CloseReason(code: CloseCode, reason: String)
}

Constructors

  • NoCloseReason

    A close frame with an empty payload.

  • CloseReason(code: CloseCode, reason: String)

    A close frame carrying a status code and an optional description.

Negotiated compression extension parameters from the WebSocket handshake. These parameters control per-message-deflate compression behavior. Obtained by calling get_compression_extensions on the Sec-WebSocket-Extensions header value.

pub type CompressionExtensions {
  CompressionExtensions(
    client_no_context_takeover: Bool,
    client_max_window_bits: option.Option(Int),
    server_no_context_takeover: Bool,
    server_max_window_bits: option.Option(Int),
  )
}

Constructors

  • CompressionExtensions(
      client_no_context_takeover: Bool,
      client_max_window_bits: option.Option(Int),
      server_no_context_takeover: Bool,
      server_max_window_bits: option.Option(Int),
    )

    Arguments

    client_no_context_takeover

    Client does not use context takeover

    client_max_window_bits

    Client’s maximum LZ77 window size for compression

    server_no_context_takeover

    Server does not use context takeover

    server_max_window_bits

    Server’s maximum LZ77 window size for compression

Context is the internal state of the WebSocket connection. It stores the remaining bytes from the decoding process, fragment accumulation and compression states.

pub opaque type Context

Control frames that can appear in WebSocket communication.

pub type Control {
  Ping(payload: BitArray)
  Pong(payload: BitArray)
  Close(reason: CloseReason)
}

Constructors

  • Ping(payload: BitArray)

    A ping control frame. Used for keepalive.

  • Pong(payload: BitArray)

    A pong control frame. Response to ping.

  • Close(reason: CloseReason)

    A close control frame. Contains the reason for closing if present.

Errors that can occur during the decoding process.

pub type DecodeError {
  InvalidFrame
  NotEnoughData(data: BitArray)
  FrameTooLarge(length: Int, limit: Int)
}

Constructors

  • InvalidFrame

    The frame is invalid.

  • NotEnoughData(data: BitArray)

    The data is not enough to decode the frame.

  • FrameTooLarge(length: Int, limit: Int)

    The frame declares a payload larger than the context’s max_frame_size.

The outcome of decoding one frame with next_frame.

pub type Decoded {
  MoreData(context: Context)
  Decoded(frame: Frame, context: Context)
}

Constructors

  • MoreData(context: Context)

    The buffer holds no further complete frame. Its leftover bytes are kept in the returned context, to be joined with the next read.

  • Decoded(frame: Frame, context: Context)

    A frame, with fragmentation reassembled and compression applied.

Each variant corresponds to a type of WebSocket frame, carrying the appropriate payload.

pub type Frame {
  Continuation(payload: BitArray)
  Text(payload: BitArray)
  Binary(payload: BitArray)
  Control(control: Control)
}

Constructors

  • Continuation(payload: BitArray)

    A continuation frame, used for fragmented messages.

  • Text(payload: BitArray)

    A text frame, containing UTF-8 encoded payload.

  • Binary(payload: BitArray)

    A binary frame, containing arbitrary binary data.

  • Control(control: Control)

    A control frame, controlling WebSocket connection.

Caps on how much data a peer can make this endpoint hold at once. Without them a peer can declare an arbitrarily large frame, or fragment a single message indefinitely, and exhaust memory.

pub type Limits {
  Limits(max_frame_size: Int, max_message_size: Int)
}

Constructors

  • Limits(max_frame_size: Int, max_message_size: Int)

    Arguments

    max_frame_size

    Largest payload a single frame may declare.

    max_message_size

    Largest payload a fragmented message may accumulate to.

Errors that can occur in next_frame. Each means the peer has broken the protocol and the connection should be closed.

pub type ProcessError {
  DecodeFailed(reason: DecodeError)
  ResolveFailed(reason: ResolveError)
}

Constructors

  • DecodeFailed(reason: DecodeError)

    Frame decoding failed with the given decode error.

  • ResolveFailed(reason: ResolveError)

    Frame resolution failed with the given resolve error.

Errors that can occur during the resolving fragments process.

pub type ResolveError {
  NotUtf8
  OrphanedContinuation
  FragmentationInterrupted
  ConcurrentFragmentation
  MessageTooLarge(size: Int, limit: Int)
  DecompressionFailed
}

Constructors

  • NotUtf8

    The complete text frame payload is not UTF-8.

  • OrphanedContinuation

    Receive continuation frame as the first frame in a fragmentation sequence.

  • FragmentationInterrupted

    Receive complete text/binary frame when resolving fragmented frame.

  • ConcurrentFragmentation

    Receive incomplete text/binary frame when resolving fragmented frame.

  • MessageTooLarge(size: Int, limit: Int)

    The fragments accumulated past the context’s max_message_size.

  • DecompressionFailed

    The payload is not a valid deflate stream, or inflating it would exceed the context’s max_message_size.

Specifies whether the endpoint is a client or server. This determines how compression parameters are interpreted.

pub type Role {
  Client
  Server
}

Constructors

  • Client

    Endpoint initiates connections

  • Server

    Endpoint accepts connections

Values

pub fn close_context(context: Context) -> Nil

Frees the compression resources. Should be called when the context is no longer needed.

Example

websocks.close_context(context)
// => Nil
pub fn compute_accept(key: String) -> String

Computes the value of the Sec-WebSocket-Accept header during the handshake. Requires Sec-WebSocket-Key header value to be present.

Example

websocks.compute_accept("dGhlIHNhbXBsZSBub25jZQ==")
// => "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
pub fn create_context(
  extensions: option.Option(CompressionExtensions),
  role: Role,
) -> Context

Creates a new context with optional compression settings and role specification. The role parameter determines how compression parameters are interpreted.

Example

let extensions = websocks.get_compression_extensions([
  "permessage-deflate",
  "client_no_context_takeover",
])

websocks.create_context(Some(extensions), websocks.Client)
// => Context
pub const default_limits: Limits

16 MiB per frame, 64 MiB per reassembled message. Use with_limits function to override the limits.

pub fn encode_binary_frame(
  payload payload: BitArray,
  context context: Context,
  masking masking: option.Option(BitArray),
) -> BitArray

Encodes a binary frame with the given payload, context and mask. If the context has compression enabled, the payload will be compressed.

pub fn encode_close_frame(
  reason reason: CloseReason,
  masking masking: option.Option(BitArray),
) -> BitArray

Encodes a close frame with the given reason and mask.

pub fn encode_ping_frame(
  payload payload: BitArray,
  masking masking: option.Option(BitArray),
) -> BitArray

Encodes a ping frame with the given payload and mask.

pub fn encode_pong_frame(
  payload payload: BitArray,
  masking masking: option.Option(BitArray),
) -> BitArray

Encodes a pong frame with the given payload and mask.

pub fn encode_text_frame(
  payload payload: BitArray,
  context context: Context,
  masking masking: option.Option(BitArray),
) -> BitArray

Encodes a text frame with the given payload, context and masking. If the context has compression enabled, the payload will be compressed.

pub fn get_compression_extensions(
  header: String,
) -> CompressionExtensions

Parses compression extension parameters out of a Sec-WebSocket-Extensions header value. Extracts context takeover settings as well as window bits.

Note that a header offering several alternatives is read as one set of parameters rather than as competing offers to choose between.

Example

let header =
  "permessage-deflate; client_no_context_takeover; client_max_window_bits=15"

websocks.get_compression_extensions(header)
// => CompressionExtensions(
//      client_no_context_takeover: True,
//      client_max_window_bits: Some(15),
//      server_no_context_takeover: False,
//      server_max_window_bits: None,
//    )
pub fn has_deflate(header: String) -> Bool

Checks whether a Sec-WebSocket-Extensions header value offers permessage-deflate.

Example

let header =
  request.get_header(req, "sec-websocket-extensions")
  |> result.unwrap("")
// => "permessage-deflate; client_no_context_takeover"

websocks.has_deflate(header)
// => True
pub const magic_string: String

Sequence of characters that is used to compute the Sec-WebSocket-Accept header during the handshake.

pub fn mask(payload: BitArray, mask: BitArray) -> BitArray

Masks the payload of any length using the provided mask. Masking is its own inverse, so this unmasks too. A mask key of no bytes leaves the payload alone.

Example

let payload = bit_array.from_string("Hello")
// Original: H(0x48) e(0x65) l(0x6c) l(0x6c) o(0x6f)
// Mask:     0x37     0xfa    0x21    0x3d    0x37
// Masked:   0x7f     0x9f    0x4d    0x51    0x58
websocks.mask(payload, <<0x37, 0xfa, 0x21, 0x3d>>)
// => <<0x7f, 0x9f, 0x4d, 0x51, 0x58>>
pub fn next_frame(
  context: Context,
) -> Result(Decoded, ProcessError)

Decodes the next frame from the context’s buffer, resolving fragmentation and decompression. Fragments are consumed internally, so MoreData always means the connection needs another read rather than another call.

Example

// 0x81 : fin=1, rsv1-3=0, opcode=1
// 0x05 : mask=0, payload length=5
let frame = <<0x81, 0x05>>
// 0x48 0x65 0x6c 0x6c 0x6f : "Hello"
let payload = <<0x48, 0x65, 0x6c, 0x6c, 0x6f>>

let context =
  websocks.create_context(None, websocks.Client)
  |> websocks.push_data(<<frame:bits, payload:bits, frame:bits>>)

let assert Ok(websocks.Decoded(frame:, context:)) =
  websocks.next_frame(context)
// frame => Text(<<"Hello">>)

// The trailing header alone is not a whole frame, so it stays buffered.
websocks.next_frame(context)
// => Ok(MoreData(context))
pub fn push_data(context: Context, data: BitArray) -> Context

Adds a read to the context’s buffer, ready to be drained by next_frame.

pub fn websocket_key() -> String

Generates a random WebSocket key for the Sec-WebSocket-Key header. Used by clients during the handshake.

Example

websocks.websocket_key()
// => "dGhlIHNhbXBsZSBub25jZQ=="
pub fn with_limits(context: Context, limits: Limits) -> Context

Replaces the context’s buffering limits. Call before any frames are processed.

Example

websocks.create_context(None, websocks.Server)
|> websocks.with_limits(websocks.Limits(
  max_frame_size: 65_536,
  max_message_size: 1_048_576,
))
Search Document