silk.http.websocket.

WebSocket frames (RFC 6455) for clients and servers, without I/O.

When to use#

Use Session when you move the bytes yourself, for example in an event loop: give it the bytes that you read with Session.receive, and write the bytes that it appends to your output buffer. For a session over a byte duplex that does the reads and writes, use silk.http.websocket_stream. Decoder and appendFrame are the lower layer, for programs that handle the protocol rules themselves.

Details#

Both roles use the same code. The only difference is the masking direction: a client masks every frame with a new 4-byte key, and a server masks none. A Session or Decoder of the server role fails on an unmasked frame, and one of the client role fails on a masked frame.

Session.receive reads frames until one event is complete:

  • Text and Binary give a complete message. Fragmented messages are joined, and Text is checked as UTF-8 as it arrives. Get the bytes with Session.message.

  • Ping was answered: the Pong is in your output buffer.

  • Pong gives the payload of a Pong.

  • Close means that the peer sent Close. If you had not sent Close, the answer Close is in your output buffer. The session is then Closed: write the output, then close the connection.

When a peer frame breaks a rule, Session.receive appends a Close with the code of the error (1002, 1007 or 1009, see closeCodeOf) to your output, the session becomes Failed, and the call fails. Write the output, then close the connection.

Errors#

WebSocketError tells which rule, limit or state check failed. OutOfMemoryError if a message buffer cannot grow.

Gotchas#

  • Extensions such as permessage-deflate are not supported. A frame with a reserved bit fails.

  • A message is joined in memory, up to maxMessageBytes. The buffer grows as frames arrive.

  • The mask keys of a client come from a generator seeded by Session.make. Give it a seed from silk.system.random.

Examples#

Answer a message from a client#

silk
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.collections.bytes { Bytes }
import silk.core.effect { Effect }
import silk.core.option { Option }
import silk.core.result { Result }
import silk.http.websocket { Event, Limits, Role, Session, WebSocketError, appendFrame, Opcode }

effect fn program() -> i32 ! WebSocketError | OutOfMemoryError ? &mut Allocator {
  // A masked Text frame "Hi", as a client sends it.
  let mut wire = Bytes.make()
  run appendFrame(&mut wire, Opcode.Text, true, b"Hi", Option<[u8; 4]>.some([1, 2, 3, 4]))

  let mut session = match move Session.make(Role.Server, Limits.defaults(), 0) {
    Result.Success {value} => move value
    Result.Failure {error} => { fail move error }
  }
  let mut output = Bytes.make()
  let received = run session.receive(Bytes.asSlice(&wire), &mut output)
  if received.consumed != Bytes.length(&wire) { return 1 }
  match received.event {
    Option.Some {value: Event.Text {length}} => {
      if length != 2 || session.message()[1] != 105 { return 2 }
    }
    _ => { return 3 }
  }
  // The reply is an unmasked Text frame in `output`.
  run session.sendText("Hello", &mut output)
  if Bytes.length(&output) != 7 { return 4 }
  return 0
}

effect fn failed<E>(error: E) -> i32 {
  drop error
  return 1
}

pub fn main() -> i32 {
  let mut allocator = Allocator.systemAllocatorProvider()
  return run Effect.catchAll(program(), failed)
    |> Effect.provideMut<Allocator>(&mut allocator)
}

Import as WebSocketCodec with import silk.http.websocket as WebSocketCodec.

Public declarations: 19.

Role#

silk
pub enum Role

The side of a connection.

Client#

silk
Client

The side that opened the connection. It masks its frames.

Server#

silk
Server

The side that accepted the connection. It does not mask its frames.

Implementation Debug for Role#

silk
impl Debug for Role

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as Client.

Opcode#

silk
pub enum Opcode

The kind of a frame.

Continuation#

silk
Continuation

A later fragment of a message (0).

Text#

silk
Text

The first frame of a Text message (1).

Binary#

silk
Binary

The first frame of a Binary message (2).

Close#

silk
Close

A Close (8).

Ping#

silk
Ping

A Ping (9).

Pong#

