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:

  1. Route-patroonmatching
  2. Validatie van HTTP-methode, host en poort
  3. Authenticatiecontrole (auth)
  4. Uitvoering van middleware (voert de handle()-methode van elke middleware achtereenvolgens uit)
  5. Permissiecontrole
  6. 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