راهنمای میدلور (Middleware)

این راهنما شما را با مراحل ساخت و استفاده از میدلور در برنامه فینچ آشنا می‌کند. میدلور مکانیزمی مناسب برای بازرسی و فیلتر کردن درخواست‌های HTTP ورودی برنامه شما فراهم می‌کند.

میدلور چیست؟

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

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

نحوه کار میدلور

هنگامی که یک درخواست با مسیری که میدلور به آن متصل است مطابقت پیدا کند، فینچ پایپ‌لاین میدلور را به ترتیب قبل از ارسال درخواست به کنترلر یا تابع index اجرا می‌کند. جریان کار به صورت زیر است:

  1. تطبیق الگوی مسیر
  2. اعتبارسنجی متد HTTP، هاست و پورت
  3. بررسی احراز هویت (auth)
  4. اجرای میدلور (متد handle() هر میدلور به ترتیب اجرا می‌شود)
  5. بررسی مجوزها
  6. اجرای کنترلر/index

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

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

  • بازگرداندن null — درخواست عبور می‌کند و به میدلور بعدی یا هندلر مسیر ادامه می‌دهد.
  • بازگرداندن یک String غیرخالی — درخواست مسدود می‌شود. پایپ‌لاین میدلور متوقف شده و مسیر مطابقت نمی‌یابد.

اگر چندین میدلور به یک مسیر متصل باشند، به صورت ترتیبی اجرا می‌شوند. اگر هر میدلوری یک رشته غیرخالی برگرداند، میدلورهای باقی‌مانده نادیده گرفته شده و درخواست رد می‌شود.

ساخت میدلور

برای ساخت میدلور، کلاس Middleware را گسترش دهید و متد handle() را بازنویسی کنید:

import 'package:finch/finch_route.dart';

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

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

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

class LogMiddleware extends Middleware {
  @override
  Future<String?> handle() async {
    print('درخواست: ${rq.method} ${rq.uri.path}');
    return null;
  }
}

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

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

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

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

یک رشته غیرخالی برگردانید تا درخواست از رسیدن به هندلر مسیر مسدود شود:

class ApiKeyMiddleware extends Middleware {
  @override
  Future<String?> handle() async {
    final apiKey = rq.header('X-API-Key');
    if (apiKey != 'my-secret-key') {
      rq.renderError(401, message: 'کلید API نامعتبر');
      return 'Unauthorized'; // مسدود کردن درخواست
    }
    return null; // اجازه ادامه درخواست
  }
}

اتصال میدلور به مسیرها

دو روش برای اتصال میدلور به مسیر وجود دارد.

۱. استفاده از سازنده (Constructor)

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

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

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

۲. استفاده از API زنجیره‌ای (Fluent API)

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

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

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

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

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

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

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

میدلور محدودکننده نرخ درخواست (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: 'تعداد درخواست‌ها بیش از حد مجاز');
      return 'Rate limit exceeded';
    }
    return null;
  }
}

میدلور 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;
  }
}

میدلور حالت تعمیر و نگهداری (Maintenance Mode)

class MaintenanceMiddleware extends Middleware {
  final bool isEnabled;

  MaintenanceMiddleware({this.isEnabled = false});

  @override
  Future<String?> handle() async {
    if (isEnabled) {
      rq.renderError(503, message: 'سرویس در حال تعمیر و نگهداری است');
      return 'Maintenance mode';
    }
    return null;
  }
}

مقایسه میدلور و کنترلر احراز هویت

ویژگی میدلور کنترلر احراز هویت
هدف فیلترینگ عمومی درخواست احراز هویت و مجوزدهی
ترتیب اجرا بعد از auth، قبل از مجوزها قبل از میدلور
نوع بازگشتی Future<String?> Future<bool>
امکان تغییر درخواست بله (rq.addParam) بله
امکان نمایش خطا بله بله
تعداد در هر مسیر چندتایی (لیست) یک عدد
ارث‌بری به فرزندان بله بله

از کنترلر احراز هویت زمانی استفاده کنید که نیاز به مدیریت کامل احراز هویت و نشست‌ها دارید. از میدلور برای بقیه موارد استفاده کنید — ثبت لاگ، اعتبارسنجی هدرها، محدودیت نرخ، CORS، فلگ‌های ویژگی و غیره.

مثال کامل

در اینجا یک مثال کامل از استفاده میدلور در یک برنامه فینچ آورده شده است:

۱. ساخت کلاس‌های میدلور:

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

۲. ثبت میدلور در مسیرها:

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

۳. دسترسی به داده‌های میدلور در کنترلر:

class HomeController extends Controller {
  Future<String> info() async {
    final middlewareParam = rq.get('middleware');
    return rq.renderString(text: 'میدلور می‌گوید: $middlewareParam');
  }
}

مرجع API

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

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

اعضای میدلور در FinchRoute

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