Skip to content

12. خدمات الويب بلغة Python

  

يمكن تنفيذ البرامج النصية بلغة Python بواسطة خادم WEB. وهذا الخادم هو الذي سيتولى استقبال طلبات العملاء. من وجهة نظر العميل، فإن استدعاء خدمة WEB يعادل طلب URL لتلك الخدمة. يمكن كتابة برنامج العميل بأي لغة برمجة، ولا سيما لغة Python. يتعين علينا أن نكون قادرين على «التواصل» مع خدمة WEB، أي فهم بروتوكول Http للاتصال بين خادم الويب وعملائه. وهذا هو الهدف من البرامج التالية.

سيتم تنفيذ نصوص خدمة الويب بواسطة خادم الويب Apache الخاص بـ WampServer. يجب وضعها في دليل معين: <WampServer>\bin\apache\apachex.y.z\cgi-bin حيث <WampServer> هو مجلد تثبيت WampServer و x.y.z هو إصدار خادم الويب Apache.

 

لا يكفي وضع نصوص Python في المجلد <cgi-bin>. بل يجب أن تشير النصّة في السطر الأول إلى مسار مترجم Python المطلوب استخدامه. ويُكتب هذا المسار في شكل تعليق:

#!D:\Programs\ActivePython\Python2.7.2\python.exe

على القارئ تكييف هذا المسار مع بيئته الخاصة.

12.1. تطبيق عميل/خادم للتاريخ/الوقت

ستكون أول خدمة ويب لدينا هي خدمة التاريخ والوقت: يتلقى العميل التاريخ والوقت الحاليين.

12.1.1. الخادم


البرنامج (web_02)


#!D:\Programs\ActivePython\Python2.7.2\python.exe

import time

# رؤوس
print "Content-Type: text/plain\n"

# إرسال الوقت إلى العميل
  # التوقيت المحلي: عدد الميلي ثانية منذ 01/01/1970
  # "تنسيق عرض التاريخ والوقت
  # d: اليوم برقمين
  # m: الشهر برقمين
  # y: السنة برقمين
  # H: الساعة 0,23
  # M: الدقائق
  # S: الثواني

print time.strftime('%d/%m/%y %H:%M:%S',time.localtime())

ملاحظات:

  • السطر 6: يجب أن يقوم البرنامج النصي بنفسه بإنشاء بعض رؤوس HTTP الخاصة بالاستجابة الموجهة إلى العميل. وستُضاف هذه الرؤوس إلى رؤوس HTTP التي ينشئها خادم Apache نفسه. تشير رأس HTTP في السطر 6 إلى العميل بأنه سيتم إرسال مورد إليه بتنسيق text/plain، أي نص غير منسق. تجدر الإشارة إلى الرمز "\n" في نهاية الرأس، والذي سيؤدي إلى إنشاء سطر فارغ بعد الرأس. وهذا أمر إلزامي: فهذا السطر الفارغ هو الذي يُشير إلى عميل HTTP بنهاية رؤوس HTTP الخاصة بالاستجابة. ثم يأتي بعد ذلك المورد الذي طلبه العميل، وهو في هذه الحالة نص غير منسق؛
  • السطر 18: المورد المرسل إلى العميل هو نص يعرض التاريخ والوقت الحاليين.

12.1.2. اختباران

يمكن تنفيذ البرنامج النصي السابق مباشرةً بواسطة مترجم Python في نافذة الأوامر كما فعلنا حتى الآن. وهذا يتيح استبعاد أي أخطاء محتملة في الصياغة أو التشغيل. ونحصل على النتيجة التالية:

1
2
3
4
cmd>%python% web_02.py
Content-Type: text/plain

24/06/11 11:16:55

بعد اختبار البرنامج النصي بهذه الطريقة، يمكن وضعه في المجلد <cgi-bin> على خادم Apache (انظر الفقرة 12). لنقم بتشغيل التطبيق WampServer. يؤدي ذلك إلى تشغيل كل من خادم الويب Apache ونظام إدارة قواعد البيانات MySQL في آن واحد. لن نستخدم في الوقت الحالي سوى خادم الويب. ثم، باستخدام متصفح، دعونا نطلب الصفحة التالية: http://localhost/cgi-bin/web_02.py:

  • في [1]: الـ URL المطلوب؛
  • إلى [2]: الرد المعروض بواسطة المتصفح؛
  • في [3]: شفرة المصدر التي استلمها متصفح الويب. وهي بالفعل تلك التي أرسلها البرنامج النصي Python.

