Skip to content

12. Usługi internetowe w języku Python

  

Skrypty w języku Python mogą być wykonywane przez serwer WEB. To właśnie serwer ten będzie nasłuchiwał żądań od klientów. Z punktu widzenia klienta wywołanie usługi WEB sprowadza się do zażądania URL tej usługi. Klient może być napisany w dowolnym języku, w szczególności w Pythonie. Musimy umieć „komunikować się” z usługą WEB, czyli rozumieć protokół komunikacji Http między serwerem WWW a jego klientami. Taki jest cel poniższych programów.

Skrypty serwisu internetowego będą uruchamiane przez serwer WWW Apache o nazwie WampServer. Należy je umieścić w specjalnym katalogu: <WampServer>\bin\apache\apachex.y.z\cgi-bin, gdzie <WampServer> to katalog instalacyjny WampServer, a x.y.z to wersja serwera WWW Apache.

 

Umieszczenie skryptów w języku Python w folderze <cgi-bin> nie wystarczy. W pierwszym wierszu skryptu należy podać ścieżkę do interpretera języka Python, który ma być używany. Ścieżka ta jest podana w formie komentarza:

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

Czytelnik powinien dostosować tę ścieżkę do swojego środowiska.

12.1. Aplikacja klient-serwer do obsługi daty i godziny

Naszą pierwszą usługą internetową będzie usługa daty i godziny: klient otrzymuje bieżącą datę i godzinę.

12.1.1. Serwer


Program (web_02)


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

import time

# nagłówki
print "Content-Type: text/plain\n"

# wysyłanie godziny do klienta
  # czas lokalny: liczba milisekund od 01.01.1970
  # „format wyświetlania daty i godziny”
  # d: dzień (2 cyfry)
  # m: miesiąc (2 cyfry)
  # y: rok (2 cyfry)
  # H: godzina 0,23
  # M: minuty
  # S: sekundy

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

Uwagi:

  • wiersz 6: skrypt musi samodzielnie wygenerować niektóre nagłówki HTTP odpowiedzi dla klienta. Zostaną one dodane do nagłówków HTTP wygenerowanych przez sam serwer Apache. Nagłówek HTTP w wierszu 6 informuje klienta, że zostanie mu wysłany zasób w formacie text/plain, czyli tekst nieformatowany. Należy zwrócić uwagę na znak „\n” na końcu nagłówka, który spowoduje wygenerowanie pustej linii po nagłówku. Jest to obowiązkowe: to właśnie ta pusta linia sygnalizuje klientowi HTTP koniec nagłówków HTTP w odpowiedzi. Następnie pojawia się zasób żądany przez klienta, w tym przypadku tekst nieformatowany;
  • wiersz 18: zasób wysłany do klienta to tekst wyświetlający bieżącą datę i godzinę.

12.1.2. Dwa testy

Powyższy skrypt można uruchomić bezpośrednio w interpreterze języka Python w oknie poleceń, tak jak robiliśmy to do tej pory. Pozwala to wyeliminować ewentualne błędy składniowe lub operacyjne. Otrzymujemy następujący wynik:

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

24/06/11 11:16:55

Po przetestowaniu skryptu w ten sposób można umieścić go w folderze <cgi-bin> na serwerze Apache (patrz akapit 12). Uruchommy aplikację WampServer. Spowoduje to uruchomienie zarówno serwera WWW Apache, jak i systemu zarządzania bazami danych MySQL. Na razie będziemy korzystać wyłącznie z serwera WWW. Następnie w przeglądarce wpiszmy następujący adres: http://localhost/cgi-bin/web_02.py:

  • w [1]: żądany plik URL;
  • na [2]: odpowiedź wyświetlona przez przeglądarkę;
  • w [3]: kod źródłowy odebrany przez przeglądarkę internetową. Jest to rzeczywiście ten sam kod, który został wysłany przez skrypt w języku Python.

Za pomocą niektórych narzędzi (w tym przypadku Firebug, wtyczka do przeglądarki Firefox) można uzyskać dostęp do nagłówków HTTP wymienianych z serwerem. Powyżej przeglądarka internetowa otrzymała następujące nagłówki 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

W wierszach 7–8 rozpoznajemy nagłówek HTTP wysłany przez skrypt w języku Python. Poprzedzające go nagłówki zostały wygenerowane przez serwer WWW Apache.

12.1.3. Zaprogramowany klient


Program (client_web_02)

Teraz napiszemy skrypt, który będzie klientem poprzedniej usługi internetowej. Wykorzystamy funkcje modułu httplib, który ułatwia pisanie klientów HTTP.


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

import httplib,re

