الفرق بين المراجعتين ل"Python/cgi"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
سطر 189: سطر 189:
 
* راجع ملفات التسجيل log files في مخدّم HTTP الذي تستخدمه (يمكن استخدام الأمر <code>tail -f logfile</code> في نافذة مستقلة).
 
* راجع ملفات التسجيل log files في مخدّم HTTP الذي تستخدمه (يمكن استخدام الأمر <code>tail -f logfile</code> في نافذة مستقلة).
 
* احرص دائمًا على مراجعة السكربت والتأكد من عدم وجود أخطاء لغوية، وذلك بتنفيذ الأمر <code>python script.py</code> مثلًا.
 
* احرص دائمًا على مراجعة السكربت والتأكد من عدم وجود أخطاء لغوية، وذلك بتنفيذ الأمر <code>python script.py</code> مثلًا.
* إن لم يحتوِ السكربت على أخطاء لغوية، جرّب إضافة العبارة import cgitb; cgitb.enable()‎ في بداية السكربت.
+
* إن لم يحتوِ السكربت على أخطاء لغوية، جرّب إضافة العبارة <code>import cgitb; cgitb.enable()</code>‎ في بداية السكربت.
 
* تأكد من إمكانية العثور على البرامج الخارجية عند تنفيذها، وهذا يعني عادة استخدام أسماء مسارات مطلقة. لا يمكن استخدام PATH لأنّه لا يمتلك قيمة مفيدة في سكربتات CGI.
 
* تأكد من إمكانية العثور على البرامج الخارجية عند تنفيذها، وهذا يعني عادة استخدام أسماء مسارات مطلقة. لا يمكن استخدام PATH لأنّه لا يمتلك قيمة مفيدة في سكربتات CGI.
 
* تأكد من أن المستخدم الذي ينفّذ السكربت الخاصّ بك قادر على القراءة والكتابة من الملفات الخارجية. يمتلك هذا المستخدم معرّفًا <code>userid</code> في مخدّم الويب الذي ينفّذ السكربت، أو يمكن أن يُعيّن المعرّف بواسطة الميزة <code>suexec</code> في مخدّم الويب.
 
* تأكد من أن المستخدم الذي ينفّذ السكربت الخاصّ بك قادر على القراءة والكتابة من الملفات الخارجية. يمتلك هذا المستخدم معرّفًا <code>userid</code> في مخدّم الويب الذي ينفّذ السكربت، أو يمكن أن يُعيّن المعرّف بواسطة الميزة <code>suexec</code> في مخدّم الويب.

مراجعة 20:04، 21 ديسمبر 2018


تقدّم هذه الوحدة عددًا من الأدوات التي تستخدم بواسطة سكربتات CGI المكتوبة في بايثون.

مقدمة

تنفّذ سكربتات CGI بواسطة مخدّم HTTP وتستخدم عادة لمعالجة مدخلات المستخدم المرسلة إلى المخدّم من خلال عنصر <FORM> أو <ISINDEX> في HTML.

تستقرّ سكربتات CGI معظم الأحيان في المجلد الخاص cgi-bin في المخدّم، ويضع مخدّم HTTP جميع المعلومات المرتبطة بالطلب (مثل اسم المضيف لدى العميل، عنوان URL المطلوب، سلسلة الاستعلام النصية، وغير ذلك الكثير) في بيئة الصدفة الخاصة بالسكربت، وينفّذ السكربت ثم يرسل مخرجاته إلى العميل مرة أخرى.

مدخلات السكربت مرتبطة بالعميل أيضًا، وفي بعض الأحيان تُقرأ بيانات النموذج بهذه الطريقة، وفي أحيان أخرى تُمرّر بيانات النموذج عبر سلسلة الاستعلام النصية "query string" كجزء من عنوان URL. أعدّت هذه الوحدة لمعالجة الحالات المختلفة وتقديم واجهة أبسط للتعامل مع سكربتات بايثون. تقدّم هذه الوحدة كذلك عددًا من الأدوات التي تساعد في تعقّب أخطاء السكربتات وتصحيحها، إلى جانب دعمها لعملية رفع الملفّات من نماذج HTML (إن كان المتصفّح يدعم ذلك).

