يُعد فلاسك إطار عمل للويب مبني بلغة بايثون، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا عدة أدوات وميزات من شأنها إنشاء تطبيقات ويب في لغة بايثون.
ستواجه خلال تطوير تطبيقات الويب حتمًا حالاتٍ يعطي فيها تطبيقك نتائجًا مخالفةً لتوقعاتك، وذلك بسبب أخطاء من قبيل الكتابة الخاطئة لاسم أحد المتغيرات، أو الاستخدام الخاطئ لحلقة "for" التكرارية أو بناء عبارة "if" الشرطية بطريقة خاطئة مسبّبةً وقوع أحد استثناءات بايثون، كما في حال استدعاء دالة ما قبل التصريح عنها، أو البحث عن صفحة غير موجودة أصلًا، فممّا لا شكّ فيه أنّك ستجد تطوير تطبيقات الويب باستخدام فلاسك أسهل وأكثر مرونة حال تعلمّك لكيفية التعامل مع الأخطاء والاستثناءات.
سنبني في هذا المقال تطبيق ويب مُصغّر بهدف تبيان كيفيّة التعامل مع الأخطاء الشائعة التي قد تواجهك أثناء تطوير تطبيقات الويب، كما سننشئ صفحات أخطاء مُخصّصة، وسنستخدم منقّح الأخطاء في فلاسك لاستكشاف الاستثناءات في حال حدوثها وإصلاحها، كما سنستخدم سجل الأحداث logging لتتبّع الأحداث في التطبيق بحثًا عن مصادر الأخطاء.
مستلزمات العمل
قبل المتابعة في هذا المقال لا بُدّ من:
- توفُّر بيئة برمجة بايثون 3 محلية، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو "flask_app".
- الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات routes ودوال العرض view في فلاسك، وفي هذا الصدد يمكنك الاطلاع على المقال كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask من لغة بايثون لفهم مبادئ فلاسك.
- فهم أساسيات لغة HTML.
الخطوة الأولى - استخدام منقح الأخطاء في فلاسك
سننشئ في هذا الخطوة تطبيقًا يحتوي على بضعة أخطاء بغية تشغيله دون استخدام منقّح الأخطاء، لنرى الآلية التي سيستجيب بها التطبيق، ومن ثمّ سنعيد تشغيله ولكن مع تفعيل وضع منقّح الأخطاء لنستخدمه في استكشاف أخطاء التطبيق وإصلاحها.
لذلك، وبعد التأكّد من تفعيل البيئة البرمجية وتثبيت فلاسك، سننشئ ملفًا ضمن المجلد "flask_app" باسم "app.py" لتحريره:
(env)user@localhost:$ nano app.py
ونكتب ضمنه الشيفرة التالية:
from flask import Flask app = Flask(__name__) @app.route('/') def index(): return render_template('index.html')
استوردنا في الجزء السابق من الشيفرة كائن فلاسك من حزمة falsk، ثم استخدمناه لإنشاء نسخةٍ فعليةٍ موجودةٍ في الذاكرة لتطبيق فلاسك Flask application instance باسم app
، ثمّ أنشأنا دالة العرض ()index
باستخدام المُزخرف ()app.route@
، الذي يحوّل دوال بايثون الاعتيادية إلى دوال عرض، وفيها تكون القيمة المُعادة استدعاءً للدالة ()render_template
، والتي تعرض بدورها قالب HTML المُسمى "index.html" في النتيحة. في هذه الشيفرة خطآن: الأوّل هو أنّنا لم نستورد دالة تصيّير القوالب ()render_template
من حزمة فلاسك قبل استدعائها، والثاني أنّ قالب HTML المسمّى "index.html" غير موجود بعد.
نحفظ الملف ونغلقه.
والآن، لا بُدّ من إرشاد فلاسك إلى موقع التطبيق (في حالتنا الملف ذو الاسم app.py) وذلك باستخدام متغير بيئة فلاسك FLASK_APP على النحو التالي (مع ملاحظة أنّنا نستخدم الأمر set
في بيئة ويندوز عوضًا عن الأمر export
):
(env)user@localhost:$ export FLASK_APP=app
بعدها سنشغل خادم التطبيق باستخدام الأمر flask run
:
(env)user@localhost:$ flask run
فتظهر في نافذة الطرفية المعلومات التالية:
* Serving Flask app 'app' (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
يحتوي الخرج السابق على عدة معلومات، مثل:
- اسم التطبيق المُشغَّل، وهو "app.py" في حالتنا.
- بيئة التشغيل الحالية التي يعمل عليها التطبيق، وهي في حالتنا بيئة الاستخدام الفعلي للتطبيق (نشر المنتج) "production"، وتُشدّد الرسالة التحذيرية هذه على كون الخادم غير مُخصّص لمرحلة نشر المنتج، وبما أنّنا نستخدمه بالواقع بغية تطوير التطبيق، فمن الممكن تجاهل الرسالة التحذيرية هذه.
- عبارة "Debug mode:off"، التي تشير إلى أن منقّح أخطاء فلاسك ليس قيد التشغيل، وبالتالي لن تتلقّى أي رسائل مفيدة حول الأخطاء الحاصلة في التطبيق، وهو أمرٌ طبيعي كوننا نعمل الآن في بيئة نشر المُنتج، فعرض رسائل تفصيلية بالأخطاء الحاصلة في هذه البيئة (نشر المنتج) يعرّض التطبيق لمخاطر أمنية ويعدّ ثغرة أمنية بحد ذاته.
- التطبيق يعمل على الرابط "/http://127.0.0.1:5000"، إذ أن "127.0.0.1" هو عنوان IP الذي يمثِّل الخادم المحلي localhost، و "5000:" هو رقم المنفذ، ولإيقاف تشغيل الخادم يمكنك الضغط على مفتاحي "CTRL+C"، ولكن لا توقفه الآن.
الآن، وبالانتقال إلى الصفحة الرئيسية للتطبيق في المتصفّح عبر الرابط:
http://127.0.0.1:5000/
ستظهر رسالة شبيهة بما يلي في الخرج:
Internal Server Error The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.
وهو خطأ الخادم الداخلي 500 Internal Server Error، الذي يمثّل استجابةً خاطئةً للخادم تشير لكونه يواجه خطأً داخليًا في شيفرة التطبيق.
وسيظهر الخرج التالي في نافذة الطرفية:
[2021-09-12 15:16:56,441] ERROR in app: Exception on / [GET] Traceback (most recent call last): File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 2070, in wsgi_app response = self.full_dispatch_request() File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1515, in full_dispatch_request rv = self.handle_user_exception(e) File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1513, in full_dispatch_request rv = self.dispatch_request() File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1499, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args) File "/home/abd/python/flask/series03/flask_app/app.py", line 8, in index return render_template('index.html') NameError: name 'render_template' is not defined 127.0.0.1 - - [12/Sep/2021 15:16:56] "GET / HTTP/1.1" 500 -
تمرُ عملية التعقّب العكسي أعلاه على الشيفرة المُسبّبة لخطأ الخادم الداخلي، إذ يشير السطر "NameError: name 'render_template' is not defined" إلى أصل المشكلة (خطأ في الاسم)، وهو في حالتنا أنّ الدالة ()render_template
غير مستوردة ما جعلها غير مُعرّفة.
وهنا نلاحظ أنّه لا بدّ من الذهاب إلى نافذة الطرفية لاكتشاف الأخطاء (وليس ضمن خرج التطبيق نفسه)، والذي يُعد أمرًا غير ملائم في الاستخدام الفعلي.
فمن الممكن الحصول على تجربة استكشاف أخطاء وإصلاحها بطريقة أفضل وأسهل عبر تمكين وضع تنقيح الأخطاء في خادم التطوير، ولإجراء ذلك سنوقف عمل الخادم عبر الضغط على مفتاحي "CTRL+C"، ثمّ سنضبط متغير بيئة فلاسك FLASK_ENV
على الوضع development
، ما يمكنّنا من تشغيل البرنامج في وضع التطوير ( الذي يُمكِّن منقّح الأخطاء) كما يلي (مع ملاحظة أنّنا نستخدم الأمر set
في بيئة ويندوز عوضًا عن الأمر export
):
(env)user@localhost:$ export FLASK_ENV=development
نشغّل الآن خادم التطوير:
(env)user@localhost:$ flask run
فيظهر في نافذة الطرفية خرجٌ مُشابه لما يلي:
* Serving Flask app 'app' (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 120-484-907
ومنه نلاحظ أنّ وضع التشغيل حاليًا هو وضع التطوير، وأنّ وضع تنقيح الأخطاء قيد التشغيل وبالتالي فإنّ مُنقّح الأخطاء مُفعّل، أمّا "Debugger PIN" فيمثّل الرمز السرّي اللازم لإلغاء قفل سجل المتصفّح (لوحة المراقبة) Browser console (وهي صَدفة shell بايثون تفاعلية يمكنك الوصول إليها بالضغط على رمز الطرفية الصغير المحاط بدائرة حمراء في الصورة أدناه).
وبتحديث الصفحة الرئيسية للتطبيق ضمن المتصفح، ستظهر الصفحة بالشّكل التالي:
وفي هذه الحالة نرى رسالة الخطأ بصياغة وطريقة عرض أسهل للفهم، إذ يشير العنوان الأوّل فيها إلى اسم استثناء بايثون المُتسبّب بالمشكلة (وهو خطأ في الاسم "NameError" في حالتنا).
أما السطر الثاني من رسالة الخطأ فيشير إلى سبب الخطأ المباشر (وهو في حالتنا أنّ الدالة ()render_template
غير مُعرّفة ما يعني أنّها غير مستوردة أصلًا)، أمّا الأسطر التالية فهي نتيجة التعقّب العكسي لشيفرة فلاسك الداخلية المُنفّذة، ومن المُفضّل قراءة هذا الجزء من الأسفل إلى الأعلى، إذ يتضمّن عادةً السطر الأخير من نتيجة التعقّب العكسي أكثر المعلومات أهمية.
ملاحظة: يمكنّك رمز الطرفية الصغير المحاط بدائرة حمراء في الصورة السابقة من تشغيل الشيفرة المكتوبة أصلًا بلغة بايثون ضمن المُتصفّح في أُطر عمل مُختلفة (وهنا ضمن ما يشبه صَدفة بايثون التفاعلية ولكن ضمن المتصفح نفسه)، وهذا أمرٌ مفيدٌ للتحقّق مثلًا من قيمة متغير ما بنفس الطريقة التي تؤديها صَدفة بايثون التفاعلية. عند الضغط على هذه الأيقونة يجب إدخال الرمز السري الخاص بالمنقّح Debugger PIN الذي حصلت عليه عند تشغيل الخادم في الخطوة السابقة، ولكن ضمن محاور هذا المقال لن نحتاج إلى استخدامها.
الآن، لإصلاح الخطأ في الاسم "NameError" الحاصل، نترك الخادم بحالة تشغيل ونفتح نافذة طرفية جديدة، وفيها نفعّل البيئة البرمجية ومن ثمّ نفتح الملف app.py لتحريره:
(env)user@localhost:$ nano app.py
ونعدله ليصبح كما يلي:
from flask import Flask, render_template app = Flask(__name__) @app.route('/') def index(): return render_template('index.html')
نحفظ الملف ونغلقه.
استوردنا في الشيفرة السابقة دالة تصيّير القوالب ()render_template
من حزمة فلاسك flask
، الأمر الذي كان ناقصًا فيما سبق مُتسبّبًا بالخطأ الحاصل.
الآن وبعد التأكّد من كون خادم التطوير ما يزال قيد التشغيل، نحدّث الصفحة الرئيسية للتطبيق ضمن المتصفح، فتظهر صفحة خطأ في الخرج، تتضمّن معلومات شبيهة بما يلي:
jinja2.exceptions.TemplateNotFound jinja2.exceptions.TemplateNotFound: index.html
وتشير رسالة الخطأ هذه لكون القالب "index.html" غير موجود.
ولإصلاح هذا الخطأ، سننشئ ملف قالب باسم "base.html" لتتمكّن القوالب الأُخرى من وراثة شيفراته بغية تجنُّب تكرار الشيفرات، كما سننشئ القالب "index.html"، الذي سيرث من القالب الرئيسي.
لذا سننشئ مجلد للقوالب باسم "templates" ضمن المجلد "flask_app"، إذ سيبحث فلاسك ضمنه عن القوالب، وبعدها سنفتح ملف القالب الأساسي المُسمى "base.html" باستخدام أي محرّر نصوص (هنا سنستخدم محرّر النصوص نانو nano:
(env)user@localhost:$ mkdir templates (env)user@localhost:$ nano templates/base.html
ثمّ نكتب الشيفرة التالية ضمن الملف base.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{% block title %} {% endblock %} - FlaskApp</title> <style> nav a { color: #d64161; font-size: 3em; margin-left: 50px; text-decoration: none; } </style> </head> <body> <nav> <a href="{{ url_for('index') }}">FlaskApp</a> <a href="#">About</a> </nav> <hr> <div class="content"> {% block content %} {% endblock %} </div> </body> </html>
نحفظ الملف ونغلقه.
يتضمّن القالب الأساسي كافّة الشيفرات المتداولة التي ستحتاجها في القوالب الأُخرى، وستُستبدل لاحقًا كتلة العنوان title
بعنوان كل صفحة وكتلة المحتوى content
بمحتواها، أمّا عن شريط التصفح فسيتضمّن رابطين، الأوّل ينقل المُستخدم إلى الصفحة الرئيسية للتطبيق باستخدام الدالة المساعدة ()url_for
لتحقيق الربط مع دالة العرض ()index
، والآخر لصفحة المعلومات حول التطبيق في حال قررت تضمينها في تطبيقك.
لذا سنفتح ملف قالب index.html، الذي سيرث شيفراته من القالب الرئيسي:
(env)user@localhost:$ nano templates/index.html
ونكنب ضمنه الشيفرة التالية:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Index {% endblock %}</h1> <h2>Welcome to FlaskApp!</h2> {% endblock %}
ثمّ نحفظ الملف ونغلقه.
وسّعنا في الشيفرة السابقة ملف القالب "base.html"، متجاوزين كتلة المحتوى content
، ثمّ استخدمنا كتلة العنوان title
لتعيين عنوان للصفحة وعرضه ضمن تنسيق عنوان من المستوى الأوّل H1
، كما كتبنا الشيفرة اللازمة لعرض رسالة ترحيب ضمن تنسيق عنوان من المستوى الثاني H2
.
الآن وبعد التأكّد من كون خادم التطوير ما يزال قيد التشغيل، نحدّث الصفحة الرئيسية للتطبيق ضمن المتصفح، فنجد أنّ التطبيق لا يعرض أي أخطاء وأنّ الصفحة الرئيسية للتطبيق تبدو بالشكل المطلوب.
ومع نهاية هذه الخطوة نكون قد استخدمنا وضع تنقيح الأخطاء وتعرفنا على كيفية التعامل مع رسائل الخطأ، وسنعمل في الخطوة التالية على إيقاف طلب ما ليستجيب برسالة خطأ من اختيارنا، وسنتعلّم كيفية إنشاء صفحات أخطاء مُخصّصة لتظهر بمثابة استجابة لطلبات معينة.
الخطوة الثانية - تخصيص صفحات أخطاء
سنتعرّف في هذه الخطوة على كيفية إلغاء طلب المستخدم في حال أراد الوصول إلى بيانات غير موجودة على الخادم والاستجابة برسالة خطأ HTTP من النوع 404، كما سنتعلّم كيفية إنشاء صفحات أخطاء مُخصّصة لتظهر بمثابة استجابة لأخطاء HTTP الشائعة، مثل خطأ الخادم الداخلي "500 Internal Server Error" وخطأ عدم توفّر البيانات المطلوبة "404 Not Found".
ولنبيّن كيفية إلغاء طلب والاستجابة بصفحة خطأ مُخصّصة بخطأ HTTP من نوع 404، سننشئ صفحةً مهمتها عرض عدد من الرسائل، وفي حال طلب رسالة غير موجودة، ستستجيب بخطأ من النوع 404.
لذا سنفتح الملف app.py لإضافة وجهةٍ route جديدة لصفحة الرسائل:
(env)user@localhost:$ nano app.py
وسنضيف الوجهة التالية إلى نهاية الملف:
# ... @app.route('/messages/<int:idx>') def message(idx): messages = ['Message Zero', 'Message One', 'Message Two'] return render_template('message.html', message=messages[idx])
نحفظ الملف ونغلقه.
استخدمنا في الشيفرة السابقة متغيرًا يدل على رقم مؤشّر الروابط باسم idx
، والمُتضمّن رقم المؤشر الذي من شأنه تحديد الرسالة المطلوب عرضها، بمعنى أنّه إذا كان الرابط هو "/messages/0"، فهذا يعني أنّ الرسالة الأولى ذات المؤشّر بالقيمة صفر هي المطلوب عرضها؛ كما استخدمنا محوّل القيم int
المسؤول عن قبول قيم من النوع "عدد صحيح موجب" فقط، ذلك لأنّ متحولات العناوين تكون افتراضيًا بنمط بيانات من النوع "سلاسل نصية".
تتضمّن دالة العرض message()
قائمة بايثون اعتيادية باسم "messages" تحتوي على ثلاث رسائل (ولكن في الواقع العملي سيكون مصدر هذه الرسائل هو قاعدة البيانات، أو واجهة API، أو أي مصدر خارجي للبيانات، وليست مخزّنة يدويًا كما هو الحال في مثالنا التوضيحي هذا)، وفيها تكون القيمة المُعادة استدعاءً للدالة ()render_template
مع وسيطين، هما: الملف "message.html" وسيطًا لملف القالب، والمتغير message
الذي سيُمرّر بدوره إلى القالب، إذ تكون قيمة هذا المتغير هي أحد عناصر القائمة "messages" (أي إحدى الرسائل) وذلك تبعًا لقيمة المتغيّر idx
الدال على رقم مؤشّر الرسالة المطلوبة والموجود ضمن الرابط.
ومن ثمّ سننشئ ملف القالب "message.html":
(env)user@localhost:$ nano templates/message.html
ونكتب ضمنه الشيفرة التالية:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Messages {% endblock %}</h1> <h2>{{ message }}</h2> {% endblock %}
نحفظ الملف ونغلقه.
وسّعنا في الشيفرة السابقة ملف القالب "base.html"، متجاوزين كتلة المحتوى content
، ثمّ أضفنا عنوانًا وهو "Messages" ضمن تنسيق عنوان من المستوى الأوّل H1
، كما عرضنا قيمة المتغير message
المُتضمّن الرسالة المطلوبة حسب الرابط ضمن تنسيق عنوان من المستوى الثاني H2
.
الآن، ويعد التأكّد من كون خادم التطوير ما يزال قيد التشغيل، سنزور الروابط التالية تباعًا في المتصفح:
http://127.0.0.1:5000/messages/0 http://127.0.0.1:5000/messages/1 http://127.0.0.1:5000/messages/2 http://127.0.0.1:5000/messages/3
ولدى زيارة الروابط الثلاث الأولى، ستظهر النصوص "Message Zero" و "Message One" و "Message Two" على الترتيب، ضمن تنسيق عنوان من المستوى الثاني H2
، في حين سيستجيب الخادم برسالة الخطأ التفصيليّة "IndexError: list index out of range" لدى زيارة الرابط الرابع (والتي تعني أنّ رقم مؤشّر الرسالة المطلوبة خارج نطاق الرسائل المتوفرّة)، لأنّنا في وضع التطوير؛ ولو كنا نعمل في بيئة نشر المنتج (التشغيل الفعلي للتطبيق) لظهر خطأ الخادم الداخلي "500 Internal Server Error"، إلّا أنّ الاستجابة الأنسب لمثل هذه الحالة بغية الإيضاح للمُستخدم ماهية الخطأ الحاصل هي الاستجابة بخطأ "404 Not Found" دلالةً على أنّ الخادم غير قادر على إيجاد رسالة برقم مؤشّر مساوٍ للرقم "3".
من الممكن استخدام دالة فلاسك المساعدة ()abort
للاستجابة برسالة خطأ من النوع "404"، ولتطبيق هذا الأمر، سنفتح الملف app.py:
(env)user@localhost:$ nano app.py
وفيه سنعدّل السطر الأوّل لنستورد دالة ()abort
، ثمّ سنعدّل دالة العرض ()message
بإضافة البنية "try … except" كما هو موضّح في الشيفرة التالية:
from flask import Flask, render_template, abort # ... # ... @app.route('/messages/<int:idx>') def message(idx): messages = ['Message Zero', 'Message One', 'Message Two'] try: return render_template('message.html', message=messages[idx]) except IndexError: abort(404)
نحفظ الملف ونغلقه.
استوردنا في الشيفرة السابقة دالة ()abort
المُستخدمة في إلغاء طلب ما والاستجابة برسالة خطأ مُحدّدة، كما استخدمنا البنية "try … except" في الدالة ()message
بغية تغليفها، إذ نحاول بدايةً الاستجابة لطلب المستخدم بالقالب messages
المتضمّن الرسالة المطلوبة وفقًا لرقم المؤشّر المطلوب في الرابط، وفي حال عدم توفّر رسالة برقم مؤشّر موافق لذلك المطلوب، سيتحقّق الاستثناء except
ونستخدمه لاكتشاف الخطأ مستخدمين استدعاء الدالة (abort(404
لإلغاء هذا الطلب الخاطئ والاستجابة له بخطأ HTTP من النوع 404 "404 Not Found".
الآن ويعد التأكّد من كون خادم التطوير ما يزال قيد التشغيل، وباستخدام المتصفح سنزور الرابط الذي تسبّب بظهور رسالة الخطأ IndexError
في المرة الماضية (أو أي رايط برقم مؤشّر أكبر من الرقم "2"):
http://127.0.0.1:5000/messages/3
فتظهر لنا الاستجابة التالية في النتيجة:
Not Found The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
وبذلك أصبح لدينا رسالة خطأ أفضل وأوضح تشير إلى عدم تمكُّن الخادم من إيجاد الرسالة المطلوبة.
أمّا فيما يلي، فسنعمل على إنشاء قالب خاص بعرض صفحة بالخطأ من النوع 404 وآخر لعرض صفحة بالخطأ من النوع 500.
بدايةً سنعرّف دالة باستخدام المُزخرف الخاص ()app.errorhandler
مهمتها التعامل مع الخطأ من النوع 404، لذا سنفتح الملف "app.py" لتحريره:
nano app.py
ونعدّل الملف ليصبح كما يلي:
from flask import Flask, render_template, abort app = Flask(__name__) @app.errorhandler(404) def page_not_found(error): return render_template('404.html'), 404 @app.route('/') def index(): return render_template('index.html') @app.route('/messages/<int:idx>') def message(idx): messages = ['Message Zero', 'Message One', 'Message Two'] try: return render_template('message.html', message=messages[idx]) except IndexError: abort(404)
نحفظ الملف ونغلقه.
استخدمنا في الشيفرة السابقة المُزخرف ()app.errorhandler@
لتعريف الدالة ()page_not_found
وتخصيصها للتعامل مع نوع خطأ بحد ذاته، إذ نمرّر لها نوع الخطأ مثل وسيط، وتكون القيمة المُعادة استدعاءً للدالة ()render_template
مع القالب المُسمّى "404.html" الذي سننشئه لاحقًا وسيطًا لها؛ كما من الممكن تغيير اسم القالب بالاسم الذي نريد، لتكون بدورها القيمة المعادة من الدالة ()render_template
هي الرقم الصحيح 404، وهذا يدل فلاسك على أنّ رمز الخطأ في الاستجابة يجب أن يكون "404"، وفي حال عدم تحديد أي رقم لدى استدعاء الدالة، فيستجيب فلاسك افتراضيًا بالرقم "200" الدال على نجاح الطلب.
الآن، سنفتح ملف القالب المُسمّى "404.html":
(env)user@localhost:$ nano templates/404.html
ونكتب ضمنه الشيفرة التالية:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} 404 Not Found. {% endblock %}</h1> <p>OOPS! Sammy couldn't find your page; looks like it doesn't exist.</p> <p>If you entered the URL manually, please check your spelling and try again.</p> {% endblock %}
نحفظ الملف ونغلقه.
في الشيفرة السابقة وكما هو الحال في جميع القوالب، وسّعنا ملف القالب "base.html" واستبدلنا محتويات كتلتي المحتوى content
والعنوان title
، وأضفنا أي شيفرات HTML نريدها، إذ أضفنا عنوانًا مُنسقًا وفق المستوى الأوّل من العناوين باستخدام الوسم <h1>
، كما استخدمنا وسم فقرة <p>
لإضافة رسالة خطأ مُخصّصة تُعلم المستخدم بكون الصفحة المطلوبة غير متوفرّة، إضافةً لرسالة تفيد أولئك المستخدمين الذين قد أدخلوا الرابط المطلوب يدويًا.
كما من الممكن إضافة أي شيفرات HTML و CSS وجافا سكربت JavaScript نريدها لبناء وتنسيق صفحات الأخطاء، كما هو الحال في أي قالب آخر.
الآن ويعد التأكّد من كون خادم التطوير ما يزال قيد التشغيل، سننتقل مُجدّدًا إلى الرابط:
http://127.0.0.1:5000/messages/3
فتظهر الصفحة بشريط تصفُّح (الموروث من القالب الرئيسي) وبرسالة الخطأ المُخصّصة التي حددناها، وسنضيف بنفس الآلية صفحة خطأ مُخصّصة لأخطاء الخادم الداخلية "500 Internal Server Error"، لذا سنفتح الملف app.py لتحريره:
(env)user@localhost:$ nano app.py
سنضيف الشيفرة المسؤولة عن التعامل مع الخطأ من النوع "500" أسفل تلك المسؤولة عن التعامل مع الخطأ من النوع "404"، كما يلي:
# ... @app.errorhandler(404) def page_not_found(error): return render_template('404.html'), 404 @app.errorhandler(500) def internal_error(error): return render_template('500.html'), 500 # ...
اعتمدنا في الشيفرة السابقة نفس الآلية المُتبعة للتعامل مع الخطأ 404، إذ استخدمنا المُزخرف ()app.errorhandler
مع الرقم "500" مثل وسيط لإنشاء دالّة باسم ()internal_error
للتعامل مع هذا النوع من الأخطاء. صيّر قالبًا باسم "500.html" ليستجيب برمز الخطأ ذو الرقم "500".
والآن ولبيان كيفيّة عرض رسالة الخطأ المُخصّصة، سنضيف إلى نهاية الملف app.py وجهةً تستجيبُ بخطأ HTTP من النوع "500"، والذي سيعيد خطأ "500 Internal Server Error" سواءٌ كان منقّح الأخطاء قيد التشغيل أم لا:
# ... @app.route('/500') def error500(): abort(500)
أنشأنا في جزء الشيفرة السابق الوجهة /500
واستخدمنا الدالة abort()
لإنشاء استجابة بخطأ HTTP من النوع "500".
نحفظ الملف ونغلقه.
ومن ثمّ سننشئ ملف القالب المُسمّى 500.html ضمن مجلد القوالب، كما يلي:
(env)user@localhost:$ nano templates/500.html
ونكتب ضمنه الشيفرة التالية:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} 500 Internal Server Error {% endblock %}</h1> <p>OOOOPS! Something went wrong on the server.</p> <p>Sammy is currently working on this issue. Please try again later.</p> {% endblock %}
ثمّ نحفظ الملف ونغلقه.
اتبعنا في الشيفرة السابقة نفس الآلية المُتبعة في القالب 404.html، إذ وسّعنا ملف القالب base.html، واستبدلنا محتويات كتلة المحتوى content
بعنوان ورسالتين مُخصّصتين لإعلام المستخدم حول خطأ الخادم الداخلي الحاصل.
الآن، وبعد التأكّد من كون خادم التطوير ما يزال قيد التشغيل، سننتقل إلى الوجهة الجديدة التي تستجيب بالخطأ من النوع "500" عبر الرابط:
http://127.0.0.1:5000/500
فستظهر صفحتنا المُخصّصة عوضًا عن صفحة الخطأ العامّة التي تظهر افتراضيًا.
ومع نهاية هذه الخطوة نكون قد تعرفنا على كيفية استخدام صفحات أخطاء مُخصّصة لأخطاء HTTP في تطبيقات فلاسك. أمّا في الخطوة التالية فسنتعرّف على كيفية استخدام السجل لتتبّع الأحداث في التطبيق، والذي من شأنه تعزيز فهمنا لآلية عمل شيفرة التطبيق ما يساعد بدوره على تطوير التطبيق واكتشاف الأخطاء وإصلاحها.
الخطوة الثالثة - استخدام السجل لتتبع أحداث التطبيق
سنستخدم في هذه الخطوة السجل لتتّبع الأحداث الحاصلة لدى تشغيل الخادم واستخدام التطبيق، ما يساعدنا في معرفة ما يحدث لشيفرة التطبيق أثناء تنفيذها، وهذا سيسهّل بدوره اكتشاف الأخطاء وإصلاحها.
لقد سبق واطلعنا على سجل الأحداث عند كل تشغيل لخادم التطوير، والذي يبدو عادةً كما يلي:
127.0.0.1 - - [21/Sep/2021 14:36:45] "GET /messages/1 HTTP/1.1" 200 - 127.0.0.1 - - [21/Sep/2021 14:36:52] "GET /messages/2 HTTP/1.1" 200 - 127.0.0.1 - - [21/Sep/2021 14:36:54] "GET /messages/3 HTTP/1.1" 404 -
إذ يتضمّن سجل الأحداث السابق المعلومات التالية:
- "127.0.0.1": عنوان المضيف الذي يعمل ضمنه الخادم.
- "[21/Sep/2021 14:36:45]": وقت وتاريخ الطلب.
- "GET": نوع طلب HTTP وهو في هذه الحالة من النوع "GET" المُستخدم لجلب البيانات.
- "messages/2/": المسار الذي طلبه المُستخدم.
- "HTTP/1.1": إصدار بروتوكول HTTP المُستخدَم.
- 200 أو 404: رمز حالة الطلب.
تساعد سجلات الأحداث هذه في تشخيص الأخطاء الحاصلة في التطبيق، كما من الممكن تسجيل مزيدٍ من المعلومات في سجلات الأحداث باستخدام مُسجّل الأحداث "app.logger" الذي يوفرّه فلاسك، وذلك بغية الحصول على معلومات تفصيلية حول طلباتٍ بحد ذاتها.
يمكننا مع مُسجّل الأحداث هذا استخدام عدة دوال تعيد معلومات حول الأحداث باختلاف مستوياتها، إذ يدل كل مستوًى على حدثٍ ذي درجة معيّنة من الخطورة، ومن هذه الدوال:
-
app.logger.debug()
: للحصول على معلومات تفصيلية حول الحدث. -
app.logger.info()
: للتأكّد من كون الأمور تسير على النحو السليم. -
app.logger.warning()
: تشير لوقوع حدث غير متوقّع، مثل انخفاض مساحة القرص الصلب التخزينية، مع بقاء التطبيق يعمل على النحو السليم. -
app.logger.error()
: تشير لوقوع خطأ في أحد أجزاء التطبيق. -
app.logger.critical()
: يشير لوقوع خطأ حرج يعرّض كامل التطبيق للتوقف عن العمل.
ولبيان كيفيّة استخدام سجّل الأحداث في فلاسك، سنفتح الملف app.py لتحريره بغية تسجيل بعض الأحداث:
(env)user@localhost:$ nano app.py
سنعدّل الدالة ()message
لتصبح كما يلي:
# ... @app.route('/messages/<int:idx>') def message(idx): app.logger.info('Building the messages list...') messages = ['Message Zero', 'Message One', 'Message Two'] try: app.logger.debug('Get message with index: {}'.format(idx)) return render_template('message.html', message=messages[idx]) except IndexError: app.logger.error('Index {} is causing an IndexError'.format(idx)) abort(404) # ...
نحفظ الملف ونغلقه.
سجّلنا في الشيفرة السابقة عددًا من الأحداث من مستوياتٍ مختلفة، إذ استخدمنا الدالة ()app.logger.info
لتسجيل حدث يعمل بصورةٍ سليمة (أي من مستوى المعلومات INFO
)؛ كما استخدمنا الدالة ()app.logger.debug
لتسجيل حدث بمعلوماتٍ تفصيلية (أي من مستوى التنقيح DEBUG
) مشيرين لحصول التطبيق في هذا الحدث ضمن الطلب على رقم مؤشّر رسالة؛ ثمّ استخدمنا الدالة ()app.logger.error
لتسجيل حقيقة كون الاستثناء IndexError
قد تحقّق بسبب ورود رقم مؤشّر خاطئ مُسبّبًا حدوث الخلل (أي حدث من مستوى الخطأ ERROR
، ذلك لأنّ خطأً ما قد حدث فعلًا).
الآن وبزيارة الرابط:
http://127.0.0.1:5000/messages/1
نحصل في الخرج على المعلومات التالية ضمن نافذة الطرفية التي يعمل ضمنها الخادم:
[2021-09-21 15:17:02,625] INFO in app: Building the messages list... [2021-09-21 15:17:02,626] DEBUG in app: Get message with index: 1 127.0.0.1 - - [21/Sep/2021 15:17:02] "GET /messages/1 HTTP/1.1" 200 -
وفيه نرى رسالة المعلومات INFO
المُسجلّة من قبل الدالة ()app.logger.info
، ورسالة التنقيح DEBUG
المُسجّلة من قبل الدالة ()app.logger.debug
والتي تبيّن رقم المؤشّر المطلوب.
الآن وبزيارة رابط برقم مؤشّر خارج المجال المتوفّر، مثل:
http://127.0.0.1:5000/messages/3
نحصل على الخرج التالي ضمن نافذة الطرفية:
[2021-09-21 15:33:43,899] INFO in app: Building the messages list... [2021-09-21 15:33:43,899] DEBUG in app: Get message with index: 3 [2021-09-21 15:33:43,900] ERROR in app: Index 3 is causing an IndexError 127.0.0.1 - - [21/Sep/2021 15:33:43] "GET /messages/3 HTTP/1.1" 404 -
وهنا نلاحظ أنّنا قد حصلنا على كل من سجلات INFO
و DEBUG
التي حصلنا عليها المرة الماضية ولكن مع سجل جديد لحدث خطأ ERROR
بسبب طلب رسالة برقم مؤشّر غير موجود وهو الرقم "3" في مثالنا.
لهذه الأحداث المُسجّلة والمعلومات التفصيلية والأخطاء المُسجلة الدور المهم في المساعدة على تحديد مكان حدوث الخطأ ما يجعل اكتشافه وإصلاحه أسهل.
وبذلك ومع نهاية هذه الخطوة نكون قد تعلمنا كيفية التعامل مع مُسجّل الأحداث في فلاسك.
الخاتمة
تعرفنا في هذا المقال على كيفية استخدام وضع التنقيح في فلاسك، واكتشاف وإصلاح بعض الأخطاء الشائعة التي قد تواجهنا خلال تطوير تطبيق ويب باستخدام فلاسك، كما تعلمنا كيفية إنشاء صفحات أخطاء مُخصّصة لأخطاء HTTP الشائعة، ونهايةً استعرضنا كيفية استخدام مسجّل الأحداث الذي يوفرّه فلاسك لتتبع الأحداث في التطبيق ما يساعد على فحص سلوك التطبيق وفهمه.
ترجمة -وبتصرف- للمقال How To Handle Errors in a Flask Application لصاحبه Abdelhadi Dyouri.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.