باستخدام بعض الأدوات (هنا Firebug، وهو مكون إضافي لمتصفح Firefox) يمكن الوصول إلى رؤوس HTTP المتبادلة مع الخادم. في المثال أعلاه، تلقى متصفح الويب رؤوس HTTP التالية:

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Date: Fri, 24 Jun 2011 09:35:02 GMT
Server: Apache/2.2.6 (Win32) PHP/5.2.5
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/plain

يمكن التعرف في السطرين 7 و8 على رأس HTTP الذي أرسله البرنامج النصي Python. أما الرؤوس التي تسبقها فقد تم إنشاؤها بواسطة خادم الويب Apache.

12.1.3. عميل مبرمج


البرنامج (client_web_02)

سنقوم الآن بكتابة برنامج نصي سيكون عميلاً للخدمة الويب السابقة. نستخدم ميزات الوحدة النمطية httplib التي تسهل كتابة عملاء HTTP.


# -*- coding=utf-8 -*-

import httplib,re

# الثوابت
HOST="localhost"
URL="/cgi-bin/web_02.py"
# الاتصال
connexion=httplib.HTTPConnection(HOST)
# المتابعة
connexion.set_debuglevel(1)
# إرسال الطلب
connexion.request("GET", URL)
# معالجة الرد
reponse=connexion.getresponse()
# المحتوى
contenu=reponse.read()
# إغلاق الاتصال
connexion.close()
print "------\n",contenu,"-----\n"
# استرداد عناصر الوقت
elements=re.match(r"^(\d\d)/(\d\d)/(\d\d) (\d\d):(\d\d):(\d\d)\s*$",contenu).groups()
print "Jour=%s,Mois=%s,An=%s,Heures=%s,Minutes=%s,Secondes=%s" % (elements[0],elements[1],elements[2],elements[3],elements[4],elements[5])

ملاحظات:

  • السطر 3: الوحدة النمطية re ضرورية للتعبيرات العادية، والوحدة النمطية httplib ضرورية لوظائف عملاء HTTP؛
  • السطر 9: يتم إنشاء اتصال HTTP مع المنفذ 80 الخاص بـ HOST المحدد في السطر 6؛
  • السطر 11: يتيح التتبع رؤية رؤوس HTTP لطلب العميل واستجابة الخادم؛
  • السطر 13: يتم طلب URL من خدمة الويب. هناك طريقتان لطلبها: باستخدام أمر HTTP GET أو POST. سيتم شرح الفرق بينهما لاحقًا. هنا سيتم طلبها باستخدام أمر HTTP GET؛
  • السطر 15: تتم قراءة استجابة الخادم. يتم هنا الحصول على الاستجابة بالكامل: رؤوس Http والمورد الذي طلبه العميل. في استجابته، قد يطلب الخادم من العميل إعادة التوجيه. في هذه الحالة، يقوم العميل httplib بإجراء إعادة التوجيه تلقائيًا. وبالتالي، فإن الاستجابة التي تم الحصول عليها هي تلك الناتجة عن إعادة التوجيه؛
  • السطر 17: تتكون الاستجابة من رؤوس HTTP والمستند الذي طلبه العميل. للحصول على رؤوس HTTP فقط، نستخدم [reponse].getHeaders(). وللحصول على المستند، نستخدم [reponse].read()؛
  • السطر 19: بمجرد الحصول على الرد من خادم الويب، يتم إغلاق الاتصال به؛
  • نعلم أن المستند المرسل من الخادم عبارة عن سطر نصي بالصيغة 15/06/11 14:56:36. في الأسطر 22-26، نستخدم تعبيرًا منتظمًا لاستخراج العناصر المختلفة من هذا السطر.

12.1.4. النتائج

send: 'GET /cgi-bin/web_02.py HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding: identity\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Tue, 14 Feb 2012 14:51:07 GMT
header: Server: Apache/2.2.17 (Win32) PHP/5.3.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
------
14/02/12 15:51:07
-----

