eventStream function
- Stream<
ServerSentEvent> events, { - Duration? keepAlive = const Duration(seconds: 15),
- int status = 200,
Streams events to the client as text/event-stream.
Return it from a handler like any other value:
Future<Object?> ticks(Request request) async => eventStream(
Stream.periodic(
const Duration(seconds: 1),
(count) => ServerSentEvent.json({'tick': count}),
),
);
The connection stays open until the stream closes or the client goes away. Compared with a WebSocket this is one-way and rides on plain HTTP, which is why it survives proxies that mangle upgrades — and why it is usually the cheaper choice for a live feed nobody talks back on.
The headers matter as much as the body. Cache-Control: no-cache stops a
proxy serving one client's stream to another, and X-Accel-Buffering: no
tells nginx not to buffer — without it a stream can sit invisible until the
buffer fills, which looks exactly like a server that has hung.
keepAlive sends a comment on an idle stream so an intermediary does not
decide the connection is dead. Set it to null to send nothing.
Implementation
Response eventStream(
Stream<ServerSentEvent> events, {
Duration? keepAlive = const Duration(seconds: 15),
int status = 200,
}) {
final source = keepAlive == null ? events : _withKeepAlive(events, keepAlive);
final body = _reported(source);
return Response(
status,
body: body.map((event) => utf8.encode(event.encode())),
headers: const {
'content-type': 'text/event-stream; charset=utf-8',
'cache-control': 'no-cache',
'x-accel-buffering': 'no',
},
// Without this the adapter buffers the body and flushes it when the stream
// ends, which for a stream that never ends means never. Every event would
// arrive at once, and a progress bar would show nothing until the work it
// reports on had finished — the exact failure `x-accel-buffering` above
// prevents at the proxy, happening in this process instead.
context: const {'shelf.io.buffer_output': false},
);
}