Skip to content

12. Servizi web in Python

  

Gli script Python possono essere eseguiti da un server web. È il server che ascolta le 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, compreso Python. Dobbiamo sapere come “comunicare” con un servizio web, ovvero comprendere il protocollo HTTP utilizzato per la comunicazione tra un server web e i suoi client. Questo è lo scopo dei programmi seguenti.

Gli script del servizio web saranno eseguiti dal server web Apache in WampServer. Devono essere collocati 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 è sufficiente inserire semplicemente gli script Python nella cartella <cgi-bin>. Lo script deve specificare il percorso dell'interprete Python da utilizzare nella prima riga. Questo percorso è incluso come commento:

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

I lettori dovrebbero 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:\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())

Note:

  • Riga 6: Lo script deve generare alcune delle intestazioni HTTP per la risposta al client stesso. Queste verranno aggiunte alle intestazioni HTTP generate dal server Apache stesso. L'intestazione HTTP alla riga 6 comunica al client che una risorsa verrà inviata in formato text/plain, ovvero testo non formattato. Si noti il "\n" alla fine dell'intestazione, che genererà una riga vuota dopo l'intestazione. Questo è obbligatorio: è questa riga vuota che segnala al client HTTP la fine delle intestazioni HTTP nella risposta. Segue la risorsa richiesta dal client, in questo caso 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. Questo aiuta a eliminare eventuali errori di sintassi o di runtime. 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 directory <cgi-bin> del server Apache (vedere il paragrafo 12). Avviamo l'applicazione WampServer. In questo modo si avvieranno sia il server web Apache che il server di database MySQL. Per ora utilizzeremo solo il server web. Quindi, utilizzando un browser, inserite il seguente URL: http://localhost/cgi-bin/web_02.py:

  • in [1]: l'URL richiesto;
  • in [2]: la risposta visualizzata dal browser;
  • in [3]: il codice sorgente ricevuto dal browser web. Si tratta infatti del codice inviato dallo script Python.

Con alcuni strumenti (in questo caso, Firebug, un plugin per il browser Firefox), è possibile accedere alle intestazioni HTTP scambiate con il server. In questo caso, 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

Le righe 7–8 sono riconoscibili come l'intestazione HTTP inviata dallo script Python. Le righe precedenti sono state generate dal server web Apache.

12.1.3. Un client programmato


Il programma (client_web_02)

Ora scriveremo uno script che fungerà da client per il servizio web precedente. Utilizzeremo le funzionalità del modulo httplib, che semplifica la scrittura di client 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])

Note:

  • Riga 3: Il modulo re è necessario per le espressioni regolari, mentre il modulo httplib serve per le funzioni client HTTP;
  • riga 9: viene creata una connessione HTTP utilizzando la porta 80 sull'HOST definito nella riga 6;
  • riga 11: il log consente di visualizzare le intestazioni HTTP della richiesta del client e della risposta del server;
  • riga 13: viene richiesto l'URL del servizio web. Esistono due modi per richiederlo: utilizzando un comando HTTP GET o POST. La differenza tra i due viene spiegata più avanti. Qui, verrà richiesto utilizzando il comando HTTP GET;
  • riga 15: viene letta la risposta del server. Qui si ottiene l'intera risposta: le intestazioni HTTP e la risorsa richiesta dal client. Nella sua risposta, il server potrebbe aver indicato al client di effettuare un reindirizzamento. In questo caso, il client httplib esegue automaticamente il reindirizzamento. La risposta ottenuta è quindi quella risultante dal reindirizzamento;
  • riga 17: la risposta è costituita dalle intestazioni HTTP e dal documento richiesto dal client. Per recuperare solo le intestazioni HTTP, utilizzare [response].getHeaders(). Per recuperare il documento, utilizzare [response].read();
  • Riga 19: una volta ottenuta la risposta dal server web, la connessione con esso viene chiusa;
  • Sappiamo che il documento inviato dal server è una riga di testo nel formato 15/06/11 14:56:36. Righe 22–26: utilizziamo un'espressione regolare per estrarre i vari 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 nella risposta del server web;
  • Riga 8: il documento inviato dal server;
  • Riga 11: il risultato della sua elaborazione;

12.2. Il server recupera i parametri inviati dal client

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

  1. richiede l'URL del servizio nel modulo

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

dove i valori validi devono essere prima codificati in modo che alcuni caratteri riservati vengano sostituiti dai loro valori esadecimali.

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

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

Content-length: N

Il resto delle intestazioni inviate dal client termina con una riga vuota. Può quindi inviare i propri dati nel formato

val1&param2=val2&param3=val3…

dove i valori devono, come nel metodo GET, essere codificati in precedenza. 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 seguente servizio web riceve tre parametri dal proprio client: last_name, first_name e age. Li recupera da una struttura simile a un dizionario denominata cgi.FieldStorage fornita dal modulo cgi. Il valore vali di un parametro parami si ottiene utilizzando vali = cgi.FieldStorage().getlist("parami"). Ciò restituisce un array di:

  • 0 elementi se il parametro parami non è presente nella richiesta del client;
  • 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 client.

Una volta recuperati i parametri, lo script li rinvia al client.


Il programma (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"))

È possibile eseguire un test utilizzando un browser web:

In [1], l'URL del servizio web. Si noti la presenza dei tre parametri last_name, first_name, 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

#    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()

Note:

  • righe 8–10: i valori dei 3 parametri inviati al servizio web;
  • riga 13: questi devono essere codificati. Ciò avviene utilizzando 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 una richiesta GET (riga 21), il client deve inserire i parametri codificati alla fine dell'URL del servizio web;
  • Le righe seguenti sono già state trattate.

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 (last_name, first_name, age);
  • riga 8: la risposta del servizio web.

12.2.4. Il client POST

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


Il programma (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. 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:

  • Nota riga 2: il metodo POST utilizzato dal client per inviare i parametri codificati:
    • L'intestazione HTTP Content-Length indica il numero di caratteri che saranno inviati al servizio web;
    • questa intestazione HTTP è seguita da una riga vuota che indica la fine delle intestazioni HTTP;
    • quindi 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 nel dizionario os.environ.


Il programma (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)

Note:

  • Riga 3: È necessario importare il modulo os per accedere alle variabili "di sistema".

Se si esegue direttamente lo script sopra riportato (ovvero come script da console anziché come script CGI), nella console verranno visualizzati 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 (dove 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

#    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. 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é il browser ha inviato al server web delle informazioni che il server ha utilizzato per generare la propria risposta. In questo caso, il client programmato non ha inviato alcuna informazione su se stesso.