Middleware

Middleware مکانیزمی برای بازرسی و فیلتر کردن درخواست‌های HTTP پیش از رسیدن آن‌ها به route handler شما فراهم می‌کند.

Middleware چیست؟

Middleware در فینچ کلاسی است که بین درخواست HTTP ورودی و route handler شما (کنترلر یا تابع index) قرار می‌گیرد. این کلاس به شما اجازه می‌دهد منطقی را پیش از رسیدن درخواست به کنترلرتان اجرا کنید — برای مثال، بررسی هدرها، لاگ‌گیری درخواست‌ها، اعتبارسنجی توکن‌ها، تغییر پارامترهای درخواست، یا مسدود کردن دسترسی غیرمجاز.

هر کلاس middleware باید از کلاس انتزاعی Middleware ارث‌بری کند و متد handle() را پیاده‌سازی کند.

Middleware چگونه کار می‌کند

وقتی درخواستی با مسیری که middleware به آن ضمیمه شده مطابقت پیدا کند، فینچ pipeline میان‌افزار (middleware) را به‌ترتیب پیش از عبور دادن درخواست به کنترلر یا تابع index اجرا می‌کند. جریان به این شکل است:

  1. تطابق الگوی مسیر (route pattern matching)
  2. اعتبارسنجی متد HTTP، host و port
  3. بررسی احراز هویت (auth)
  4. اجرای Middleware (متد handle() هر middleware را به‌ترتیب اجرا می‌کند)
  5. بررسی مجوز (permission)
  6. اجرای کنترلر/index

مقادیر بازگشتی

متد handle() یک Future<String?> برمی‌گرداند:

  • بازگرداندن null — درخواست عبور می‌کند و به middleware بعدی یا به route handler ادامه می‌یابد.
  • بازگرداندن یک String غیرخالی — درخواست مسدود می‌شود. pipeline میان‌افزار متوقف می‌شود و مسیر match نمی‌شود.

اگر چندین middleware به یک مسیر ضمیمه شده باشند، آن‌ها به‌ترتیب (sequentially) اجرا می‌شوند. اگر هر middleware‌ای یک رشته غیرخالی برگرداند، بقیه middlewareها نادیده گرفته می‌شوند و درخواست رد می‌شود.

ساخت یک Middleware

برای ساخت یک middleware، از کلاس Middleware ارث‌بری کنید و متد handle() را override کنید:

import 'package:finch/finch_route.dart';

class MyMiddleware extends Middleware {
  @override
  Future<String?> handle() async {
    // منطق شما اینجا
    return null; // اجازه ادامه درخواست
  }
}

دسترسی به درخواست

درون یک middleware، می‌توانید با استفاده از rq (که توسط کلاس پایه Middleware فراهم شده) به درخواست جاری دسترسی داشته باشید:

class LogMiddleware extends Middleware {
  @override
  Future<String?> handle() async {
    print('Request: ${rq.method} ${rq.uri.path}');
    return null;
  }
}

افزودن پارامترها

می‌توانید داده را از یک middleware با استفاده از rq.addParam() به درخواست تزریق کنید. این داده برای کنترلرها و قالب‌های پایین‌دستی (downstream) در دسترس خواهد بود:

class TestMiddleware extends Middleware {
  @override
  Future<String?> handle() async {
    rq.addParam('middleware', 'Test Middleware Active');
    return null;
  }
}

مسدود کردن یک درخواست

برای جلوگیری از رسیدن درخواست به route handler، یک رشته غیرخالی برگردانید:

class ApiKeyMiddleware extends Middleware {
  @override
  Future<String?> handle() async {
    // rq.headers شیء HttpHeaders دارت را برمی‌گرداند
    // یک مقدار هدر خاص را با ['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'; // درخواست را مسدود می‌کند
    }
    return null; // اجازه ادامه درخواست
  }
}

ضمیمه کردن Middleware به مسیرها

