Browse the docs

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:

  1. It calls inspect. If it fails, nothing is sent and the request stays active, so you can call reject.

  2. It calls the DecisionHandler. The decision is checked before any output.

  3. For Reject, it sends that status. For Accept, it sends 101 with the accept value and the protocol, flushes it, and lends the channel to your callback.

Errors#

  • UpgradeError if the request is not a valid handshake, a limit is exceeded, or the decision is not valid. No response was sent.

  • ServerError from silk.http.server if the output fails.

  • The errors of the decision handler and the callback.

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. Offers are ignored, and an Accept with extensions: true fails with UnsupportedNegotiation.

  • This module does only the handshake. It does not read or write frames.

Examples#

Inspect a handshake#

silk
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#

silk
pub struct Limits

The limits of a handshake. A zero limit allows none of that resource.

Field maxProtocols#

silk
pub maxProtocols: usize

The maximum number of offered subprotocols, in all Sec-WebSocket-Protocol fields.

Field maxProtocolBytes#

silk
pub maxProtocolBytes: usize

The maximum length of one offered subprotocol.

Field maxExtensionBytes#

silk
pub maxExtensionBytes: usize

The maximum total length of the Sec-WebSocket-Extensions values.

Field maxResponseBytes#

silk
pub maxResponseBytes: usize

The maximum size of the response head, with the fields that are added.

Field maxOwnedBytes#

silk
pub maxOwnedBytes: usize

The maximum memory for the response.

Implementation Copy for Limits#

silk
impl Copy for Limits

Implementation Limits#

silk
impl Limits

Associated function defaults#

silk
pub fn defaults() -> Limits

Returns the default limits: 32 protocols, 128 bytes for each protocol, 4096 extension bytes, 8192 response bytes, and 16384 owned bytes.

ValidationReason#

silk
pub enum ValidationReason

The rule of the request that failed.

Request#

silk
Request

The method is not GET, or the version is not HTTP/1.1.

Singleton#

silk
Singleton

A required field is missing, or a field that is allowed one time occurs more.

Host#

silk
Host

Host is not a valid authority.

Tokens#

silk
Tokens

Upgrade or Connection is not a valid token list, or does not have websocket or upgrade.

ConnectionClose#

silk
ConnectionClose

Connection has close.

Version#

silk
Version

Sec-WebSocket-Version is not a decimal number.

Key#

silk
Key

Sec-WebSocket-Key is not 16 bytes in canonical padded Base64.

Body#

silk
Body

The request can have a body.

Expect#

silk
Expect

The request has Expect.

Origin#

silk
Origin

Origin is not one HTTP or HTTPS origin, or null.

DuplicateProtocol#

silk
DuplicateProtocol

A subprotocol is offered two times.

Extensions#

silk
Extensions

Sec-WebSocket-Extensions is not valid.

Implementation Debug for ValidationReason#

silk
impl Debug for ValidationReason

Operation debug#

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

Writes the name of the variant, such as Request.

LimitKind#

silk
pub enum LimitKind

The limit that was exceeded.

Protocols#

silk
Protocols

Too many subprotocols.

ProtocolBytes#

silk
ProtocolBytes

A subprotocol is too long.

ExtensionBytes#

silk
ExtensionBytes

The extension fields are too long.

ResponseBytes#

silk
ResponseBytes

The response head is too large.

OwnedBytes#

silk
OwnedBytes

The response needs too much memory.

SizeOverflow#

silk
SizeOverflow

A size does not fit in usize.

Implementation Debug for LimitKind#

silk
impl Debug for LimitKind

Operation debug#

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

Writes the name of the variant, such as Protocols.

DecisionReason#

silk
pub enum DecisionReason

The rule of the decision that failed.

Protocol#

silk
Protocol

The selected protocol is not one of the offered protocols. The compare is case-sensitive.

silk
Header

An extra header replaces a field that the handshake sets, such as Sec-WebSocket-Protocol or Content-Length.

Status#

silk
Status

A Reject status is less than 200.

Implementation Debug for DecisionReason#

silk
impl Debug for DecisionReason

