Middleware
Middleware biedt een mechanisme om HTTP-verzoeken te inspecteren en te filteren voordat ze je routehandler bereiken.
Wat is middleware?
Middleware in Finch is een klasse die zich tussen het inkomende HTTP-verzoek en je routehandler (controller of index-functie) bevindt. Hiermee kun je logica uitvoeren voordat een verzoek je controller bereikt — bijvoorbeeld het controleren van headers, het loggen van verzoeken, het valideren van tokens, het aanpassen van verzoekparameters of het blokkeren van ongeautoriseerde toegang.
Elke middlewareklasse moet de abstracte klasse Middleware uitbreiden en de handle()-methode implementeren.
Hoe middleware werkt
Wanneer een verzoek overeenkomt met een route waaraan middleware is gekoppeld, voert Finch de middleware-pipeline in volgorde uit voordat het verzoek wordt doorgegeven aan de controller of index-functie. De flow is:
- Route-patroonmatching
- Validatie van HTTP-methode, host en poort
- Authenticatiecontrole (
auth) - Uitvoering van middleware (voert de
handle()-methode van elke middleware achtereenvolgens uit) - Permissiecontrole
- Uitvoering van controller/index
Retourwaarden
De handle()-methode retourneert een Future<String?>:
- Retourneer
null— het verzoek gaat door en vervolgt naar de volgende middleware of naar de routehandler. - Retourneer een niet-lege
String— het verzoek wordt geblokkeerd. De middleware-pipeline stopt en de route wordt niet gematcht.
Als er meerdere middlewares aan een route zijn gekoppeld, worden ze na elkaar uitgevoerd. Als een middleware een niet-lege string retourneert, worden de overige middlewares overgeslagen en wordt het verzoek geweigerd.
Een middleware aanmaken
Om een middleware aan te maken, breid je de klasse Middleware uit en override je de handle()-methode:
import 'package:finch/finch_route.dart';
class MyMiddleware extends Middleware {
@override
Future<String?> handle() async {
// Jouw logica hier
return null; // Sta het verzoek toe om door te gaan
}
}
Toegang tot het verzoek
Binnen een middleware heb je toegang tot het huidige verzoek via rq (aangeboden door de basisklasse Middleware):
class LogMiddleware extends Middleware {
@override
Future<String?> handle() async {
print('Request: ${rq.method} ${rq.uri.path}');
return null;
}
}
Parameters toevoegen
Je kunt gegevens in het verzoek injecteren vanuit een middleware met rq.addParam(). Deze gegevens zijn vervolgens beschikbaar voor controllers en sjablonen verderop in de keten:
class TestMiddleware extends Middleware {
@override
Future<String?> handle() async {
rq.addParam('middleware', 'Test Middleware Active');
return null;
}
}
Een verzoek blokkeren
Retourneer een niet-lege string om te voorkomen dat het verzoek de routehandler bereikt:
class ApiKeyMiddleware extends Middleware {
@override
Future<String?> handle() async {
// rq.headers retourneert Dart's HttpHeaders
// lees een specifieke headerwaarde met ['header-name']?.first
final apiKey = rq.headers['x-api-key']?.first ?? '';
if (apiKey != 'my-secret-key') {
rq.renderError(401, message: 'Invalid API key');
return 'Unauthorized'; // Blokkeert het verzoek
}
return null; // Sta het verzoek toe om door te gaan
}
}
Middleware koppelen aan routes
Er zijn twee manieren om middleware aan een route te koppelen.
1. Via de constructor
Geef een lijst met middleware-instanties door aan de middlewares-parameter van FinchRoute:
final testMiddleware = TestMiddleware();
final logMiddleware = LogMiddleware();
FinchRoute(
path: '/info',
index: homeController.info,
middlewares: [testMiddleware, logMiddleware],
);
2. Via de fluent API
Gebruik de .middleware()-methode om middleware aan een route te koppelen (chaining):
FinchRoute(
path: '/info',
index: homeController.info,
).middleware(TestMiddleware()).middleware(LogMiddleware());
Middleware op bovenliggende routes
Wanneer middleware wordt gekoppeld aan een bovenliggende route met children, wordt de middleware uitgevoerd voordat een kindroute wordt verwerkt. Hiermee kun je gedeelde logica (zoals authenticatie of logging) toepassen op een hele groep routes:
FinchRoute(
path: '/admin',
middlewares: [AdminMiddleware()],
children: [
FinchRoute(
path: '/dashboard',
index: adminController.dashboard,
),
FinchRoute(
path: '/users',
index: adminController.users,
),
],
);
In dit voorbeeld wordt AdminMiddleware uitgevoerd vóór elk verzoek naar /admin/dashboard of /admin/users.
Praktijkvoorbeelden
Rate-limiting-middleware
class RateLimitMiddleware extends Middleware {
static final Map<String, List<DateTime>> _requests = {};
final int maxRequests;
final Duration window;
RateLimitMiddleware({
this.maxRequests = 100,
this.window = const Duration(minutes: 1),
});
@override
Future<String?> handle() async {
final ip = rq.clientIP;
final now = DateTime.now();
_requests[ip] = (_requests[ip] ?? [])
..removeWhere((t) => now.difference(t) > window)
..add(now);
if (_requests[ip]!.length > maxRequests) {
rq.renderError(429, message: 'Too many requests');
return 'Rate limit exceeded';
}
return null;
}
}
CORS-middleware
class CorsMiddleware extends Middleware {
final String allowedOrigin;
CorsMiddleware({this.allowedOrigin = '*'});
@override
Future<String?> handle() async {
rq.response.headers.add('Access-Control-Allow-Origin', allowedOrigin);
rq.response.headers.add('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
rq.response.headers.add('Access-Control-Allow-Headers', 'Content-Type, Authorization');
return null;
}
}
Onderhoudsmodus-middleware
class MaintenanceMiddleware extends Middleware {
final bool isEnabled;
MaintenanceMiddleware({this.isEnabled = false});
@override
Future<String?> handle() async {
if (isEnabled) {
rq.renderError(503, message: 'Service is under maintenance');
return 'Maintenance mode';
}
return null;
}
}
Middleware versus Auth Controller
| Kenmerk | Middleware | Auth Controller |
|---|---|---|
| Doel | Algemene verzoekfiltering | Authenticatie & autorisatie |
| Uitvoeringsvolgorde | Na auth, vóór permissies | Vóór middleware |
| Retourtype | Future<String?> |
Future<bool> |
| Kan verzoek aanpassen | Ja (rq.addParam) |
Ja |
| Kan fouten renderen | Ja | Ja |
| Meerdere per route | Ja (lijst) | Eén per route |
| Overgeërfd door kinderen | Ja | Ja |
Gebruik Auth Controller wanneer je volledige authenticatie en sessiebeheer nodig hebt. Gebruik Middleware voor al het andere — logging, headervalidatie, rate limiting, CORS, feature flags, enz.
Volledig voorbeeld
Hier is een volledig voorbeeld van het gebruik van middleware in een Finch-applicatie:
1. Middlewareklassen aanmaken:
// lib/middleware/test_middleware.dart
import 'package:finch/finch_route.dart';
class TestMiddleware extends Middleware {
@override
Future<String?> handle() async {
rq.addParam('middleware', 'Test Middleware Active');
return null;
}
}
2. Middleware registreren in je routes:
// lib/route/web_route.dart
import '../middleware/test_middleware.dart';
final testMiddleware = TestMiddleware();
List<FinchRoute> routes = [
FinchRoute(
key: 'root.info',
path: 'info',
extraPath: ['api/info'],
index: homeController.info,
middlewares: [testMiddleware],
),
];
3. Toegang tot middleware-gegevens in je controller:
class HomeController extends Controller {
Future<String> info() async {
final middlewareParam = rq.get('middleware');
return rq.renderString(text: 'Middleware says: $middlewareParam');
}
}
API-referentie
Middleware (abstracte klasse)
| Lid | Type | Beschrijving |
|---|---|---|
rq |
Request (getter) |
Toegang tot het huidige HTTP-verzoekobject |
handle() |
Future<String?> |
Override om middlewarelogica te implementeren. Retourneer null om door te laten, een niet-lege string om te blokkeren. |
Middleware-leden van FinchRoute
| Lid | Type | Beschrijving |
|---|---|---|
middlewares |
List<Middleware> |
Lijst met middleware-instanties die aan de route zijn gekoppeld |
middleware(m) |
FinchRoute |
Fluent-methode om een middleware toe te voegen; retourneert de route voor chaining |
handleMiddlewares() |
Future<bool> |
Voert alle middleware in volgorde uit; retourneert true als alles is geslaagd |