silk.http.websocket_client.

The client side of the WebSocket opening handshake (RFC 6455, version 13).

When to use#

Call handshake on a connection from silk.http.exchange to open a WebSocket session for a ws or wss URI. connect of silk.http.websocket_native opens the connection for you. Then use the WebSocket of silk.http.websocket_stream, or split it for fibers.

Details#

handshake sends GET with Upgrade: websocket, a fresh Sec-WebSocket-Key from Random, version 13, and the subprotocols you offer. It accepts only a 101 response with Upgrade: websocket, Connection: upgrade, and the right Sec-WebSocket-Accept. Bytes that the server sent after the 101 head go to the session.

Errors#

  • HandshakeError if the server answered with another status or a wrong 101 response.

  • ExchangeError, RequestError, and StreamError from the request and the session.

Gotchas#

  • The connection is used up: after an error it is closed.

  • Extensions such as permessage-deflate are not offered, so a response with one fails.

Examples#

Open a session over a scripted transport#

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.core.result { Result }
import silk.io.byte_duplex { ByteDuplex, ReadTransfer }
import silk.io.memory_byte_duplex { MemoryByteDuplex, MemoryReadEvent, MemoryWriteAction, MemoryWriteEvent }
import silk.http.exchange as Client
import silk.http.exchange { ExchangeError, Limits }
import silk.http.headers { Headers }
import silk.http.message { Header, ValueError, Version }
import silk.http.origin { Origin, OriginError }
import silk.http.request { RequestError }
import silk.http.transport { HttpTransport, TransportError }
import silk.http.websocket { Event }
import silk.http.websocket_client { ClientOptions, HandshakeError, Opened, handshake }
import silk.http.websocket_stream { StreamError }
import silk.system.monotonic_clock { MonotonicClock }
import silk.system.monotonic_clock_native { OsMonotonicClock }
import silk.system.random { Random }
import silk.system.system_clock { Instant, SystemClock }
import silk.uri.reference { ParseError }
import silk.uri.uri { Uri }

// A transport that owns a memory duplex, so the session can own it.
struct Scripted {
  memory: MemoryByteDuplex,
}

impl HttpTransport for Scripted {
  effect fn readSomeRaw(self: &mut Self, output: &mut [u8], deadline: Option<Instant>) -> ReadTransfer
    ! TransportError | OutOfMemoryError
    ? &mut MonotonicClock | &mut Allocator | &mut Random {
    let attempted = run Effect.result(ByteDuplex.readSome(&mut output, move deadline))
      |> Effect.provideMut<ByteDuplex>(&mut self.memory)
    return match move attempted {
      Result.Success {value} => move value
      Result.Failure {error} => { fail TransportError.Plain {error: move error} }
    }
  }

  effect fn writeSomeRaw(self: &mut Self, input: &[u8], deadline: Option<Instant>) -> usize
    ! TransportError | OutOfMemoryError
    ? &mut MonotonicClock | &mut Allocator | &mut Random {
    let attempted = run Effect.result(ByteDuplex.writeSome(input, move deadline))
      |> Effect.provideMut<ByteDuplex>(&mut self.memory)
    return match move attempted {
      Result.Success {value} => value
      Result.Failure {error} => { fail TransportError.Plain {error: move error} }
    }
  }

  effect fn flush(self: &mut Self, deadline: Option<Instant>) -> ()
    ! TransportError | OutOfMemoryError
    ? &mut MonotonicClock | &mut Allocator | &mut Random {
    drop deadline
    return ()
  }

  effect fn close(self: &mut Self) -> () ! TransportError {
    return ()
  }
}

// Always returns zero bytes, so the key is `AAAAAAAAAAAAAAAAAAAAAA==`.
struct ZeroRandom {}

impl Random for ZeroRandom {
  effect fn fillBytes(self: &mut Self, output: &mut [u8]) -> () {
    let mut index: usize = 0
    while index < output.length {
      output[index] = 0
      index = index + 1
    }
    return ()
  }
}

