M1. Outils de l'internet, année 2008/2009

TP 4 - WSGI, SimpleTAL et OpenLDAP

V. Poupet, A. Ballier

D'après le TP de 2007 par N. Ollinger, E. Jeandel et V. Bernardi

Dans cette fiche, on notera en gras coloré les parties des commandes et fichiers de configurations qui doivent être modifiés pour être adaptés à votre environnement de travail : ne les recopiez pas ! modifiez-les !

Ce TP est constitué de deux parties indépendantes: une introduction à WSGI (une façon générique d'écrire des applications web en python) avec l'utilisation d'un outil pour écrire des templates de pages web SimpleTAL et la mise en place d'un serveur LDAP, OpenLDAP.

1. WSGI

Vous avez déjà vu comment écrire des applications CGI en python, un serveur HTTP en python ou encore en utilisant mod_python. Si le fait que, à chaque fois, la syntaxe et le façon de faire diffère un peu alors WSGI est fait pour vous!

Hello world en WSGI

def application(environ, start_response):
    start_response('200 OK',[('Content-type','text/html')])
    return ['<html><body>Hello World!</body></html>']

Ceci ressemble vaguement à la syntaxe de mod_python, mais regardons de plus près. Vous connaissez certainement déjà la syntaxe d'une réponse HTTP, et bien start_response permet de définir ce que le serveur qui implémentera votre application WSGI va répondre. Dans notre cas nous renvoyons l'état "200 OK" et précisons que le contenu est du type "text/html". Rien de bien transcendant ici.

Un serveur HTTP en python implémentant l'interface WSGI

Recopiez le code du "Hello World" précédent dans un fichier python. Par la suite nous nommerons ce fichier toto.py.
from toto import application
from wsgiref import simple_server
httpd = simple_server.WSGIServer(('',8000),simple_server.WSGIRequestHandler)
httpd.set_app(application)
httpd.serve_forever()
Créez un tel fichier dans le même répertoire que votre toto.py précédent et exécutez-le. Maintenant pointez votre navigateur vers l'adresse http://localhost:8000.

Jusqu'ici tout va bien... vous avez une belle page HTML qui s'affiche avec un contenu super intéressant.

WSGI via CGI

#! /usr/bin/env python
from toto import application
from wsgiref.handlers import CGIHandler
CGIHandler().run(application)
Recopiez ce bout de code et mettez le dans un fichier où apache va exécuter les fichiers CGI.
Attention, n'oubliez pas de mettre le fichier toto.py dans le même répertoire ou d'ajuster votre PYTHONPATH pour que python puisse importer toto.py

Intéressant, la même application marche aussi en CGI!

WSGI via mod_python

Téléchargez ce fichier

Ces lignes de configuration dans le fichier httpd.conf dans la section de mod_python vous permettront d'exécuter votre application WSGI. Le fichier que vous venez de télécharger vous permettra de faire la passerelle:

PythonHandler wsgi_handler
PythonOption WSGI.Application toto::application
Modifiez votre configuration d'apache pour exécuter votre application WSGI via mod_python.

Décorations en python

La syntaxe utilisée dans WSGI peut être un peu rébarbative, définir la réponse, le content-type à chaque fois, etc. Heureusement, nous allons faire appel aux décorations de python pour ça!

def f(fu):
    def r(s):
        return fu(s+"toto")
    return r

@f
def g(s):
    return s

print g("titi")

Comment ça peut nous servir à nous?

class header_http:
    def __init__(self,f):
            self.f=f
    def __call__(self,environ, start_response):
        start_response('200 OK',[('Content-type','text/html')])
        return self.f(environ,start_response);

class html_page:
    def __init__(self,f):
        self.f=f
    def __call__(self,environ, start_response):
        return ['<html>']+self.f(environ,start_response)+['</html>']


class body_content:
        def __init__(self, f):
                self.f = f
        def __call__(self,environ,start_response):
                return ['<body>']+self.f(environ,start_response)+['</body>']

@header_http
@html_page
@body_content
def application(environ,start_response):
        return ['Hello World!']

C'est un peu plus long mais il vous suffit de mettre ces fonctions dans un autre fichier python et votre application se résumera à:

@header_http
@html_page
@body_content
def application(environ,start_response):
        return ['Hello World!']

C'est un peu de l'"overkill" de faire comme ça, mais essayons d'aller plus loin.

Et ça continue encore et encore...

Désormais vous avez compris que WSGI vous permet d'écrire des applications web que l'on peut déployer facilement de moultes façons différentes. Il ne vous reste plus qu'une chose à faire...

Reprenez votre code python du TP précédent et transformez-le en une classe auth_required qui pourra décorer les fonctions nécessitant une authentification. Cette fonction devra soit afficher la "page" décorée si l'authentification est valide soit proposer de s'authentifier.

Vous pourrez vous inspirer du code suivant:

from paste.request import parse_formvars
from paste.request import get_cookie_dict


class auth_required:
        params={}
        def __init__(self,f):
                self.f = f
        def mysr(self,status, headers):
                headers.append(('Set-Cookie','login='+self.params['login']))
                headers.append(('Set-Cookie','pass='+self.params['pass']))
                return self.sr(status,headers)
        def check_auth(self):
            if('login' in self.params and 'pass' in self.params):
               verification login/password
               return 1 si ok
            return 0
        def __call__(self,environ,start_response):
                auth_ok = 0
                self.sr = start_response
                self.params = get_cookie_dict(environ)
                auth_ok = self.check_auth()
                if(auth_ok == 0):
                    self.params = parse_formvars(environ)
                    auth_ok = self.check_auth()
                if(auth_ok):
                    return self.f(environ,self.mysr)
                else:
                    start_response('403 Forbidden',[('Content-type','text/html')])
                    return """
                            code html du formulaire
                           """

En disséquant un peu le code, on a la fonction get_cookie_dict qui nous donne un dictionnaire contenant les cookies, la fonction parse_fromvars les variables POST et GET. Ainsi en décorant n'importe quelle fonction par ce décorateur, on est sûr que l'utilisateur est authentifié lorsqu'il accède au contenu, sinon il est renvoyé sur le formulaire d'authentification.

@auth_required
@header_http
@html_page
@body_content
def application(environ,start_response):
        return ['Hello World!']

C'est fou, maintenant cette page nécessite d'être authentifié à notre base de données... En plus, ça pourra constituer le début de votre projet!

2. SimpleTAL

SimpleTAL est une implémentation python d'un standard permettant de faire des templates de pages Web. Parce que, vous l'avez compris, écrire une page HTML complète dans du code python et la renvoyer dans une fonction n'est pas forcément une manière très élégante de faire. Grâce à ce système de templates, vous pouvez écrire du code "presque" HTML dans un fichier séparé tout en gardant l'aspect dynamique. Voici un exemple simpliste d'utilisation de SimpleTAL:

from simpletal import simpleTAL, simpleTALES
import StringIO

@auth_required
@header_http
@html_page
@body_content
def application(environ, start_response):
        s=StringIO.StringIO()
        context = simpleTALES.Context()
        context.addGlobal ("title", "Hello World")
        templateFile = open ("foo.tpl", 'r')
        template = simpleTAL.compileHTMLTemplate (templateFile)
        templateFile.close()
        template.expand (context, s)
        return [s.getvalue()]

Le fichier "foo.tpl" contenant:

<h1 tal:content="title">The title goes here</h1>
Apprenez à vous servir de SimpleTAL en parcourant les exemples du site et faites en sorte que votre page "Hello World" devienne un "Hello Toto" où Toto est le login de l'utilisateur (ou le nom si vous y avez accès).

3. OpenLDAP

OpenLDAP fournit une implémentation libre et complète du standard LDAP. La distribution OpenLDAP inclut le serveur LDAP slapd, le serveur de réplication slurpd, que nous n'utiliserons pas, et divers utilitaires pour interagir avec les serveurs en question. Voir la documentation complète du serveur slapd.

Pour fonctionner, le serveur slapd a besoin de l'existence (dans votre civet) des répertoires suivants:

var/run/
var/db/openldap

De plus, nous allons créer notre base de donnée LDAP en l'enracinant ici:dc=example,dc=com. Créons donc un répertoire pour stocker la base de donnée LDAP:

var/db/openldap/example-com

Enfin, créons dans etc/ le fichier slapd.conf de configuration du serveur slapd:

include         /home/monnomamoi/outils/etc/schema/core.schema
include         /home/monnomamoi/outils/etc/schema/cosine.schema
include         /home/monnomamoi/outils/etc/schema/inetorgperson.schema
include         /home/monnomamoi/outils/etc/schema/nis.schema
pidfile /home/monnomamoi/outils/var/run/slapd.pid
password-hash {MD5}
loglevel        -1
moduleload /usr/lib/ldap/back_bdb.so
database bdb
suffix "dc=example, dc=com"
rootdn "cn=jimbob, dc=example, dc=com"
rootpw dirtysecret
directory       /home/monnomamoi/outils/var/db/openldap/example-com
index   uid     eq
index   cn,gn,mail eq,sub
index sn eq,sub
index ou eq
index default eq,sub
index telephonenumber
cachesize 10000
checkpoint 128 15
Ajoutez slapd aux services lancés par votre civet et testez le grâce aux commandes ldapadd et ldapsearch. Vous pouvez pour vous aider utiliser le fichier LDIF nommé ExampleCompany.ldif et utiliser des commandes du genre ldapadd -x -W -D "cn=jimbob,dc=example,dc=com" -f ExampleCompany.ldif -H ldap://127.0.0.1:9119.