Skip to content

RequestContext

import 'package:gisila/gisila.dart';

Per-request state managed by the framework. The request-id middleware creates one for every request; later middleware and the route fill it in.

class RequestContext {
RequestContext({
required String requestId,
required Request request,
Principal? principal,
Map<String, Object?>? extra,
});
final String requestId;
final Request request;
Principal? principal;
final Map<String, Object?> extra;
bool get isAuthenticated;
T db<T>();
CacheProvider get cache;
T cacheAs<T extends CacheProvider>();
CancellationToken get cancellation;
void addAbandonedWork(Future<Object?> work);
List<Future<void>> get abandonedWork;
T service<T extends Object>();
T singleton<T extends Object>();
static RequestContext? maybeOf(Request request);
}

When to use: declare a RequestContext parameter on a controller method when you need the principal, the request id, the raw request or the database session. Inside a Service it is available as ctx. In Shelf middleware, get it with RequestContext.maybeOf(request).

Member Type Description
requestId String The id from the request-id middleware: the incoming X-Request-Id (when it is 1 to 128 characters of [A-Za-z0-9._:-]) or a random 16-character id. Echoed on the response.
request Request The Shelf request. In a controller, this is the request as it reached the route.
principal Principal? The authenticated caller. Set by the route’s auth guard, so it is only populated on routes that carry a guard (@RequireAuth, @Permissions, @AnyPermission, @Roles, or the matching RouteConfig fields). On unguarded and @Public routes it is null even when the client sent credentials.
extra Map<String, Object?> Scratch space shared by middleware, services and handlers for this request. Framework keys start with gisila..
isAuthenticated bool principal != null.
T db<T>()

Returns the per-request database session opened by AppConfig.database, cast to T (for gisila_orm, ctx.db<Database>()). Throws StateError when no database is configured.

CacheProvider get cache

The configured CacheProvider. Throws StateError when AppConfig.cache is not set.

final hit = await ctx.cache.get('user:$id');
await ctx.cache.set('user:$id', jsonEncode(user), ttl: const Duration(minutes: 5));
T cacheAs<T extends CacheProvider>()

The cache cast to its concrete type (for backend-specific operations). Throws TypeError when the provider is another type.

CancellationToken get cancellation

Cancelled when the framework gives up on the request (a route or app timeout fired). Dart futures can’t be aborted, so the handler keeps running; check the token between steps of long work.

void addAbandonedWork(Future<Object?> work)
List<Future<void>> get abandonedWork

The timeout middleware records the still-running handler here. The database session middleware waits for this work before closing the session, and then closes it with ok: false (rolled back). You only call addAbandonedWork if you write your own timeout-like middleware.

T service<T extends Object>()

Returns the request’s instance of a service registered with app.registerService<T>(), creating it on first use and caching it for the rest of the request. Throws StateError when no factory is registered.

T singleton<T extends Object>()

Returns the app-wide singleton registered as T. Throws StateError when none is registered.

static RequestContext? maybeOf(Request request)

Reads the context attached to request, or null when there is none (outside a gisila pipeline).

Example

@Get('/me')
@RequireAuth()
Future<Map<String, Object?>> me(RequestContext ctx) async => {
'id': ctx.principal!.id,
'roles': ctx.principal!.roles.toList(),
'requestId': ctx.requestId,
};
// In middleware:
Middleware tenantMiddleware() => (inner) => (request) {
final tenant = request.headers['x-tenant-id'];
if (tenant == null) throw BadRequest('Missing X-Tenant-Id');
RequestContext.maybeOf(request)?.extra['tenantId'] = tenant;
return inner(request);
};

See also: Request lifecycle, Services and dependency scopes.

Cooperative cancellation signal for one request.

class CancellationToken {
bool get isCancelled;
Future<void> get whenCancelled;
void cancel();
void throwIfCancelled();
}

When to use: in handlers that do several slow steps (batch writes, calls to other services). Once cancelled, the client already has its 504 and any database work will be rolled back, so stop early.

Member Description
isCancelled true once cancelled.
whenCancelled Completes when cancelled.
cancel() Cancels the token (the timeout middleware calls it). Idempotent.
throwIfCancelled() Throws RequestCancelledException when cancelled.
@Post('/reindex', config: RouteConfig(timeout: Duration(seconds: 30)))
Future<void> reindex(RequestContext ctx, SearchService search) async {
for (final batch in await search.batches()) {
ctx.cancellation.throwIfCancelled();
await search.index(batch);
}
}
class RequestCancelledException implements Exception {
const RequestCancelledException();
}

Thrown by CancellationToken.throwIfCancelled(). The client has already been answered by then, so what happens to it afterwards only affects logs.

Constants for the framework’s entries in request.context and RequestContext.extra. You need them only when writing low-level middleware.

Constant Value Holds
kContextKey 'gisila.context' The RequestContext in Shelf’s request.context. Use RequestContext.maybeOf instead.
kServiceResolverKey 'gisila.service_resolver' The service resolver installed by GisilaApp (in extra).
kSingletonResolverKey 'gisila.singleton_resolver' The singleton resolver installed by GisilaApp (in extra).
kCancellationKey 'gisila.cancellation' The request’s CancellationToken (in extra).
kAbandonedWorkKey 'gisila.abandoned_work' Abandoned handler work (in extra).
kDbKey 'gisila.db.session' The database session (in extra). See Database and cache.
kCacheKey 'gisila.cache' The cache provider (in extra).
kRouteTemplateKey 'gisila.route_template' The matched route’s path template, used by metrics (in extra).