silk.http.websocket_stream.

WebSocket sessions over a byte duplex, for clients and servers, with a split form for fibers.

When to use#

After an opening handshake, make a WebSocket around the connection with WebSocket.make. Then call WebSocket.readEvent in a loop, and answer with WebSocket.writeText or WebSocket.writeBinary. silk.http.websocket_client and silk.http.websocket_upgrade do the handshake and make the session for you.

When one fiber must wait for messages while other fibers send, split the session with fromHalves. The ReadHalf reads and answers Pings and Closes, and each WriteHalf (clone it for each fiber) sends. Writes from several fibers are sent one at a time, never mixed.

Details#

A session reads into its own buffer, and keeps the bytes that came after a frame for the next call. Each readEvent returns one complete event:

  • Text and Binary: the message is in WebSocket.message until the next readEvent.

  • Ping: the Pong was already sent.

  • Pong: the payload of a Pong.

  • Close: the peer sent Close. The answer Close was already sent, and the session is Closed. Close the connection.

A frame that breaks a rule fails the call with Protocol. Before that, the session sends a Close with the code of the error. Every later call fails.

Errors#

StreamError keeps the frame error or the transport error. OutOfMemoryError if a buffer cannot grow.

Gotchas#

  • A deadline applies to one call. A read that passes its deadline fails the session, as any transport error does.

  • The session does not close the transport. Close it when the session is Closed or failed.

Examples#

Answer a message over a memory duplex#

silk
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.collections.bytes { Bytes }
import silk.collections.list { List }
import silk.core.effect { Effect }
import silk.core.option { Option }
import silk.io.memory_byte_duplex { MemoryByteDuplex, MemoryReadEvent, MemoryWriteAction, MemoryWriteEvent }
import silk.system.monotonic_clock { MonotonicClock }
import silk.system.monotonic_clock_native { OsMonotonicClock }
import silk.system.random { Random }
import silk.system.random_native { OsRandom }
import silk.system.system_clock { Instant, SystemClock }
import silk.http.transport as Transport
import silk.http.websocket { Event, Opcode, Role, appendFrame }
import silk.http.websocket_stream { Options, StreamError, WebSocket }

effect fn program() -> i32
  ! StreamError | OutOfMemoryError
  ? &mut Allocator | &mut MonotonicClock | &mut Random {
  // The client sends one masked Text frame, and the server accepts all writes.
  let mut wire = Bytes.make()
  run appendFrame(&mut wire, Opcode.Text, true, b"ping", Option<[u8; 4]>.some([9, 8, 7, 6]))
  let mut reads = List<MemoryReadEvent>.make()
  run reads.append(MemoryReadEvent.Data {readyAt: SystemClock.make(0, 0), bytes: move wire})
  let mut writes = List<MemoryWriteEvent>.make()
  run writes.append(MemoryWriteEvent {
    readyAt: SystemClock.make(0, 0),
    action: MemoryWriteAction.Accept {count: 64},
  })
  let mut memory = run MemoryByteDuplex.make(move reads, move writes, 64, 8, Option<i32>.none())

  let mut socket = run WebSocket.make(Transport.plain(&mut memory), Role.Server, Options.defaults(), b"")
  match run socket.readEvent(Option<Instant>.none()) {
    Event.Text {..} => {
      if socket.messageText() != "ping" { return 1 }
    }
    _ => { return 2 }
  }
  run socket.writeText("pong", Option<Instant>.none())
  drop socket
  // An unmasked Text frame: 0x81, length 4, then "pong".
  if memory.outbound() != b"\x81\x04pong" { return 3 }
  return 0
}

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

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

Import as WebSocket with import silk.http.websocket_stream { WebSocket }.

Public declarations: 10.

StreamError#

silk
pub union StreamError

The error of a session over a byte duplex.

Protocol#

silk
Protocol

A frame or state rule failed. See WebSocketError of silk.http.websocket.

Field error#

silk
pub error: WebSocketError

The rule that failed.

Transport#

silk
Transport

The transport failed, or a deadline passed.

Field error#