Jour=14,Mois=02,An=12,Heures=15,Minutes=51,Secondes=07

ملاحظات:

  • السطر 1: رؤوس Http المرسلة من العميل إلى خادم الويب؛
  • الأسطر 2-6: رؤوس HTTP الخاصة باستجابة خادم الويب؛
  • السطر 8: المستند الذي أرسله الخادم؛
  • السطر 11: نتيجة معالجته؛

12.2. استرداد الخادم للمعلمات المرسلة من العميل

في بروتوكول HTTP، يتوفر للعميل طريقتان لتمرير المعلمات إلى خادم الويب:

  1. يطلب URL من الخدمة بالصيغة

GET url?param1=val1&param2=val2&param3=val3… HTTP/1.0

حيث يجب أن تخضع القيم vali مسبقًا لعملية ترميز بحيث يتم استبدال بعض الأحرف المحجوزة بقيمتها السداسية العشرية.

  1. يطلب معرف الخدمة URL بالصيغة
POST url HTTP/1.0

ثم يضع الرأس التالي ضمن رؤوس HTTP المرسلة إلى الخادم:

Content-length: N

تنتهي العناوين التالية التي يرسلها العميل بسطر فارغ. ويمكنه عندئذٍ إرسال بياناته بالشكل التالي

val1&param2=val2&param3=val3…

حيث يجب ترميز vali مسبقًا، كما هو الحال مع الطريقة GET. يجب أن يكون عدد الأحرف المرسلة إلى الخادم هو N، حيث N هي القيمة المعلنة في الرأس:

Content-length: N

12.2.1. خدمة الويب

تتلقى خدمة الويب التالية 3 معلمات من عميلها: nom، prenom، age. وتقوم باستردادها من نوع من القاموس يُسمى cgi.FieldStorage والذي توفره الوحدة النمطية cgi. يتم الحصول على القيمة vali لمعلمة parami بواسطة vali=cgi.FieldStorage().getlist("parami"). ونحصل على مصفوفة من:

  • 0 عنصر إذا لم يكن المعلمة parami موجودة في طلب العميل؛
  • عنصر واحد إذا كان المعلمة parami موجودة مرة واحدة في طلب العميل؛
  • n عنصرًا إذا كان المعلمة parami موجودة n مرة في طلب العميل.

بمجرد استرداد المعلمات، يقوم البرنامج النصي بإرسالها إلى العميل.


البرنامج (web_03)


#!D:\Programs\ActivePython\Python2.7.2\python.exe

import cgi

# رؤوس الرسائل
print "Content-Type: text/plain\n"

# استرداد الخادم للمعلومات المرسلة من العميل
# هنا الاسم الأول=P&الاسم الأخير=N&العمر=A
formulaire=cgi.FieldStorage()

# يتم إرسالها إلى العميل
print "informations recues du service web [prenom=%s,nom=%s,age=%s]" % (formulaire.getlist("prenom"),formulaire.getlist("nom"),formulaire.getlist("age"))

يمكن إجراء اختبار باستخدام متصفح ويب:

في [1]، يظهر URL الخاص بخدمة الويب. تجدر الإشارة إلى وجود المعلمات الثلاثة nom وprenom وage. في [2]، رد خدمة الويب.

12.2.2. العميل GET


البرنامج (client_web_03_GET)


# -*- coding=utf-8 -*-

import httplib,urllib

# الثوابت
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42

# يجب ترميز المعلمات قبل إرسالها إلى الخادم
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# توضع المعلمات في نهاية URL
URL+="?"+params
# الاتصال
connexion=httplib.HTTPConnection(HOST)
# التتبع
connexion.set_debuglevel(1)
# إرسال الطلب
connexion.request("GET",URL)
# معالجة الرد
reponse=connexion.getresponse()
# المحتوى
contenu=reponse.read()
print contenu,"\n"
# إغلاق الاتصال
connexion.close()

ملاحظات:

  • الأسطر 8-10: قيم المعلمات الثلاثة المرسلة إلى خدمة الويب؛
  • السطر 13: يجب ترميزها. ويتم ذلك باستخدام الطريقة urlencode في الوحدة النمطية urllib. يتم استيراد هذه الوحدة النمطية في السطر 3. تقبل هذه الطريقة كمعلمة قاموسًا {param1:val1, param2:val2, ...}؛
  • السطر 15: في الأمر GET (السطر 21)، يجب على العميل وضع المعلمات المشفرة في نهاية الأمر URL الخاص بخدمة الويب؛
  • وقد تمت مناقشة الأسطر التالية سابقًا.

