Introduction au langage Python par l'exemple


précédentsommairesuivant

XIII. Des services Web en Python

Image non disponible

Les scripts Python peuvent être exécutés par un serveur Web. C'est ce dernier qui sera à l'écoute des demandes clientes. Du point de vue du client, appeler un service Web revient à demander l'URL de ce service. Le client peut être écrit avec n'importe quel langage, notamment en Python. Il nous faut savoir "converser" avec un service Web, c'est-à-dire comprendre le protocole HTTP de communication entre un serveur Web et ses clients. C'est le but des programmes qui suivent.

Les scripts de service Web seront exécutés par le serveur Web Apache de WampServer. Il faut les placer dans un répertoire particulier : <WampServer>\bin\apache\apachex.y.z\cgi-bin<WampServer> est le dossier d'installation de WampServer et x.y.z est la version du serveur Web Apache.

Image non disponible

Placer les scripts Python dans le dossier <cgi-bin> n'est pas suffisant. Il faut que le script indique en première ligne le chemin de l'interpréteur Python à utiliser. Ce chemin est mis sous la forme d'un commentaire :

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

Le lecteur adaptera ce chemin à son propre environnement.

XIII-A. Application client/ serveur de date/heure

Notre premier service Web sera un service de date et heure : le client reçoit la date et l'heure courantes.

XIII-A-1. Le serveur

Le programme (web_02)
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
#!D:\Programs\ActivePython\Python2.7.2\python.exe

import time

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

# envoi heure au client
  # localtime : nb de millisecondes depuis 01/01/1970
  # "format affichage date-heure
  # d: jour sur 2 chiffres
  # m: mois sur 2 chiffres
  # y : année sur 2 chiffres
  # H : heure 0,23
  # M : minutes
  # S: secondes

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

Notes :

  • ligne 6 : le script doit générer lui-même certains des entêtes HTTP de la réponse au client. Ils viendront s'ajouter aux entêtes HTTP générés par le serveur Apache lui-même. L'entête HTTP de la ligne 6 indique au client qu'on va lui envoyer une ressource au format text/plain, c'est-à-dire du texte non formaté. On notera le "\n" à la fin de l'entête qui va générer une ligne vide derrière l'entête. C'est obligatoire : c'est cette ligne vide qui signale au client HTTP la fin des entêtes HTTP de la réponse. Vient ensuite la ressource demandée par le client, ici un texte non formaté ;
  • ligne 18 : la ressource envoyée au client est un texte affichant la date et l'heure courantes.

XIII-A-2. Deux tests

Le script précédent peut être exécuté directement par l'interpréteur Python dans une fenêtre de commandes comme nous l'avons fait jusqu'à maintenant. Cela permet d'éliminer d'éventuelles erreurs de syntaxe ou de fonctionnement. On obtient le résultat suivant :

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

24/06/11 11:16:55

Lorsque le script a été ainsi testé, on peut le placer dans le dossier <cgi-bin> du serveur Apache. Lançons l'application WampServer. Cela lance à la fois un serveur Web Apache et un SGBD MySQL. Nous n'utiliserons pour le moment que le serveur Web. Puis avec un navigateur, demandons l'URL suivante : http://localhost/cgi-bin/web_02.py :

Image non disponible
  • en [1] : l'URL demandée ;
  • en [2] : la réponse affichée par le navigateur ;
  • en [3] : le code source reçu par le navigateur Web. C'est bien celui envoyé par le script Python.

Avec certains outils (ici Firebug, un plugin du navigateur Firefox) on peut avoir accès aux entêtes HTTP échangés avec le serveur. Ci-dessus, le navigateur Web a reçu les entêtes HTTP suivants :

 
Sélectionnez
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

On reconnaît lignes 7-8, l'entête HTTP envoyé par le script Python. Ceux qui précèdent ont été générés par le serveur Web Apache.

XIII-A-3. Un client programmé

On écrit maintenant un script qui sera client du service Web précédent. On utilise les fonctionnalités du module httplib qui facilite l'écriture de clients HTTP.

Le programme (client_web_02)
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
# -*- coding=utf-8 -*-

import httplib,re