silk
pub error: TransportError

The transport error.

Truncated#

silk
Truncated

The transport ended before the Close of the peer.

PreviousFailure#

silk
PreviousFailure

An earlier transport error or truncation ended the session.

Implementation Debug for StreamError#

silk
impl Debug for StreamError

Operation debug#

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

Writes the variant and its fields, such as Io { error: ... }.

Options#

silk
pub struct Options

The options of a session.

Field limits#

silk
pub limits: Limits

The frame and message limits.

Field readCapacity#

silk
pub readCapacity: usize

The size of one read from the transport. It is more than zero.

Implementation Copy for Options#

silk
impl Copy for Options

Implementation Options#

silk
impl Options

Associated function defaults#

silk
pub fn defaults() -> Options

Returns the default options: the default limits and reads of 16384 bytes.

WebSocket#

silk
pub struct WebSocket<P>

A WebSocket session that owns its transport P.

Details#

Make it with WebSocket.make after the opening handshake. P must provide HttpTransport, as NativeTransport of silk.http.exchange_native does for a socket or a TLS connection.

Implementation WebSocket<P>#

silk
impl<P> WebSocket<P>

Associated function make#

silk
pub effect fn make(
    transport: P,
    side: Role,
    options: Options,
    buffered: &[u8],
  ) -> WebSocket<P> ! StreamError | OutOfMemoryError ? &mut Allocator | &mut Random

Returns an open session for role over transport.

Details#

buffered holds bytes that were read with the handshake and come before any later read. A client draws the seed of its mask keys from Random.

Errors#

Protocol with InvalidLimits if maxMessageBytes or readCapacity is zero. OutOfMemoryError if the buffers cannot be allocated.

Method state#

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

Returns the state of the session.

Method message#

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

Returns the last Text or Binary message. It is valid until the next readEvent.

Method messageText#

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

Returns the last Text message as text. It is empty after a Binary message.

Method transport#

silk
pub fn transport<'socket>(self: &'socket mut Self) -> &'socket mut P

Returns the transport, to close it or to look at it.

Method readEvent#

silk
pub effect fn readEvent(self: &mut Self, deadline: Option<Instant>) -> Event
  ! StreamError | OutOfMemoryError
  ? &mut Allocator | &mut MonotonicClock | &mut Random
  where
    &mut P provides &HttpTransport from &mut HttpTransport | &mut MonotonicClock | &mut Allocator | &mut Random

Reads frames until one event is complete, and returns it.

Details#

A Ping is answered and a Close is answered before the call returns. The message of a Text or Binary event is in WebSocket.message.

Errors#

  • Protocol for a frame that breaks a rule, or when the session is Closed.

  • Io if the transport fails or deadline passes, Truncated if it ends before a Close.

  • OutOfMemoryError if a buffer cannot grow.

Method writeText#

silk
pub effect fn writeText(self: &mut Self, text: string, deadline: Option<Instant>) -> ()
  ! StreamError | OutOfMemoryError
  ? &mut Allocator | &mut MonotonicClock | &mut Random
  where
    &mut P provides &HttpTransport from &mut HttpTransport | &mut MonotonicClock | &mut Allocator | &mut Random

Sends text as one Text frame, and flushes it.

Errors#

Protocol if the session is not open, Io if the transport fails or deadline passes. OutOfMemoryError if a buffer cannot grow.

Method writeBinary#

silk
pub effect fn writeBinary(self: &mut Self, bytes: &[u8], deadline: Option<Instant>) -> ()
  ! StreamError | OutOfMemoryError
  ? &mut Allocator | &mut MonotonicClock | &mut Random
  where
    &mut P provides &HttpTransport from &mut HttpTransport | &mut MonotonicClock | &mut Allocator | &mut Random

Sends bytes as one Binary frame, and flushes it.

Errors#

Protocol if the session is not open, Io if the transport fails or deadline passes. OutOfMemoryError if a buffer cannot grow.

Method writeFragment#

