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#
HandshakeErrorif the server answered with another status or a wrong 101 response.ExchangeError,RequestError, andStreamErrorfrom the request and the session.
Gotchas#
The connection is used up: after an error it is closed.
Extensions such as
permessage-deflateare not offered, so a response with one fails.
Examples#
Open a session over a scripted transport#
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#
pub union HandshakeErrorWhy the server's answer is not a valid WebSocket handshake.
Status#
StatusThe server answered with this status instead of 101.
Field code#
pub code: u16The status code.
Upgrade#
UpgradeThe 101 response has no Upgrade: websocket or no Connection: upgrade.
Accept#
AcceptSec-WebSocket-Accept is missing, repeated, or does not match the key.
Protocol#
ProtocolThe server selected a subprotocol that was not offered, or sent more than one.
Extension#
ExtensionThe server selected an extension. None are offered.
Implementation Copy for HandshakeError#
impl Copy for HandshakeErrorImplementation Debug for HandshakeError#
impl Debug for HandshakeErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the variant and its fields, such as Status { code: 404 }.
ClientOptions#
pub struct ClientOptions<'value>The options of handshake.
Field stream#
pub stream: OptionsThe options of the session.
Field protocols#
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#
pub deadline: Option<Instant>The deadline of the handshake on the MonotonicClock, or None.
Implementation ClientOptions<'value>#
impl<'value> ClientOptions<'value>Associated function defaults#
pub fn defaults() -> ClientOptions<'value>Returns the default options: default session options, no subprotocol, and no deadline.
Opened#
pub struct Opened<P>An open client session and the subprotocol that the server selected.
Field socket#
pub socket: WebSocket<P>The session.
Field protocol#
pub protocol: BytesThe selected subprotocol, or empty when the server selected none.
handshake#
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 RandomSends 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#
HandshakeErrorif the server did not accept the handshake.RequestErrorif the request cannot be made, for example for a conflicting field.ExchangeErrorif the exchange fails or the deadline passes.StreamErrorif the session options are not valid.OutOfMemoryErrorif 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.