# constantes
HOST="localhost"
URL="/cgi-bin/web_02.py"
# connexion
connexion=httplib.HTTPConnection(HOST)
# suivi
connexion.set_debuglevel(1)
# envoi de la requête
connexion.request("GET", URL)
# traitement de la réponse
reponse=connexion.getresponse()
# contenu
contenu=reponse.read()
# fermeture connexion
connexion.close()
print "------\n",contenu,"-----\n"
# récupération des éléments de l'heure
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])

Notes :

  • ligne 3 : le module re est nécessaire aux expressions régulières, le module httplib aux fonctions des clients HTTP ;
  • ligne 9 : une connexion HTTP est créée avec le port 80 de HOST défini ligne 6 ;
  • ligne 11 : le suivi permet de voir les entêtes HTTP de la demande du client et de la réponse du serveur ;
  • ligne 13 : l'URL du service Web est demandée. Il y a deux façons de la demander : à l'aide d'une commande HTTP GET ou POST. La différence entre les deux est expliquée plus loin. Ici elle sera demandée avec la commande HTTP GET ;
  • ligne 15 : la réponse du serveur est lue. C'est l'ensemble de la réponse qui est ici obtenue : entêtes HTTP et ressources demandées par le client. Dans sa réponse, le serveur a pu demander au client de se rediriger. Dans ce cas, le client httplib fait automatiquement la redirection. La réponse obtenue est donc celle issue de la redirection ;
  • ligne 17 : la réponse est composée des entêtes Http et du document demandé par le client. Pour obtenir seulement les entêtes HTTP, on utilisera [reponse].getHeaders(). Pour obtenir le document, on utilise [reponse].read() ;
  • ligne 19 : une fois obtenue la réponse du serveur Web, la connexion à celui-ci est fermée ;
  • on sait que le document envoyé par le serveur est une ligne de texte de la forme 15/06/11 14:56:36. Lignes 22-26, on utilise une expression régulière pour récupérer les différents éléments de cette ligne.
Résultats
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
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

Notes :

  • ligne 1 : les entêtes HTTP envoyés par le client au serveur Web ;
  • lignes 2-6 : les entêtes HTTP de la réponse du serveur Web ;
  • ligne 8 : le document envoyé par le serveur ;
  • ligne 11 : le résultat de son exploitation ;

XIII-B. Récupération par le serveur des paramètres envoyés par le client

Dans le protocole HTTP, un client a deux méthodes pour passer des paramètres au serveur Web :

1. Il demande l'URL du service sous la forme

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

où les valeurs vali doivent au préalable subir un encodage afin que certains caractères réservés soient remplacés par leur valeur hexadécimale ;

2. Il demande l'URL du service sous la forme

POST url HTTP/1.0

puis, parmi les entêtes HTTP envoyés au serveur, place l'entête suivant :

Content-length: N

La suite des entêtes envoyés par le client se termine par une ligne vide. Il peut alors envoyer ses données sous la forme

val1&param2=val2&param3=val3…

où les vali doivent, comme pour la méthode GET, être préalablement encodées. Le nombre de caractères envoyés au serveur doit être N où N est la valeur déclarée dans l'entête

Content-length: N

XIII-B-1. Le service Web

Le service Web qui suit reçoit trois paramètres de son client : nom, prenom, age. Il les récupère dans une sorte de dictionnaire nommé cgi.FieldStorage fourni par le module cgi. La valeur vali d'un paramètre parami est obtenue par vali=cgi.FieldStorage().getlist("parami"). On obtient un tableau de :

  • 0 élément si le paramètre parami n'est pas présent dans la requête du client ;
  • 1 élément si le paramètre parami est présent une fois dans la requête du client ;
  • n éléments si le paramètre parami est présent n fois dans la requête du client.

Une fois qu'il a récupéré les paramètres, le script les renvoie au client.

Le programme (web_03)
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
#!D:\Programs\ActivePython\Python2.7.2\python.exe

import cgi

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

# récupération par le serveur des informations envoyées par le client
# ici prenom=P&nom=N&age=A
formulaire=cgi.FieldStorage()

# on les renvoie au client
print "informations recues du service web [prenom=%s,nom=%s,age=%s]" % (formulaire.getlist("prenom"),formulaire.getlist("nom"),formulaire.getlist("age"))

Un test peut être fait avec un navigateur Web :

Image non disponible

En [1], l'URL du service Web. On notera la présence des trois paramètres nom, prenom, age. En [2] la réponse du service Web.