silk
Pong

A Pong (10).

Implementation Debug for Opcode#

silk
impl Debug for Opcode

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as Text.

Limits#

silk
pub struct Limits

The limits of a session or decoder.

Field maxMessageBytes#

silk
pub maxMessageBytes: usize

The maximum length of one message, all fragments together. It is more than zero.

Implementation Copy for Limits#

silk
impl Copy for Limits

Implementation Limits#

silk
impl Limits

Associated function defaults#

silk
pub fn defaults() -> Limits

Returns the default limits: messages of at most 1048576 bytes.

ProtocolReason#

silk
pub enum ProtocolReason

The frame rule that a peer broke. The Close for it has code 1002.

MaskRequired#

silk
MaskRequired

A client frame is not masked.

MaskForbidden#

silk
MaskForbidden

A server frame is masked.

Rsv#

silk
Rsv

A reserved bit is set.

ReservedOpcode#

silk
ReservedOpcode

The opcode is reserved.

NonMinimalLength#

silk
NonMinimalLength

The length uses more bytes than necessary.

LengthHighBit#

silk
LengthHighBit

The highest bit of a 64-bit length is set.

ControlTooLarge#

silk
ControlTooLarge

A control frame has more than 125 bytes.

ControlFragmented#

silk
ControlFragmented

A control frame is not final.

UnexpectedContinuation#

silk
UnexpectedContinuation

A Continuation frame came with no message to continue.

ExpectedContinuation#

silk
ExpectedContinuation

A Text or Binary frame came before the last fragment of the previous message.

ClosePayloadLength#

silk
ClosePayloadLength

A Close frame has a payload of exactly one byte.

CloseCode#

silk
CloseCode

A Close frame has a code that is not allowed.

FrameAfterClose#

silk
FrameAfterClose

A frame came after the Close of the peer.

Implementation Debug for ProtocolReason#

silk
impl Debug for ProtocolReason

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as MaskRequired.

Utf8Component#

silk
pub enum Utf8Component

The text that is not valid UTF-8. The Close for it has code 1007.

Text#

silk
Text

A Text message.

CloseReason#

silk
CloseReason

The reason of a Close frame.

Implementation Debug for Utf8Component#

silk
impl Debug for Utf8Component

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as Text.

CloseDataReason#

silk
pub enum CloseDataReason

The rule of CloseData.make that failed.

Code#

silk
Code

The code is not allowed in a Close that you send.

ReasonTooLarge#

silk
ReasonTooLarge

The reason has more than 123 bytes.

Implementation Debug for CloseDataReason#

silk
impl Debug for CloseDataReason

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as Code.

State#

silk
pub enum State

The state of a Session.

Open#

silk
Open

Messages can be sent and received.

CloseSent#

silk
CloseSent

This side sent Close and waits for the Close of the peer. Frames are still received.

Closed#

silk
Closed

Both sides sent Close. Write the output that is left, then close the connection.

Failed#

silk
Failed

A rule or limit check failed. Write the output that is left, then close the connection.

Implementation Debug for State#

silk
impl Debug for State

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as Open.

WebSocketError#

silk
pub union WebSocketError

The error of a WebSocket operation.

Details#

closeCodeOf gives the Close code that a peer error gets: 1002 for ProtocolError, 1007 for InvalidUtf8, and 1009 for MessageTooLarge.

InvalidLimits#

silk
InvalidLimits

maxMessageBytes is zero.

InvalidState#

silk
InvalidState

The operation is not allowed in the state of the session.

Field state#

silk
pub state: State

The state.

PreviousFailure#

silk
PreviousFailure

An earlier error ended the session or decoder.

MessageTooLarge#

silk
MessageTooLarge

A message is longer than maxMessageBytes.

Field length#

silk
pub length: u64

The length of the message so far, with the frame that broke the limit.

Field allowed#

silk
pub allowed: usize

The value of maxMessageBytes.

ProtocolError#

silk
ProtocolError

A peer frame broke a rule of RFC 6455.

Field reason#

silk
pub reason: ProtocolReason

