مسیریابی

مسیرها در فینچ با استفاده از اشیاء FinchRoute که درون یک تابع مسیریابی جمع‌آوری شده‌اند، تعریف می‌شوند. تابع مسیریابی با app.addRouting() ثبت شده و برای هر درخواست ورودی فراخوانی می‌شود.

تابع مسیریابی

import 'package:finch/finch_route.dart';
import '../controllers/home_controller.dart';

final homeController = HomeController();

Future<List<FinchRoute>> getWebRoute() async {
  return [
    FinchRoute(
      key: 'root',
      path: '/',
      methods: Methods.ONLY_GET,
      index: homeController.index,
    ),
  ];
}

آن را در app.dart ثبت کنید:

app.addRouting(getWebRoute);

پارامترهای FinchRoute

پارامتر نوع توضیح
key String شناسه منحصربه‌فرد مسیر. برای تولید URL استفاده می‌شود ($e.routeUrl('key'))
path String بخش مسیر URL. از {param} یا :param و wildcard * پشتیبانی می‌کند
methods List<String> متدهای HTTP مجاز. از ثابت‌های Methods استفاده کنید
index Future<String> Function()? تابع handler (بدون پارامتر rq؛ از Context.rq یا متد controller استفاده کنید)
controller Controller? نمونه controller. وقتی index تنظیم نشده باشد، index() آن فراخوانی می‌شود
children List<FinchRoute> مسیرهای فرزند تودرتو. مسیر نسبت به والد است
extraPath List<String> مسیرهای اضافی که به همین مسیر map می‌شوند
auth AuthController? نگهبان احراز هویت برای این مسیر
permissions List<String> رشته‌های دسترسی که پس از auth بررسی می‌شوند
middlewares List<Middleware> زنجیره middleware که قبل از handler اجرا می‌شود
hosts List<String> محدود کردن به hostnameهای خاص (['*'] = همه)
ports List<int> محدود کردن به پورت‌های خاص ([] = همه)
params Map<String, dynamic> پارامترهای پیش‌فرض قالب
excludePaths List<String> زیرمسیرهایی که از این مسیر مستثنا شده‌اند
apiDoc Future<ApiDoc>? Function()? مستندات API برای Swagger
widget String مسیر یک فایل قالب برای رندر مستقیم، بدون نیاز به controller یا index (به بخش «مسیرهای Widget» در پایین مراجعه کنید)
title String عنوان صفحه، در قالب‌ها به‌صورت {{ $e.pageTitle }} در دسترس است

از میان widget، index یا controller فقط یکی را برای هر مسیر استفاده کنید. اگر بیش از یکی تنظیم شود، ابتدا widget بررسی می‌شود، سپس index، و در نهایت controller.index() به‌عنوان جایگزین.

متدهای HTTP

// ثابت‌های دسترسی از کلاس Methods:
Methods.ONLY_GET    // ['GET']
Methods.ONLY_POST   // ['POST']
Methods.ONLY_PUT    // ['PUT']
Methods.ONLY_DELETE // ['DELETE']
Methods.GET_POST    // ['POST', 'GET']
Methods.ALL         // تمام متدهای HTTP استاندارد

// یا یک لیست سفارشی:
methods: [Methods.GET, Methods.POST, Methods.DELETE]

پارامترهای مسیر

از دستور {name} برای ضبط بخش‌های URL استفاده کنید. آن‌ها را در controller با rq.getParam('name') بخوانید:

FinchRoute(
  key: 'users.show',
  path: 'users/{id}', // یا 'users/:id'
  methods: Methods.ONLY_GET,
  index: userController.show,
),

نکته: بخش‌های پویای مسیر می‌توانند با استفاده از هر دو syntax {param} یا :param تعریف شوند. هر دو پشتیبانی می‌شوند و عملکرد یکسانی دارند.

// در UserController:
Future<String> show() async {
  var id = rq.getParam('id');
  return rq.renderData(data: {'id': id});
}

مسیرهای Widget

هنگامی که یک مسیر فقط نیاز به رندر یک قالب دارد، بدون هیچ منطق سفارشی‌ای، به‌جای نوشتن یک controller، widget را به مسیر یک فایل قالب تنظیم کنید:

FinchRoute(
  key: 'about',
  path: 'about',
  title: 'About Us',
  widget: 'pages/about',
),

widget قبل از index و controller بررسی می‌شود و — برخلاف آن دو — منتظر اجرای auth مخصوص به خودِ این مسیر نمی‌ماند؛ فقط دسترسی‌های (permissions) به‌ارث‌رسیده از یک مسیر والد که از قبل احراز هویت شده بررسی می‌شوند. اگر یک مسیر widget به احراز هویت مخصوص به خودش نیاز دارد، به‌جای تکیه بر فیلد auth خودِ مسیر widget، auth را به یک مسیر والد ضمیمه کنید، یا از index/controller استفاده کنید تا بررسی auth به‌صورت عادی اجرا شود.

مسیرهای Wildcard و استثناها

مسیری که با * پایان می‌یابد، خودِ مسیر و هر زیرمسیر زیر آن را match می‌کند — این برای handlerهای catch-all مانند fallback یک single-page app یا یک پراکسی فایل عمومی مفید است:

FinchRoute(
  key: 'spa.fallback',
  path: 'app/*',
  index: homeController.spaIndex,
  excludePaths: ['app/api'], // همچنان برای این زیرمسیر به مسیرهای دیگر واگذار می‌شود
),

excludePaths زیرمسیرهایی را فهرست می‌کند که نباید توسط wildcard بلعیده شوند، بنابراین مسیرهای خاص‌تر (یا فایل‌های استاتیک) زیر wildcard همچنان قابل‌دسترسی باقی می‌مانند.

مسیرهای تودرتو (children)

