آشنایی و درک مفهوم Django Middlewares

میان افزار در جنگو
زمان مطالعه: 5 دقیقه

با سلام و درود، خب فرض اول ما این هست که اسناد خود جنگو در مورد میان افزارها (Middlewares) را مطالعه کرده اید. و اینکه در این مطلب با توجه به عنوان ما صرفا میخواهیم با مفهوم میان افزارها در جنگو آشنا بشیم، قصد نداریم اینجا نمونه کدهای فراوان داشته باشیم و یا بخواهیم خیلی تخصصی به این مطلب بپردازیم، صرفا میخواهیم آشنایی اولیه پیدا کنیم و بعدا فرصت پیش آمد حتما خیلی تخصصی تر بهش خواهم پرداخت.

میان افزار (middleware) چیست؟

میان افزارها، هوک-hook هایی هستند که به اصلاح request و response ها می پردازند. تعریف خود اسنادجنگو رو زیر آوردم: (برای هوک یه لینک روی خودش گذاشتم ولی به فارسی و مختصر : کد ماشینی که در یک ماژول یا پیمانه از سیستم عامل جای داده می شود تا کنترل را به روالی که وظیفه ای اضافی انجام می دهد انتقال داده و در محلی متفاوت از ماژول اصلی ذخیره نماید)

Middleware is a framework of hooks into Django’s request/response processing. It’s a light, low-level “plugin” system for globally altering Django’s input or output.

میان افزار چارچوبی از هوک در پردازش request یا response در جنگو می باشد. که افزونه-پلاگین سبک و سطح پایین برای تغییر ورودی و خروجی های محلی جنگو می باشد.

Django Docs

چه زمانی از میان افزار-middleware استفاده کنیم؟

می توانید از میان افزار برای اصلاح request، برای مثال HttpRequest  که به سمت view ارسال شده استفاده کنید. یا اینکه میخواهید HttpResponse  که از سمت vierw برگشته را تغییر دهید. هر جفت این کارها با استفاده از میان‌افزارها انجام می شوند.

ممکنه که بخواهید عملی را قبل از اینکه view اجرا شود انجام دهید. در اینجور موارد می توانید از میان افزار استفاده کنید.

جنگو تعدادی میان افزار پیشفرض ارائه داده است برای مثال : AuthenticationMiddleware

خیلی رایجه که شما از request.user داخل view استفاده کنید. در اینجا جنگو می خواهد که ویژگی های user روی request قبل از این که view ای اجرا شود، تنظیم شود. برای رسیدن به این هدف جنگو از رویکرد میان افزار استفاده کرده است و AuthenticationMiddleware فراهم کرده که می تواند شيء request را تغییر دهد.

به طور مشابه شما ممکن هست که اپلیکیشنی داشته باشید که با کاربرانی از timezone های مختلف کار بکند. شما میخواهید که زمانی که صفحه ای را به کاربر نشان می دهید از timezone خود کاربر استفاده کنید. شما میخواهید که به timezone کاربر در تمامی viewها دسترسی داشته باشید.منطقی است که در چنین حالتی آن را در session اضافه کنید. پس می توانید میان افزاری مانند زیر را اضافه کنید:

class TimezoneMiddleware(object):
	def process_request(self, request):
		# Assuming user has a OneToOneField to a model called Profile
		# And Profile stores the timezone of the User.
		request.session['timezone'] = request.user.profile.timezone

TimezoneMiddleware وابسته به request.user می باشد. و request.user در AuthenticationMiddleware جمع شده است. بنابراین TimezoneMiddlewareکه توسط ما نوشته می شود باید در settings.MIDDLEWARE_CLASSES پس از AuthenticationMiddleware  که توسط جنگو فراهم شده است بیاید.

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

مواردی که به هنگام میان افزار باید به یاد داشته باشید

  • چینش میان افزار ها مهم می باشد
  • یک میان افزار باید از یک شیء کلاس گسترش داده شود.
  • یک میان افزار برای پیاده سازی و عدم پیاده سازی متد ها آزادی عمل دارد.
  • یک میان افزار ممکن است process_request  پیاده سازی کند، اما ممکن نیست process_response  و process_view پیاده سازی کند.
  • یک میان افزار ممکن است process_response  پیاده سازی کنم اما process_request پیاده سازی نمی کند.

AuthenticationMiddleware  فقط process_request  پیاده سازی می کند و process_response را پیاده سازی نمی کند. می تونید اینجا یه نگاهی بهش بیاندازید.

GZipMiddleware فقط process_response  پیاده سازی می کند و process_request پیاده سازی نمی کند. این مورد رو هم می تونید اینجا نگاهی بیاندازید.

حتما دو تا لینک فوق را نگاه بیاندازید و متوجه بحث فوق بشید 🙂

خب حالا بیاید تا چند تا میان افزار بنویسیم

اول از همه مطمئن باشید که یک پروژه جنگو با url و viewe دارید که دسترسی به view دارید، چرا که ما با request.user سروکار درایم، و همچنین مطمئن شوید که authentication  به درستی برای شما تنظیم شده باشد و request.user موارد درستی را در این view چاپ می کند.

در یکی از app هایی که دارید، فایل middleware.py را بسازید.

من مثال ها رو با توجه به سایت Agiliq پیش میرم. appای به نام books داریم، در نتیجه داخل books/middleware.py خواهیم نوشت.

class BookMiddleware(object):
	def process_request(self, request):
		print "Middleware executed"

