Un petite intro à la norme WSGI :
création d'un formulaire de connexion WSGI compliante, se servant de wsgiref, beaker (et un peu de cgi).
Bon, une petite définition du WSGI s'impose, même si c'est pas le but premier de ce billet.
Je vous renvoie à la PEP 333 pour plus de détails .
En python, il existe la norme WSGI qui décrit la manière dont doivent communiquer les applications webs avec leurs serveurs.
Une appli WSGI compliante doit implémenter 2 choses :
- une partie serveur (aussi appelée gateway)
- une partie application.
La partie serveur :
- va recevoir la requête du client,
- va ensuite appeler la partie application en lui donnant la requête,
- et va retourner la réponse de la partie application au client.
la partie application va fournir un objet callable qui prend 2 paramètres : environ et start_response.
Et c'est cet objet qui va retourner (à la partie serveur) la réponse à la requête du client ( en gros ce qu'il va voir afficher ).
Mais il existe aussi les composants middlewares ...
Un composant middleware est un composant que l'on va pouvoir placer entre la partie serveur et la partie application : il va pouvoir jouer les 2 rôles selon qui l'appelle.
Et comme un dessin vaut mieux qu'un long discours, voici une image qui illustre le concept :
elle est tirée de ce tuto.
Bon la théorie c'est enfin finie.
Place au code.
Nous allons écrire une petite appli qui va réaliser un pauvre formulaire de connexion ( le pseudo et le mot de passe doivent être les mêmes pour être "authentifié").
Pour se faire, on va se servir du module wsgiref ainsi que du module beaker ( fournit un middleware qui va nous permettre de gérer les sessions ) ( on va aussi se servir du module cgi mais il fait parti de la bibliothèque standard).
Si vous ne les avez pas un simple :
# easy_install wsgiref
# easy_install beaker
suffira.
Voici la partie serveur (fichier serveur.py ) :
#-*- coding:Utf-8 -*-
import sys
from wsgiref.simple_server import WSGIServer, WSGIRequestHandler
from middleware import Connexion
from application import Application
from beaker.middleware import SessionMiddleware
#sys.argv[1] adresse du serveur
#sys.argv[2] port du serveur
serveur = WSGIServer((sys.argv[1], int( sys.argv[2]) ), WSGIRequestHandler)
maSession = SessionMiddleware(
Connexion( Application() ),
key='monCookie',
)
serveur.set_app(maSession )
serveur.serve_forever()
Rien de bien compliqué.
On crée notre serveur web via WSGIServer() qui prend en paramètre :
- un tuple contenant l'adresse et le port où il va écouter,
- et WSGIRequestHandler , qui va gérer les requêtes.
Ensuite, on crée un SessionMiddleware, qui va être "le gestionnaire" de variables de notre session. Il prend ici 2 paramètres:
- Connexion( Application() ) : Comme dit plus haut, SessionMiddleware est un middleware donc nous devons lui passer en paramètre notre partie Application ; ou un middleware si nous en avons un.
Et on en a un : Connexion(), qui prend en paramètre notre partie application : Application().
- key='monCookie' : le nom de notre cookie, qui va contenir l'id de notre session ( chiffré).
enfin on lie notre serveur WSGI à notre middleware maSession ( bah sinon comment ils auraient pu communiquer) grâce à set_app(), puis on démarre le serveur via serve_forever() .
C'est terminé pour la partie serveur.
Place à la partie application (application.py ) :
#-*- coding:Utf-8 -*-
class Application(object):
def __call__(self, environ, start_response):
start_response('200 OK', [('content-type', 'text/html')])
return ["ok']
C'est tout petit !
Application sera appelé que si on arrive à s'authentifier, sinon c'est notre middleware Connexion() qui s'en occupe.
Sa méthode __call__() contient bien 2 paramètres :
- environ : dictionnaire contenant certaines variables d'environnement, la valeur de notre cookie, l'URL, la session du client et plein d'autres choses.
- start_response : va nous permettre de commencer à répondre au client.
start_response() va prendre 2 paramètres :
- '200 OK' : une chaine contenant un code HTTP suivi de son message, cf ici (200 signifie que la requête a été traitée avec succès)
- une liste contenant des tuples qui définissent l'entête de notre réponse HTTP . Ici il n'y a qu'un tuple : ('content-type', 'text/html') afin d'indiquer que l'on aura affaire à une page html.
Enfin on retourne une liste contenant "ok".
Ce sera le contenu de la page que verront les utilisateurs authentifiés.
Voilà, il nous reste plus qu'à voir notre middleware Connexion() (middleware.py) :
#-*- coding:Utf-8 -*-
from cgi import FieldStorage
class Connexion(object):
def __init__(self, application):
self.application = application
def __call__(self, environ, start_response):
session = environ['beaker.session']
#si c'est la première fois que le client se connecte sur le serveur
if not session.has_key('connecte'):
session['connecte'] = 0
session.save()
return self.formulaire(environ, start_response)
champs = FieldStorage( fp = environ['wsgi.input'], environ = environ)
#si on a réussi à se logguer ou si on l'est déjà
if session['connecte'] == 1 or self.login( champs ) :
#on le met à 1 et on sauve la session
session['connecte'] = 1
session.save()
return self.application(environ, start_response)
#sinon on affiche le formulaire de connexion
return self.formulaire(environ, start_response)
def login(self,champs):
if not champs.has_key('pseudo') or not champs.has_key('mdp'):
return False
return champs['pseudo'].value == champs['mdp'].value
def formulaire(self, environ, start_response):
start_response('200 OK',[('Content-type', 'text/html')])
return ["""
<html>
<head>
<title>Connexion</title>
</head>
<body>
<form action = "" method = "post" autocomplete = "off" >
<fieldset>
<legend>Remplissez tous les champs</legend>
<p> Pseudo : <input type = "text" name = "pseudo" /></p>
<p> Mot de passe : <input type = "password" name = "mdp" /></p>
<input type = "submit" value = "OK" />
</fieldset>
</form>
</body>
</html>
"""]
Quand notre middleware est appelée par la partie serveur, alors Connexion() joue le rôle de la partie Application et on rentre donc dans le __call__(), qui prend bien environ et start_response en paramètres.
Voici le fonctionnement __call__() :
si la session du client n'a pas la variable "connecte" alors le client vient tout juste de se connecter :
- On crée donc cette variable que l'on met à 0, pour non authentifié : session['connecte'] = 0
- On doit après save() notre session pour que la modification soit bien effectuée.
- Et on appelle formulaire() qui retourne à la partie serveur la réponse au client : un formulaire de connexion ( Connexion() joue toujours le rôle de la partie Application).
Ensuite, il faut s'authentifier : pour ce faire il faut récupérer les valeurs des champs du formulaire ; on a donc droit à cette jolie ligne :
champs = FieldStorage(fp = environ['wsgi.input'], environ = environ)
FieldStorage va contenir les valeurs des éventuels paramètres de la requête, il prend 2 paramètres :
fp : le fichier que va lire FieldStorage : ce sera environ['wsgi.input'] qui va lui permettre de lire le corps de la requete HTTP, et qui va donc lui permettre de récupérer les éventuels paramètres qui nous intéressent ( à savoir pseudo et mdp ).
environ : cf plus haut, on lui passe logiquement "notre" environ .
Ensuite, si la variable de sseion "connecte" vaut 1 alors on est déjà authentifié : on appelle donc la "véritable" partie Application, Connexion() joue alors le rôle de la partie serveur.
Sinon on tente de se logguer via la méthode login() .
login() :
- Si la méthode de la requête HTTP est "GET" alors on a pas validé le formulaire ( on accède à l'appli web via un navigateur ), donc champs ne contient pas [ has_key() ] les paramètres pseudo et mdp donc on retourne logiquement False.
- Si c'est du "POST" alors on a validé le formulaire : on compare bêtement la valeur des paramètres pseudo et mdp ( notez bien le .value ), et on retourne True ou False.
Si on a réussi à se logger() alors on met la variable de la session "connecte" à 1, on sauvegarde() session et on appelle la partie Application ( Connexion() joue le rôle de la partie serveur. ) .
Sinon on retourne le formulaire() ( Connexion() joue le rôle de la partie Application ).
Et c'est enfin fini.
Pour tout erreur de ma part, suggestion, question ou si vous aimez le saucisson n'hésitez pas.