الوحدة cgi‎‎ في بايثون

من موسوعة حسوب


تقدّم هذه الوحدة عددًا من الأدوات التي تستخدم بواسطة سكربتات 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

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

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

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

يمكن استخدام الشيفرة التالية إن كان هناك احتمال أن يُرسل المستخدم أكثر من قيمة تحمل الاسم عينه:

item = form.getvalue("item")
if isinstance(item, list):
    # يطلب المستخدم أكثر من عنصر واحد
else:
    # يطلب المستخدم عنصرًا واحدًا فقط

هذه الحالة شائعة جدًّا، فعلى سبيل المثال قد يتضمّن النموذج عددًا من مربعات الاختيار checkboxes التي تحمل نفس الاسم:

<input type="checkbox" name="item" value="1" />
<input type="checkbox" name="item" value="2" />

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

user = form.getvalue("user").upper()

ولكن المشكلة في هذه الشيفرة هو أنّ عليك أن تتوقع أنّ العميل سيقدّم مدخلات صالحة للاستخدام في سكربتاتك. فعلى سبيل المثال، إن أضيفت عبارة user=foo إلى سلسلة الاستعلام النصية، فإنّ السكربت سيتوقف عن العمل؛ وذلك لأنّ التابع getvalue("user")‎ سيعيد قائمة عوضًا عن سلسلة نصية، وليس بالإمكان استدعاء التابع upper()‎ على قائمة (لأنّ القوائم لا تمتلك تابعًا بهذا الاسم) وتكون النتيجة الحصول على الاستثناء AttributeError.

ولهذا فإنّ الطريقة المناسبة لقراءة البيانات من النماذج هي استخدام شيفرة تتحقّق ممّا إذا كانت القيمة المستحصلة قيمة مفردة أو قائمة من القيم؛ ولكن هذه طريقة مزعجة وتؤدي إلى الحصول على شيفرة صعبة القراءة.

الطريقة الأنسب هي استخدام التابعين getfirst() و getlist().

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

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

الدالة cgi.parse()‎

تحلّل الدالة الاستعلام الموجود في بيئة معينة أو في ملف معيّن.

الدالة cgi.parse_qs()

هذه الدالة مهملة في هذه الوحدة، وهي موجودة لغرض التوافق مع الإصدارات السابقة من بايثون. استخدم الدالة urllib.parse.parse_qs()‎ عوضًا عنها. 

الدالة cgi.parse_qsl()

هذه الدالة مهملة في هذه الوحدة، وهي موجودة لغرض التوافق مع الإصدارات السابقة من بايثون. استخدم الدالة urllib.parse.parse_qsl()‎‎ عوضًا عنها.

الدالة cgi.parse_multipart()

تحلّل الدالة المدخلات من نوع multipart/form-data (للملفات المرفوعة).

الدالة cgi.parse_header()

تحلّل الدالة ترويسة MIME (مثل Content-Type) إلى قيمة رئيسة وقاموسٍ من المعاملات.

الدالة cgi.test()

سكربت اختبار CGI، يمكن استخدامه كبرنامج رئيسي. تكتب الدالة ترويسة HTTP مصغّرة وتنسّق جميع المعلومات المقدّمة إلى السكربت في نموذج HTML.

الدالة cgi.print_environ()‎

تنسّق الدالة بيئة الصدفة بصيغة HTML.

الدالة cgi.print_form()

تنسّق الدالة النموذج بصيغة HTML.

الدالة cgi.print_directory()‎

تنسّق الدالة المجلّد الحالي بصيغة HTML.

الدالة cgi.print_environ_usage()

تطبع الدالة قائمة من متغيرات البيئة المفيدة (المستخدمة من قبل CGI) بصيغة HTML.

الدالة cgi.escape()‎

تحوّل الدالة الحروف '&' و '>' و '<' في السلسلة النصية المعطاة إلى تسلسل حروف آمن في HTML. تُستخدم هذه الدالة لعرض النصوص التي قد تحتوي على مثل هذه المحارف.

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

إن كنت تستدعي برنامجًا خارجيًا (بواسطة الدالة 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؛ لأنّ السكربت لن يعمل في معظم الأنظمة، إضافة إلى أنّ ذلك قد يتسبب في حدوث مشاكل أمنية.

انظر أيضًا

مصادر