12.2.3. النتائج

1
2
3
4
5
6
7
8
dos>%python% client_03_GET.py
send: 'GET /cgi-bin/web_03.py?nom=de+la+Huche&age=42&prenom=Jean-Paul HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding: identity\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Wed, 15 Jun 2011 13:22:15 GMT
header: Server: Apache/2.2.6 (Win32) PHP/5.2.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
informations recues du client [['Jean-Paul'],['de la Huche'],['42']]

ملاحظات:

  • السطر 2: لاحظ ترميز المعلمات (الاسم، الاسم الأول، العمر)؛
  • السطر 8: استجابة خدمة الويب.

12.2.4. العميل POST

العميل POST مشابه للعميل GET باستثناء أن المعلمات المشفرة لم تعد جزءًا من الهدف URL. وقد تم تمريرها كحجة ثالثة في الطلب POST (السطر 19).


البرنامج (client_web_03_POST)


# -*- coding=utf-8 -*-

import httplib,urllib

# الثوابت
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42

# يجب ترميز المعلمات قبل إرسالها إلى الخادم
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# الاتصال
connexion=httplib.HTTPConnection(HOST)
# المتابعة
connexion.set_debuglevel(1)
# إرسال الطلب
connexion.request("POST",URL,params)
# معالجة الرد
reponse=connexion.getresponse()
# المحتوى
contenu=reponse.read()
print contenu,"\n"
# إغلاق الاتصال
connexion.close()

12.2.5. النتائج

1
2
3
4
5
6
7
8
dos>%python% client_03_POST.py
send: 'POST /cgi-bin/web_03.py HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding:identity\r\nContent-Length: 39\r\n\r\nnom=de+la+Huche&age=42&prenom=Jean-Paul'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Fri, 24 Jun 2011 12:03:31 GMT
header: Server: Apache/2.2.6 (Win32) PHP/5.2.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
informations recues du service web [prenom=['Jean-Paul'],nom=['de la Huche'],age=['42']]

ملاحظات:

  • يُلاحظ في السطر 2 الطريقة التي يستخدمها العميل POST لإرسال المعلمات المشفرة:
    • تشير رأس Http Content-Length إلى عدد الأحرف التي سيتم إرسالها إلى خدمة الويب؛
    • ويلي رأس Http هذا سطر فارغ يشير إلى نهاية رؤوس Http؛
    • ثم يتم إرسال الأحرف الـ 39 الخاصة بالمعلمات المشفرة.
  • السطر 8: استجابة خدمة الويب.

12.3. استرداد متغيرات البيئة من خدمة ويب

12.3.1. خدمة الويب

يتم تنفيذ البرنامج النصي CGI بلغة Python في بيئة نظام تحتوي على سمات. تتوفر هذه السمات وقيمها في قاموس os.environ.


البرنامج (web_04)


#!D:\Programs\ActivePython\Python2.7.2\python.exe

import os

# رؤوس
print "Content-Type: text/plain\n"
# معلومات البيئة
for (cle,valeur) in os.environ.items():
    print "%s : %s" % (cle,valeur)

ملاحظات:

  • السطر 3: يجب استيراد الوحدة النمطية os للحصول على متغيرات «النظام».

إذا تم تنفيذ البرنامج النصي أعلاه مباشرةً (c.a.d. كبرنامج نصي في وحدة التحكم وليس كبرنامج CGI)، فسنحصل على النتائج التالية في وحدة التحكم:

Content-Type: text/plain

