Mercurial en tant qu'application WSGI

Logo Mercurial

Aujourd'hui, j'ai soumis un PR (pour FreeBSD), concernant une mise à jour majeur, du gestionnaire de version, Mercurial.

J'en ai profité pour le tester un peu plus en profondeur, notamment, je me suis intéressé au module, hgweb. Il permet d'afficher via une page Web, un dépôt Mercurial.

WSGI, est spécification (PEP 3333) définissant une « interface » commune entre des serveurs et des applications Web écrite en Python.

Les serveurs WSGI permettent de se « passer » d'un serveur, tels que Apache, NGinx, Lighttpd, ... Ils fonctionnent de manière standalone. On peut bien entendu les coupler à ces serveurs Web, mais il faudra les proxyfier.

Pour comprendre un minimum le contenu de cet article, il faut avoir quelques notions concernant le langage Python, et Mercurial.

Création d'un dépôt

Dans cette partie, nous allons voir comment mettre en place (sans rentrer dans les détails) un dépôt pour un projet.

olivier@bornem:~ $ mkdir -p ~/projet/.hg ; cd ~/projet
olivier@bornem:~ $ touch .hg/hgrc

La seconde ligne crée le fichier de configuration (vide pour l'instant), nécessaire au bon fonctionnement de Mercurial. Nous allons voir comment le remplir hgrc(5) :

olivier@bornem:~/projet $ cat >> .hg/hgrc
[ui]
username = Olivier Duchateau
verbose = True
^C

Nous allons déposer des fichiers (ils correspondront à notre premier projet).

olivier@bornem:~/projet $ cp /etc/hosts .
olivier@bornem:~/projet $ cp /etc/fstab .
olivier@bornem:~/projet $ cp /etc/shells .
olivier@bornem:~/projet $ hg add
ajout de fstab
ajout de hosts
ajout de shells
olivier@bornem:~/projet $
olivier@bornem:~/projet $ hg commit

La dernière ligne permet « d'enregistrer » l'historique du projet. Vous pouvez modifier l'un ou l'ensemble des fichiers, il faudra dans tous les cas, mettre à jour l'historique avec la commande hg commit.

Maintenant, nous pouvons mettre en place notre application WSGI, et la faire « tourner » à l'aide de quelques serveurs.

L'arborescence de notre application Web

On quitte le répertoire de notre projet, pour aller dans celui du serveur Web.

olivier@bornem:~ $ mkdir ~/htdocs ; cd ~/htdocs
olivier@bornem:~/htdocs $ cat > hgwebdir.cfg
[web]
contact = Olivier Duchateau
description =  Mes fichiers de configuration (ordinateur : bornem)
adress = 127.0.0.1

[paths]
bornem-config = /home/olivier/projet
olivier@bornem:~/htdocs $

Vous pouvez voir ci-dessus, le contenu du fichier de configuration (hgwebdir.cfg) de notre dépôt Mercurial.

Le script Python, de notre application (hgweb.py), qui utilise le module hgweb.

#! /usr/bin/env python
# -*- coding: iso-8859-15 -*-
#

import os

# Le chemin vers le fichier de configuration du dépôt
_basedir = os.path.abspath(os.path.dirname(__file__))
hg_dir = os.path.join(_basedir, "hgwebdir.cfg")

del os

from mercurial.hgweb.hgwebdir_mod import hgwebdir
app = hgwebdir(hg_dir)

Le fichier hgweb.py est situé dans le dossier htdocs/.

Gunicorn

Gunicorn, est bien sûr un serveur WSGI écrit en Python. Nous allons l'utiliser sans framework.

Il se lance à partir de la ligne de commande. Cependant, on peut créer un fichier de configuration. C'est ce que nous allons mettre en place.

#! /usr/bin/env python
# -*- coding: iso-8859-15 -*-

import multiprocessing

bind = "127.0.0.1:5050"
workers = multiprocessing.cpu_count() * 2 + 1
pidfile = "/tmp/gunicorn.pid"

Explication :

  • La page Web sera accessible à partir de l'adresse 127.0.0.1:5050 (notez bien le port)
  • le numéro du processus sera stocké dans le fichier /tmp/gunicorn.pid
  • le nombre de processus est défini par la variable workers, (choix du nombre)

Pour lancer notre application :

olivier@bornem:~/htdocs $ gunicorn -c ~/gunicorn.py hgweb:app
2011-11-05 19:37:37 [32209] [INFO] Starting gunicorn 0.13.4
2011-11-05 19:37:37 [32209] [INFO] Listening at: http://127.0.0.1:5050 (32209)
2011-11-05 19:37:37 [32209] [INFO] Using worker: sync
2011-11-05 19:37:37 [32211] [INFO] Booting worker with pid: 32211
2011-11-05 19:37:37 [32212] [INFO] Booting worker with pid: 32212
2011-11-05 19:37:38 [32213] [INFO] Booting worker with pid: 32213

Pour admirer le résultat, il faut faire pointer votre navigateur à l'adresse 127.0.0.1:5050.

La dernière partie de la ligne de commande, n'est sans doute pas très claire.

En fait, Gunicorn, utilise la même « syntaxe » que Python, lors de l'importation des modules. hgweb est le nom du module (fichier hgweb.py), quant à app est la référence à notre application.

Si l'on souhaite lancer Gunicorn en dehors du dossier htdocs/, il faudra créer un fichier __init__.py à l'intérieur de celui-ci.

La commande devient donc :

olivier@bornem:~ $ gunicorn -c gunicorn.py htdocs.hgweb:app

wsgiref

Il s'agit d'un module présent nativement dans la bibliothèque standard de Python. Il implémente les spécifications WSGI.

Pour l'utiliser, il faut « importer » le module wsgiref.simple_server.

Ci-dessous, un exemple pour lancer l'interface Web de notre dépôt Mercurial.

#! /usr/bin/env python
# -*- coding: iso-8859-15 -*-

import wsgiref.simple_server
#import wsgiref.simple_server

import Documents.hgweb


if __name__ == "__main__":
    wsgi = wsgiref.simple_server.make_server("127.0.0.1", 5050,
                                             Documents.hgweb.app)
    soc_tcp = wsgi.socket.getsockname()
    print "\t* Running on http://%s:%s" % (soc_tcp[0], soc_tcp[1])
    wsgi.serve_forever()

gevent

Nous allons utiliser le module wsgi. Dans cet exemple le fichier __init__.py est indispensable.

Ci dessous le fichier (runserver.wsgi), qui nous permettra de lancer notre application Web.

#! /usr/bin/env python
# -*- coding: iso-8859-15 -*-
#

import sys
try:
		from gevent.wsgi import WSGIServer
except ImportError, e:
		print "Le module 'gevent' est manquant !"
		sys.exit()

import htdocs.hgweb

if __name__ == "__main__":
		WSGIServer(("127.0.0.1", 5050), 
                                     application=htdocs.hgweb.app, 
                                     log=None).serve_forever()

L'application se lance de cette façon.

olivier@bornem:~ $ python runserver.wsgi

Werkzeug

Il s'agit d'un couteau suisse pour tout ce qui concerne les applications WSGI, il est utilisé notamment dans les frameworks, Flask, et tipfy.

Le fichier (runserver.wsgi) ressemble à ça :

#! /usr/bin/env python
# -*- coding: iso-8859-15 -*-
#

import multiprocessing
import sys
try:
		import werkzeug.serving
except ImportError, e:
		print "Le module 'werkzeug' est absent !"

import htdocs.hgweb

if __name__ == "__main__":
		werkzeug.serving.run_simple("127.0.0.1", 5050,
				htdocs.hgweb.app, 
				use_reloader=True, 
				processes=multiprocessing.cpu_count() * 2 + 1)

L'application se lance comme ceci :

olivier@bornem:~ $ python runserver.wsgi

Nous n'avons pas fait le tour des serveurs WSGI, mais nous avons vu un aperçu comment les utiliser pour afficher des pages Web dynamique en Python.