Skip to content

12. Servizi web in Python

  

Gli script Python possono essere eseguiti da un server WEB. È quest’ultimo che resterà in ascolto delle richieste dei client. Dal punto di vista del client, chiamare un servizio WEB equivale a richiedere l’URL di quel servizio. Il client può essere scritto in qualsiasi linguaggio, in particolare in Python. È necessario saper «comunicare» con un servizio WEB, ovvero comprendere il protocollo di comunicazione Http tra un server Web e i suoi client. Questo è l’obiettivo dei programmi che seguono.

Gli script del servizio web saranno eseguiti dal server web Apache di WampServer. È necessario collocarli in una directory specifica: <WampServer>\bin\apache\apachex.y.z\cgi-bin, dove <WampServer> è la cartella di installazione di WampServer e x.y.z è la versione del server web Apache.

 

Non basta inserire gli script Python nella cartella <cgi-bin>. È necessario che lo script indichi nella prima riga il percorso dell'interprete Python da utilizzare. Questo percorso va inserito sotto forma di commento:

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

Il lettore dovrà adattare questo percorso al proprio ambiente.

12.1. Applicazione client/server per data e ora

Il nostro primo servizio web sarà un servizio di data e ora: il client riceve la data e l'ora correnti.

12.1.1. Il server


Il programma (web_02)


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

import time

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

# invio dell'ora al cliente
  # ora locale: numero di millisecondi dal 01/01/1970
  # "formato di visualizzazione data-ora
  # d: giorno a 2 cifre
  # m: mese a 2 cifre
  # y: anno a 2 cifre
  # H: ora 0,23
  # M: minuti
  # S: secondi

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

Note:

  • riga 6: lo script deve generare autonomamente alcune delle intestazioni HTTP della risposta al client. Queste si aggiungeranno alle intestazioni HTTP generate dal server Apache stesso. L’intestazione HTTP della riga 6 indica al client che gli verrà inviata una risorsa in formato text/plain, ovvero testo non formattato. Da notare il "\n" alla fine dell’intestazione, che genererà una riga vuota dopo l’intestazione. È obbligatorio: è proprio questa riga vuota che segnala al client HTTP la fine delle intestazioni HTTP della risposta. Segue poi la risorsa richiesta dal client, in questo caso un testo non formattato;
  • riga 18: la risorsa inviata al client è un testo che mostra la data e l'ora correnti.

12.1.2. Due test

Lo script precedente può essere eseguito direttamente dall’interprete Python in una finestra di comando, come abbiamo fatto finora. Ciò consente di eliminare eventuali errori di sintassi o di funzionamento. Si ottiene il seguente risultato:

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

24/06/11 11:16:55

Una volta testato lo script in questo modo, è possibile inserirlo nella cartella <cgi-bin> del server Apache (cfr. paragrafo 12). Avviamo l’applicazione WampServer. In questo modo vengono avviati sia un server web Apache che un SGBD MySQL. Per il momento utilizzeremo solo il server web. Quindi, con un browser, accediamo alla seguente pagina URL: http://localhost/cgi-bin/web_02.py:

  • in [1]: l’URL richiesta;
  • in [2]: la risposta visualizzata dal browser;
  • in [3]: il codice sorgente ricevuto dal browser web. È proprio quello inviato dallo script Python.

Con alcuni strumenti (in questo caso Firebug, un plugin del browser Firefox) è possibile accedere alle intestazioni HTTP scambiate con il server. Nell’esempio sopra riportato, il browser web ha ricevuto le seguenti intestazioni 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

Nelle righe 7-8 si riconosce l’intestazione HTTP inviata dallo script Python. Quelle precedenti sono state generate dal server web Apache.

12.1.3. Un client programmato


Il programma (client_web_02)

Ora scriviamo uno script che fungerà da client del servizio web precedente. Utilizziamo le funzionalità del modulo httplib che semplifica la scrittura di client HTTP.


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

import httplib,re