The rule.

InvalidCloseData#

silk
InvalidCloseData

The Close data that you gave is not valid.

Field reason#

silk
pub reason: CloseDataReason

The rule.

InvalidUtf8#

silk
InvalidUtf8

Text is not valid UTF-8.

Field component#

silk
pub component: Utf8Component

The text that failed.

ControlPayloadTooLarge#

silk
ControlPayloadTooLarge

A Ping or Pong payload that you gave has more than 125 bytes.

Field length#

silk
pub length: usize

The length of the payload.

FragmentOrder#

silk
FragmentOrder

A fragment does not continue the message that you are sending, or a message starts before the last fragment of the previous one.

Implementation Copy for WebSocketError#

silk
impl Copy for WebSocketError

Implementation Debug for WebSocketError#

silk
impl Debug for WebSocketError

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the variant and its fields, such as MessageTooLarge { length: 70000, allowed: 65536 }.

closeCodeOf#

silk
pub fn closeCodeOf(error: &WebSocketError) -> u16

Returns the Close code for a peer error: 1002 for ProtocolError, 1007 for InvalidUtf8, 1009 for MessageTooLarge, and 1011 for any other error.

ControlPayload#

silk
pub struct ControlPayload

The payload of a Ping or Pong, at most 125 bytes.

Implementation Copy for ControlPayload#

silk
impl Copy for ControlPayload

Implementation ControlPayload#

silk
impl ControlPayload

Associated function make#

silk
pub fn make(bytes: &[u8]) -> Result<ControlPayload, WebSocketError>

Returns a copy of bytes as a payload.

Errors#

ControlPayloadTooLarge if bytes has more than 125 bytes.

Method asSlice#

silk
pub fn asSlice<'payload>(self: &'payload Self) -> &'payload [u8]

Returns the payload bytes.

CloseReason#

silk
pub struct CloseReason

The reason of a Close frame: valid UTF-8, at most 123 bytes.

Implementation Copy for CloseReason#

silk
impl Copy for CloseReason

Implementation CloseReason#

silk
impl CloseReason

Method asSlice#

silk
pub fn asSlice<'reason>(self: &'reason Self) -> &'reason [u8]

Returns the reason bytes. They are valid UTF-8.

CloseData#

silk
pub struct CloseData

The code and reason of a Close frame that you send. Make it with CloseData.make.

Implementation Copy for CloseData#

silk
impl Copy for CloseData

Implementation CloseData#

silk
impl CloseData

Associated function make#

silk
pub fn make(code: u16, reason: string) -> Result<CloseData, WebSocketError>

Returns Close data with code and a copy of reason.

Errors#

  • InvalidCloseData with Code if code cannot be sent. Allowed codes are 1000 through 1003, 1007 through 1009, 1011, and 3000 through 4999. A received Close can also carry 1010 and 1012 through 1014.

  • InvalidCloseData with ReasonTooLarge if reason has more than 123 bytes.

Method code#

silk
pub fn code(self: &Self) -> u16

Returns the code.

Method reason#

silk
pub fn reason<'data>(self: &'data Self) -> &'data CloseReason

Returns the reason.

PeerCloseData#

silk
pub union PeerCloseData

The code and reason in the Close of the peer.

Absent#

silk
Absent

The Close had no payload. RFC 6455 reports this as code 1005.

Present#

silk
Present

The Close had a code and a reason.

Field code#

silk
pub code: u16

The code.

Field reason#

silk
pub reason: CloseReason

The reason. It can be empty.

Implementation Copy for PeerCloseData#

silk
impl Copy for PeerCloseData

Event#

silk
pub union Event

One complete event from the peer.

Text#

silk
Text

A Text message of length bytes, valid UTF-8. Get it with Session.message or Decoder.message before the next call that receives.

Field length#

silk
pub length: usize

The length of the message.

Binary#

silk
Binary

A Binary message of length bytes. Get it with Session.message or Decoder.message before the next call that receives.

Field length#

silk
pub length: usize

The length of the message.

Ping#

silk
Ping