silk
pub effect fn writeFragment(
    self: &mut Self,
    kind: MessageKind,
    payload: &[u8],
    fin: bool,
    deadline: Option<Instant>,
  ) -> ()
  ! StreamError | OutOfMemoryError
  ? &mut Allocator | &mut MonotonicClock | &mut Random
  where
    &mut P provides &HttpTransport from &mut HttpTransport | &mut MonotonicClock | &mut Allocator | &mut Random

Sends one fragment of a message, and flushes it. kind counts only for the first fragment, and fin marks the last one.

Errors#

Protocol if the session is not open, Io if the transport fails or deadline passes. OutOfMemoryError if a buffer cannot grow.

Method writePing#

silk
pub effect fn writePing(self: &mut Self, payload: &[u8], deadline: Option<Instant>) -> ()
  ! StreamError | OutOfMemoryError
  ? &mut Allocator | &mut MonotonicClock | &mut Random
  where
    &mut P provides &HttpTransport from &mut HttpTransport | &mut MonotonicClock | &mut Allocator | &mut Random

Sends a Ping with payload (at most 125 bytes), and flushes it.

Errors#

Protocol if the session is not open or the payload is too large, Io if the transport fails or deadline passes. OutOfMemoryError if a buffer cannot grow.

Method sendClose#

silk
pub effect fn sendClose(self: &mut Self, data: Option<CloseData>, deadline: Option<Instant>) -> ()
  ! StreamError | OutOfMemoryError
  ? &mut Allocator | &mut MonotonicClock | &mut Random
  where
    &mut P provides &HttpTransport from &mut HttpTransport | &mut MonotonicClock | &mut Allocator | &mut Random

Sends a Close, and flushes it. Then read with readEvent until the Close event of the peer.

Details#

data gives the code and reason. With None, the Close has no payload. It does nothing if this side already sent Close.

Errors#

Protocol if the session failed, Io if the transport fails or deadline passes. OutOfMemoryError if a buffer cannot grow.

TransportHalves#

silk
pub struct TransportHalves<R, W>

The two halves of a transport, for fromHalves.

Field reader#

silk
pub reader: R

The half that reads.

Field writer#

silk
pub writer: W

The half that writes.

Parts#

silk
pub struct Parts

The state of a session without its transport, from WebSocket.detach. Give it to fromHalves.

Detached#

silk
pub struct Detached<P>

A session and its transport, taken apart by WebSocket.detach.

Field transport#

silk
pub transport: P

The transport.

Field parts#

silk
pub parts: Parts

The rest of the session.

Implementation WebSocket<P>#

silk
impl<P> WebSocket<P>

Method detach#

silk
pub fn detach(self: Self) -> Detached<P>

Takes the session apart, to split its transport and then call fromHalves.

Split#

silk
pub struct Split<R, W>

A split session: the ReadHalf for one fiber, and a WriteHalf to clone for each fiber that sends.

Field reader#

silk
pub reader: ReadHalf<R, W>

The half that reads.

Field writer#

silk
pub writer: WriteHalf<W>

The half that sends.

ReadHalf#

silk
pub struct ReadHalf<R, W>

The receiving half of a split session. One fiber reads with it.

Details#

ReadHalf.readEvent answers a Ping with a Pong and a Close with a Close. It sends the answer at once when no WriteHalf is sending, or else the writer that sends sends it next.

WriteHalf#

silk
pub struct WriteHalf<W>

A sending handle of a split session. Clone it with WriteHalf.clone for each fiber that sends.

Details#

Each send waits until no other writer is sending, so frames are never mixed. A Close from the peer, or a failure of the reader, makes later sends fail.

fromHalves#

silk
pub effect fn fromHalves<R, W>(halves: TransportHalves<R, W>, parts: Parts) -> Split<R, W>
! OutOfMemoryError
? &mut Allocator

Makes a split session from the halves of a transport and the Parts of a session.

When to use#

Call it with the halves of the transport that WebSocket.detach gave you, for example from Connection.split of silk.net.socket_native.

Errors#

OutOfMemoryError if the shared state cannot be allocated.

Implementation ReadHalf<R, W>#

