Skip to content

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

  

يمكن تنفيذ البرامج النصية بلغة Python بواسطة خادم الويب. فالخادم هو الذي يستقبل طلبات العملاء. من وجهة نظر العميل، فإن استدعاء خدمة ويب يعادل طلب عنوان URL لتلك الخدمة. ويمكن كتابة برنامج العميل بأي لغة، بما في ذلك Python. ونحتاج إلى معرفة كيفية "التواصل" مع خدمة الويب، أي فهم بروتوكول 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

#    headers
print "Content-Type: text/plain\n"

#    dispatch time to customer
  #  localtime: number of milliseconds since 01/01/1970
  #    "date-time display format
  #    d: 2-digit day
  #    m: 2-digit month
  #    y: 2-digit year
  #    H: hour 0.23
  #    M: minutes
  #    S: seconds

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. في الوقت الحالي، سنستخدم خادم الويب فقط. بعد ذلك، باستخدام متصفح، أدخل عنوان URL التالي: 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

#    constant
HOST="localhost"
URL="/cgi-bin/web_02.py"
#    connection
connexion=httplib.HTTPConnection(HOST)
#    follow-up
connexion.set_debuglevel(1)
#  send request
connexion.request("GET", URL)
#    response processing
reponse=connexion.getresponse()
#    content
contenu=reponse.read()
#    locking connection
connexion.close()
print "------\n",contenu,"-----\n"
#    recovery of time elements
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 على المضيف المحدد في السطر 6؛
  • السطر 11: يسمح لك السجل (log) بعرض رؤوس HTTP لطلب العميل واستجابة الخادم؛
  • السطر 13: يتم طلب عنوان URL لخدمة الويب. هناك طريقتان لطلبه: باستخدام أمر HTTP GET أو POST. سيتم شرح الفرق بين الاثنين لاحقًا. هنا، سيتم طلبه باستخدام أمر HTTP GET؛
  • السطر 15: يتم قراءة استجابة الخادم. يتم الحصول على الاستجابة الكاملة هنا: رؤوس HTTP والمورد المطلوب من قبل العميل. في استجابته، قد يكون الخادم قد أوعز للعميل بإعادة التوجيه. في هذه الحالة، يقوم عميل httplib تلقائيًا بإجراء إعادة التوجيه. وبالتالي، فإن الاستجابة التي تم الحصول عليها هي تلك الناتجة عن إعادة التوجيه؛
  • السطر 17: تتكون الاستجابة من رؤوس HTTP والمستند الذي طلبه العميل. لاسترداد رؤوس HTTP فقط، استخدم [response].getHeaders(). لاسترداد المستند، استخدم [response].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

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

  1. يطلب عنوان URL للخدمة بالشكل
POST url HTTP/1.0

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

Content-length: N

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

val1&param2=val2&param3=val3…

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

Content-length: N

12.2.1. خدمة الويب

تستقبل خدمة الويب التالية ثلاثة معلمات من عميلها: last_name و first_name و 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

#    headers
print "Content-Type: text/plain\n"

#  server retrieves information sent by the client
#    here firstname=P&lastname=N&age=A
formulaire=cgi.FieldStorage()

#  we send them back to the customer
print "informations recues du service web [prenom=%s,nom=%s,age=%s]" % (formulaire.getlist("prenom"),formulaire.getlist("nom"),formulaire.getlist("age"))

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

في [1]، عنوان URL لخدمة الويب. لاحظ وجود المعلمات الثلاثة last_name و first_name و age. في [2]، استجابة خدمة الويب.

12.2.2. عميل GET


البرنامج (client_web_03_GET)

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

import httplib,urllib

#    constant
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42

#  parameters must be encoded before being sent to the server
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
#  parameters are set at the end of URL
URL+="?"+params
#    connection
connexion=httplib.HTTPConnection(HOST)
#    follow-up
connexion.set_debuglevel(1)
#  send request
connexion.request("GET",URL)
#    response processing
reponse=connexion.getresponse()
#    content
contenu=reponse.read()
print contenu,"\n"
#  closing the connection
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: لاحظ ترميز المعلمات (last_name، first_name، age)؛
  • السطر 8: استجابة خدمة الويب.

12.2.4. عميل POST

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


البرنامج (client_web_03_POST)

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

import httplib,urllib

#    constant
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42

#  parameters must be encoded before being sent to the server
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
#    connection
connexion=httplib.HTTPConnection(HOST)
#    follow-up
connexion.set_debuglevel(1)
#  send request
connexion.request("POST",URL,params)
#    response processing
reponse=connexion.getresponse()
#    content
contenu=reponse.read()
print contenu,"\n"
#  closing the connection
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)

1
2
3
4
5
6
7
8
9
#   !D:\Programs\ActivePython\Python2.7.2\python.exe

import os

#    headers
print "Content-Type: text/plain\n"
#    environmental news
for (cle,valeur) in os.environ.items():
    print "%s : %s" % (cle,valeur)

ملاحظات:

  • السطر 3: يجب استيراد وحدة os للوصول إلى متغيرات "system".

إذا قمت بتشغيل البرنامج النصي أعلاه مباشرة (أي كبرنامج نصي للوحدة الطرفية بدلاً من برنامج نصي 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

#    constant
HOST="localhost"
URL="/cgi-bin/web_04.py"
#    connection
connexion=httplib.HTTPConnection(HOST)
#  send request
connexion.request("GET", URL)
#    response processing
reponse=connexion.getresponse()
#    content
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

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