يجب أن تكون مخرجات سكربتات CGI مقسومة إلى قسمين، يفصل بينهما سطر فارغ. يتضمّن القسم الأول عددًا من الترويسات والتي تخبر العميل بطبيعة البيانات المرسلة إليه. يبين المثال التالي كيفية إنشاء قسم ترويسات بسيط:

print("Content-Type: text/html")    
print()                             # السطر الفارغ يعني نهاية قسم الترويسات

أما القسم الثاني فيتضمّن عادة شيفرة HTML، والتي تسمح لبرنامج العميل بأن يعرض النصوص بطريقة منسّقة وجميلة باستخدام الترويسات والصور وغيرها. تطبع الشيفرة التالية شيفرة HTML بسيطة:

print("<TITLE>CGI script output</TITLE>")
print("<H1>This is my first CGI script</H1>")
print("Hello, world!")

استخدام وحدة cgi

للبدء باستخدام الوحدة يجب استيرادها بواسطة الشيفرة التالية:

import cgi

وعند كتابة سكربت جديد أضف السطرين التاليين إلى بداية السكربت:

import cgitb
cgitb.enable()

تفعّل الشيفرة السابقة أداة خاصّة للتعامل مع الاستثناءات تعرض تقريرًا مفصّلا في متصفح الويب عند حدوث الأخطاء. إن كنت تفضل إخفاء الأخطاء عن مستخدمي الشيفرة الخاصّة بك، يمكنك حفظ التقارير إلى ملفات، وذلك باستخدام الشيفرة التالية:

import cgitb
cgitb.enable(display=0, logdir="/path/to/logdir")

تقدّم التقارير التي تنتجها هذه الأداة معلومات مفيدة من شأنها توفير الكثير من الوقت في تعقّب الأخطاء وإصلاحها. يمكن الاستغناء عن هذه الأداة بعد التأكد من أن السكربت خالٍ من الأخطاء ويعمل بصورة جيدة.

يمكن الوصول إلى البيانات المرسلة إلى المخدّم عن طريق الصنف FieldStorage، وإن كان النموذج يتضمّن حروفًا بترميز غير ترميز ASCII فيمكن استخدام المعامل المفتاحي encoding لتعيين قيمة الترميز المستخدم في الملف الحاوي على النموذج، ويمكن الوصول إلى الترميز المستخدم عادة في الوسم META في قسم HEAD من ملف HTML أو عن طريق الترويسة Content-Type).

يقرأ الصنف محتويات النموذج من المدخلات القياسية أو من البيئة (بالاعتماد على قيمة عدد من متغيرات البيئة والمعيّنة حسب معايير CGI). قد يستهلك الصنف المدخلات القياسية؛ لذا يجب تهيئته مرة واحدة فقط.

تشبه نسخ الصنف FieldStorage قواميس بايثون إلى حدّ كبير، إذ يمكن فهرسة الكائن كما هو الحال مع القواميس، ويمكن اختبار وجود عنصر معيّن باستخدام المعامل in، ويدعم الكائن كذلك استخدام التابع keys()‎ الخاصّ بالقواميس إضافة إلى الدالة الداخلية len()‎. لا تظهر حقول النموذج التي تتضمّن سلاسل نصية فارغة في القاموس وسيجري تجاهلها، وللإبقاء على مثل هذه القيم، يجب إعطاء المعامل المفتاحي keep_blank_values القيمة True عند إنشاء نسخة من الصنف FieldStorage.

فعلى سبيل المثال تتحقّق الشيفرة التالية (والتي تفترض أنّ الترويسة Content-Type والسطر الفارغ قد طُبعا مسبقًا) من أن الحقلين name و addr هما حقلان لا يحتويان على سلاسل نصية فارغة:

form = cgi.FieldStorage()
if "name" not in form or "addr" not in form:
    print("<H1>Error</H1>")
    print("Please fill in the name and addr fields.")
    return