A Ping. A Session already put the Pong in the output.

Field payload#

silk
pub payload: ControlPayload

The payload.

Pong#

silk
Pong

A Pong.

Field payload#

silk
pub payload: ControlPayload

The payload.

Close#

silk
Close

The Close of the peer.

Field data#

silk
pub data: PeerCloseData

The code and reason of the peer.

Implementation Copy for Event#

silk
impl Copy for Event

Received#

silk
pub struct Received

The result of one receive call.

Field consumed#

silk
pub consumed: usize

How many input bytes were used. Give the rest again in the next call.

Field event#

silk
pub event: Option<Event>

The event that is complete, if any. With None, all input was used and more is needed.

Implementation Copy for Received#

silk
impl Copy for Received

Decoder#

silk
pub struct Decoder

Splits bytes into frames and checks them, for one role. It does not answer Pings or Closes: Session does that.

Details#

Decoder.feed keeps a partial frame between calls. Fragments of a message are joined in an internal buffer, up to maxMessageBytes. Control frames can come between fragments.

Implementation Decoder#

silk
impl Decoder

Associated function make#

silk
pub fn make(side: Role, limits: Limits) -> Result<Decoder, WebSocketError>

Returns a decoder for frames that a peer of role sends to you. A server decodes with Role.Client frames: give the role of this side, and it checks the masking of the peer.

Errors#

InvalidLimits if maxMessageBytes is zero.

Method message#

silk
pub fn message<'decoder>(self: &'decoder Self) -> &'decoder [u8]

Returns the last complete Text or Binary message. It is empty before the first one, and it is cleared by the next Decoder.feed.

Method feed#

silk
pub effect fn feed(self: &mut Self, input: &[u8]) -> Received
  ! WebSocketError | OutOfMemoryError
  ? &mut Allocator

Reads frames from input until one event is complete or the input is used.

Details#

The message of the previous Text or Binary event is cleared first. After a Close event, any later frame fails with FrameAfterClose.

Errors#

ProtocolError, MessageTooLarge or InvalidUtf8 for a bad frame, and PreviousFailure after one of them. OutOfMemoryError if the message buffer cannot grow.

appendFrame#

silk
pub effect fn appendFrame(
  output: &mut Bytes,
  opcode: Opcode,
  fin: bool,
  payload: &[u8],
  mask: Option<[u8; 4]>,
) -> () ! OutOfMemoryError ? &mut Allocator

Appends one frame to output.

Details#

With a mask key, the payload is masked with it, as a client must do. Without one, the frame is not masked, as a server must do. fin marks the last frame of a message. This function does not check the order of fragments or the length of control payloads: Session does.

Errors#

OutOfMemoryError if output cannot grow.

MessageKind#

silk
pub enum MessageKind

The kind of a message that you send in fragments with Session.sendFragment.

Text#

silk
Text

A Text message. Each fragment must be valid UTF-8 on its own, so that the message is too.

Binary#

silk
Binary

A Binary message.

Session#

silk
pub struct Session

A WebSocket session without I/O: it receives bytes, and appends the frames to send to an output buffer.

Details#

Session.receive answers a Ping with a Pong and a Close with a Close, in the output. The send functions append one frame each. Write the output to the connection in the order it was appended. When the state is Closed or Failed, write the output that is left and close the connection.

Implementation Session#

silk
impl Session

Associated function make#

silk
pub fn make(side: Role, limits: Limits, seed: u64) -> Result<Session, WebSocketError>

Returns an open session for role. seed seeds the mask keys of a client: give it a value from silk.system.random. A server ignores it.

Errors#

InvalidLimits if maxMessageBytes is zero.

Method side#

silk
pub fn side(self: &Self) -> Role

Returns the role.

Method state#

silk
pub fn state(self: &Self) -> State

Returns the state.

Method advance#

silk
pub fn advance(self: &mut Self, state: State) -> ()

Moves the state forward to state, for two sessions that share one connection, such as the receive and send sides of a split session.

Details#

Failed and Closed are final. CloseSent replaces only Open. Open changes nothing.