TMP : C:\Users\SERGET~1\AppData\Local\Temp
COMPUTERNAME : GPORTPERS3
USERDOMAIN : Gportpers3
VS100COMNTOOLS : D:\Programs\dotnet\Visual Studio 10\Common7\Tools\
VISUALSTUDIODIR : D:\Documents\Visual Studio 2010
PSMODULEPATH : C:\Windows\system32\WindowsPowerShell\v1.0\Modules\
COMMONPROGRAMFILES : C:\Program Files (x86)\Common Files
PROCESSOR_IDENTIFIER : Intel64 Family 6 Model 42 Stepping 7, GenuineIntel
PROGRAMFILES : C:\Program Files (x86)
PROCESSOR_REVISION : 2a07
SYSTEMROOT : C:\Windows
PATH : D:\Programs\ActivePython\Python2.7.2\;D:\Programs\ActivePython\Python2.7.2\Scripts;C:\Program Files\Common Files\Microsoft Shared\Windows Live;...
PROGRAMFILES(X86) : C:\Program Files (x86)
WINDOWS_TRACING_FLAGS : 3
TEMP : C:\Users\SERGET~1\AppData\Local\Temp
COMMONPROGRAMFILES(X86) : C:\Program Files (x86)\Common Files
PROCESSOR_ARCHITECTURE : x86
ALLUSERSPROFILE : C:\ProgramData
LOCALAPPDATA : C:\Users\Serge TahÚ\AppData\Local
HOMEPATH : \Users\Serge TahÚ
PROGRAMW6432 : C:\Program Files
USERNAME : Serge TahÚ
LOGONSERVER : \\GPORTPERS3
PROMPT : $P$G
SESSIONNAME : Console
PROGRAMDATA : C:\ProgramData
PATHEXT : .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.py;.pyw
FP_NO_HOST_CHECK : NO
WINDIR : C:\Windows
PYTHON : D:\Programs\python\python2.7.2\python
WINDOWS_TRACING_LOGFILE : C:\BVTBin\Tests\installpackage\csilogfile.log
HOMEDRIVE : C:
SYSTEMDRIVE : C:
COMSPEC : C:\Windows\system32\cmd.exe
NUMBER_OF_PROCESSORS : 8
VBOX_INSTALL_PATH : D:\Programs\systeme\Oracle\VirtualBox\
APPDATA : C:\Users\Serge TahÚ\AppData\Roaming
PROCESSOR_LEVEL : 6
PROCESSOR_ARCHITEW6432 : AMD64
COMMONPROGRAMW6432 : C:\Program Files\Common Files
OS : Windows_NT
PUBLIC : C:\Users\Public
USERPROFILE : C:\Users\Serge TahÚ

في متصفح الويب (حيث يتم عندئذٍ تنفيذ البرنامج النصي cgi)، نحصل على النتائج التالية:

 

تجدر الإشارة إلى أن البيئة الناتجة تختلف باختلاف سياق التنفيذ.

12.3.2. البرنامج العميل


البرنامج (client_web_04)


# -*- coding=utf-8 -*-

import httplib

# الثوابت
HOST="localhost"
URL="/cgi-bin/web_04.py"
# الاتصال
connexion=httplib.HTTPConnection(HOST)
# إرسال الطلب
connexion.request("GET", URL)
# معالجة الرد
reponse=connexion.getresponse()
# المحتوى
print reponse.read()

12.3.3. النتائج

SERVER_SOFTWARE : Apache/2.2.17 (Win32) PHP/5.3.5
SCRIPT_NAME : /cgi-bin/web_04.py
SERVER_SIGNATURE :
REQUEST_METHOD : GET
SERVER_PROTOCOL : HTTP/1.1
QUERY_STRING :
SYSTEMROOT : C:\Windows
SERVER_NAME : localhost
REMOTE_ADDR : 127.0.0.1
SERVER_PORT : 80
SERVER_ADDR : 127.0.0.1
DOCUMENT_ROOT : D:/Programs/sgbd/wamp/www/
COMSPEC : C:\Windows\system32\cmd.exe
SCRIPT_FILENAME : D:/Programs/sgbd/wamp/bin/apache/Apache2.2.17/cgi-bin/web_04.py
SERVER_ADMIN : admin@localhost
PATH : D:\Programs\ActivePython\Python2.7.2\;D:\Programs\ActivePython\Python2.7.
2\Scripts;C:\Program Files\Common Files\Microsoft Shared\Windows Live;...
HTTP_HOST : localhost
PATHEXT : .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.py;.pyw
REQUEST_URI : /cgi-bin/web_04.py
WINDIR : C:\Windows
GATEWAY_INTERFACE : CGI/1.1
REMOTE_PORT : 58468
HTTP_ACCEPT_ENCODING : identity

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