XIII-B-2. Le client GET

Le programme (client_web_03_GET)
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
# -*- coding=utf-8 -*-

import httplib,urllib

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

# les paramètres doivent être encodés avant d'être envoyés au serveur
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# les paramètres sont mis à la fin de l'URL
URL+="?"+params
# connexion
connexion=httplib.HTTPConnection(HOST)
# suivi
connexion.set_debuglevel(1)
# envoi de la requête
connexion.request("GET",URL)
# traitement de la réponse
reponse=connexion.getresponse()
# contenu
contenu=reponse.read()
print contenu,"\n"
# fermeture de la connexion
connexion.close()

Notes :

  • lignes 8-10 : les valeurs des trois paramètres envoyés au service Web ;
  • ligne 13 : il faut les encoder. Cela se fait à l'aide de la méthode urlencode du module urllib. Ce module est importé ligne 3. La méthode admet comme paramètre un dictionnaire {param1:val1, param2:val2…} ;
  • ligne 15 : dans une commande GET (ligne 21), le client doit mettre les paramètres encodés à la fin de l'URL du service Web ;
  • les lignes suivantes ont déjà été vues.
Les résultats
Sélectionnez
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']]

Notes :

  • ligne 2 : noter l'encodage des paramètres (nom, prenom, age) ;
  • ligne 8 : la réponse du service Web.

XIII-B-3. Le client POST

Le client POST est analogue au client GET si ce n'est que les paramètres encodés ne font plus partie de l'URL cible. Ils sont passés comme troisième argument de la requête POST (ligne 19).

Le programme (client_web_03_POST)
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
# -*- coding=utf-8 -*-

import httplib,urllib

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

# les paramètres doivent être encodés avant d'être envoyés au serveur
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# connexion
connexion=httplib.HTTPConnection(HOST)
# suivi
connexion.set_debuglevel(1)
# envoi de la requête
connexion.request("POST",URL,params)
# traitement de la réponse
reponse=connexion.getresponse()
# contenu
contenu=reponse.read()
print contenu,"\n"
# fermeture de la connexion
connexion.close()
Les résultats
Sélectionnez
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']]

Notes :

  • on notera ligne 2, la méthode utilisée par le client POST pour envoyer les paramètres encodés :
  • l'entête HTTP Content-Length indique le nombre de caractères qui vont être envoyés au service Web,
  • cet entête HTTP est ensuite suivi d'une ligne vide indiquant la fin des entêtes HTTP,
  • ensuite les 39 caractères des paramètres encodés sont envoyés ;
  • ligne 8 : la réponse du service Web.

XIII-C. Récupération des variables d'environnement d'un service Web

XIII-C-1. Le service Web

Le script cgi Python s'exécute dans un environnement système qui possède des attributs. Ceux-ci et leurs valeurs sont disponibles dans un dictionnaire os.environ.

Le programme (web_04)
Sélectionnez
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"
# infos d'environnement
for (cle,valeur) in os.environ.items():
    print "%s : %s" % (cle,valeur)

Notes :

  • ligne 3 : il faut importer le module os pour disposer des variables "système".

Si on exécute directement le script ci-dessus (c'est-à-dire en script console et non cgi), on obtient dans la console les résultats suivants :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
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Ú

Dans un navigateur Web (c'est alors le script cgi qui est exécuté), on obtient les résultats suivants :

Image non disponible

On notera que selon le contexte d'exécution, l'environnement obtenu n'est pas le même.

XIII-C-2. Le client programmé

Le programme (client_web_04)
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
# -*- coding=utf-8 -*-

import httplib

# constantes
HOST="localhost"
URL="/cgi-bin/web_04.py"
# connexion
connexion=httplib.HTTPConnection(HOST)
# envoi de la requête
connexion.request("GET", URL)
# traitement de la réponse
reponse=connexion.getresponse()
# contenu
print reponse.read()
Résultats
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
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

On notera que le client programmé ne reçoit pas exactement la même réponse que le navigateur Web. C'est parce que ce dernier a envoyé au serveur Web des informations qui ont été utilisées par le serveur Web pour créer sa réponse. Ici, le client programmé n'a envoyé aucune information sur lui-même.


précédentsommairesuivant

  

Licence Creative Commons
Le contenu de cet article est rédigé par Serge Tahé et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.