Skip to content

12. خدمات وب در پایتون

  

اسکریپت‌های پایتون می‌توانند توسط یک سرور WEB اجرا شوند. این سرور است که به درخواست‌های کلاینت گوش می‌دهد. از دیدگاه کلاینت، فراخوانی یک سرویس WEB معادل درخواست URL آن سرویس است. کلاینت می‌تواند به هر زبانی، از جمله پایتون، نوشته شود. ما باید بتوانیم با یک سرویس WEB «ارتباط» برقرار کنیم، یعنی پروتکل ارتباطی Http بین یک وب‌سرور و کلاینت‌های آن را درک کنیم. این هدف برنامه‌های بعدی است.

اسکریپت‌های سرویس وب توسط وب‌سرور آپاچی برای WampServer اجرا خواهند شد. آن‌ها باید در یک دایرکتوری خاص قرار گیرند: <WampServer>\bin\apache\apachex.y.z\cgi-bin، که در آن <WampServer> مسیر نصب WampServer و x.y.z نسخه وب‌سرور آپاچی است.

 

فقط قرار دادن اسکریپت‌های پایتون در پوشه <cgi-bin> کافی نیست. اسکریپت باید در خط اول مسیر تفسیرگر پایتون مورد استفاده را مشخص کند. این مسیر به‌عنوان یک نظر (comment) درج می‌شود:

#!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"

#زمان ارسال به کلاینت
  # localtime: تعداد میلی‌ثانیه‌ها از ۱ ژانویه ۱۹۷۰
  # "قالب نمایش تاریخ و زمان
  # d: روز (۲ رقم)
  # m: ماه دو رقمی
  # y: سال (۲ رقم)
  # H: ساعت 0.23
  # M: دقیقه
  # S: ثانیه‌ها

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

یادداشت‌ها:

  • خط ۶: اسکریپت باید خود برخی از سربرگ‌های HTTP را در پاسخ به کلاینت تولید کند. این سربرگ‌ها به سربرگ‌های HTTP تولید شده توسط خود سرور آپاچی اضافه می‌شوند. سربرگ HTTP در خط ۶ به کلاینت می‌گوید که یک منبع با فرمت text/plain در حال ارسال است، یعنی متن بدون قالب‌بندی. به «\n» در انتهای هدر توجه کنید که یک خط خالی پس از هدر ایجاد می‌کند. این امر الزامی است: این خط خالی به کلاینت HTTP سیگنال پایان هدرهای HTTP در پاسخ را می‌دهد. در ادامه، منبعی که توسط کلاینت درخواست شده است می‌آید، که در این مورد متن بدون قالب‌بندی است؛
  • خط ۱۸: منبعی که برای کلاینت ارسال می‌شود، متنی است که تاریخ و زمان فعلی را نمایش می‌دهد.

12.1.2. دو تست

اسکریپت بالا را می‌توان مستقیماً توسط تفسیرگر پایتون در یک پنجرهٔ فرمان اجرا کرد، همان‌طور که تا اینجا انجام داده‌ایم. این کار به حذف هرگونه خطای نحوی یا خطای زمان اجرا کمک می‌کند. نتیجهٔ زیر به دست می‌آید:

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

24/06/11 11:16:55

پس از آنکه اسکریپت به این روش آزمایش شد، می‌توان آن را در پوشه <cgi-bin> روی سرور آپاچی قرار داد (به پاراگراف ۱۲ مراجعه کنید). بیایید برنامه WampServer را اجرا کنیم. این کار هم یک سرور وب آپاچی و هم یک سیستم مدیریت پایگاه داده MySQL را راه‌اندازی می‌کند. فعلاً، ما فقط از وب‌سرور استفاده خواهیم کرد. سپس، با استفاده از یک مرورگر وب، بیایید URL زیر را درخواست کنیم: http://localhost/cgi-bin/web_02.py:

  • به [1]: URL درخواستی؛
  • به [2]: پاسخی که توسط مرورگر نمایش داده می‌شود؛
  • در [3]: کد منبع دریافت‌شده توسط مرورگر وب. این دقیقاً همان چیزی است که توسط اسکریپت پایتون ارسال شده است.

با استفاده از ابزارهای خاصی (در این مورد 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

خطوط ۷–۸ هدر HTTP ارسال‌شده توسط اسکریپت پایتون را نشان می‌دهند. هدرهای قبلی توسط وب‌سرور آپاچی تولید شده‌اند.

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])