میان افزار ساخته شده را در MIDDLEWARE_CLASSES اضافه می کنیم:

MIDDLEWARE_CLASSES = (
	'books.middleware.BookMiddleware',
	'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',
)

با هر request به هر url. باید عبارت زیر روی کنسول نمایش داده شود.

Middleware executed

تغییر BookMiddleware.process_request به شکل زیر خواهد بود:

class BookMiddleware(object):
	def process_request(self, request):
		print "Middleware executed"
		print request.user

دادن یه request مجدد روی url . یک ارور نمایان می شود:

'WSGIRequest' object has no attribute 'user'

چرا ارور فوق اتفاق افتاد؟؟ چون که هنوز ویژگی user روی request  تنظیم نشده است.

حال می آییم ترتیب چینش میان افزار ها را تغییر می دهیم، در نتیجه BookMiddleware  بعد از AuthenticationMiddleware می آوریم:

MIDDLEWARE_CLASSES = (
	'django.contrib.sessions.middleware.SessionMiddleware',
	'django.middleware.common.CommonMiddleware',
	'django.middleware.csrf.CsrfViewMiddleware',
	'django.contrib.auth.middleware.AuthenticationMiddleware',
	'books.middleware.BookMiddleware',
	'django.contrib.messages.middleware.MessageMiddleware',
	'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

مجدد request ای روی یک url داشته باشید. باید متن زیر روی کنسول نمایان شود:

Middleware executed
<username>

این میگه که process_request  روی میان افزار بر مبنای چینششان و لیست شدنشان در settings.MIDDLEWARE_CLASSES اجرا می شود.

می توانید آن را در ادامه تایید کنید. میان افزار دیگری در middleware.py خودتان اضافه کنید.

class AnotherMiddleware(object):
	def process_request(self, request):
		print "Another middleware executed"

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

MIDDLEWARE_CLASSES = (
	'django.contrib.sessions.middleware.SessionMiddleware',
	'django.middleware.common.CommonMiddleware',
	'django.middleware.csrf.CsrfViewMiddleware',
	'django.contrib.auth.middleware.AuthenticationMiddleware',
	'books.middleware.BookMiddleware',
	'books.middleware.AnotherMiddleware',
	'django.contrib.messages.middleware.MessageMiddleware',
	'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

حال خروجی به صورت زیر خواهد بود:

Middleware executed
<username>
Another middleware executed

چگونه برگشتن (returning) HttpResponse از process_request  چیزها را تغییر می دهد

BookMiddleware  را تغغیر می دهیم و به شکل زیر می شود:

class BookMiddleware(object):
	def process_request(self, request):
		print "Middleware executed"
		print request.user
		return HttpResponse("some response")

یکی از url ها را امتحان کنید و خروجی به شکل زیر خواهد بود:

Middleware executed
<username>

به دو تا نکته توجه داشته باشید:

  • view شما دیگر اجرایی نخواهد شد و مهم نیست کدام url را امتحان کنید، شما “some response” را خواهید دید.
  • AnotherMiddleware.process_request دیگر اجرا نخواهد شد.

پس اگر یک process_request() از میان‌افزار، یک شیء HttpResponse  برگرداند، سپس process_request  از هر میان افزار بعدی ای، کنار گذاشته-bupass می شود. (شاید چون معادلات فارسی استفاده نکردم یه مقدار جمله گیج کننده باشه، ولی لطفا چند مرتبه با توجه به علائم نگارشی و دقت جمله را بخوانید، مطمینم متوجه می شوید). شما به ندرت این کار را می کردید یا در پروژه های خودتان به ان نیازد داشته اید.

کار با process_response

متد process_response را به هر جفت میان‌افزار ها اضافه کنید.

class AnotherMiddleware(object):
	def process_request(self, request):
		print "Another middleware executed"

	def process_response(self, request, response):
		print "AnotherMiddleware process_response executed"
		return response

class BookMiddleware(object):
	def process_request(self, request):
		print "Middleware executed"
		print request.user
		return HttpResponse("some response")
		#self._start = time.time()

	def process_response(self, request, response):
		print "BookMiddleware process_response executed"
		return response

یک url را امتحان کنید. خروجی به شکل زیر خواهد بود:

Middleware executed
<username>
Another middleware executed
AnotherMiddleware process_response executed
BookMiddleware process_response executed

AnotherMiddleware.process_response() قبل از BookMiddleware.process_response() اجرا می شود، درحالی که AnotherMiddleware.process_request() بعد از BookMiddleware.process_request() اجرا می شود. پس process_response() برعکس چیزی که در process_request رخ می دهد را پیروی می کند. process_response() برای آخرین میان افزار اجرا می شود سپس دومی از آحرین میان افزار و به همین ترتیب تا به اولین میان افزار می رسد.

process_view

جنگو پردازش process_view() های میان افزار را به ترتیب تعریف شده در MIDDLEWARE_CLASSES (بالا به پایین) انجام می دهد. شبیه به ترتیب process_request()

همچینن اگر هر process_view() یک شیء HttpResponse برگرداند، سپس فراخوانی های بعدی process_view() کنار گذاشته می شوند و اجرا نمی شود.

مرجع اصلی این نوشته سایت Agiliq بود. این مطلب هم اگر دوست داشتید برای مطالعه بیشتر پیگیر باشید.

برای آشنایی با سیکل request و response در جنگو نیز اینجا رو دوست داشتید یه نگاه بیاندازید.