Method message#

silk
pub fn message<'session>(self: &'session Self) -> &'session [u8]

Returns the last complete Text or Binary message. It is valid until the next Session.receive.

Method messageText#

silk
pub fn messageText<'session>(self: &'session Self) -> string<'session>

Returns the last complete Text message as text. It is empty if the last message was Binary.

Method receive#

silk
pub effect fn receive(self: &mut Self, input: &[u8], output: &mut Bytes) -> Received
  ! WebSocketError | OutOfMemoryError
  ? &mut Allocator

Reads frames from input until one event is complete or the input is used, and appends the frames that answer the peer to output.

Details#

  • A Ping is answered with a Pong, unless this side already sent Close.

  • A Close is answered with a Close that has the same code, unless this side already sent Close. The state becomes Closed.

  • A peer error appends a Close with closeCodeOf the error, unless this side already sent Close. The state becomes Failed.

Errors#

  • InvalidState if the state is Closed.

  • ProtocolError, MessageTooLarge or InvalidUtf8 for a bad frame, PreviousFailure if the state is Failed.

  • OutOfMemoryError if a buffer cannot grow.

Method sendText#

silk
pub effect fn sendText(self: &mut Self, text: string, output: &mut Bytes) -> ()
  ! WebSocketError | OutOfMemoryError
  ? &mut Allocator

Appends text as one Text frame.

Errors#

InvalidState if the state is not Open, FragmentOrder while a fragmented message is being sent. OutOfMemoryError if output cannot grow.

Method sendBinary#

silk
pub effect fn sendBinary(self: &mut Self, bytes: &[u8], output: &mut Bytes) -> ()
  ! WebSocketError | OutOfMemoryError
  ? &mut Allocator

Appends bytes as one Binary frame.

Errors#

InvalidState if the state is not Open, FragmentOrder while a fragmented message is being sent. OutOfMemoryError if output cannot grow.

Method sendFragment#

silk
pub effect fn sendFragment(
    self: &mut Self,
    kind: MessageKind,
    payload: &[u8],
    fin: bool,
    output: &mut Bytes,
  ) -> ()
  ! WebSocketError | OutOfMemoryError
  ? &mut Allocator

Appends one fragment of a message. kind gives the kind of the message, and is checked only on the first fragment. fin marks the last fragment.

Details#

Control frames (Session.sendPing, Session.sendClose) can go between fragments. A Text fragment must be complete UTF-8, because it is text.

Errors#

InvalidState if the state is not Open. OutOfMemoryError if output cannot grow.

Method sendPing#

silk
pub effect fn sendPing(self: &mut Self, payload: &[u8], output: &mut Bytes) -> ()
  ! WebSocketError | OutOfMemoryError
  ? &mut Allocator

Appends a Ping with payload.

Errors#

InvalidState if the state is not Open, ControlPayloadTooLarge if payload has more than 125 bytes. OutOfMemoryError if output cannot grow.

Method sendPong#

silk
pub effect fn sendPong(self: &mut Self, payload: &[u8], output: &mut Bytes) -> ()
  ! WebSocketError | OutOfMemoryError
  ? &mut Allocator

Appends a Pong with payload, for example as a heartbeat. A Ping from the peer is answered by Session.receive, so you do not need this for that.

Errors#

InvalidState if the state is not Open, ControlPayloadTooLarge if payload has more than 125 bytes. OutOfMemoryError if output cannot grow.

Method sendClose#

silk
pub effect fn sendClose(self: &mut Self, data: Option<CloseData>, output: &mut Bytes) -> ()
  ! WebSocketError | OutOfMemoryError
  ? &mut Allocator

Appends a Close. The state becomes CloseSent, and then Closed when the Close of the peer arrives.

Details#

data gives the code and reason. With None, the Close has no payload. If the state is already CloseSent or Closed, it does nothing.

Errors#

PreviousFailure if the state is Failed. OutOfMemoryError if output cannot grow.

Category: HTTP.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2, x86_64-pc-windows-msvc, wasm64-unknown-unknown.

View the source of this page