effect fn program() -> i32
  ! HandshakeError | ExchangeError | RequestError | StreamError | OutOfMemoryError
  ? &mut Allocator | &mut MonotonicClock | &mut Random {
  let uri = match Uri.parse("ws://example.com/chat") {
    Result<Uri, ParseError>.Success {value} => value
    Result<Uri, ParseError>.Failure {..} => { return 1 }
  }
  let origin = match Origin.fromUri(&uri) {
    Result<Origin, OriginError>.Success {value} => value
    Result<Origin, OriginError>.Failure {..} => { return 2 }
  }
  // The 101 response, then a Text frame "hi" from the server.
  let response = run Bytes.copy(b"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ICX+Yqv66kxgM0FcWaLWlFLwTAI=\r\n\r\n\x81\x02hi")
  let mut reads = List<MemoryReadEvent>.make()
  run reads.append(MemoryReadEvent.Data {readyAt: SystemClock.make(0, 0), bytes: move response})
  let mut writes = List<MemoryWriteEvent>.make()
  run writes.append(MemoryWriteEvent {readyAt: SystemClock.make(0, 0), action: MemoryWriteAction.Accept {count: 4096}})
  let memory = run MemoryByteDuplex.make(move reads, move writes, 4096, 4, Option<i32>.none())
  let connection = run Client.makeOwned(
    Scripted {memory: move memory},
    origin,
    Version.Http11,
    Limits.defaults(),
    Option<Instant>.none(),
  )
  let entries: [Header<'static>; 0] = []
  let headers = match Headers.make(&entries, Limits.defaults().head.values) {
    Result<Headers, ValueError>.Success {value} => value
    Result<Headers, ValueError>.Failure {..} => { return 3 }
  }
  let opened = run handshake(move connection, &uri, &headers, ClientOptions.defaults())
  let Opened<Scripted> {socket, protocol} = move opened
  if Bytes.length(&protocol) != 0 { return 4 }
  let mut session = move socket
  match run session.readEvent(Option<Instant>.none()) {
    Event.Text {..} => {
      if session.messageText() != "hi" { return 5 }
    }
    _ => { return 6 }
  }
  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 = ZeroRandom {}
  return run Effect.catchAll(program(), failed)
    |> Effect.provideMut<Allocator>(&mut allocator)
    |> Effect.provideMut<MonotonicClock>(&mut clock)
    |> Effect.provideMut<Random>(&mut random)
}

Import as WebSocketClient with import silk.http.websocket_client as WebSocketClient.

Public declarations: 4.

HandshakeError#

silk
pub union HandshakeError

Why the server's answer is not a valid WebSocket handshake.

Status#

silk
Status

The server answered with this status instead of 101.

Field code#

silk
pub code: u16

The status code.

Upgrade#

silk
Upgrade

The 101 response has no Upgrade: websocket or no Connection: upgrade.

Accept#

silk
Accept

Sec-WebSocket-Accept is missing, repeated, or does not match the key.

Protocol#

silk
Protocol

The server selected a subprotocol that was not offered, or sent more than one.

Extension#

silk
Extension

The server selected an extension. None are offered.

Implementation Copy for HandshakeError#

silk
impl Copy for HandshakeError

Implementation Debug for HandshakeError#

silk
impl Debug for HandshakeError

Operation debug#

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

Writes the variant and its fields, such as Status { code: 404 }.

ClientOptions#

silk
pub struct ClientOptions<'value>

The options of handshake.

Field stream#

silk
pub stream: Options

The options of the session.

Field protocols#

silk
pub protocols: Option<&'value [u8]>

The subprotocols to offer, as the value of Sec-WebSocket-Protocol, such as chat, v2. With None, no subprotocol is offered.

Field deadline#

silk
pub deadline: Option<Instant>

The deadline of the handshake on the MonotonicClock, or None.

Implementation ClientOptions<'value>#

silk
impl<'value> ClientOptions<'value>

Associated function defaults#

silk
pub fn defaults() -> ClientOptions<'value>

Returns the default options: default session options, no subprotocol, and no deadline.

Opened#

silk
pub struct Opened<P>

An open client session and the subprotocol that the server selected.

Field socket#

silk
pub socket: WebSocket<P>

The session.

Field protocol#

silk
pub protocol: Bytes

The selected subprotocol, or empty when the server selected none.

handshake#

silk
pub effect fn handshake<'value, P>(
  connection: Connection<P>,
  uri: &Uri<'value>,
  headers: &Headers<'value>,
  options: ClientOptions<'value>,
) -> Opened<P>
! HandshakeError | ExchangeError | RequestError | StreamError | OutOfMemoryError
? &mut MonotonicClock | &mut Allocator | &mut Random
where
  &mut P provides &HttpTransport from &mut HttpTransport,
  &mut P provides &HttpTransport from &mut HttpTransport | &mut MonotonicClock | &mut Allocator | &mut Random

Sends the opening handshake for uri on connection, checks the answer, and returns the open session.

Details#

uri is a ws, wss, http, or https URI of the origin of connection. headers are sent too, for example Origin or Cookie. They cannot hold the fields that the handshake makes.

Errors#

  • HandshakeError if the server did not accept the handshake.

  • RequestError if the request cannot be made, for example for a conflicting field.

  • ExchangeError if the exchange fails or the deadline passes.

  • StreamError if the session options are not valid.

  • OutOfMemoryError if a buffer cannot grow.

The connection is closed after any error.

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