Middleware نرم افزاری است که به منظور مدیریت درخواست ها و پاسخ ها به فرایند پردازش درخواست (Pipeline) یک برنامه اضافه می شود. هر Middleware دو وظیفه اصلی دارد:
- تصمیم می گیرد که درخواست را در فرایند پردازش (Pipeline) به بخش دیگر بفرستد یا نه.
- می تواند عملیاتی را قبل یا بعد از بخش بعدی در فرایند پردازش (Pipeline) انجام دهد.
Request delegate ها قبلا فرایند پردازش درخواست (Request Pipeline) را می ساختند. Request delegate ها درخواستی که بر بستر HTTP به سمت سرور آمده است را مدیریت می کنند.
Request Delegate ها با استفاده از Run و Map و با استفاده از Extension Method ها پیکربندی شده اند. یک Request Delegate می تواند بصورت in-line و بعنوان یک متد ناشناس ( به نام in-line middleware) مشخص شود، یا می تواند در یک کلاس قابل استفاده ی مجدد تعریف شود. این کلاس ها و متدهای ناشناس in-line، middleware یا Middleware نامیده می شوند. هر Middleware در فرایند پردازش درخواست (Request Pipeline) مسئول فراخوانی Middleware بعدی در فرایند پردازش (Pipeline) و یا اتصال کوتاه (short-circuiting) این درخواست می باشد. وقتی که یک middleware اتصال کوتاه برقرار می کند به آن Terminal Middleware می گویند زیرا از اجرا شدن Middleware بعدی برای پردازش درخواست جلوگیری می کند. در حقیقت Terminal Middleware آخرین Middleware است که برای پردازش درخواست اجرا می شود و پس از آن دیگر هیچ یک از Middleware ها اجرا نخواهند شد و پاسخ به سمت کاربر برگردادنده خواهد شد.
این مقاله تفاوت بین فرایند پردازش درخواست در ASP.NET Core و ASP.NET 4.x را شرح می دهند.
ساخت یک Middleware Pipeline با IApplicationBuilder
فرایند پردازش درخواست در ASP.NET Core شامل یک سلسله مراتب از request delegate ها می باشد، که یکی پس از دیگری اجرا می شوند. طرح زیر این مفهوم را نشان می دهد. مسیر حرکت درخواست برای اجرا شدن از فلش های زیر پیروی می کند. درخواست به اولین Middleware وارد شده و سپس به بعدی و همینطور حرکت می کند تا به آخرین Middleware که همان Terminal Middleware است برسد. پس از آن همان مسیر را برگشته و پاسخ به کاربر ارسال می شود. به شکل زیر دقت کنید:
هر delegate می تواند عملیات هایی را قبل و بعد از delegate بعدی انجام دهد. Delegate مربوط به کنترل خطاها (Exception Handling) باید جلوتر از بقیه فراخوانی شوند، که بتوانند به Exception هایی که بعدا در مراحل پردازش درخواست رخ می دهد رسیدگی کند.
ساده ترین برنامه ی ممکن در ASP.NET Core یک Request Delegate را راه اندازی می کند که قابلیت مدیریت تمام درخواست ها را دارد. این مورد شامل یک فرایند پردازش درخواست واقعی نیست. به جای آن، یک تابع ناشناس به منظور پاسخ به تمام درخواست های HTTP فراخوانده می شود.
می توان چند Request Delegate را با استفاده از Use به یکدیگر متصل کرد. مقدار next درحقیقت Delegate بعدی در Pipeline را نمایش می دهد. شما می توانید فرایند پردازش را با عدم فراخوانی پارامتر بعدی تبدیل به اتصال کوتاه (short-circuit) کنید و در همین نقطه پاسخ را به کاربر بازگردانید. معمولا می توانید عملیات ها را هم قبل و هم بعد از delegate بعدی انجام دهید، همانطور که در پایین نمایش داده شده است:
وقتی یک delegate درخواست را به delegate بعدی ارسال نمی کند، این اتفاق اتصال کوتاه (short-circuiting) کردن فرایند پردازش درخواست (request pipeline) نامیده می شود. اتصال کوتاه معمولا کار مطلوبی می باشد، زیرا جلوی کار غیرضروری را می گیرد. برای نمونه، Static File Middleware می تواند با پردازش کردن یک درخواست برای یک static file و short-circuiting کردن بقیه ی فرایند پردازش، بعنوان یک Terminal Middleware عمل کند. آن Middleware که قبل از Middleware پایانی که بقیه ی پردازش را تخریب می کند به pipeline اضافه شده است، همچنان بعد از دستورهای next.Invoke کد را پردازش می کند.
Run پارامتر next را دریافت نمی کند. اولین Run همیشه Terminal است و به فرایند پردازش خاتمه می دهد. Run یک اصل توافق شده می باشد تا به فرایند پردازش درخواست پایان دهد. برخی از Middleware ها ممکن است متدهای Run[Middleware] را که در آخر pipeline اجرا می شوند، فراخوانی کنند. مثلا در مثال زیر WriteAsync یک متد Run است که به فرایند پردازش درخواست پایان می دهد و پاسخ را به کاربر باز می گرداند.
در مثال بالا Run بعنوان پاسخ می نویسد: "Hello from 2nd delegate." و سپس pipeline را تخریب می کند. اگر یک Use یا Run دیگر، بعد ازRun اضافه شوند، آنها فراخوانی نخواهند شد.
ترتیب Middleware
طرح پایین شکل کامل فرایند پردازش درخواست ارسال شده به برنامه در ASP.NET Core MVC و Razor Pages را نشان می دهد. می توانید ببینید که در یک نرم افزار معمولی middleware های موجود ترتیب بندی شده اند و اینکه middleware های اختصاصی که خودمان نوشته ایم، کجا اضافه شده اند. شما کنترل کامل به منظور مرتب کردن middleware ها و یا inject کردن Middleware های اختصاصی جدید را دارید.
ترتیبی که middleware ها در متد Startup.Configure اضافه شده اند، همان ترتیبی است که middleware ها توسط درخواست کاربر فراخوانده می شوند. این ترتیب برای امنیت، کارایی و کارآمدی ضروری است.
متد Startup.Configureکه در زیر نمایش داده شده است، middleware های مربوط به امنیت برنامه را با ترتیب پیشنهادی ما اضافه می کند:
- آن Middleware هایی که هنگام ساخت یک وب سایت جدید با سیستم کاربران پیشفرض NET Core اضافه نشده اند، کامنت شده اند.
- همه ی middleware ها احتیاج ندارند تا در یک ترتیب بخصوص قرار بگیرند، اما خیلی از آنها نیاز دارند. برای مثال:
UseCors, UseAuthentication و UseAuthorization باید در ترتیب نشان داده شده قرار بگیرند.
متد Startup.Configure زیر middleware ها را برای سناریوهای معمول در برنامه ها، اضافه می کند.
- مدیریت خطاها (Exception Handling)
- وقتی نرم افزار در محیط توسعه اجرا می شود:
- Developer Exception Page Middleware خطاهای زمان اجرای برنامه را گزارش می دهد.
- Database Error Page Middleware خطاهای زمان اجرای دیتابیس را گزارش می دهد.
- وقتی نرم افزار در محیط اجرایی اجرا می شود:
- Exception Handler Middleware ، خطاهایی که در middleware های بعدی اجرا می شود را مدیریت می کند.
- HTTP Strict Transport Security Protocol (HSTS) Middleware ، عنوان Strict-Transport-Security را اضافه می کند.
- HTTPS Redirection Middleware (UseHttpsRedirection)، درخواست هایی را که با پروتکل HTTP به برنامه آمده اند را به پروتکل HTTPS منتقل می کند.
- Static File Middleware (UseStaticFiles) فایل ها را فراهم می کند و در فرایند پردازش درخواست اتصال کوتاه ایجاد می کند.
- Cookie Policy Middleware (UseCookiePolicy) conforms the app to the EU General Data Protection Regulation (GDPR) regulations.
- Routing Middleware (UseRouting) مسیریابی درخواست ها را بر عهده دارد.
- Authentication Middleware (UseAuthentication) تلاش می کند تا کاربران را قبل از دستیابی به منابع برنامه، احراز هویت کند.
- Authorization Middleware (UseAuthorization) حق دسترسی کاربر به منابع خاصی از سیستم را کنترل می کند.
- Session Middleware (UseSession) ، اگر برنامه از Session استفاده کند، مدیریت آنها را برعهده دارد. این Middleware باید بعد از Cookie Policy و قبل از MVC صدا زده شود.
- Endpoint Routing Middleware (UseEndpoints with MapRazorPages) برای افزودن فرایند پردازش درخواست از طریق Razor Pages استفاده می شود.
UseExceptionHandler اولین middleware اضافه شده به فرایند پردازش درخواست می باشد. بنابراین، Exception Handler Middleware هر مورد غیر معمول که در فراخوانی های بعدی رخ می دهد را مدیریت می کند.
Static File Middleware زودتر در فرایند پردازش درخواست pipeline فرخوانی می شود که بتواند درخواست ها و اتصالات کوتاه را بدون رفتن به Middleware های بعدی مدیریت کند. Static File Middleware هیچ دسترسی خاصی را بررسی نمی کند. اگر درخواست کاربر توسط Static File Middleware مدیریت نشده باشد به Authentication Middleware فرستاده می شود، که احراز هویت کاربر را انجام می دهد. Authentication درخواست های احراز هویت نشده را اتصال کوتاه نمی کند. با اینکه Authentication Middleware احراز هویت درخواست ها را بررسی می کند، عمل بررسی حق دسترسی و رد درخواست ها فقط بعد از اینکه MVC یک Razor Page و یا MVC controller و action را انتخاب کند اتفاق می افتد.
مثال بعدی یک ترتیب ثبت middleware را نشان می دهد که درخواست ها برای فایل ها توسط Static File Middleware و قبل از Response Compression Middleware مدیریت می کند. Static files با این ترتیب middleware هایی که مشاهده می کنید، فشرده سازی نشده اند. جواب های Razor Pages می تواند فشرده سازی شوند.
برای اپلیکیشن های تک صفحه ای (SPAs)، UseSpaStaticFiles، معمولا در پایان فرایند پردازش درخواست ها می آید. SPA middleware درآخر می آید:
- زیرا می خواید به Middleware های دیگر اجازه پاسخ به درخواست های مرتبط را بدهد.
- برای اجازه دادن به SPAs هایی با client-side routing برای اجرای تمام route هایی که توسط برنامه سمت سرور ناشناخته اند.
گسترش فرایند پردازش درخواست
Map بعنوان یک قرارداد برای گسترش دادن فرایند پردازش درخواست به کار می رود. Map فرایند پردازش درخواست را براساس مسیر درخواست گسترش می دهد. اگر مسیر درخواست با مسیر داده شده شروع شود، شاخه جدید از Middleware ها اجرا خواهد شد. در حقیقت شما می توانید فرایند پردازش درخواست را به چند شاخه تقسیم کنید که بر اساس مسیر درخواست، هر کدام از این شاخه ها ممکن است اجرا شوند.
جدول زیر درخواست ها و جواب ها را از http://localhost:1234 و با استفاده کد قبلی نشان می دهد. در جدول زیر مسیرهایی متفاوت به برنامه داده شده و جواب هایی متفاوت بازگردانده شده که نشان دهنده تغییر شاخه اجرای فرایند پردازش درخواست (Request Pipeline) براساس مسیرهای متفاوت است.
Map از خاصیت اجرای تو در تو پشتیبانی می کند، برای مثال:
Map همچنان می تواند چندین بخش را همزمان تطبیق دهد.
MapWhen فرایند پردازش درخواست را بر اساس نتیجه ی داده شده گسترش می دهد و شاخه جدید تولید می کند. هر نتیجه از نوع Func<HttpContext, bool> می تواند برای map کردن درخواست ها به یک شاخه جدید از فرایند پردازش درخواست استفاده شود.
جدول زیر درخواست ها و جواب ها را از http://localhost:1234 و با استفاده کد قبلی نشان می دهد:
متد UseWhen همچنین براساس نتیجه Predicate داده شده برای فرایند پردازش درخواست، شاخه ای جدید تولید می کند. برخلاف MapWhen، این شاخه دوباره به pipeline اصلی اضافه می شود البته در صورتی که اتصال کوتاه نکند یا شامل Terminal Middleware نباشد:
در مثال بالا، پاسخ "Hello from main pipeline." برای تمام درخواست ها نوشته شده است. اگر درخواست شامل واژه branch در query string خود باشد، شاخه جدیدی اجرا می شود که این شاخه در متد HandleBranchAndRejoin قرار گرفته است.