یادداشت‌ها:

  • خط ۳: ماژول re برای عبارات باقاعده و ماژول httplib برای توابع کلاینت HTTP مورد نیاز است؛
  • خط ۹: یک اتصال HTTP با استفاده از پورت ۸۰ ماژول HOST، همان‌طور که در خط ۶ تعریف شده، برقرار می‌شود؛
  • خط ۱۱: لاگ به شما امکان می‌دهد تا سربرگ‌های HTTP درخواست کلاینت و پاسخ سرور را مشاهده کنید؛
  • خط ۱۳: سرویس وب URL فراخوانی می‌شود. دو روش برای درخواست آن وجود دارد: استفاده از فرمان HTTP GET یا POST. تفاوت بین این دو در ادامه توضیح داده شده است. در اینجا، با استفاده از فرمان HTTP GET درخواست خواهد شد؛
  • خط ۱۵: پاسخ سرور خوانده می‌شود. در اینجا کل پاسخ دریافت می‌شود: هدرهای HTTP و منبعی که توسط کلاینت درخواست شده است. در پاسخ خود، سرور ممکن است به کلاینت دستور داده باشد که هدایت (redirect) کند. در این صورت، کلاینت httplib به طور خودکار هدایت را انجام می‌دهد. بنابراین، پاسخ دریافت‌شده همان چیزی است که در نتیجه هدایت به دست می‌آید؛
  • خط ۱۷: پاسخ شامل سربرگ‌های HTTP و سند درخواستی کلاینت است. برای بازیابی فقط سربرگ‌های HTTP، از [reponse].getHeaders() استفاده می‌کنیم. برای بازیابی سند، از [reponse].read() استفاده می‌کنیم؛
  • خط ۱۹: پس از دریافت پاسخ از سرور وب، اتصال به آن بسته می‌شود؛
  • ما می‌دانیم که سند ارسال‌شده توسط سرور یک خط متن در قالب 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

یادداشت‌ها:

  • خط ۱: سربرگ‌های HTTP ارسال‌شده توسط کلاینت به وب‌سرور؛
  • خطوط ۲–۶: سربرگ‌های HTTP در پاسخ وب‌سرور؛
  • خط ۸: سندی که توسط سرور ارسال شده است؛
  • خط ۱۱: نتیجهٔ پردازش آن؛

12.2. سرور در حال بازیابی پارامترهای ارسال‌شده توسط کلاینت

در پروتکل HTTP، کلاینت دو روش برای ارسال پارامترها به سرور وب دارد:

  1. در پروتکل HTTP، کلاینت دو روش برای ارسال پارامترها به وب‌سرور دارد:

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. سرویس وب

سرویس وب زیر سه پارامتر را از کلاینت خود دریافت می‌کند: 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"

#بازیابی اطلاعات ارسال‌شده توسط کلاینت توسط سرور
# در اینجا: first_name=P&surname=N&age=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()

یادداشت‌ها:

  • خطوط ۸–۱۰: مقادیر سه پارامتر ارسال‌شده به سرویس وب؛
  • خط ۱۳: این‌ها باید رمزگذاری شوند. این کار با استفاده از متد urlencode ماژول urllib انجام می‌شود. این ماژول در خط ۳ وارد شده است. این متد یک دیکشنری {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']]

یادداشت‌ها:

  • خط ۲: به رمزگذاری پارامترها (نام خانوادگی، نام، سن) توجه کنید؛
  • خط ۸: پاسخ سرویس وب.

12.2.4. کلاینت POST

کلاینت POST مشابه کلاینت GET است، با این تفاوت که پارامترهای رمزگذاری‌شده دیگر بخشی از هدف URL نیستند. آنها به‌عنوان سومین آرگومان درخواست POST (خط ۱۹) ارسال می‌شوند.


برنامه (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']]

یادداشت‌ها:

  • توجه خط ۲: متدی که توسط کلاینت POST برای ارسال پارامترهای رمزگذاری‌شده استفاده می‌شود:
    • سربرگ HTTP Content-Length تعداد کاراکترهایی را که باید به سرویس وب ارسال شود مشخص می‌کند؛
    • سپس یک خط خالی که پایان هدرهای HTTP را نشان می‌دهد، به دنبال این هدر HTTP می‌آید؛
    • سپس ۳۹ کاراکتر پارامترهای رمزگذاری‌شده ارسال می‌شوند.
  • خط ۸: پاسخ سرویس وب.

12.3. بازیابی متغیرهای محیطی از یک سرویس وب

12.3.1. سرویس وب

اسکریپت CGI پایتون در یک محیط سیستمی اجرا می‌شود که دارای ویژگی‌ها است. ویژگی‌ها و مقادیر آن‌ها در یک دیکشنری به نام 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)

نکات:

  • خط ۳: برای دسترسی به متغیرهای «system» باید ماژول 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

باید توجه داشت که کلاینت برنامه‌ریزی‌شده دقیقاً همان پاسخی را که مرورگر وب دریافت می‌کند، دریافت نمی‌کند. این به این دلیل است که مرورگر وب اطلاعاتی را به سرور وب ارسال کرد که سرور وب از آن برای تولید پاسخ خود استفاده کرد. در این مورد، کلاینت برنامه‌ریزی‌شده هیچ اطلاعاتی در مورد خود ارسال نکرد.