AkurAI Build
Menu

Public repository / 439f0dcabf5cdf2b120c7724be895cf1df30507f

AkurAI-Framework / site/content/docs/realtime.md

Latest change 5458532ad0ba451030d0554d03be8ff383ccddb8 - feat(ws,http): pure-std WebSocket (RFC 6455) + Server-Sent Events by Ólafur Búi Ólafsson

# Realtime: WebSocket & SSE

The framework ships two ways to push data to a connected client, both written
from scratch on `std` with zero runtime dependencies: **WebSocket** (RFC 6455,
bidirectional) and **Server-Sent Events** (the WHATWG `text/event-stream` spec,
server→client). Both follow the standards to the letter — a browser's native
`WebSocket` and `EventSource` talk to them directly, no client library needed.

## How it works under the hood

A normal handler returns a `Response`. A realtime handler instead returns a
`Reply::Upgrade`: the server writes the response head, then hands the raw socket
to a closure that owns it for the rest of the connection. That closure runs on a
**dedicated thread**, so a long-lived stream never ties up a request-pool worker.
TLS still terminates at the edge (nginx/Caddy); the server speaks plain HTTP.

## Server-Sent Events

A one-way stream of text events. Ideal for live counters, notifications, logs,
progress, and live-reload — anything the server pushes and the client only reads.
The browser reconnects automatically.

```rust
use akurai_http::{sse, Reply};

fn events() -> Reply {
    sse(|mut sink| {
        let mut n = 0;
        loop {
            n += 1;
            sink.event("tick", &n.to_string())?; // event: tick\ndata: N\n\n
            std::thread::sleep(std::time::Duration::from_secs(1));
        }
    })
}
```

The `SseSink` handles the wire format: `data`, named `event`, `id`, `retry`, and
`comment` (for keep-alive heartbeats). Multi-line data is split into multiple
`data:` lines per the spec, and every send is flushed so events arrive
immediately. When the client disconnects the next write fails and the closure
returns — the thread ends cleanly.

On the client, it is one line:

```js
const es = new EventSource("/api/events");
es.addEventListener("tick", (e) => console.log("tick", e.data));
```

The built-in demo endpoint is `GET /api/events`.

## WebSocket

Full-duplex messages over a single connection. Use it for chat, collaborative
editing, live dashboards, and anything where the client also sends.

```rust
use akurai_ws::{upgrade, Message};

// in a handler returning `akurai_http::Reply`:
upgrade(req, |mut conn| {
    while let Some(msg) = conn.recv()? {
        match msg {
            Message::Text(t) => conn.send_text(&t)?,   // echo
            Message::Binary(b) => conn.send_binary(&b)?,
            Message::Close(_) => break,
            _ => {}
        }
    }
    Ok(())
})
```

`upgrade` performs the RFC 6455 handshake — it validates the `Upgrade`,
`Connection`, `Sec-WebSocket-Key`, and `Sec-WebSocket-Version` headers and
answers with the SHA-1 + base64 `Sec-WebSocket-Accept` token, or returns `400`
if the request is not a valid WebSocket upgrade.

`WsConn` does the rest of the protocol for you:

- **Framing & masking** — client frames must be masked (and are rejected if not);
  server frames are sent unmasked, per spec.
- **Fragmentation** — multi-frame messages are reassembled before you see a
  `Text` or `Binary`.
- **Ping/pong** — pings are answered with pongs automatically.
- **Close handshake** — a peer close is echoed and surfaced as `Message::Close`.
- **Validation** — text is UTF-8 checked (close `1007` on violation); oversized
  frames close `1009`; protocol errors close `1002`.

On the client:

```js
const ws = new WebSocket("ws://localhost:8090/api/ws");
ws.onmessage = (e) => console.log("echo:", e.data);
ws.onopen = () => ws.send("hello");
```

The built-in demo endpoint is `GET /api/ws` (an echo server).

## Choosing between them

- **SSE** — server→client only, text, auto-reconnect, trivial client. Reach for
  it first; most "live" features are one-directional.
- **WebSocket** — both directions, text or binary, lower overhead per message.
  Use it when the client must send too.

## Limits

Each realtime connection holds one OS thread for its lifetime — the right model
at the small/medium scale the framework targets, not for tens of thousands of
idle connections. WebSocket negotiates no extensions (no `permessage-deflate`),
and a single frame or reassembled message is capped at 16 MiB to bound memory.