Operation debug#

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

Writes the name of the variant, such as Protocol.

UpgradeError#

silk
pub union UpgradeError

The error of a handshake. No error sends a response. Use reject to send one.

Validation#

silk
Validation

The request is not a valid handshake.

Field reason#

silk
pub reason: ValidationReason

The rule that failed.

UnsupportedVersion#

silk
UnsupportedVersion

The request asks for another WebSocket version. reject sends 426.

Field supported#

silk
pub supported: u16

The supported version: 13.

Limit#

silk
Limit

A limit was exceeded.

Field kind#

silk
pub kind: LimitKind

The limit.

InvalidDecision#

silk
InvalidDecision

The decision cannot make a valid response.

Field reason#

silk
pub reason: DecisionReason

The rule that failed.

UnsupportedNegotiation#

silk
UnsupportedNegotiation

The decision asks for extensions, which are not supported.

Value#

silk
Value

A response value is not valid.

Field error#

silk
pub error: ValueError

The value error.

Implementation Debug for UpgradeError#

silk
impl Debug for UpgradeError

Operation debug#

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

Writes the name of the variant and its fields, such as Validation { ... }.

Offer#

silk
pub struct Offer<'head>

A valid handshake request, from inspect. It borrows the request head.

Implementation Offer<'head>#

silk
impl<'head> Offer<'head>

Method target#

silk
pub fn target(self: &Self) -> RequestTarget<'head>

Returns the request target, such as /chat.

Method host#

silk
pub fn host(self: &Self) -> &'head [u8]

Returns the value of Host.

Method origin#

silk
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#

silk
pub fn protocolCount(self: &Self) -> usize

Returns the number of offered subprotocols.

Method protocolAt#

silk
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#

silk
pub fn extensionsPresent(self: &Self) -> bool

Returns whether the request offers extensions. They are ignored.

Method acceptInto#

silk
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#

silk
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#

  • Validation with Key if key is not 16 bytes in canonical padded Base64.

  • Limit with ResponseBytes if output is shorter than 28 bytes.

Outcome#

silk
pub union Outcome<A>

The result of withUpgrade.

Upgraded#

silk
Upgraded

The 101 response was sent, and the callback finished.

Field value#

silk
pub value: A

The value of the callback.

Rejected#

silk
Rejected

The decision was Reject, and its response was sent.

Field status#

silk
pub status: u16

The status that was sent.

Decision#

silk
pub union Decision<'value>

The answer of a DecisionHandler.

Accept#

silk
Accept

Accepts the handshake.

Field protocol#

silk
pub protocol: Option<string<'value>>

The selected subprotocol, or None. It must be one of the offered protocols, with the same case.

Field headers#

silk
pub headers: Option<Headers<'value>>

More fields for the response, such as Set-Cookie. They cannot replace the fields of the handshake.

Field extensions#

silk
pub extensions: bool

Set it to false. True fails with UnsupportedNegotiation.

Reject#

silk
Reject

Rejects the handshake with a normal HTTP response with no body.

Field status#

silk
pub status: Status

The status, 200 or more. For example, 403.

Field headers#

silk
pub headers: Option<Headers<'value>>

More fields for the response, such as WWW-Authenticate.

DecisionHandler#

silk
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#

silk
effect<'call> fn decide<'call, 'head: 'call>(handler: &'call Self, offer: &'call Offer<'head>) -> Decision<
    'call,
  > ! D ? R

Returns the Decision for offer. The decision can borrow from the handler or the offer.

inspect#

silk
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#

  • Validation if a rule fails. The reason tells which.

  • UnsupportedVersion if the version is a number other than 13.

  • Limit if the subprotocols or extensions exceed limits.

rejectionStatus#

silk
pub fn rejectionStatus(error: &UpgradeError) -> u16

Returns the status that reject sends for error: 417 for Expect, 426 for a version, and 400 for all others.

withUpgrade#

silk
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 MonotonicClock

Checks 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#

  • UpgradeError before any output. The request is still active, so you can call reject.

  • ServerError if 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#

silk
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 MonotonicClock

Sends 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.

View the source of this page