print("<p>name:", form["name"].value)
print("<p>addr:", form["addr"].value)
...further form processing here...

الحقول في هذه الشيفرة والتي تم الوصول إليها باستخدام العبارة form[key]‎ هي بحدّ ذاتها نسخ من الكائن FieldStorage (أو MiniFieldStorage وذلك حسب ترميز النموذج). يمكن الوصول إلى قيمة الحقل النصية باستخدام الخاصية value في نسخ الكائن، ويعيد التابع getvalue()‎ السلسلة النصية مباشرة، ويستقبل كذلك معاملًا آخر اختياريًا يُستخدم لتعيين القيمة التي يعيدها التابع إن لم يكن المفتاح المطلوب موجودًا.

إن تضمّنت البيانات المرسلة من النموذج على حقول متعددة تحمل الاسم ذاته، فإنّ الكائن المستحصل من العبارة form[key]‎ لن يكون كائن FieldStorage أو MiniFieldStorage ولكن قائمة من هذه الكائنات. وكذلك الأمر بالنسبة للتابع form.getvalue(key)‎ إذ سيعيد قائمة من السلاسل النصية في مثل هذه الحالة. إن كنت تتوقع حدوث هذه الحالة (أي عندما يتضمّن نموذج HTML الخاصّ بك على عدد من الحقول التي تحمل الاسم عينه) فاستخدم التابع getlist()‎ والذي يعيد قائمة من القيم دائمًا (وبهذه الصورة لن تضطر إلى التعامل مع الحالات الخاصة التي تتضمّن عنصرًا واحدًا فقط).

تربط الشيفرة التالية أي عدد من الحقول التي تحمل الاسم username ويفصل بين قيمها بفاصلة:

value = form.getlist("username")
usernames = ",".join(value)

إن كان الحقل يمثّل ملفًّا مرفوعًا، فإنّ الوصول إلى قيمة الحقل باستخدام الخاصية value أو باستخدام التابع getvalue()‎ سيؤدي إلى قراءة الملف بأكمله في الذاكرة على هيئة بايتات، وقد يكون هذا أمرًا غير مرغوب به؛ لذا يمكن التحقّق من رفع الملف إما باختبار الخاصية filename أو الخاصية file، ثم يمكن بعد ذلك قراءة البيانات من الخاصية file قبل إغلاقه تلقائيًا كجزء من عملية التخلص من نسخة الصنف FieldStorage (القيمة المعادة من التابعين read()‎ و readline()‎ هي البايتات):

fileitem = form["userfile"]
if fileitem.file:
    # It's an uploaded file; count lines
    linecount = 0
    while True:
        line = fileitem.file.readline()
        if not line: break
        linecount = linecount + 1

يمكن استخدام كائنات FieldStorage مع عبارات with، والتي ستغلق هذه الكائنات عند انتهاء عمل العبارة.

تعين القيمة ‎-1 للخاصية done في الكائن عند حدوث خطأ أثناء جلب محتويات الملف المرفوع (عندما يقاطع المستخدم عملية إرسال النموذج بضغطه لزر الرجوع أو زر الإلغاء مثلًا).