# costanti
HOST="localhost"
URL="/cgi-bin/web_02.py"
# connessione
connexion=httplib.HTTPConnection(HOST)
# monitoraggio
connexion.set_debuglevel(1)
# invio della richiesta
connexion.request("GET", URL)
# elaborazione della risposta
reponse=connexion.getresponse()
# contenuto
contenu=reponse.read()
# chiusura della connessione
connexion.close()
print "------\n",contenu,"-----\n"
# recupero degli elementi relativi all'ora
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])

Note:

  • riga 3: il modulo re è necessario per le espressioni regolari, mentre il modulo httplib è necessario per le funzioni dei client HTTP;
  • riga 9: viene creata una connessione HTTP con la porta 80 di HOST definita alla riga 6;
  • riga 11: il monitoraggio consente di visualizzare le intestazioni HTTP della richiesta del client e della risposta del server;
  • riga 13: viene richiesta la risorsa URL del servizio web. Esistono due modi per richiederla: tramite il comando HTTP GET o POST. La differenza tra i due viene spiegata più avanti. In questo caso verrà richiesta con il comando HTTP GET;
  • riga 15: viene letta la risposta del server. Qui si ottiene la risposta completa: intestazioni HTTP e risorsa richiesta dal client. Nella sua risposta, il server potrebbe aver chiesto al client di reindirizzarsi. In questo caso, il client httplib esegue automaticamente il reindirizzamento. La risposta ottenuta è quindi quella risultante dal reindirizzamento;
  • riga 17: la risposta è composta dalle intestazioni HTTP e dal documento richiesto dal client. Per ottenere solo le intestazioni HTTP, si utilizzerà [reponse].getHeaders(). Per ottenere il documento, si utilizza [reponse].read();
  • riga 19: una volta ottenuta la risposta dal server web, la connessione con esso viene chiusa;
  • si sa che il documento inviato dal server è una riga di testo del formato 15/06/11 14:56:36. Righe 22-26: si utilizza un'espressione regolare per estrarre i diversi elementi di questa riga.

12.1.4. Risultati

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

Note:

  • riga 1: le intestazioni HTTP inviate dal client al server web;
  • righe 2-6: le intestazioni HTTP della risposta del server web;
  • riga 8: il documento inviato dal server;
  • riga 11: il risultato della sua elaborazione;

12.2. Recupero da parte del server dei parametri inviati dal client

Nel protocollo HTTP, un client dispone di due metodi per trasmettere parametri al server web:

  1. richiede il servizio URL nella forma

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

dove i valori vali devono essere preventivamente sottoposti a codifica affinché alcuni caratteri riservati vengano sostituiti dal loro valore esadecimale.

  1. richiede l’URL del servizio nella forma
POST url HTTP/1.0

quindi, tra le intestazioni HTTP inviate al server, inserisce la seguente intestazione:

Content-length: N

Il resto delle intestazioni inviate dal client termina con una riga vuota. A questo punto, il client può inviare i propri dati nel formato

val1&param2=val2&param3=val3…

dove i valori vali devono, come per il metodo GET, essere preventivamente codificati. Il numero di caratteri inviati al server deve essere N, dove N è il valore dichiarato nell'intestazione:

Content-length: N

12.2.1. Il servizio web

Il servizio web che segue riceve 3 parametri dal proprio client: nom, prenom, age. Li recupera da una sorta di dizionario denominato cgi.FieldStorage fornito dal modulo cgi. Il valore vali di un parametro parami si ottiene tramite vali=cgi.FieldStorage().getlist("parami"). Si ottiene un array di:

  • 0 elementi se il parametro parami non è presente nella richiesta del cliente;
  • 1 elemento se il parametro parami è presente una volta nella richiesta del client;
  • n elementi se il parametro parami è presente n volte nella richiesta del cliente.

Una volta recuperati i parametri, lo script li restituisce al cliente.


Il programma (web_03)


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

import cgi

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

# recupero da parte del server delle informazioni inviate dal client
# qui nome=P&cognome=N&età=A
formulaire=cgi.FieldStorage()

# vengono rinviate al client
print "informations recues du service web [prenom=%s,nom=%s,age=%s]" % (formulaire.getlist("prenom"),formulaire.getlist("nom"),formulaire.getlist("age"))

È possibile effettuare un test con un browser web:

