Skip to main content

Module sse

Module sse 

Source
Expand description

Server-Sent Events: a response that sends events as they happen (Rails’ ActionController::Live with SSE).

A handler returns stream()(state, step): Ocre calls step for each event, and the Worker streams every event to the client as soon as it is produced, until step returns None. The response is axum’s Sse (Content-Type: text/event-stream, Cache-Control: no-cache), which browsers read with new EventSource(url) or htmx’s SSE extension. Pace events with sleep.

Streams fit work that one request follows from start to end: the progress of an import, tokens from an AI model, a countdown. To push changes to many open pages (a new comment), use realtime channels, which do not keep a request open per page.

§Free plan

  • CPU: only the work of each step counts toward the 10 ms per request; waiting in sleep or on I/O does not.
  • Duration: a response may stream for as long as the client stays connected; when the client goes away, the stream stops at the next event.
  • One invocation: the whole stream is one request. Its binding calls count toward the per-request limits (50 subrequests on the free plan, D1 queries included), so a stream cannot poll D1 every second for minutes; end it and let the client reconnect, or use realtime.
  • Reconnects: EventSource reconnects about 3 s after a stream ends, and each reconnect is a new request (100,000 a day on the free plan). Send a last event (e.g. event: done) on which the page calls source.close(), or set Event::retry to reconnect less often.

§Examples

use std::time::Duration;

use axum::{Router, routing::get};
use ocre::{Ctx, sse::{self, Event}};

pub fn routes() -> Router<Ctx> {
    Router::new().route("/countdown", get(countdown))
}

/// `data: 3`, `data: 2`, `data: 1` one second apart, then `event: done`.
async fn countdown() -> impl axum::response::IntoResponse {
    sse::stream(Some(3), |left: Option<u32>| async move {
        let left = left?;
        ocre::sleep(Duration::from_secs(1)).await;
        Some(match left {
            0 => (Event::default().event("done").data(""), None),
            n => (Event::default().data(n.to_string()), Some(n - 1)),
        })
    })
}

In the page: const source = new EventSource("/countdown"); source.addEventListener("done", () => source.close());.

Structs§

Event
Server-sent event
Sse
An SSE response

Functions§

stream
An event stream: step(state) gives the next event and the next state, or None to end the stream.