silk
impl<R, W> ReadHalf<R, W>

Method state#

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

Returns the state of the receive side.

Method message#

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

Returns the last Text or Binary message. It is valid until the next readEvent.

Method messageText#

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

Returns the last Text message as text. It is empty after a Binary message.

Method readEvent#

silk
pub effect fn readEvent(self: &mut Self, deadline: Option<Instant>) -> Event
  ! StreamError | OutOfMemoryError
  ? &mut Allocator | &mut MonotonicClock | &mut Random
  where
    &mut R provides &HttpTransport from &mut HttpTransport | &mut MonotonicClock | &mut Allocator | &mut Random,
    &mut W provides &HttpTransport from &mut HttpTransport | &mut MonotonicClock | &mut Allocator | &mut Random

Reads frames until one event is complete, and returns it.

Details#

A Ping is answered and a Close is answered: at once if no writer is sending, else right after the frame that is being sent. The message of a Text or Binary event is in ReadHalf.message. deadline applies to the reads and to the answers this call sends.

Errors#

  • Protocol for a frame that breaks a rule, or when the session is Closed.

  • Io if the transport fails or deadline passes, Truncated if it ends before a Close.

  • OutOfMemoryError if a buffer cannot grow.

Implementation WriteHalf<W>#

silk
impl<W> WriteHalf<W>

Method clone#

silk
pub effect fn clone(self: &Self) -> WriteHalf<W> ! OutOfMemoryError ? &mut Allocator

Returns another handle to the same session, for another fiber.

Errors#

OutOfMemoryError if the handle cannot be allocated.

Method state#

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

Returns the state of the send side.

Method writeText#

silk
pub effect fn writeText(self: &Self, text: string, deadline: Option<Instant>) -> ()
  ! StreamError | OutOfMemoryError
  ? &mut Allocator | &mut MonotonicClock | &mut Random
  where
    &mut W provides &HttpTransport from &mut HttpTransport | &mut MonotonicClock | &mut Allocator | &mut Random

Sends text as one Text frame, and flushes it. It waits while another writer sends.

Errors#

Protocol if the session is not open, Io if the transport fails or deadline passes. OutOfMemoryError if a buffer cannot grow.

Method writeBinary#

silk
pub effect fn writeBinary(self: &Self, bytes: &[u8], deadline: Option<Instant>) -> ()
  ! StreamError | OutOfMemoryError
  ? &mut Allocator | &mut MonotonicClock | &mut Random
  where
    &mut W provides &HttpTransport from &mut HttpTransport | &mut MonotonicClock | &mut Allocator | &mut Random

Sends bytes as one Binary frame, and flushes it. It waits while another writer sends.

Errors#

Protocol if the session is not open, Io if the transport fails or deadline passes. OutOfMemoryError if a buffer cannot grow.

Method writePing#

silk
pub effect fn writePing(self: &Self, payload: &[u8], deadline: Option<Instant>) -> ()
  ! StreamError | OutOfMemoryError
  ? &mut Allocator | &mut MonotonicClock | &mut Random
  where
    &mut W provides &HttpTransport from &mut HttpTransport | &mut MonotonicClock | &mut Allocator | &mut Random

Sends a Ping with payload (at most 125 bytes), and flushes it.

Errors#

Protocol if the session is not open or the payload is too large, Io if the transport fails or deadline passes. OutOfMemoryError if a buffer cannot grow.

Method sendClose#

silk
pub effect fn sendClose(self: &Self, data: Option<CloseData>, deadline: Option<Instant>) -> ()
  ! StreamError | OutOfMemoryError
  ? &mut Allocator | &mut MonotonicClock | &mut Random
  where
    &mut W provides &HttpTransport from &mut HttpTransport | &mut MonotonicClock | &mut Allocator | &mut Random

Sends a Close, and flushes it. The reader then reads until the Close event of the peer.

Details#

data gives the code and reason. With None, the Close has no payload. It does nothing if this side already sent Close.

Errors#

Protocol if the session failed, Io if the transport fails or deadline passes. OutOfMemoryError if a buffer 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