آموزش Middleware در جنگو (Django)
در مواردی، نیاز به اجرای یک قطعه کد در هر درخواستی که جنگو کنترل می کند خواهید داشت. این کد ممکن است نیاز باشد درخواست را قبل از آن که view آن را کنترل کند، ویرایش کند. این کد ممکن است نیاز باشد اطلاعات درباره ی درخواست برای اهداف debug و غیره ثبت کند.
می توان این کار را با فریم ورک یا چارچوب middleware انجام داد، که مجموعه ای از hook ها درون پردازش request/response می باشند. این چارچوب یک سیستم "plug-in" سطح پایین و light قادر به تغییر سراسری ورودی و خروجی جنگو (Django) می باشد.
هر جزء middleware مسئول انجام برخی کارهای خاص می باشد. در صورتی که این کتاب را کامل خوانده باشید، middleware را بارها و بارها دیده اید:
- تمام ابزار session و کاربر که در بخش کاربران عضویت و session مشاهده شد توسط چند تکه ی کوچک از middleware ممکن می شود (بیشتر به خصوص، middleware، request.session و request.user را برای شما در view ها قابل دسترس می کند).
- sitewide cache بحث شده در مبحث caching در واقع تنها یک قطعه از middleware می باشد که در صورتی که پاسخ برای view مورد نظر cache شده باشد، فراخوانی آن تابع view را دور می زند.
- برنامه های flatpages، redirects و csrf در پکیج django.contrib تمام کارهای خارق العاده خود را از طریق اجزای middleware انجام می دهند.
این آموزش از کتاب، ماهیت middleware و نحوه ی کارکرد آن را به طور عمیق تر بحث کرده و نحوه ی نوشتن middleware خودتان را توضیح می دهد.
Middleware چیست؟
اجازه دهید یا یک مثال خیلی ساده شروع کنیم.
سایت های پر ترافیک اغلب نیاز به قرار دادن جنگو در پشت یک پروکسی load-balancing دارند. این می تواند موجب پیچیدگی های کوچکی شود و یکی از این پیچیدگی ها این است که IP از راه دور هر درخواست (request.META["REMOTE_IP"]) متعادل کننده ی بار آن خواهد بود، نه در واقع IP ای که request ایجاد می کند. متعادل کننده های بار (load balancers) توسط یک header ویژه به نام X-Forwarded-For جهت درخواست آدرس IP واقعی با این موضوع سر و کار دارند.
(در دست ترجمه ...):
class SetRemoteAddrFromForwardedFor(object):
def process_request(self, request):
try:
real_ip = request.META['HTTP_X_FORWARDED_FOR']
except KeyError:
pass
else:
# HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs.
# Take just the first one.
real_ip = real_ip.split(",")[0]
request.META['REMOTE_ADDR'] = real_ip
(توجه: اگر چه HTTP header با نام X-Forwarded-For وجود دارد، جنگو آن را به صورت request.META['HTTP_X_FORWARDED_FOR'] قابل دسترس می کند. به استثنای content-length و content-type، هر HTTP header ای درون درخواست با تبدیل تمام کاراکتر به حروف بزرگ، به کلید های request.META تبدیل می شود، به جای هر hyphen با خط تیره و یک پیشوند HTTP_ به نام آن.)
در صورتی که این middleware نصب شده باشد، مقدار هر X‑Forwarded‑For به طور خودکار درون request.META['REMOTE_ADDR'] درج می شود. این بدین معنی است که برنامه ی جنگوی شما نسبت به این موضوع که آیا آن ها پشت یک پروکسی load-blancing هستند یا خیر نگرانی نخواهد داشت، آن ها می توانند به سادگی به request.META['REMOT_ADDR'] دسترسی پیدا کنند و آن در صورتی که یک پروکسی استفاده شده باشد یا خیر کار خواهد کرد.
در واقع، این یک نیاز به اندازه ی کافی رایج می باشد که این قسمت از middleware در یک بخش داخلی جنگو (Django) باشد. آن در django.middleware.http قرار دارد.
نصب Middleware
در صورتی که این کتاب را به صورت کامل خوانده باشید، مثال هایی از نصب middleware را تاکنون مشاهده کرده اید؛ بسیاری از مثال ها در آموزش های قبلی کتاب، دارای بعضی middleware های ضروری می باشد. برای کامل کردن، در زیر نحوه ی نصب middleware وجود دارد.
جهت فعال کردن یک جزء middleware، آیتم یا جزء مورد نظر را به تاپی MIDDLEWARE_CLASSES موجود در ماژول تنظیمات (settings.py) اضافه کنید. درون MIDDLEWARE_CLASSES، هر جزء middleware توسط یک رشته نشان داده شده است: مسیر کامل نام کلاس middleware. برای مثال، در زیر MIDDLEWARE_CLASSES پیشفرض ساخته شده توسط django‑admin startproject وجود دارد:
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)
نصب جنگو نیاز به هیچ middleware ای ندارد – در صورت تمایل، MIDDLEWARE_CLASSES می تواند خالی باشد – ولی توصیه می شود که CommonMiddleware را که به طور مختصر توضیح می دهیم فعال کنید.
ترتیب قرار گیری مهم می باشد. درون درخواست و فازهای view، جنگو middlewre را با توجه به ترتیب داده شده ی آن ها در MIDDLEWARE_CLASSES بکار می برد، و در پاسخ و فازهای exception، جنگو middleware را با طور برعکس ترتیب بکار می برد. بدین معنی که، جنگو با MIDDLEWARE_CLASSES به صورت یک نوعی از "wrapper" در اطراف تابع view رفتار می کند: در درخواست به سمت پایین لیست به view حرکت کرده، و در پاسخ بر می گردد.
متدهای Middleware
اکنون که ماهیت middleware و نحوه عمکرد آن را می دانید، اجازه دهید به تمام متدهایی که کلاس های middleware می توانند تعریف کنند نگاهی بیاندازیم.
مقدار ده اولیه: __init__(self)
از __init__() جهت راه اندازی systemwide برای یک کلاس middleware داده شده استفاده کنید.
(در دست ترجمه ...)، هر کلاس middleware فعال شده ای تنها یک بار در هر پردازش سرور instantiated شده است. این بدین معنی است که __init__() تنها یک بار فراخونی می شود – در زمان راه اندازی سرور – نه برای درخواست های فردی.
دلیل رایج برای اجرای یک متد __init__() بررسی این موضوع می باشد که middleware واقعا ضروری می باشد یا خیر. در صورتی که __init__()، django.core.exceptions.MiddlewareNotUsed را بوجود آورد، در آن هنگام جنگو middleware را از توده ی middleware حذف خواهد کرد. ممکن است از خصوصیت برای بررسی برخی قسمت های نرم افزار که کلاس middleware ضروری می باشد استفاده کنید، یا بررسی این که آیا سرور در حالت debug اجرا می شود یا خیر، (در دست ترجمه ...).
در صورتی که یک کلاس middleware یک متد __init__() تعریف کند، متد نباید هیچ آرگومانی بیشتر از self دریافت کند.
پردازشگر درخواست: process_request(self, request)
این متد به محض این که درخواست دریافت شود فراخوانی می شود – قبل از آن که جنگو URL را برای تعیین view ای که باید اجرا شود تجزیه کند. این متد شیء HttpRequest را ارسال می کند، که شما ممکن است در صورت تمایل آن را اصلاح کنید.
process_request() باید یا یک شیء HttpResponse و یا None بر گرداند.
- در صورتی که None بر گرداند، جنگو پردازش این request را ادامه خواهد داد، اجرای هر middleware دیگر و سپس view متناظر.
- در صورتی که یک شیء HttpResponse بر گرداند، جنگو هر دوی هر middleware دیگر (از هر نوع) یا view متناظر را فراخوانی نخواهد کرد. جنگو بلافاصله آن HttpResponse را بر خواهد گرداند.
پردازشگر View: process_view(self, request, view, args, kwargs)
این متد بعد از آن که پردازشگر درخواست فراخوانی شده و تعیین view جهت اجرا توسط جنگو صورت گرفت فراخوانی می شود، ولی قبل آن view واقعا اجرا می شود.
آرگومان های ارسال شده به این view در جدول شماره ی 1-17 نشان داده شده اند.
جدول شماره 1-2
آرگومان | توضیح |
---|---|
request | شیء HttpRequest. |
view | تابع پایتون که جنگو برای کنترل این request فراخوانی خواهد کرد. این آرگومان واقعا خود شیء تابع می باشد، نه نام تابع به صورت یک رشته. |
args | لیست آرگومان های موضعی ه به view ارسال خواهد شد، شامل آرگومان request نمی شود (که همواره اولین آرگومان view می باشد). |
kwargs | دیکشنری آرگومان های کیورد که به view ارسال خواهند شد |
درست مثل process_request()، process_view() باید یک شیء HttpResponse یا None بر گرداند.
- در صورتی که None بر گرداند، جنگو پردازش این درخواست اجرای هر middleware دیگر و سپس view متناظر را ادامه خواهد داد.
- در صورتی که یک شیء HttpResponse بر گرداند، جنگو هر دوی هر middleware دیگر (از هر نوع) یا view متناظر را فراخوانی نخواهد کرد. جنگو بلافاصله آن HttpResponse را بر خواهد گرداند.
پردازشگر پاسخ: process_response(self, request,response)
این متد بعد از فراخوانی شدن تابع view و تولید شدن response فراخوانی می شود. در اینجا، پردازشگر محتوای یک پاسخ را تغییر داده یا اصلاح می کند. یک مورد استفاده ی آشکار، فشرده سازی محتوا می باشد، از قبیل gzip کردن HTML درخواست.
پارامترها باید کاملا واضح و آشکار باشند: request شیء درخواست می باشد، و response شیء پاسخ برگردانده شده از view است.
بر خلاف درخواست و پردازشگرهای view که ممکن است None برگردانند، process_response() باید یک شیء HttpResponse برگرداند. آن reponse می تواند همان اصل ارسال شده به تابع (احتمالا اصلاح شده) باشد یا یک نمونه ی جدید باشد.
پردازشگر خطا: process_exception(self, request,exception)
این متد تنها در صورتی که چیزی اشتباه شود و view یک خطای cache نشده ایجاد کند فراخوانی می شود. می توان از این hook برای ارسال تذکرهای خطا استفاده کرد،
پارامترهای این تابع همسان با شیء های request ای می باشد که تا کنون با آن سر و کار داشته ایم، و exception شیء واقعی Exception ایجاد توسط تابع view می باشد.
process_exception() باید یا یک None و یا یک شیء HttpResponse برگرداند.
- در صورتی که None برگرداند، جنگو پردازش این درخواست را با کنترل خطای داخلی فریم ورک ادامه خواهد داد.
- در صورتی که یک شیء HttpResponse برگرداند، جنگو از آن پاسخ به جای کنترل خطای داخلی چارچوب استفاده می کند.
نکته
جنگو تعداد کلاس های middleware (توضیح داده شده در بخش بعدی) ارائه می کند که مثال های خوبی را ایجاد می کند. خواندن کد آن ها احساس خوبی نسبت به قدرت middleware به شما می دهد.
همچنین می توانید تعدادی مثال های ایجاد شده توسط جامعه ی جنگو توزیع شده است را در wiki جنگو بیابید:
http://code.djangoproject.com/wiki/ContributeMiddleware
Middleware داخلی
فریم ورک یا چارچوب جنگو (Django) برخی middleware های داخلی را که با مشکلات رایج سر و کار دارند را ارائه میکند، که در بخش های زیر بحث می کنیم.
Middleware پشتیبانی Authentication
کلاس Middleware: django.contrib.ath.middleware.AuthenticationMiddleware.
این middleware پشتیبانی authentication را فعال می کند. این middleware اتریبیوت request.user نشان داده شده در کاربر وارد شده ی فعلی به هر شیء HttpRequest ورودی اضافه می کند.
برای اطلاعات بیشتر به بخش کاربران عضویت و session مراجعه کنید.
"Common" Middleware
کلاس middleware: django.middleware.common.CommonMiddleware.
این middleware، تهسیلاتی برای افراد کمال گرا اضافه کرده است:
- (در دست ترجمه ...):
import re DISALLOWED_USER_AGENTS = ( re.compile(r'^OmniExplorer_Bot'), re.compile(r'^Googlebot') )
به import re توجه داشته باشید، چرا که DISALLOWED_USER_AGENT نیازمند مقادیر آن جهت کامپایل regex ها می باشد (مثلا، خروجی re.compile()). فایل تنظیمات پایتونی می باشد، بنابراین برای بکار بردن عبارات import درون آن کاملا مناسب می باشد.
- باز نویسی بر اساس تنظیمات APPEND_SLASH و PREPEND_WWW انجام می دهد: در صورتی که APPEND_SLASH دارای مقدار True باشد، URL هایی که علامت "/" جلو را نداشته باشند به URL همسان دارای علامت "/" جلو تغییر مسیر داده خواهند شد، مگر آن که قسمت آخر مسیر حاوی یک پایان باشد. بنابراین foo.com/bar به foo.com/bar/ تغییر مسیر داده می شود، ولی foo.com/bar/file.txt بدون تغییر ارسال می شود.
در صورتی که PREPEND_WWW مقدار True داشته باشد، URL هایی که "www." ابتدایی را نداشته باشند، به URL همسان با "www." در ابتدای آن تغییر مسیر داده می شوند.
هر دوی این option ها به معنای عادی سازی URL ها می باشند. فلسفه این است که هر URL باید در یک – و تنها یک – مکان وجود دارشته باشد. از نظر فنی آدرس example.com/bar متفاوت از example.com/bar/ می باشد، که به نوبه ی خود متفاوت از www.example.com/bar/ می باشد. یک indexer موتور جستجو با این ها به صورت آدرس های جدا رفتار می کند، که برای رتبه ی موتور جستجوی سایت مضر می باشد، بنابراین عادی سازی URL ها بهترین عمل می باشد.
- ETag ها را بر اساس تنظیم USE_ETAGS کنترل می کند: ETag ها یک سطح بهینه سازی HTTP برای cache صفحات به طور مشروط می باشد. در صورتی که USE_ETAGS مقدار True باشد، جنگو توسط محتوای صفحه ی MD5-hashing یک Etag برای هر درخواست محاسبه می کند، و در صورت لزوم مواظب ارسال پاسخ های Not Modified خواهد بود.
همچنین توجه داشته باشید که یک GET middleware شرطی، که به طور اختصار توضیح داده شده، ETag ها و یک مقدار بیشتر را کنترل می کند.
فشرده سازی Middleware
کلاس middleware: django.middleware.gzip.GZipMiddleware.
این middleware به طور خودکار محتوا را برای مرورگرهایی که فشرده سازی gzip را می فهمند (تمام مرورگرهای مدرن) فشرده می سازد. این middleware می تواند تا حد زیادی مقدار bandwidth ای که یک وب سرور مصرف می کند را کاهش دهد. در عوض مقداری زمان جهت فشرده سازی صفحات خواهد برد.
ما معمولا سرعت را bandwith ترجیح می دهیم، ولی در صورتی که شما بر عکس این را ترجیح می دهید، تنها کافیست این middleware را فعال کنید.
GET Middleware شرطی
کلاس middleware: django.middleware.http.ConditionalGetMiddleware.
این middleware اعمال شرطی GET را پشتیبانی می کند. در صورتی که پاسخ دارای یک Last-Modified یا Etag یا header باشد، و درخواست دارای If-None-Match یا If-Modified-Since باشد، پاسخ توسط یک پاسخ 304 ("Not modified") جایگزین می شود. پشتیبانی ETag وابسته به تنظیم USE_ETAGS می باشد و انتظار دارد که header پاسخ Etag قبلا قرار گرفته باشد. همانطور که در بالا بحث شد، هدر ETag توسط common middleware قرار گرفته است.
همچنین محتوای هر پاسخ به یک درخواست HEAD را نیز حذف می کند و هدرهای پاسخ DATE و Content-Length را برای هر درخواستی قرار می دهد.
پشتیبانی پروکسی معکوس (X-Forwarded-For Middleware)
کلاس middleware: django.middleware.http.SetRemoteAddrFromForwardedFor.
این مثالی است که در بخش "Middleware چیست؟" بررسی شد. این middleware، request.META['REMOTE_ADDR'] بر اساس request.META['HTTP_X_FORWARDED_FOR'] قرار می دهد، در صورتی که دومی قرار بگیرد. این در صورتی که در پشت یک پروکسی معکوس نشسته باشین که موجب شود هر REMOTE_ADDR درخواست به 127.0.0.1 قرار گرفته باشد مفید است.
خطر!
این middleware، HTTP_X_FORWARDED_FOR را معتبر نمی سازد.
در صورتی که در پشت یک پروکسی معکوس نمی باشید که به طور خودکار HTTP_X_FORWARDED_FOR قرار گرفته باشد، از این middleware استفاده نکنید. هر کسی می تواند مقدار HTTP_X_FORWARDED_FOR فریب دهد، و به دلیل آنکه این REMOTE_ADDR بر اساس HTTP_X_FORWARDED_FOR قرار داده شده است، بدین معنی است که هر کسی می تواند آدرس IP خود را جا بزند.
تنها زمانی که کاملا به مقدار HTTP_X_FORWARDED_FOR اعتماد دارید از این middleware استفاده کنید.
Middleware پشتیبانی Session
کلاس middleware: django.contrib.sessions.middleware.SessionMiddleware.
این middleware پشتیبانی session را فعال می کند. برای جزئیات بیشتر به بخش کاربران عضویت و session مراجعه کنید.
Sitewide Cache Middleware
کلاس middleware: django.middleware.cache.UpdateCacheMiddleware و django.middleware.cache.FetchFromCacheMiddleware.
این middleware ها با یکدیگر برای cache هر صفحه ی تولید شده توسط جنگو کار می کنند. این موضوع به تفصیل در مبحث caching بحث شده است.
Transaction Middleware
کلاس middleware: django.middleware.transaction.TransactionMiddleware.
این middleware، COMMIT یا ROLLBACK پایگاه داده را به فاز request/response پیوند می دهد. در صورتی که یک تابع view با موفقیت اجرا شود، یک COMMIT صادر می شود. در صورتی که view یک خطا ایجاد کند، یک ROLLBACK صادر می شود.
ترتیب این middleware درون لیست اهمیت دارد. ماژول های middleware خارج از اجرای آن با commit‑on‑save اجرا می شوند – رفتار پیشفرض جنگو. ماژول های middleware داخل آن (دیرتر درون لیست می آیند) که در زیر کنترل transaction همسان به صورت توابع view خواهد بود اجرا می شوند.