مسیرهای فرزند نسبت به مسیر والد هستند:

FinchRoute(
  key: 'example',
  path: 'example',
  index: homeController.redirectToRoot,
  children: [
    FinchRoute(
      key: 'example.form.get',
      path: 'form',
      methods: Methods.ONLY_GET,
      index: homeController.exampleForm,
    ),
    FinchRoute(
      key: 'example.form.post',
      path: 'form',
      methods: Methods.ONLY_POST,
      index: authController.loginPost,
    ),
    FinchRoute(
      key: 'example.panel',
      path: 'panel',
      methods: Methods.ALL,
      auth: authController,
      permissions: ['admin'],
      index: homeController.exampleAuth,
    ),
  ],
),

مسیرهای اضافی

extraPath پیشوندهای URL اضافی را به همان مسیر و فرزندان آن map می‌کند:

FinchRoute(
  key: 'root.mysql',
  path: 'example/mysql',
  extraPath: ['api/example/mysql'],
  methods: Methods.GET_POST,
  index: homeController.exampleMysql,
),

ایجاد چندین مسیر مشابه

FinchRoute.makeList چندین مسیر ایجاد می‌کند که پیکربندی یکسانی دارند اما فقط در path تفاوت دارند — مناسب برای نام‌های مستعار (alias) یا endpointهای تقریباً یکسان:

var routes = FinchRoute.makeList(
  paths: ['users', 'members', 'people'],
  methods: Methods.ONLY_GET,
  controller: UserController(),
  auth: AppAuthController(),
  permissions: ['user.read'],
  key: 'user.alias', // تبدیل می‌شود به 'user.alias.1'، 'user.alias.2'، 'user.alias.3'
);

Middleware

یک یا چند نمونه Middleware را به یک مسیر (یا یک زنجیره fluent .middleware()) ضمیمه کنید تا منطق فیلتر کردن درخواست قبل از اجرای handler اجرا شود — لاگ‌گیری، بررسی هدر، rate limiting، CORS و دغدغه‌های cross-cutting مشابه. برای API کامل و ترتیب اجرا نسبت به auth و permissions به Middleware Guide مراجعه کنید.

فیلتر Host و Port

یک مسیر را به hostها یا پورت‌های خاص محدود کنید:

FinchRoute(
  key: 'root.localhost',
  path: 'example/host',
  hosts: ['localhost'],
  ports: [80, 8085],
  index: homeController.renderLocalhost,
  methods: Methods.ALL,
),
FinchRoute(
  key: 'root.host',
  path: 'example/host',
  ports: [80, 8085],
  hosts: ['127.0.0.1'],
  index: homeController.render127001,
  methods: Methods.ALL,
),

نگهبان احراز هویت

برای محافظت از یک مسیر، یک AuthController ضمیمه کنید. اگر auth() مقدار false برگرداند، درخواست قبل از رسیدن به handler رد می‌شود:

FinchRoute(
  key: 'admin.panel',
  path: 'admin/panel',
  auth: AppAuthController(),
  permissions: ['admin'],
  index: adminController.panel,
),

برای جزئیات پیاده‌سازی به Auth Controller مراجعه کنید.

کش مسیر

از پسوند .cache() برای کش کردن پاسخ‌ها استفاده کنید:

FinchRoute(
  key: 'root.route',
  path: 'example/route',
  methods: Methods.ONLY_GET,
  index: homeController.exampleRoute,
).cache(
  cacheDuration: Duration(minutes: 10),
  cacheType: [CacheParam.path, CacheParam.method, CacheParam.language],
  cacheSource: CacheSource.file,
),

برای جزئیات کامل به Route Cache مراجعه کنید.

تولید URL از کلیدهای مسیر

در قالب‌ها، از $e.routeUrl('key') برای تولید URL استفاده کنید:

<a href="{{ $e.routeUrl('example.panel') }}">Panel</a>
<a href="{{ $e.routeUrl('users.show', {'id': user.id}) }}">View User</a>

دریافت همه مسیرها

فهرست کامل مسیرها را در زمان اجرا دریافت کنید:

var routes = await app.getAllRoutes();

نحوه عملکرد تطابق

برای هر درخواست ورودی، فینچ فهرست مسیریابی که با app.addRouting() ثبت کرده‌اید را به‌ترتیب، از بالا به پایین، پیمایش می‌کند:

  1. فیلتر Host/Port — اگر hosts یا ports با درخواست مطابقت نداشته باشند، مسیر بلافاصله رد می‌شود.
  2. تطابق Path — مقدار path مسیر (به‌همراه هر extraPath) با مسیر درخواست مقایسه می‌شود و در این فرآیند جایگزین‌های {param}/:param و پسوندهای wildcard * تفکیک می‌شوند.
  3. اگر path مطابقت داشته باشد اما methods شامل متد HTTP درخواست نباشد، مسیر به‌عنوان یافت‌نشده در نظر گرفته می‌شود و تطابق به مسیر بعدی ادامه می‌یابد.
  4. اگر مسیر دارای children باشد، به‌محض تطابق پیشوند مسیر والد، فینچ به داخل آن‌ها بازگشت (recurse) می‌کند — auth و middlewares روی والد همچنان ابتدا اجرا می‌شوند و برای هر فرزند اعمال می‌شوند.
  5. اولین مسیری که به‌طور کامل مطابقت دارد (path، method، host، port، auth، permissions، middleware) درخواست را مدیریت می‌کند؛ هیچ مسیر دیگری امتحان نمی‌شود.
  6. اگر هیچ‌چیزی مطابقت نداشته باشد، فینچ به سرویس‌دهی یک فایل استاتیک از publicDir برمی‌گردد، و اگر آن فایل هم وجود نداشته باشد 404 برمی‌گرداند.

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