درک معماری Middleware در جنگو و نحوه ساخت و مدیریت آن

این مقاله ساختار Middleware در جنگو، نحوه عملکرد آن در چرخه درخواست و پاسخ، روش فعال‌سازی، ترتیب اجرا، ساخت Middleware سفارشی، استفاده از متدهای ویژه مانند process_view، process_exception و process_template_response، و نکات مهم درباره پاسخ‌های استریمی را بررسی می‌کند.

Middleware جنگوprocess_viewrequest/response

~7 دقیقه مطالعه • بروزرسانی ۲۳ اسفند ۱۴۰۴

مقدمه

Middleware در جنگو یک سیستم افزونه‌ای سطح پایین است که امکان تغییر جهانی ورودی‌ها و خروجی‌های برنامه را فراهم می‌کند. هر Middleware مسئول انجام یک وظیفه مشخص است. برای مثال، AuthenticationMiddleware کاربر را از طریق سشن به درخواست متصل می‌کند.


این مقاله نحوه کار Middleware، روش فعال‌سازی آن و نحوه نوشتن Middleware سفارشی را توضیح می‌دهد.


نوشتن Middleware سفارشی

یک Middleware در جنگو یک callable است که یک request دریافت کرده و یک response بازمی‌گرداند. Middleware می‌تواند به صورت تابع یا کلاس نوشته شود.


Middleware به صورت تابع

def simple_middleware(get_response):
    # One-time configuration and initialization.

    def middleware(request):
        # Code before view is called.
        response = get_response(request)
        # Code after view is called.
        return response

    return middleware

Middleware به صورت کلاس

class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration.

    def __call__(self, request):
        # Code before view.
        response = self.get_response(request)
        # Code after view.
        return response

get_response ممکن است ویو واقعی باشد یا Middleware بعدی در زنجیره. Middleware نیازی ندارد بداند دقیقاً چه چیزی در ادامه قرار دارد.


نکات مربوط به __init__

  • فقط یک آرگومان get_response دریافت می‌کند.
  • فقط یک‌بار هنگام شروع سرور اجرا می‌شود.

غیرفعال کردن Middleware

اگر در __init__ استثنای MiddlewareNotUsed پرتاب شود، جنگو آن Middleware را حذف می‌کند.


فعال‌سازی Middleware

برای فعال‌سازی، مسیر کامل Middleware را در لیست MIDDLEWARE تنظیمات قرار دهید:


MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ترتیب Middleware بسیار مهم است، زیرا برخی از آن‌ها به Middlewareهای قبلی وابسته هستند.


ترتیب اجرا و مدل لایه‌ای

در مرحله درخواست، Middlewareها از بالا به پایین اجرا می‌شوند. در مرحله پاسخ، به ترتیب معکوس اجرا می‌شوند.


این ساختار مانند یک پیاز است: هر Middleware یک لایه است که درخواست را به لایه بعدی می‌فرستد و پاسخ را از لایه داخلی دریافت می‌کند.


متدهای ویژه در Middlewareهای کلاسی

متد process_view()

قبل از اجرای ویو فراخوانی می‌شود.

  • request: شیء درخواست.
  • view_func: تابع ویو.
  • view_args: آرگومان‌های موقعیتی.
  • view_kwargs: آرگومان‌های کلیدی.

اگر None برگرداند، پردازش ادامه می‌یابد. اگر HttpResponse برگرداند، ویو اجرا نمی‌شود.


نکته: دسترسی به request.POST در این مرحله می‌تواند مانع تغییر upload_handlers شود.


متد process_exception()

وقتی ویو استثنا پرتاب کند، این متد فراخوانی می‌شود.


اگر HttpResponse برگرداند، همان پاسخ به مرورگر ارسال می‌شود. در غیر این صورت، هندلینگ پیش‌فرض اجرا می‌شود.


متد process_template_response()

اگر پاسخ یک TemplateResponse باشد، این متد پس از اجرای ویو فراخوانی می‌شود.


می‌تواند پاسخ را تغییر دهد یا یک پاسخ جدید ایجاد کند.


مدیریت پاسخ‌های استریمی

StreamingHttpResponse ویژگی content ندارد. بنابراین Middleware باید بررسی کند که پاسخ استریمی است یا خیر:


if response.streaming:
    response.streaming_content = wrap_streaming_content(response.streaming_content)
else:
    response.content = alter_content(response.content)

محتوای استریمی ممکن است بسیار بزرگ باشد، بنابراین Middleware نباید آن را یک‌باره مصرف کند. تنها باید آن را wrap کند:


def wrap_streaming_content(content):
    for chunk in content:
        yield alter_content(chunk)

جمع‌بندی

Middleware یکی از قدرتمندترین بخش‌های معماری جنگو است که امکان کنترل کامل چرخه درخواست و پاسخ را فراهم می‌کند. با درک نحوه اجرای لایه‌ای، متدهای ویژه و نحوه مدیریت پاسخ‌های استریمی، می‌توان Middlewareهای سفارشی و کارآمدی برای نیازهای مختلف توسعه داد.


مدیریت پاسخ‌های استریمی