In [1], l'URL del servizio web. Si noti la presenza dei tre parametri nom, prenom, age. In [2] la risposta del servizio web.

12.2.2. Il client GET


Il programma (client_web_03_GET)


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

import httplib,urllib

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

# i parametri devono essere codificati prima di essere inviati al server
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# i parametri vengono inseriti alla fine dell'URL
URL+="?"+params
# connessione
connexion=httplib.HTTPConnection(HOST)
# monitoraggio
connexion.set_debuglevel(1)
# invio della richiesta
connexion.request("GET",URL)
# elaborazione della risposta
reponse=connexion.getresponse()
# contenuto
contenu=reponse.read()
print contenu,"\n"
# chiusura della connessione
connexion.close()

Note:

  • righe 8-10: i valori dei 3 parametri inviati al servizio web;
  • riga 13: è necessario codificarli. Ciò avviene tramite il metodo urlencode del modulo urllib. Questo modulo viene importato alla riga 3. Il metodo accetta come parametro un dizionario {param1:val1, param2:val2, ...};
  • riga 15: in un comando GET (riga 21), il client deve inserire i parametri codificati alla fine del URL del servizio web;
  • le righe successive sono già state esaminate.

12.2.3. I risultati

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

Note:

  • riga 2: notare la codifica dei parametri (cognome, nome, età);
  • riga 8: la risposta del servizio web.

12.2.4. Il client POST

Il client POST è analogo al client GET, tranne per il fatto che i parametri codificati non fanno più parte del URL di destinazione. Sono stati passati come terzo argomento della richiesta POST (riga 19).


Il programma (client_web_03_POST)


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

import httplib,urllib

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

# i parametri devono essere codificati prima di essere inviati al server
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# connessione
connexion=httplib.HTTPConnection(HOST)
# monitoraggio
connexion.set_debuglevel(1)
# invio della richiesta
connexion.request("POST",URL,params)
# elaborazione della risposta
reponse=connexion.getresponse()
# contenuto
contenu=reponse.read()
print contenu,"\n"
# chiusura della connessione
connexion.close()

12.2.5. I risultati

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

Note:

  • si noti, alla riga 2, il metodo utilizzato dal client POST per inviare i parametri codificati:
    • l'intestazione HTTP Content-Length indica il numero di caratteri che verranno inviati al servizio web;
    • questa intestazione HTTP è seguita da una riga vuota che indica la fine delle intestazioni HTTP;
    • successivamente vengono inviati i 39 caratteri dei parametri codificati.
  • riga 8: la risposta del servizio web.

12.3. Recupero delle variabili d’ambiente da un servizio web

12.3.1. Il servizio web

Lo script CGI Python viene eseguito in un ambiente di sistema dotato di attributi. I suoi attributi e i relativi valori sono disponibili in un dizionario denominato os.environ.


Il programma (web_04)


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

import os

# intestazioni
print "Content-Type: text/plain\n"
# informazioni sull'ambiente
for (cle,valeur) in os.environ.items():
    print "%s : %s" % (cle,valeur)

Note:

  • riga 3: è necessario importare il modulo os per poter disporre delle variabili "di sistema".

Se si esegue direttamente lo script sopra riportato (c.a.d, come script da console e non come CGI), nella console si ottengono i seguenti risultati:

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Ú

In un browser web (in questo caso viene eseguito lo script CGI), si ottengono i seguenti risultati:

 

Si noti che, a seconda del contesto di esecuzione, l’ambiente ottenuto non è lo stesso.

12.3.2. Il client programmato


Il programma (client_web_04)


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

import httplib

# costanti
HOST="localhost"
URL="/cgi-bin/web_04.py"
# connessione
connexion=httplib.HTTPConnection(HOST)
# invio della richiesta
connexion.request("GET", URL)
# elaborazione della risposta
reponse=connexion.getresponse()
# contenuto
print reponse.read()

12.3.3. Risultati

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

Si noti che il client programmato non riceve esattamente la stessa risposta del browser web. Questo perché quest'ultimo ha inviato al server web delle informazioni che sono state utilizzate dal server web per creare la sua risposta. In questo caso, il client programmato non ha inviato alcuna informazione su se stesso.