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: день (2 цифри)
  # m: місяць (2 цифри)
  # y: рік у 2 цифри
  # 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. Наразі ми будемо використовувати лише веб-сервер. Далі за допомогою браузера відкриємо наступну сторінку 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

# константи
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 відсутній у запиті клієнта;
  • 1 елемент, якщо параметр parami присутній 1 раз у запиті клієнта;
  • 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: значення 3 параметрів, надісланих до веб-сервісу;
  • рядок 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

Слід зауважити, що запрограмований клієнт не отримує точно таку саму відповідь, як веб-браузер. Це пов’язано з тим, що веб-браузер надіслав веб-серверу інформацію, яку той використав для формування відповіді. У цьому випадку запрограмований клієнт не надіслав жодної інформації про себе.