12. Des services web en Python
![]() |
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 où <WampServer> est le dossier d'installation de WampServer et x.y.z est la version du serveur web Apache.
![]() |
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 :
Le lecteur adaptera ce chemin à son propre environnement.
12.1. 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.
12.1.1. Le serveur
#!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.
12.1.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'éléminer d'éventuelles erreurs de syntaxe ou de fonctionnement. On obtient le résultat suivant :
Lorsque le script a été ainsi testé, on peut le placer dans le dossier <cgi-bin> du serveur Apache (cf paragraphe 12). 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 :
![]() |
- 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 :
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.
12.1.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.
# -*- 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 expression 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 ressource demandée 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.
12.1.4. Résultats
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 ;
12.2. 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 :
- il demande l'URL du service sous la forme
GET url?param1=val1¶m2=val2¶m3=val3… HTTP/1.0
où les valeurs vali doivent être au préalable subir un encodage afin que certains caractères réservés soient remplacés par leur valeur hexadécimale.
- il demande l'URL du service sous la forme
puis parmi les entêtes Http envoyés au serveur place l'entête suivant :
La suite des entêtes envoyés par le client se terminent par une ligne vide. Il peut alors envoyer ses données sous la forme
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 :
12.2.1. Le service web
Le service web qui suit reçoit 3 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 1 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 récupérés les paramètres, le script les renvoie au client.
#!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 :
![]() |
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.
12.2.2. Le client GET
# -*- 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 3 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.
12.2.3. Les résultats
Notes :
- ligne 2 : noter l'encodage des paramètres (nom, prenom, age) ;
- ligne 8 : la réponse du service web.
12.2.4. 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 3ième argument de la requête POST (ligne 19).
# -*- 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()
12.2.5. Les résultats
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.
12.3. Récupération des variables d'environnement d'un service web
12.3.1. Le service web
Le script cgi Python s'exécute dans un environnement système qui possède des attributs. Ses attributs et leurs valeurs sont disponibles dans un dictionnaire os.environ.
#!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.a.d. en script console et non cgi), on obtient dans la console les résultats suivants :
Dans un navigateur web (c'est alors le script cgi qui est exécuté), on obtient les résultats suivants :
![]() |
On notera que selon le contexte d'exécution, l'environnement obtenu n'est pas le même.
12.3.2. Le client programmé
# -*- 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()
12.3.3. Résultats
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.




