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:
TextandBinarygive a complete message. Fragmented messages are joined, and Text is checked as UTF-8 as it arrives. Get the bytes withSession.message.Pingwas answered: the Pong is in your output buffer.Ponggives the payload of a Pong.Closemeans that the peer sent Close. If you had not sent Close, the answer Close is in your output buffer. The session is thenClosed: 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-deflateare 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 fromsilk.system.random.
Examples#
Answer a message from a client#
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#
pub enum RoleThe side of a connection.
Client#
ClientThe side that opened the connection. It masks its frames.
Server#
ServerThe side that accepted the connection. It does not mask its frames.
Implementation Debug for Role#
impl Debug for RoleOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as Client.
Opcode#
pub enum OpcodeThe kind of a frame.
Continuation#
ContinuationA later fragment of a message (0).
Text#
TextThe first frame of a Text message (1).
Binary#
BinaryThe first frame of a Binary message (2).
Close#
CloseA Close (8).
Ping#
PingA Ping (9).
Pong#
PongA Pong (10).
Implementation Debug for Opcode#
impl Debug for OpcodeOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as Text.
Limits#
pub struct LimitsThe limits of a session or decoder.
Field maxMessageBytes#
pub maxMessageBytes: usizeThe maximum length of one message, all fragments together. It is more than zero.
Implementation Copy for Limits#
impl Copy for LimitsImplementation Limits#
impl LimitsAssociated function defaults#
pub fn defaults() -> LimitsReturns the default limits: messages of at most 1048576 bytes.
ProtocolReason#
pub enum ProtocolReasonThe frame rule that a peer broke. The Close for it has code 1002.
MaskRequired#
MaskRequiredA client frame is not masked.
MaskForbidden#
MaskForbiddenA server frame is masked.
Rsv#
RsvA reserved bit is set.
ReservedOpcode#
ReservedOpcodeThe opcode is reserved.
NonMinimalLength#
NonMinimalLengthThe length uses more bytes than necessary.
LengthHighBit#
LengthHighBitThe highest bit of a 64-bit length is set.
ControlTooLarge#
ControlTooLargeA control frame has more than 125 bytes.
ControlFragmented#
ControlFragmentedA control frame is not final.
UnexpectedContinuation#
UnexpectedContinuationA Continuation frame came with no message to continue.
ExpectedContinuation#
ExpectedContinuationA Text or Binary frame came before the last fragment of the previous message.
ClosePayloadLength#
ClosePayloadLengthA Close frame has a payload of exactly one byte.
CloseCode#
CloseCodeA Close frame has a code that is not allowed.
FrameAfterClose#
FrameAfterCloseA frame came after the Close of the peer.
Implementation Debug for ProtocolReason#
impl Debug for ProtocolReasonOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as MaskRequired.
Utf8Component#
pub enum Utf8ComponentThe text that is not valid UTF-8. The Close for it has code 1007.
Text#
TextA Text message.
CloseReason#
CloseReasonThe reason of a Close frame.
Implementation Debug for Utf8Component#
impl Debug for Utf8ComponentOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as Text.
CloseDataReason#
pub enum CloseDataReasonThe rule of CloseData.make that failed.
Code#
CodeThe code is not allowed in a Close that you send.
ReasonTooLarge#
ReasonTooLargeThe reason has more than 123 bytes.
Implementation Debug for CloseDataReason#
impl Debug for CloseDataReasonOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as Code.
State#
pub enum StateThe state of a Session.
Open#
OpenMessages can be sent and received.
CloseSent#
CloseSentThis side sent Close and waits for the Close of the peer. Frames are still received.
Closed#
ClosedBoth sides sent Close. Write the output that is left, then close the connection.
Failed#
FailedA rule or limit check failed. Write the output that is left, then close the connection.
Implementation Debug for State#
impl Debug for StateOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as Open.
WebSocketError#
pub union WebSocketErrorThe 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#
InvalidLimitsmaxMessageBytes is zero.
InvalidState#
InvalidStateThe operation is not allowed in the state of the session.
Field state#
pub state: StateThe state.
PreviousFailure#
PreviousFailureAn earlier error ended the session or decoder.
MessageTooLarge#
MessageTooLargeA message is longer than maxMessageBytes.
Field length#
pub length: u64The length of the message so far, with the frame that broke the limit.
Field allowed#
pub allowed: usizeThe value of maxMessageBytes.
ProtocolError#
ProtocolErrorA peer frame broke a rule of RFC 6455.
Field reason#
pub reason: ProtocolReasonThe rule.
InvalidCloseData#
InvalidCloseDataThe Close data that you gave is not valid.
Field reason#
pub reason: CloseDataReasonThe rule.
InvalidUtf8#
InvalidUtf8Text is not valid UTF-8.
Field component#
pub component: Utf8ComponentThe text that failed.
ControlPayloadTooLarge#
ControlPayloadTooLargeA Ping or Pong payload that you gave has more than 125 bytes.
Field length#
pub length: usizeThe length of the payload.
FragmentOrder#
FragmentOrderA 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#
impl Copy for WebSocketErrorImplementation Debug for WebSocketError#
impl Debug for WebSocketErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the variant and its fields, such as
MessageTooLarge { length: 70000, allowed: 65536 }.
closeCodeOf#
pub fn closeCodeOf(error: &WebSocketError) -> u16Returns the Close code for a peer error: 1002 for ProtocolError, 1007 for InvalidUtf8,
1009 for MessageTooLarge, and 1011 for any other error.
ControlPayload#
pub struct ControlPayloadThe payload of a Ping or Pong, at most 125 bytes.
Implementation Copy for ControlPayload#
impl Copy for ControlPayloadImplementation ControlPayload#
impl ControlPayloadAssociated function make#
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#
pub fn asSlice<'payload>(self: &'payload Self) -> &'payload [u8]Returns the payload bytes.
CloseReason#
pub struct CloseReasonThe reason of a Close frame: valid UTF-8, at most 123 bytes.
Implementation Copy for CloseReason#
impl Copy for CloseReasonImplementation CloseReason#
impl CloseReasonMethod asSlice#
pub fn asSlice<'reason>(self: &'reason Self) -> &'reason [u8]Returns the reason bytes. They are valid UTF-8.
CloseData#
pub struct CloseDataThe code and reason of a Close frame that you send. Make it with CloseData.make.
Implementation Copy for CloseData#
impl Copy for CloseDataImplementation CloseData#
impl CloseDataAssociated function make#
pub fn make(code: u16, reason: string) -> Result<CloseData, WebSocketError>Returns Close data with code and a copy of reason.
Errors#
InvalidCloseDatawithCodeifcodecannot 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.InvalidCloseDatawithReasonTooLargeifreasonhas more than 123 bytes.
Method code#
pub fn code(self: &Self) -> u16Returns the code.
Method reason#
pub fn reason<'data>(self: &'data Self) -> &'data CloseReasonReturns the reason.
PeerCloseData#
pub union PeerCloseDataThe code and reason in the Close of the peer.
Absent#
AbsentThe Close had no payload. RFC 6455 reports this as code 1005.
Present#
PresentThe Close had a code and a reason.
Field code#
pub code: u16The code.
Field reason#
pub reason: CloseReasonThe reason. It can be empty.
Implementation Copy for PeerCloseData#
impl Copy for PeerCloseDataEvent#
pub union EventOne complete event from the peer.
Text#
TextA Text message of length bytes, valid UTF-8. Get it with Session.message or
Decoder.message before the next call that receives.
Field length#
pub length: usizeThe length of the message.
Binary#
BinaryA Binary message of length bytes. Get it with Session.message or Decoder.message
before the next call that receives.
Field length#
pub length: usizeThe length of the message.
Ping#
PingA Ping. A Session already put the Pong in the output.
Field payload#
pub payload: ControlPayloadThe payload.
Pong#
PongA Pong.
Field payload#
pub payload: ControlPayloadThe payload.
Close#
CloseThe Close of the peer.
Field data#
pub data: PeerCloseDataThe code and reason of the peer.
Implementation Copy for Event#
impl Copy for EventReceived#
pub struct ReceivedThe result of one receive call.
Field consumed#
pub consumed: usizeHow many input bytes were used. Give the rest again in the next call.
Field event#
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#
impl Copy for ReceivedDecoder#
pub struct DecoderSplits 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#
impl DecoderAssociated function make#
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#
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#
pub effect fn feed(self: &mut Self, input: &[u8]) -> Received
! WebSocketError | OutOfMemoryError
? &mut AllocatorReads 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#
pub effect fn appendFrame(
output: &mut Bytes,
opcode: Opcode,
fin: bool,
payload: &[u8],
mask: Option<[u8; 4]>,
) -> () ! OutOfMemoryError ? &mut AllocatorAppends 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#
pub enum MessageKindThe kind of a message that you send in fragments with Session.sendFragment.
Text#
TextA Text message. Each fragment must be valid UTF-8 on its own, so that the message is too.
Binary#
BinaryA Binary message.
Session#
pub struct SessionA 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#
impl SessionAssociated function make#
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#
pub fn side(self: &Self) -> RoleReturns the role.
Method state#
pub fn state(self: &Self) -> StateReturns the state.
Method advance#
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#
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#
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#
pub effect fn receive(self: &mut Self, input: &[u8], output: &mut Bytes) -> Received
! WebSocketError | OutOfMemoryError
? &mut AllocatorReads 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
closeCodeOfthe error, unless this side already sent Close. The state becomesFailed.
Errors#
InvalidStateif the state isClosed.ProtocolError,MessageTooLargeorInvalidUtf8for a bad frame,PreviousFailureif the state isFailed.OutOfMemoryErrorif a buffer cannot grow.
Method sendText#
pub effect fn sendText(self: &mut Self, text: string, output: &mut Bytes) -> ()
! WebSocketError | OutOfMemoryError
? &mut AllocatorAppends 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#
pub effect fn sendBinary(self: &mut Self, bytes: &[u8], output: &mut Bytes) -> ()
! WebSocketError | OutOfMemoryError
? &mut AllocatorAppends 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#
pub effect fn sendFragment(
self: &mut Self,
kind: MessageKind,
payload: &[u8],
fin: bool,
output: &mut Bytes,
) -> ()
! WebSocketError | OutOfMemoryError
? &mut AllocatorAppends 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#
pub effect fn sendPing(self: &mut Self, payload: &[u8], output: &mut Bytes) -> ()
! WebSocketError | OutOfMemoryError
? &mut AllocatorAppends 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#
pub effect fn sendPong(self: &mut Self, payload: &[u8], output: &mut Bytes) -> ()
! WebSocketError | OutOfMemoryError
? &mut AllocatorAppends 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#
pub effect fn sendClose(self: &mut Self, data: Option<CloseData>, output: &mut Bytes) -> ()
! WebSocketError | OutOfMemoryError
? &mut AllocatorAppends 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.