slate

slate:ws

WebSockets, written in slate over the slate:http upgrade seam.

import { serve } from slate:http
import { accept } from slate:ws

serve(port, handler, (req, socket, head) ->
    val ws = accept(req, socket, head).value

    ws.onMessage((m) -> ws.send("echo:" + m)))
open(url)a promise of a result holding the connection — the client
accept(req, socket, head)a result holding the connection — the server
accepting(req)whether this request is a WebSocket handshake
framed(payload, opcode)a frame a server writes, for a program doing its own writing
maskedFrame(payload, opcode)the same frame a client writes, which is masked
unframed(bytes)the other direction

open(url) — the client

import { open } from slate:ws

async main()
    val made = await open("wss://example.com/socket")

    if !made.ok then return print(made.error)

    val c = made.value

    c.onMessage((m) -> print("said", m))
    c.onClose((code, why) -> print("gone", code))
    c.send("hello")

main()

A promise of a result and not a promise that fails, which is connect‘s shape in slate:net: a server that is not there is a condition a client was always going to deal with. ws:// and wss:// are the only two schemes, and a url that is not one is refused in the same sentence wherever the program runs.

The connection is the same object accept answersonMessage, onBinary, onClose, send, sendBytes, ping, close — so a program that speaks WebSocket does not know which end it is.

A client masks every frame it sends and a server masks none, which is RFC 6455’s rule and not an implementation detail: masking exists so that a hostile page cannot steer an intermediary into reading payload bytes as a request of its own, and only the browser’s direction has that problem. Each end fails the connection when the other gets it wrong.

Under slate js the client is the HOST’s. A browser has no socket at all, so the framing here has nothing to write onto and the work goes to the WebSocket object the page already has; the JavaScript page says what that changes. The one visible difference is ping, which refuses there: a browser writes the protocol’s control frames itself and gives a page no way to.

The upgrade seam

serve takes a third function, and so does serveStream. A request that says Connection: upgrade reaches it with (req, conn, leftover).

The leftover bytes matter. A client may put its first frame in the same packet as the handshake, and those bytes are already off the socket — so they are handed to accept, which seeds the connection’s buffer with them.

A server with no upgrade function answers 501 rather than going quiet. The client asked for a protocol this program does not speak, which it can act on; a socket nothing will ever answer is not.

What is in the module

The framing, the masking, the fragment reassembly and base64 are all slate. &, |, ^, << and >> are slate’s own on 64-bit integers, and toBytes/fromBytes are the byte surface.

SHA-1 is the one thing reached for, from slate:crypto. This is not a use of SHA-1 that its weakness touches: nothing is signed, and the protocol wants a value only the other end could have computed.

Search

Esc
to navigate to open Esc to close