Request lifecycle
What it is
Section titled “What it is”A gisila app is one Shelf handler built by GisilaApp.buildHandler(). It is a fixed stack of framework middleware, your middleware, a router, and for each route a small per-route pipeline that ends in the code the generator wrote for your controller method. Knowing the order tells you what runs before what, which requests reach the database, and where errors are caught.
When to use it
Section titled “When to use it”Read this when you need to decide:
- where to put a piece of cross-cutting behaviour (global middleware, named middleware, a service);
- why a request was rejected before your code ran (CORS, rate limit, body size, auth);
- whether a request opened a database session or was logged;
- which timeout or limit wins when you set both app-wide and per route.
When not to use it
Section titled “When not to use it”You don’t need any of this to write ordinary controllers: annotate methods, return values, throw HttpExceptions. Start with Controllers and come back when something surprises you.
How it works
Section titled “How it works”socket │ ▼ App pipeline (installed by buildHandler, outermost first) 1 security headers ............ AppConfig.securityHeaders (on by default) 2 request id + RequestContext .. AppConfig.requestIdHeader 3 health check ................ AppConfig.healthCheckPath (answers here) 4 metrics + /metrics .......... AppConfig.metrics 5 gzip ........................ AppConfig.compression 6 ETags ....................... AppConfig.etags 7 cache on ctx ................ AppConfig.cache 8 CORS (answers pre-flights) .. AppConfig.cors 9 app rate limit (429) ........ AppConfig.rateLimit 10 body size limit (413) ....... AppConfig.maxRequestBodyBytes 11 access log .................. AppConfig.logRequests 12 error mapper ................ exceptions → JSON envelope 13 database session ............ AppConfig.database (open … close(ok)) 14 your app.use() middleware 15 Server / X-Powered-By headers 16 app timeout (504) ........... AppConfig.requestTimeout 17 router ...................... no match → JSON 404, then static mounts │ ▼ Route pipeline (gisilaRoute, from the merged RouteConfig) a CORS override b route rate limit (429) c content-type whitelist (415) d route body cap (413) e RouteConfig.middleware + @UseMiddleware f route timeout (504) g auth guard: authenticator → 401, permission resolver → 403 │ ▼ Generated handler bind parameters (path, query, header, body, Form, Service, ctx) call your method render the result (Response as-is, void → empty, else JSON)Some consequences:
- Early rejections are cheap. Pre-flights, rate-limited requests and oversized bodies never open a database session and never reach your code.
- Errors are caught in one place. Anything thrown below step 12 (your middleware, services, controllers, binding) becomes a JSON error. The steps above it answer their own errors (
429,413,403for a refused pre-flight) and are not routed through the error mapper orerrorHandler. - Limits stack. An app-wide rate limit, timeout or body cap and a per-route one both apply. The tighter one decides.
- Authentication is per route. The authenticator runs at step g, only for routes with a guard. On unguarded and
@Publicroutes,ctx.principalisnull. - The database session wraps your middleware and handler. It is opened before
app.use()middleware runs and closed after the response is built, committed when the status is below 500.
The request context
Section titled “The request context”Step 2 creates a RequestContext and attaches it to the Shelf request. Later steps add to it: the cache (7), the database session (13), the service and singleton resolvers, the principal (g). Your method receives it by declaring a RequestContext parameter, and services get it as ctx. Use ctx.extra to pass data from your middleware to services.
Timeouts and cancellation
Section titled “Timeouts and cancellation”A timeout at step 16 or f answers 504 immediately, but Dart can’t abort the running handler. The framework cancels ctx.cancellation and records the handler as abandoned; the database session is closed (rolled back) only once the handler finishes. Long handlers should check ctx.cancellation.throwIfCancelled() between steps.
Startup and shutdown
Section titled “Startup and shutdown”start() calls init() (connect the cache, create factory singletons, run onStart hooks), builds the handler and binds the socket. stop() stops accepting connections, waits up to drainTimeout for in-flight requests, runs onStop hooks in reverse, disposes singletons in reverse, disconnects the cache and disposes the database provider.
Example
Section titled “Example”A request POST /orders with a JSON body, on a route with @RequireAuth() and config: RouteConfig(rateLimit: ...):
- Gets a request id, passes CORS and the app limits.
- Opens a database session.
- Passes your tenant middleware from
app.use(). - Matches the route; passes the route rate limit.
- The JWT authenticator verifies the token and sets
ctx.principal. - The generated handler binds
OrderForm(400 on validation errors) and resolvesOrderService. OrderService.placethrowsConflict('Duplicate order').- The error mapper renders
409; the session closes withok: true(409 < 500), so it commits. - The access log writes one line with status 409, and the response leaves with the request id and security headers.