char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somrearrange libs into lib and extensions into extsf4a0f5b

main
3.0 KiB45 linesraw

WebSocket

Implements the WHATWG WebSockets Standard client API (new WebSocket(), send(), close(), onopen/onmessage/onerror/onclose, and CloseEvent) over RFC 6455, using an in-tree decoder and fragment assembler, fastwebsockets for masked writes, and the same rustls (webpki roots, ring) TLS configuration as fetch.

Behavior notes

  • Framing runs on the Tokio runtime shared with fetch and filesystem work (avril_host::Host). The read half lives in its own task because decoding consumes frame bytes across await points and is therefore not cancellation-safe; received messages and control frames cross to the connection task over a bounded channel. Reads stop when the guest falls behind (TCP back-pressure).
  • Incoming messages are limited to 16 MiB, including the total of all fragments. A message over this limit fails the connection with close code 1009, before reading the excess payload.
  • Sends are queued in call order and awaited only for the write acknowledgement, so bufferedAmount — not the socket — is the user-visible back-pressure signal, as in browsers.
  • The close handshake waits up to 30 seconds (RFC 6455 §7.1.5's "reasonable time") for the peer's close frame before giving up and reporting an abnormal closure (1006).
  • After exchanging close frames, the client waits up to two seconds for the server to close TCP. If the server does not close, the client closes its own transport; either outcome is reported as a clean close.
  • Close codes reported in close events follow RFC 6455 §7.1.5: the code of the first close frame received (1005 when it carries none), or 1006 when no close frame was received.
  • close() before the connection is established fails the connection (error then close) rather than racing the handshake. The cancellation reaches the connection loop whatever it is doing at the time, so a connection that completes its handshake in the same instant still reports the failure (error then close, without open) instead of an open socket.
  • The event pump keeps a strong reference to the socket until its close event, so a socket with listeners is never collected while open (per the spec's GC rules); a socket without listeners is also held, which is permitted but not required.
  • Permission checks happen synchronously in the constructor, as in fetch.

not implemented

  • permessage-deflate (no Sec-WebSocket-Extensions is offered; a response offering one fails the connection)
  • proxy support, cookies, HSTS, and the other fetch integrations beyond permission checks
  • Sec-WebSocket-Protocol is validated against the response, but Sec-WebSocket-Extensions negotiation is absent
  • the Origin request header (there is no ambient origin in this runtime)
  • connection-establishment timeouts (a black-holed server is governed by the OS TCP timeouts)
  • ordinary writes are not interruptible: a stalled data or Pong write can delay processing incoming close frames or a queued close() call.
1# WebSocket
2
3Implements the WHATWG [WebSockets Standard](https://websockets.spec.whatwg.org/) client API
4(`new WebSocket()`, `send()`, `close()`, `onopen/onmessage/onerror/onclose`, and `CloseEvent`)
5over RFC 6455, using an in-tree decoder and fragment assembler, `fastwebsockets` for
6masked writes, and the same rustls (webpki roots, ring) TLS configuration as `fetch`.
7
8## Behavior notes
9
10- Framing runs on the Tokio runtime shared with fetch and filesystem work (`avril_host::Host`). The read half lives in its
11  own task because decoding consumes frame bytes across await points and is therefore not
12  cancellation-safe; received messages and control frames cross to the connection task over
13  a bounded channel. Reads stop when the guest falls behind (TCP back-pressure).
14- Incoming messages are limited to 16 MiB, including the total of all fragments. A message
15  over this limit fails the connection with close code 1009, before reading the excess payload.
16- Sends are queued in call order and awaited only for the write acknowledgement, so
17  `bufferedAmount` — not the socket — is the user-visible back-pressure signal, as in
18  browsers.
19- The close handshake waits up to 30 seconds (RFC 6455 §7.1.5's "reasonable time") for the
20  peer's close frame before giving up and reporting an abnormal closure (1006).
21- After exchanging close frames, the client waits up to two seconds for the server to close
22  TCP. If the server does not close, the client closes its own transport; either outcome is
23  reported as a clean close.
24- Close codes reported in `close` events follow RFC 6455 §7.1.5: the code of the first close
25  frame received (1005 when it carries none), or 1006 when no close frame was received.
26- `close()` before the connection is established fails the connection (error then close)
27  rather than racing the handshake. The cancellation reaches the connection loop whatever it
28  is doing at the time, so a connection that completes its handshake in the same instant still
29  reports the failure (error then close, without `open`) instead of an open socket.
30- The event pump keeps a strong reference to the socket until its close event, so a socket
31  with listeners is never collected while open (per the spec's GC rules); a socket without
32  listeners is also held, which is permitted but not required.
33- Permission checks happen synchronously in the constructor, as in `fetch`.
34
35## not implemented
36
37- `permessage-deflate` (no `Sec-WebSocket-Extensions` is offered; a response offering one
38  fails the connection)
39- proxy support, cookies, HSTS, and the other fetch integrations beyond permission checks
40- `Sec-WebSocket-Protocol` is validated against the response, but `Sec-WebSocket-Extensions`
41  negotiation is absent
42- the `Origin` request header (there is no ambient origin in this runtime)
43- connection-establishment timeouts (a black-holed server is governed by the OS TCP timeouts)
44- ordinary writes are not interruptible: a stalled data or Pong write can delay processing
45  incoming close frames or a queued `close()` call.