دو روش برای ضمیمه کردن middleware به یک مسیر وجود دارد.

۱. استفاده از Constructor

یک لیست از نمونه‌های middleware را به پارامتر middlewares در FinchRoute پاس دهید:

final testMiddleware = TestMiddleware();
final logMiddleware = LogMiddleware();

FinchRoute(
  path: '/info',
  index: homeController.info,
  middlewares: [testMiddleware, logMiddleware],
);

۲. استفاده از Fluent API

از متد .middleware() برای زنجیر کردن middleware به یک مسیر استفاده کنید:

FinchRoute(
  path: '/info',
  index: homeController.info,
).middleware(TestMiddleware()).middleware(LogMiddleware());

Middleware روی مسیرهای والد

وقتی middleware به یک مسیر والد که دارای children است ضمیمه شود، middleware پیش از پردازش هر مسیر فرزند اجرا می‌شود. این به شما اجازه می‌دهد منطق مشترک (مانند احراز هویت یا لاگ‌گیری) را روی کل یک گروه از مسیرها اعمال کنید:

FinchRoute(
  path: '/admin',
  middlewares: [AdminMiddleware()],
  children: [
    FinchRoute(
      path: '/dashboard',
      index: adminController.dashboard,
    ),
    FinchRoute(
      path: '/users',
      index: adminController.users,
    ),
  ],
);

در این مثال، AdminMiddleware پیش از هر درخواست به /admin/dashboard یا /admin/users اجرا خواهد شد.

مثال‌های عملی

Middleware محدودسازی نرخ (Rate Limiting)

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;
  }
}

Middleware CORS

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;
  }
}

Middleware حالت تعمیر (Maintenance Mode)

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 در برابر Auth Controller

ویژگی Middleware Auth Controller
هدف فیلتر کردن درخواست به‌صورت general-purpose احراز هویت و مجوزدهی
ترتیب اجرا پس از auth، پیش از permissions پیش از middleware
نوع بازگشتی Future<String?> Future<bool>
امکان تغییر درخواست بله (rq.addParam) بله
امکان رندر خطا بله بله
چند مورد در هر مسیر بله (لیست) یک مورد در هر مسیر
به ارث‌رسیده توسط فرزندان بله بله

از Auth Controller زمانی استفاده کنید که به احراز هویت کامل و مدیریت session نیاز دارید. از Middleware برای هر چیز دیگری استفاده کنید — لاگ‌گیری، اعتبارسنجی هدر، محدودسازی نرخ (rate limiting)، CORS، feature flagها و غیره.

مثال کامل

در ادامه یک مثال کامل از استفاده از middleware در یک اپلیکیشن فینچ آمده است:

۱. ساخت کلاس‌های middleware:

// 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;
  }
}

۲. ثبت middleware در مسیرهای شما:

// 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],
  ),
];

۳. دسترسی به داده middleware در کنترلر شما:

class HomeController extends Controller {
  Future<String> info() async {
    final middlewareParam = rq.get('middleware');
    return rq.renderString(text: 'Middleware says: $middlewareParam');
  }
}

مرجع API

Middleware (کلاس انتزاعی)

عضو نوع توضیح
rq Request (getter) دسترسی به شیء درخواست HTTP جاری
handle() Future<String?> برای پیاده‌سازی منطق middleware override کنید. برای عبور null و برای مسدود کردن یک رشته غیرخالی برگردانید.

اعضای Middleware در FinchRoute

عضو نوع توضیح
middlewares List<Middleware> لیستی از نمونه‌های middleware ضمیمه‌شده به مسیر
middleware(m) FinchRoute متد fluent برای افزودن یک middleware؛ مسیر را برای زنجیره‌سازی (chaining) برمی‌گرداند
handleMiddlewares() Future<bool> تمام middlewareها را به‌ترتیب اجرا می‌کند؛ اگر همه موفق بودند true برمی‌گرداند