# stałe
HOST="localhost"
URL="/cgi-bin/web_02.py"
# połączenie
connexion=httplib.HTTPConnection(HOST)
# śledzenie
connexion.set_debuglevel(1)
# wysyłanie zapytania
connexion.request("GET", URL)
# przetwarzanie odpowiedzi
reponse=connexion.getresponse()
# treść
contenu=reponse.read()
# zamknięcie połączenia
connexion.close()
print "------\n",contenu,"-----\n"
# pobieranie elementów czasu
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])

Uwagi:

  • wiersz 3: moduł re jest niezbędny do obsługi wyrażeń regularnych, a moduł httplib do obsługi funkcji klientów HTTP;
  • wiersz 9: tworzone jest połączenie HTTP z portem 80 modułu HOST zdefiniowanego w wierszu 6;
  • wiersz 11: monitorowanie pozwala wyświetlić nagłówki HTTP żądania klienta oraz odpowiedzi serwera;
  • wiersz 13: wysyłane jest żądanie do serwisu internetowego URL. Istnieją dwa sposoby jej wywołania: za pomocą polecenia HTTP GET lub POST. Różnica między nimi została wyjaśniona poniżej. W tym przypadku zostanie ona wywołana za pomocą polecenia HTTP GET;
  • wiersz 15: odczytywana jest odpowiedź serwera. Otrzymujemy tutaj całą odpowiedź: nagłówki HTTP oraz zasób żądany przez klienta. W swojej odpowiedzi serwer mógł poprosić klienta o przekierowanie. W takim przypadku klient httplib automatycznie dokonuje przekierowania. Otrzymana odpowiedź jest zatem wynikiem tego przekierowania;
  • wiersz 17: odpowiedź składa się z nagłówków HTTP oraz dokumentu żądanego przez klienta. Aby uzyskać wyłącznie nagłówki HTTP, należy użyć funkcji [reponse].getHeaders(). Aby uzyskać dokument, należy użyć funkcji [reponse].read();
  • wiersz 19: po otrzymaniu odpowiedzi z serwera WWW połączenie z nim zostaje zamknięte;
  • wiemy, że dokument wysłany przez serwer to wiersz tekstu w postaci 15/06/11 14:56:36. W wierszach 22–26 używamy wyrażenia regularnego, aby wyodrębnić poszczególne elementy tego wiersza.

12.1.4. Wyniki

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

Uwagi:

  • wiersz 1: nagłówki HTTP wysłane przez klienta do serwera WWW;
  • wiersze 2–6: nagłówki HTTP z odpowiedzi serwera WWW;
  • wiersz 8: dokument wysłany przez serwer;
  • wiersz 11: wynik jego przetworzenia;

12.2. Pobieranie przez serwer parametrów wysłanych przez klienta

W protokole HTTP klient ma dwie metody przekazywania parametrów do serwera WWW:

  1. wysyła żądanie usługi URL w postaci

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

gdzie wartości vali muszą zostać wcześniej zakodowane, tak aby niektóre znaki zarezerwowane zostały zastąpione ich wartościami szesnastkowymi.

  1. żąda kodu URL usługi w postaci
POST url HTTP/1.0

a następnie wśród nagłówków HTTP wysyłanych do serwera umieszcza następujący nagłówek:

Content-length: N

Kolejne nagłówki wysyłane przez klienta kończą się pustym wierszem. Następnie może wysłać swoje dane w postaci

val1&param2=val2&param3=val3…

gdzie ciągi vali muszą, podobnie jak w przypadku metody GET, zostać wcześniej zakodowane. Liczba znaków wysyłanych do serwera musi wynosić N, gdzie N jest wartością zadeklarowaną w nagłówku:

Content-length: N

12.2.1. Usługa internetowa

Poniższa usługa internetowa otrzymuje od klienta trzy parametry: nom, prenom, age. Pobiera je z pewnego rodzaju słownika o nazwie cgi.FieldStorage, dostarczonego przez moduł cgi. Wartość vali parametru parami uzyskuje się za pomocą funkcji vali=cgi.FieldStorage().getlist("parami"). Otrzymujemy tablicę zawierającą:

  • 0 elementów, jeśli parametr parami nie występuje w żądaniu klienta;
  • 1 element, jeśli parametr parami występuje 1 raz w żądaniu klienta;
  • n elementów, jeśli parametr parami występuje n razy w żądaniu klienta.

Po pobraniu parametrów skrypt zwraca je klientowi.


Program (web_03)


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

import cgi

# nagłówki
print "Content-Type: text/plain\n"

# odzyskiwanie przez serwer informacji wysłanych przez klienta
# tutaj imię=P&nazwisko=N&wiek=A
formulaire=cgi.FieldStorage()

# są one odsyłane do klienta
print "informations recues du service web [prenom=%s,nom=%s,age=%s]" % (formulaire.getlist("prenom"),formulaire.getlist("nom"),formulaire.getlist("age"))

Test można przeprowadzić za pomocą przeglądarki internetowej:

W [1] znajduje się URL z serwisu internetowego. Należy zwrócić uwagę na obecność trzech parametrów: nom, prenom, age. W [2] znajduje się odpowiedź serwisu internetowego.

12.2.2. Klient GET


Program (client_web_03_GET)


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

import httplib,urllib

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

# parametry muszą zostać zakodowane przed wysłaniem do serwera
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# parametry umieszcza się na końcu URL
URL+="?"+params
# połączenie
connexion=httplib.HTTPConnection(HOST)
# śledzenie
connexion.set_debuglevel(1)
# wysłanie żądania
connexion.request("GET",URL)
# przetwarzanie odpowiedzi
reponse=connexion.getresponse()
# treść
contenu=reponse.read()
print contenu,"\n"
# zamknięcie połączenia
connexion.close()

Uwagi:

  • wiersze 8–10: wartości trzech parametrów wysłanych do serwisu internetowego;
  • wiersz 13: należy je zakodować. Odbywa się to za pomocą metody urlencode modułu urllib. Moduł ten jest importowany w wierszu 3. Metoda przyjmuje jako parametr słownik {param1:val1, param2:val2, ...};
  • wiersz 15: w poleceniu GET (wiersz 21) klient musi umieścić zakodowane parametry na końcu URL serwisu internetowego;
  • kolejne wiersze zostały już omówione.

12.2.3. Wyniki

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

Uwagi:

  • wiersz 2: zwróć uwagę na sposób zakodowania parametrów (nazwisko, imię, wiek);
  • wiersz 8: odpowiedź serwisu internetowego.

12.2.4. Klient POST

Klient POST jest analogiczny do klienta GET, z tą różnicą, że zakodowane parametry nie są już częścią docelowego URL. Zostały one przekazane jako trzeci argument żądania POST (wiersz 19).


Program (client_web_03_POST)


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

import httplib,urllib

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

# parametry muszą zostać zakodowane przed wysłaniem na serwer
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# połączenie
connexion=httplib.HTTPConnection(HOST)
# śledzenie
connexion.set_debuglevel(1)
# wysyłanie żądania
connexion.request("POST",URL,params)
# przetwarzanie odpowiedzi
reponse=connexion.getresponse()
# treść
contenu=reponse.read()
print contenu,"\n"
# zamknięcie połączenia
connexion.close()

12.2.5. Wyniki

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

Uwagi:

  • zwróć uwagę na wiersz 2, gdzie pokazano metodę używaną przez klienta POST do wysyłania zakodowanych parametrów:
    • nagłówek HTTP Content-Length wskazuje liczbę znaków, które zostaną wysłane do serwisu internetowego;
    • po tym nagłówku HTTP następuje pusty wiersz sygnalizujący koniec nagłówków HTTP;
    • następnie wysyłanych jest 39 znaków zakodowanych parametrów.
  • wiersz 8: odpowiedź serwisu internetowego.

12.3. Pobieranie zmiennych środowiskowych z serwisu internetowego

12.3.1. Serwis internetowy

Skrypt CGI w języku Python działa w środowisku systemowym, które posiada atrybuty. Jego atrybuty i ich wartości są dostępne w słowniku o nazwie os.environ.


Program (web_04)


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

import os

# nagłówki
print "Content-Type: text/plain\n"
# informacje o środowisku
for (cle,valeur) in os.environ.items():
    print "%s : %s" % (cle,valeur)

Uwagi:

  • wiersz 3: należy zaimportować moduł os, aby uzyskać dostęp do zmiennych „systemowych”.

Jeśli uruchomimy bezpośrednio powyższy skrypt (c.a.d jako skrypt konsolowy, a nie cgi), w konsoli otrzymamy następujące wyniki:

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Ú

W przeglądarce internetowej (w tym przypadku wykonywany jest skrypt CGI) otrzymujemy następujące wyniki:

 

Należy zauważyć, że w zależności od kontekstu wykonania uzyskane środowisko nie jest takie samo.

12.3.2. Zaprogramowany klient


Program (client_web_04)


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

import httplib

# stałe
HOST="localhost"
URL="/cgi-bin/web_04.py"
# połączenie
connexion=httplib.HTTPConnection(HOST)
# wysyłanie żądania
connexion.request("GET", URL)
# przetwarzanie odpowiedzi
reponse=connexion.getresponse()
# treść
print reponse.read()

12.3.3. Wyniki

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

Należy zauważyć, że zaprogramowany klient nie otrzymuje dokładnie takiej samej odpowiedzi jak przeglądarka internetowa. Wynika to z faktu, że przeglądarka wysłała do serwera internetowego informacje, które zostały wykorzystane przez serwer do utworzenia odpowiedzi. W tym przypadku zaprogramowany klient nie wysłał żadnych informacji o sobie.