StreamingHttpResponse از هر دو نوع iterator هم‌زمان و غیرهم‌زمان پشتیبانی می‌کند. بنابراین Middleware که قصد wrap کردن محتوای استریمی را دارد باید نوع iterator را تشخیص دهد. اگر Middleware شما باید هر دو حالت را پشتیبانی کند، از StreamingHttpResponse.is_async استفاده کنید.


مدیریت استثناها در Middleware

جنگو استثناهای ایجادشده توسط ویو یا Middleware را به پاسخ HTTP مناسب تبدیل می‌کند. استثناهای شناخته‌شده به کدهای 4xx تبدیل می‌شوند و استثناهای ناشناخته به 500.


این تبدیل قبل و بعد از هر Middleware انجام می‌شود، مانند یک لایه نازک بین لایه‌های «پیاز». بنابراین:

  • Middleware همیشه یک HttpResponse دریافت می‌کند، نه یک استثنا.
  • اگر Middleware بعدی Http404 پرتاب کند، Middleware شما فقط یک پاسخ با status_code = 404 دریافت می‌کند.

برای غیرفعال کردن این تبدیل و مشاهده مستقیم استثناها، مقدار DEBUG_PROPAGATE_EXCEPTIONS را برابر True قرار دهید.


پشتیبانی از Async در Middleware

Middleware می‌تواند فقط هم‌زمان، فقط غیرهم‌زمان یا ترکیبی باشد. جنگو درخواست‌ها را برای سازگاری با Middleware تطبیق می‌دهد، اما این کار ممکن است باعث کاهش کارایی شود.


پرچم‌های قابلیت

برای مشخص کردن نوع پشتیبانی، این ویژگی‌ها را روی Middleware تنظیم کنید:

  • sync_capable: پیش‌فرض True.
  • async_capable: پیش‌فرض False.

اگر هر دو مقدار True باشند، جنگو درخواست را بدون تبدیل ارسال می‌کند. برای تشخیص async بودن، بررسی کنید که آیا get_response یک coroutine است یا نه.


دکوراتورهای کمکی

جنگو دکوراتورهای زیر را برای تعیین قابلیت‌ها ارائه می‌دهد:

  • sync_only_middleware()
  • async_only_middleware()
  • sync_and_async_middleware()

نمونه Middleware ترکیبی

from asgiref.sync import iscoroutinefunction
from django.utils.decorators import sync_and_async_middleware

@sync_and_async_middleware
def simple_middleware(get_response):
    if iscoroutinefunction(get_response):

        async def middleware(request):
            response = await get_response(request)
            return response

    else:

        def middleware(request):
            response = get_response(request)
            return response

    return middleware

نکته: حتی اگر ویو async باشد، ممکن است Middleware شما در حالت sync فراخوانی شود اگر Middlewareهای هم‌زمان بین شما و ویو وجود داشته باشند.


Middleware غیرهم‌زمان به صورت کلاس

from asgiref.sync import iscoroutinefunction, markcoroutinefunction

class AsyncMiddleware:
    async_capable = True
    sync_capable = False

    def __init__(self, get_response):
        self.get_response = get_response
        if iscoroutinefunction(self.get_response):
            markcoroutinefunction(self)

    async def __call__(self, request):
        response = await self.get_response(request)
        return response

ارتقای Middlewareهای قدیمی

جنگو برای سازگاری با Middlewareهای قبل از نسخه 1.10، کلاس MiddlewareMixin را ارائه می‌دهد.


قابلیت‌های MiddlewareMixin

  • متد __init__() که get_response را ذخیره می‌کند.
  • متد __call__() که:
process_request() را اجرا می‌کند
get_response() را فراخوانی می‌کند
process_response() را اجرا می‌کند

در حالت MIDDLEWARE_CLASSES، متد __call__() نادیده گرفته می‌شود.


تفاوت‌های رفتاری بین MIDDLEWARE و MIDDLEWARE_CLASSES

۱. جریان پاسخ

در MIDDLEWARE، فقط Middlewareهایی که درخواست را دیده‌اند، پاسخ را نیز خواهند دید.


در MIDDLEWARE_CLASSES، همه process_responseها اجرا می‌شوند.


۲. مدیریت استثنا

در MIDDLEWARE، process_exception فقط استثناهای ویو را مدیریت می‌کند.


در MIDDLEWARE_CLASSES، استثناهای process_request نیز مدیریت می‌شوند.


۳. استثنا در process_response

در MIDDLEWARE_CLASSES، استثنا باعث پرش همه Middlewareهای قبلی و بازگشت 500 می‌شود.


در MIDDLEWARE، استثنا به پاسخ مناسب تبدیل شده و به Middleware بعدی ارسال می‌شود.


جمع‌بندی

Middleware در جنگو یک ابزار قدرتمند برای کنترل چرخه درخواست و پاسخ است. با درک رفتار استریمی، مدیریت استثنا، پشتیبانی از async و سازگاری با نسخه‌های قدیمی، می‌توان Middlewareهای پیشرفته و بهینه‌ای برای نیازهای مختلف توسعه ایجاد کرد.


نوشته و پژوهش شده توسط دکتر شاهین صیامی