تتيح مسوّدة معيار رفع عدد من الملفات باستخدام حقل واحد (باستخدام الترميز multipart/*‎)، فإنّ العناصر المستحصلة تكون على هيئة كائن FieldStorage شبيه بالقاموس. ويمكن التحقّق من حدوث هذه الحالة بواسطة الخاصية type، والتي يجب أن تكون multipart/form-data (أو أيّ نوع MIME آخر يطابق العبارة multipart/*‎)، وفي هذه الحالة يمكن المرور على عناصر الكائن تعاوديًا كما هو الحال مع كائن النموذج الأساسي.

إذا أرسل النموذج إلى المخدّم بالأسلوب القديم (كسلسلة استعلام نصية أو كبيانات ذات جزء واحد من نوع application/x-www-form-urlencoded) فإنّ العناصر ستكون نسخًا من الصنف MiniFieldStorage. وفي هذه الحالة تحمل الخصائص list و file و filename القيمة None دائمًا.

يتضمّن النموذج المرسل بطريقة POST والذي يضمّ سلسلة استعلام نصية أيضًا عناصر FieldStorage و MiniFieldStorage.

ملاحظات:

  • تُغلَق الخاصية file تلقائيًا عند التخلص من كائن FieldStorage التابعة له في الإصدار 3.4 من بايثون.
  • أصبح الصنف FieldStorage داعمًا لبروتوكول إدارة السياق context management في الإصدار 3.5 من بايثون.

الواجهة البرمجية لوحدة cgi

تتضمّن الواجهة البرمجية لوحدة cgi تابعين بسيطين، يمكن بواسطتهما معالجة بيانات النموذج بصورة عامة، دون الحاجة للتأكد من أنّ القيم المرسلة تمتلك الاسم ذاته أم لا.

دوالّ الوحدة cgi

تقدّم الدالة مجموعة من الدوال المساعدة:

معالجة المسائل الأمنية

إن كنت تستدعي برنامجًا خارجيًا (بواسطة الدالة os.system()‎ أو os.popen()‎ أو أي دالة أخرى مشابهة) فعليك أن تحرص جيّدًا على عدم تمرير السلاسل النصية التي تستقبلها من العميل إلى الصدفة. تعدّ هذه الحالة من الخروقات الأمنية الشائعة والتي يستغلّها المخترقون الأذكياء على الويب لاستغلال سكربتات CGI الضعيفة لتنفيذ الأوامر في الصدفة. كذلك لا يمكن الوثوق بعناوين URL أو أي جزء منها بل وحتى أسماء الحقول؛ إذ ليس من الضروري أن يأتي الطلب من النموذج الخاصّ بك فقط.

إن كنت بحاجة إلى تمرير سلسلة نصية من نموذج إلى الصدفة، ولتكون في مأمن من الخروقات الأمنية، يجب عليك أن تحرص على أن تتضمّن السلسلة النصية أرقامًا وحروفًا وخطوط فاصلة dashes وشرطات سفلية underscores ونقاط periods فقط.

تثبيت سكربت CGI في أنظمة يونكس

يجب عليك مراجعة توثيق مخدّم HTTP الذي تستخدمه وكذلك مراجعة مدير النظام المحلي لمعرفة المجلد الذي يجب أن تُثبّت فيه سكربتات CGI، وعادة ما يكون المجلد الذي يحمل الاسم cgi-bin في المخدّم.

احرص على أن يكون سكربتك سهل القراءة وقابلًا للتنفيذ من قبل الآخرين، ويجب أن يكون نمط ملفات يونكس هو النمط الثماني 0o755 (استخدم الأمر chmod 0755 filename). احرص كذلك على أن يتضمّن السطر الأول في السكربت الرمز ‎#! في العمود الأول متبوعًا بمسار مفسّر بايثون، فمثلًا:

#!/usr/local/bin/python

احرص على أن يكون مفسّر بايثون موجودًا وقابلًا للتشغيل من قبل المستخدم الذي يحمل الاسم "others".

احرص على أن تكون جميع الملفات التي يقرأها السكربت أو يكتب فيها قابلة للقراءة والكتابة من قبل المستخدم الذي يحمل الاسم "others"، ويجب أن تمتلك الملفات القابلة للقراءة الوضع 0o644 والملفات القابلة للكتابة الوضع 0o666. سبب ذلك هو أنّ مخدّم HTTP سينفّذ السكربت الخاص بك -ولأسباب أمنية- تحت اسم المستخدم "nobody" ودون أيّ امتيازات خاصة. يمكن لهذا المستخدم أن يقرأ الملفات (يكتب فيها وينفّذها) التي يمكن لأي شخص أن يقرأها (يكتب فيها وينفّذها). إضافة إلى أنّ المجلّد الحالي في وقت التنفيذ يكون مختلفًا (عادة ما يكون مجلد cgi-bin في المخدّم) وكذلك الأمر بالنسبة متغيرات البيئة إذ أنّها تكون مختلفة من تلك التي تحصل عليها عند الولوج إلى المخدّم. ويمكن القول أنّه يجب أن لا تعتمد على مسار البحث عن الملفات القابلة للتنفيذ (PATH) أو وحدة مسار البحث في بايثون (PYTHONPATH) لأنّهما لن يقدّما قيمًا مفيدة.

إن كنت بحاجة إلى تحميل وحدات معيّنة من مجلد غير موجود في المسار الافتراضي لوحدة مسار البحث في بايثون، يمكنك تغيير المسار في السكربت الخاص بك، قبل استيراد الوحدات الأخرى، فعلى سبيل المثال:

import sys
sys.path.insert(0, "/usr/home/joe/lib/python")
sys.path.insert(0, "/usr/local/lib/python")

بهذه الطريقة سيجري البحث في المجلد الأخير أوّلًا.

تختلف تعليمات التثبيت في الأنظمة الأخرى؛ لذا تحقّق من توثيق مخدّم HTTP الذي تستخدمه (تتضمّن التوثيقات عادة قسمًا خاصًّا بسكربتات CGI).

اختبار سكربت CGI

لن تعمل سكربتات CGI بصورة عامة عند تنفيذها في سطر الأوامر، وقد يعمل السكربت في سطر الأوامر على نحوٍ جيّد، ولكن يمكن لهذا السكربت أن يتسبب بمشاكل كبيرة عند تشغيله في المخدّم. ومع ذلك فهناك سبب واحد فقط يدعو إلى اختبار السكربتات في سطر الأوامر، وهو أنّ هذه السكربتات قد تتضمن أخطاء لغوية، وإن حدث ذلك فإنّ مفسّر بايثون لن ينفّذ هذه السكربتات إطلاقًا، وسيرسل مخدّم HTTP خطأ قد يكون مبهمًا بالنسبة للعميل.

إن السكربت الخاصّ بك خاليًا من الأخطاء ولكنّه مع ذلك لا يعمل، فراجع القسم تنقيح سكربتات CGI.

تنقيح سكربتات CGI

تأكد في البداية من عدم وجود أي أخطاء أثناء تثبيت السكربت (راجع قسم تثبيت سركبت CGI في أنظمة يونكس) للمزيد من التفاصيل.

للتحقّق من أنّ عملية التثبيت قد تمّت على ما يرام، يمكن تثبيت نسخة من ملف هذه الوحدة (cgi.py) كسكربت CGI. يؤدي تنفيذ هذا الملف كسكربت إلى تخلّص الملف من البيئة الخاصة به إضافة إلى محتويات النموذج في نموذج HTML. امنح الملف الصلاحيات المطلوبة، ثم أرسله كطلب، فإن كان الملف مثبّتًا في مجلد cgi-bin القياسي، فسيكون بالإمكان إرسال الملف كطلب عن طريق إدخال العنوان التالي في متصفح الويب:

http://yourhostname/cgi-bin/cgi.py?name=Joe+Blow&addr=At+Home

إن كانت النتيجة هي ظهور الخطأ 404 فهذا يعني أنّ المخدّم لم يتمكّن من العثور على السكربت، ويمكن أن يعني هذا أنّك بحاجة إلى تثبيت السكربت في مجلّد مختلف. أما في حال ظهور خطأ مغاير فيعني ذلك وجود خطأ في عملية التثبيت، وأنّك لن تكون قادرًا على المتابعة ما لم تقم بمعالجة المشكلة. أما إن حصلت على قائمة مرتّبة ومنسّقة من البيئة ومحتويات النموذج (في هذا المثال سيظهر الحقل "addr" مع القيمة "At Home" والحقل "name" مع القيمة "Joe Blow")، فهذا يعني أنّ السكربت cgi.py مثبّت بطريقة صحيحة. يمكنك اتباع الخطوات السابقة مع السكربت الخاص بك؛ وبهذا تكون قادرًا على تنقيحه.

الخطوة التالية هي استدعاء الدالة test()‎ في الوحدة cgi من داخل السكربت الخاصّ بك، وذلك باستبدال الشيفرة الرئيسية في السكربت بالعبارة

cgi.test()

يجب أن تحصل على نفس النتائج التي حصلت عليها عند تثبيت ملف cgi.py كسكربت.

إن أطلق سكربت بايثون اعتيادي استثناءً غير معالج (لأي سبب من الأسباب: خطأ في كتابة اسم الوحدة، عدم القدرة على فتح ملفّ معين... الخ)، فإنّ مفسّر بايثون سيطبع رسالة خطأ منسّقة ثم يوقف عمل السكربت. ينفّذ مفسّر بايثون الأمر ذاته مع الاستثناءات التي تطلقها سكربتات CGI، ولكنّ رسالة الخطأ ستذهب على الأرجح إلى ملفات التسجيل log files في مخدّم HTTP أو يجري تجاهلها تمامًا.

يمكن إرسال رسائل الأخطاء إلى متصفّح الويب بسهولة باستخدام الوحدة cgitb، وذلك بإضافة الأسطر التالية إلى بداية السكربت:

import cgitb
cgitb.enable()

تؤدي إضافة الأسطر السابقة إلى عرض تقرير مفصّل عند حدوث أي مشكلة في السكربت، ويمكن الاستفادة من هذا التقرير في معرفة السبب الرئيسي لتوقّف السكربت عن العمل.

إن كنت تشكّ في وجود مشكلة عند استيراد الوحدة cgitb فيمكن استخدام الطريقة التالية (والتي تستخدم وحدات بايثون الداخلية فقط):

import sys
sys.stderr = sys.stdout
print("Content-Type: text/plain")
print()
...your code here...

تعتمد هذه الطريقة على مفسّر بايثون في طباعة رسالة الخطأ، وتعرض المخرجات على هيئة نصوص صرفة palin text، الأمر الذي يؤدي إلى عدم معالجة شيفرة HTML. إن كان السكربت يعمل بصورة جيدة فإنّ شيفرة HTML الخام ستُعرض على العميل، وإن أطلق السكربت استثناءً فإنّ الشيفرة ستعرض رسالة الخطأ بعد طباعة السطرين الأوليين على الأرجح، وبما أنّ شيفرة HTML لن تفسّر فإنّ رسالة الخطأ ستكون سهلة القراءة.

حلول للمشاكل الشائعة

  • تخزّن معظم مخدمات HTTP مخرجات سكربتات CGI مؤقّتًا إلى حين اكتمال عمل السكربت. وهذا يعني عدم إمكانية عرض تقرير بسير عمل السكربت للعميل أثناء تنفيذ السكربت.
  • راجع تعليمات التثبيت في قسم (تثبيت سكربت CGI في أنظمة يونكس).
  • راجع ملفات التسجيل log files في مخدّم HTTP الذي تستخدمه (يمكن استخدام الأمر tail -f logfile في نافذة مستقلة).
  • احرص دائمًا على مراجعة السكربت والتأكد من عدم وجود أخطاء لغوية، وذلك بتنفيذ الأمر python script.py مثلًا.
  • إن لم يحتوِ السكربت على أخطاء لغوية، جرّب إضافة العبارة import cgitb; cgitb.enable()‎ في بداية السكربت.
  • تأكد من إمكانية العثور على البرامج الخارجية عند تنفيذها، وهذا يعني عادة استخدام أسماء مسارات مطلقة. لا يمكن استخدام PATH لأنّه لا يمتلك قيمة مفيدة في سكربتات CGI.
  • تأكد من أن المستخدم الذي ينفّذ السكربت الخاصّ بك قادر على القراءة والكتابة من الملفات الخارجية. يمتلك هذا المستخدم معرّفًا userid في مخدّم الويب الذي ينفّذ السكربت، أو يمكن أن يُعيّن المعرّف بواسطة الميزة suexec في مخدّم الويب.
  • لا تمنح سكربت CGI الوضع set-uid؛ لأنّ السكربت لن يعمل في معظم الأنظمة، إضافة إلى أنّ ذلك قد يتسبب في حدوث مشاكل أمنية.

انظر أيضًا

مصادر