Server-sent events and static files
import 'package:gisila/gisila.dart';Server-sent events
Section titled “Server-sent events”sseResponse
Section titled “sseResponse”Response sseResponse( Stream<SseEvent> events, { Duration? keepAlive = const Duration(seconds: 15), Map<String, String> headers = const {},})A text/event-stream response that sends events as they arrive.
When to use: one-way live updates to browsers (order status, notifications, progress) over plain HTTP. Use WebSockets (package:shelf_web_socket) when the client also needs to send messages on the same connection.
| Parameter | Default | Description |
|---|---|---|
events |
required | The event stream. An error on it ends the response. |
keepAlive |
15 seconds | Interval of : comment lines that keep proxies from closing idle connections. null sends none. |
headers |
{} |
Extra response headers. |
The response sets cache-control: no-cache and x-accel-buffering: no (so nginx doesn’t buffer) and turns off Shelf’s output buffering. When the client disconnects, the subscription to events is cancelled: release resources in the stream’s onCancel. Compression and ETags leave event streams alone, and the access log never reads them.
@Get('/orders/{id}/events')@RequireAuth()Future<Response> events(String id, RequestContext ctx, OrderService orders) async { final since = lastEventId(ctx.request); return sseResponse( orders.updates(id, since: since) .map((o) => SseEvent.json(o, event: 'order', id: '${o.version}')), );}Route and app timeouts measure how long the handler takes to return the Response, not how long the stream stays open, so they don’t cut an event stream short. The request’s database session is closed once the handler returns, so open your own connection for queries made while streaming.
SseEvent
Section titled “SseEvent”One server-sent event.
class SseEvent { const SseEvent({String? data, String? event, String? id, Duration? retry, String? comment}); factory SseEvent.json(Object? value, {String? event, String? id}); final String? data; final String? event; final String? id; final Duration? retry; final String? comment; String encode();}| Field | Description |
|---|---|
data |
Text sent as data: lines; line breaks become separate lines that the browser joins back with \n. |
event |
Event type, for eventSource.addEventListener(type, ...). Must be one line. |
id |
Sent back by a reconnecting browser as Last-Event-ID. Must be one line without NUL. |
retry |
How long the browser waits before reconnecting. |
comment |
A : comment line, ignored by browsers. |
SseEvent.json(value) |
An event whose data is jsonEncode(value). |
encode() |
The wire format. Throws ArgumentError for a multi-line event or id. |
lastEventId
Section titled “lastEventId”String? lastEventId(Request request)The Last-Event-ID header a reconnecting browser sent, so the stream can resume after the last event the client saw.
Static files
Section titled “Static files”StaticFilesConfig
Section titled “StaticFilesConfig”class StaticFilesConfig { const StaticFilesConfig({ String? indexFile = 'index.html', bool spaFallback = false, Duration maxAge = Duration.zero, RegExp? immutable, bool allowDotfiles = false, });}Settings for GisilaApp.serveStatic and staticFilesHandler.
When to use: to serve a built frontend or public uploads from the same process as the API. Behind nginx or a CDN, let them serve files when you can.
| Field | Default | Description |
|---|---|---|
indexFile |
'index.html' |
Served for a directory path. null serves none. |
spaFallback |
false |
Answer unknown GET paths that have no file extension and accept text/html with the root indexFile, so a client-side router can handle them. API calls and missing assets still get a 404. |
maxAge |
Duration.zero |
Cache-Control: public, max-age=... for non-HTML files. HTML always gets no-cache. |
immutable |
null |
Paths (relative to the directory) matching it get max-age=31536000, immutable. Use it for content-hashed names: RegExp(r'^assets/.*\.[0-9a-f]{8,}\.'). |
allowDotfiles |
false |
Serve names starting with a dot (.env, .git). Leave it off. |
Files carry a weak ETag and Last-Modified and answer 304 to If-None-Match / If-Modified-Since. Only GET and HEAD are served.
app.serveStatic('/', 'web/dist', config: StaticFilesConfig( spaFallback: true, maxAge: const Duration(hours: 1), immutable: RegExp(r'^assets/.*\.[0-9a-f]{8,}\.'), ));app.serveStatic('/uploads', 'storage/public');staticFilesHandler
Section titled “staticFilesHandler”Handler staticFilesHandler(String directory, [StaticFilesConfig config = const StaticFilesConfig()])A Shelf handler serving files under directory. Paths it has no file for return Router.routeNotFound, so a Router moves on to its next route. Never serves anything outside directory: .., encoded slashes and NUL are refused, and symlinks that lead out are treated as missing. No directory listings. Throws ArgumentError when directory does not exist.
When to use: to mount static files on your own Router. With GisilaApp, serveStatic is simpler.