silk.http.websocket_upgrade.
The server side of the WebSocket opening handshake (RFC 6455, version 13).
When to use#
In an silk.http.server request callback, call withUpgrade with a DecisionHandler. The
handler looks at the Offer and returns a Decision: accept, maybe with a subprotocol, or
reject with a status. After an accept, your callback gets the channel for WebSocket frames, for
example through silk.http.websocket_server.
Use inspect and acceptInto alone to check a handshake without a server.
Details#
inspect checks a parsed request head. It does not allocate. The request must be an HTTP/1.1
GET with a valid Host, Upgrade: websocket, Connection: upgrade, a 16-byte key, version
13, and no body or Expect.
withUpgrade does these steps:
It calls
inspect. If it fails, nothing is sent and the request stays active, so you can callreject.It calls the
DecisionHandler. The decision is checked before any output.For
Reject, it sends that status. ForAccept, it sends 101 with the accept value and the protocol, flushes it, and lends the channel to your callback.
Errors#
UpgradeErrorif the request is not a valid handshake, a limit is exceeded, or the decision is not valid. No response was sent.ServerErrorfromsilk.http.serverif the output fails.The errors of the decision handler and the callback.
Gotchas#
Originis only a hint from the browser. It is not authentication. Check it in the decision if you need it.Extensions such as
permessage-deflateare not supported. Offers are ignored, and anAcceptwithextensions: truefails withUnsupportedNegotiation.This module does only the handshake. It does not read or write frames.
Examples#
Inspect a handshake#
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
import silk.http.head {
Limits as HeadLimits,
ParseError,
ParsedRequest,
RequestHead,
RequestParser,
parseRequest
}
import silk.http.headers { Limits as ValueLimits }
import silk.core.option { Option }
import silk.core.result { Result }
import silk.http.websocket_upgrade { Limits, Offer, UpgradeError, acceptInto, inspect, rejectionStatus }
fn headLimits() -> HeadLimits {
return HeadLimits {
maxHeadBytes: 4096,
maxStartLineBytes: 1024,
maxFieldLineBytes: 1024,
maxOwnedBytes: 8192,
values: ValueLimits {
maxMethodBytes: 16,
maxTargetBytes: 1024,
maxNameBytes: 64,
maxValueBytes: 1024,
maxFields: 32,
maxFieldBytes: 4096,
maxOwnedBytes: 8192,
},
}
}
fn equal(left: &[u8], right: &[u8]) -> bool {
if left.length != right.length {
return false
}
let mut index: usize = 0
while index < left.length {
if left[index] != right[index] {
return false
}
index = index + 1
}
return true
}
effect fn program() -> i32
! OutOfMemoryError
? &mut Allocator {
// The sample handshake from RFC 6455.
let wire = b"GET /chat HTTP/1.1\r\nHost: server.example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Protocol: chat, superchat\r\nSec-WebSocket-Version: 13\r\n\r\n"
let parsed = match move run parseRequest(&wire, headLimits()) {
Result<ParsedRequest, ParseError>.Success { value } => move value
Result<ParsedRequest, ParseError>.Failure { error: _ } => {
return 1
}
}
let head = match RequestParser.head(&parsed.parser) {
Result<RequestHead, ParseError>.Success { value } => value
Result<RequestHead, ParseError>.Failure { error: _ } => {
return 1
}
}
let offer = match move inspect(&head, Limits.defaults()) {
Result<Offer, UpgradeError>.Success { value } => move value
Result<Offer, UpgradeError>.Failure { error: _ } => {
return 1
}
}
if offer.protocolCount() != 2 {
return 1
}
let first = match offer.protocolAt(0) {
Option.Some { value } => value
Option.None => {
return 1
}
}
if !equal(first, b"chat") {
return 1
}
let mut accept: [u8; 28] = [0; 28]
let written = match move offer.acceptInto(&mut accept) {
Result<usize, UpgradeError>.Success { value } => value
Result<usize, UpgradeError>.Failure { error: _ } => {
return 1
}
}
if written != 28 || !equal(&accept, b"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") {
return 1
}
// The same value, from the key alone.
let mut again: [u8; 28] = [0; 28]
let direct = match move acceptInto(b"dGhlIHNhbXBsZSBub25jZQ==", &mut again) {
Result<usize, UpgradeError>.Success { value } => value
Result<usize, UpgradeError>.Failure { error: _ } => {
return 1
}
}
if direct != 28 || !equal(&again, &accept) {
return 1
}
// An old protocol version fails. `reject` would answer with 426.
let old = b"GET /chat HTTP/1.1\r\nHost: server.example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 8\r\n\r\n"
let oldParsed = match move run parseRequest(&old, headLimits()) {
Result<ParsedRequest, ParseError>.Success { value } => move value
Result<ParsedRequest, ParseError>.Failure { error: _ } => {
return 1
}
}
let oldHead = match RequestParser.head(&oldParsed.parser) {
Result<RequestHead, ParseError>.Success { value } => value
Result<RequestHead, ParseError>.Failure { error: _ } => {
return 1
}
}
return match move inspect(&oldHead, Limits.defaults()) {
Result<Offer, UpgradeError>.Success { value: _ } => 1
Result<Offer, UpgradeError>.Failure { error } => {
if rejectionStatus(&error) != 426 {
return 1
}
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 WebSocketUpgrade with import silk.http.websocket_upgrade as WebSocketUpgrade.
Public declarations: 14.
Limits#
pub struct LimitsThe limits of a handshake. A zero limit allows none of that resource.
Field maxProtocols#
pub maxProtocols: usizeThe maximum number of offered subprotocols, in all Sec-WebSocket-Protocol fields.
Field maxProtocolBytes#
pub maxProtocolBytes: usizeThe maximum length of one offered subprotocol.
Field maxExtensionBytes#
pub maxExtensionBytes: usizeThe maximum total length of the Sec-WebSocket-Extensions values.
Field maxResponseBytes#
pub maxResponseBytes: usizeThe maximum size of the response head, with the fields that are added.
Field maxOwnedBytes#
pub maxOwnedBytes: usizeThe maximum memory for the response.
Implementation Copy for Limits#
impl Copy for LimitsImplementation Limits#
impl LimitsAssociated function defaults#
pub fn defaults() -> LimitsReturns the default limits: 32 protocols, 128 bytes for each protocol, 4096 extension bytes, 8192 response bytes, and 16384 owned bytes.
ValidationReason#
pub enum ValidationReasonThe rule of the request that failed.
Request#
RequestThe method is not GET, or the version is not HTTP/1.1.
Singleton#
SingletonA required field is missing, or a field that is allowed one time occurs more.
Host#
HostHost is not a valid authority.
Tokens#
TokensUpgrade or Connection is not a valid token list, or does not have websocket or
upgrade.
ConnectionClose#
ConnectionCloseConnection has close.
Version#
VersionSec-WebSocket-Version is not a decimal number.
Key#
KeySec-WebSocket-Key is not 16 bytes in canonical padded Base64.
Body#
BodyThe request can have a body.
Expect#
ExpectThe request has Expect.
Origin#
OriginOrigin is not one HTTP or HTTPS origin, or null.
DuplicateProtocol#
DuplicateProtocolA subprotocol is offered two times.
Extensions#
ExtensionsSec-WebSocket-Extensions is not valid.
Implementation Debug for ValidationReason#
impl Debug for ValidationReasonOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as Request.
LimitKind#
pub enum LimitKindThe limit that was exceeded.
Protocols#
ProtocolsToo many subprotocols.
ProtocolBytes#
ProtocolBytesA subprotocol is too long.
ExtensionBytes#
ExtensionBytesThe extension fields are too long.
ResponseBytes#
ResponseBytesThe response head is too large.
OwnedBytes#
OwnedBytesThe response needs too much memory.
SizeOverflow#
SizeOverflowA size does not fit in usize.
Implementation Debug for LimitKind#
impl Debug for LimitKindOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as Protocols.
DecisionReason#
pub enum DecisionReasonThe rule of the decision that failed.
Protocol#
ProtocolThe selected protocol is not one of the offered protocols. The compare is case-sensitive.
Header#
HeaderAn extra header replaces a field that the handshake sets, such as Sec-WebSocket-Protocol or
Content-Length.
Status#
StatusA Reject status is less than 200.
Implementation Debug for DecisionReason#
impl Debug for DecisionReasonOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as Protocol.
UpgradeError#
pub union UpgradeErrorThe error of a handshake. No error sends a response. Use reject to send one.
Validation#
ValidationThe request is not a valid handshake.
Field reason#
pub reason: ValidationReasonThe rule that failed.
UnsupportedVersion#
UnsupportedVersionThe request asks for another WebSocket version. reject sends 426.
Field supported#
pub supported: u16The supported version: 13.
Limit#
LimitA limit was exceeded.
Field kind#
pub kind: LimitKindThe limit.
InvalidDecision#
InvalidDecisionThe decision cannot make a valid response.
Field reason#
pub reason: DecisionReasonThe rule that failed.
UnsupportedNegotiation#
UnsupportedNegotiationThe decision asks for extensions, which are not supported.
Value#
ValueA response value is not valid.
Field error#
pub error: ValueErrorThe value error.
Implementation Debug for UpgradeError#
impl Debug for UpgradeErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant and its fields, such as Validation { ... }.
Offer#
pub struct Offer<'head>A valid handshake request, from inspect. It borrows the request head.
Implementation Offer<'head>#
impl<'head> Offer<'head>Method target#
pub fn target(self: &Self) -> RequestTarget<'head>Returns the request target, such as /chat.
Method host#
pub fn host(self: &Self) -> &'head [u8]Returns the value of Host.
Method origin#
pub fn origin(self: &Self) -> Option<&'head [u8]>Returns the value of Origin, or None if there is none. The value can be null.
Method protocolCount#
pub fn protocolCount(self: &Self) -> usizeReturns the number of offered subprotocols.
Method protocolAt#
pub fn protocolAt(self: &Self, index: usize) -> Option<&'head [u8]>Returns the offered subprotocol at index, in the order of the request, or None.
Method extensionsPresent#
pub fn extensionsPresent(self: &Self) -> boolReturns whether the request offers extensions. They are ignored.
Method acceptInto#
pub fn acceptInto(self: &Self, output: &mut [u8]) -> Result<usize, UpgradeError>Writes the 28-byte Sec-WebSocket-Accept value for the key to output, and returns 28.
Errors#
Limit with ResponseBytes if output is shorter than 28 bytes.
acceptInto#
pub fn acceptInto(key: &[u8], output: &mut [u8]) -> Result<usize, UpgradeError>Writes the 28-byte Sec-WebSocket-Accept value for key to output, and returns 28.
Details#
It removes spaces and tabs around key first. It does not check the rest of the handshake. If
it fails, output is not changed.
Errors#
ValidationwithKeyifkeyis not 16 bytes in canonical padded Base64.LimitwithResponseBytesifoutputis shorter than 28 bytes.
Outcome#
pub union Outcome<A>The result of withUpgrade.
Upgraded#
UpgradedThe 101 response was sent, and the callback finished.
Field value#
pub value: AThe value of the callback.
Rejected#
RejectedThe decision was Reject, and its response was sent.
Field status#
pub status: u16The status that was sent.
Decision#
pub union Decision<'value>The answer of a DecisionHandler.
Accept#
AcceptAccepts the handshake.
Field protocol#
pub protocol: Option<string<'value>>The selected subprotocol, or None. It must be one of the offered protocols, with the same
case.
Field headers#
pub headers: Option<Headers<'value>>More fields for the response, such as Set-Cookie. They cannot replace the fields of the
handshake.
Field extensions#
pub extensions: boolSet it to false. True fails with UnsupportedNegotiation.
Reject#
RejectRejects the handshake with a normal HTTP response with no body.
Field status#
pub status: StatusThe status, 200 or more. For example, 403.
Field headers#
pub headers: Option<Headers<'value>>More fields for the response, such as WWW-Authenticate.
DecisionHandler#
pub interface DecisionHandler<D, ?R>Decides if a WebSocket handshake is accepted. withUpgrade calls it with the Offer after
the request passed inspect, and checks the Decision before any output.
Gotchas#
Origin is only a hint from the browser. It is not authentication. Check it in the decision if
you need it. Extensions such as permessage-deflate are not supported, so an Accept with
extensions: true fails with UnsupportedNegotiation.
Operation decide#
effect<'call> fn decide<'call, 'head: 'call>(handler: &'call Self, offer: &'call Offer<'head>) -> Decision<
'call,
> ! D ? RReturns the Decision for offer. The decision can borrow from the handler or the offer.
inspect#
pub fn inspect<'head>(head: &RequestHead<'head>, limits: Limits) -> Result<
Offer<'head>,
UpgradeError,
>Checks that head is a valid WebSocket handshake, and returns its Offer.
It does not allocate or use services.
Errors#
Validationif a rule fails. The reason tells which.UnsupportedVersionif the version is a number other than 13.Limitif the subprotocols or extensions exceedlimits.
rejectionStatus#
pub fn rejectionStatus(error: &UpgradeError) -> u16Returns the status that reject sends for error: 417 for Expect, 426 for a version, and
400 for all others.
withUpgrade#
pub effect<'env> fn withUpgrade<
'loan,
'request: 'loan,
'connection: 'request,
'transport: 'connection,
'callback,
A,
D,
E,
?DecisionRequirements,
?CallbackRequirements,
P,
H: DecisionHandler<D ? DecisionRequirements> + 'env,
'env,
>(
request: &'loan mut Request<'request, 'connection, 'transport, P>,
limits: Limits,
deadline: Option<Instant>,
handler: H,
use: for<'call, 'transportView: 'call> once fn<'callback>(
&'call mut BufferedDuplex<'transportView, P>,
) -> once Effect<'call; A ! E ? CallbackRequirements>,
) -> Outcome<A>
! D | E | UpgradeError | ServerError | OutOfMemoryError
? DecisionRequirements | CallbackRequirements | &mut Allocator | &mut MonotonicClock
where &'transport mut P provides &ByteDuplex from &mut ByteDuplex | &mut MonotonicClockChecks the handshake, asks handler, and sends the response. After an accept, it lends the
channel to use.
Details#
The handler gets the Offer. The response is made and checked before any output, so the
decision can borrow from the offer. The channel keeps any bytes that the client sent after the
request head. Give the same deadline that you used for the request.
Errors#
UpgradeErrorbefore any output. The request is still active, so you can callreject.ServerErrorif the output fails.The errors of the handler and of
use.
Gotchas#
After the 101 response starts, a transport error ends the connection. This function does not read or write frames, and does not do TLS.
reject#
pub effect fn reject<'loan, 'request: 'loan, 'connection: 'request, 'transport: 'connection, P>(
request: &'loan mut Request<'request, 'connection, 'transport, P>,
error: &UpgradeError,
limits: Limits,
deadline: Option<Instant>,
) -> ()
! UpgradeError | ServerError | OutOfMemoryError
? &mut Allocator | &mut MonotonicClock
where &'transport mut P provides &ByteDuplex from &mut ByteDuplex | &mut MonotonicClockSends the response for a handshake error, with no body.
Details#
It sends the status of rejectionStatus. For UnsupportedVersion, it also sends
Sec-WebSocket-Version: 13. Call it only before a 101 response starts.
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.