Qu’est-ce que WSGI et à quoi ça sert ?
mercredi 3 juillet 2013 à 12:36On va dire que je me répète, mais WSGI est typiquement le cas d’une notion simple et super mal expliquée sur le Web.
C’est terrible ça, une vrai maladie. Ça me donne envie de faire des vidéos comme le joueur du Grenier pour râler à voix haute.
Bref.
Donc WSGI a le même but que FastCGI, SCGI, or AJP : c’est une norme qui sert à définir comment un serveur Python et son application peuvent discuter. Ça été pondu pour que tous les serveurs et toutes les applications Python qui respectent la norme soient garantis de pouvoir s’interfacer.
Ça, c’est la définition que vous voyez partout. Super, mais concrètement ça veut dire quoi ?
WSGI, c’est juste une convention de discussion
Un bon dessin vaut mieux…
D’un côté vous avez des serveurs comme ceux de cherrypy, de paste, de werkzeug ou comme la commande runserver de Django, ou tels que les serveurs gunicorn, Apache (avec mod_wsgi). Ces logiciels ont pour boulot d’accueillir une requête HTTP et de renvoyer la réponse. Ils ont des problématiques comme : gérer les sockets, gérer plusieurs processus en parallèle, éviter qu’un pirate se balade sur votre serveur, etc.
De l’autre côté vous avez votre application. 9 fois sur 10, votre site Web, donc votre code. Sa problématique c’est de recevoir une requête, la comprendre, et générer une réponse en tapant dans la base de données, et en faisant sa popotte HTML.
Il faut donc que votre serveur dialogue avec votre app, qu’il lui passe la requête, que votre app la gère, retourne la réponse, et file la réponse au serveur. Alors le serveur envoie la réponse au navigateur.
C’est la chose la plus mal expliquée : il y a deux parties à WSGI. Une pour le serveur, et une pour l’application.
Un serveur est dit “compatible WSGI” quand il est capable de transmettre une requête HTTP normale à votre application via le protocole WSGI, et qu’il est capable de récupérer une réponse HTTP depuis votre application via le protocole WSGI pour en faire une requête HTTP normale.
Une application est dite “compatible WSGI” quand elle est capable de recevoir une requête HTTP transformée en un objet Python selon la norme WSGI, et qu’elle retourne une réponse HTTP sous la forme d’un objet Python selon la norme WSGI.
J’avais demandé concrètement, show me the fucking code !
Côté serveur WSGI, on doit lui passer le point d’entrée de votre app.
Le point d’entrée est un module Python qui contient une variable nommé application
.
C’est tout.
La variable application doit contenir votre application compatible WSGI.
Une application compatible WSGI a un certain nombre de méthodes pour accepter en paramètres un objet requête HTTP, et retourner un objet réponse HTTP, mais la bonne nouvelle, c’est que vous n’avez absolument pas besoin de savoir comment ça marche.
En effet, tous les frameworks compatibles WSGI (bottle, django, cherrypy, etc) ont une fonction qui retourne cette application toute faite.
Par exemple, avec bottle, mon fichier wsgi va contenir :
from site import app application = app
C’est tout. app
, le décorateur qui permet de faire @app.route('/')
dans le code du site bottle, est déjà une application WSGI.
Pour Django, ça va donner un truc comme :
import os # ont dit quel est le module de settings Django os.environ["DJANGO_SETTINGS_MODULE"] = "project.settings" # et on fabrique l'application WSGI avec une fonction # que Django nous donne from django.core.wsgi import get_wsgi_application application = get_wsgi_application()
Le simple fait qu’il y ait une variable nommée application
fait que le serveur va se débrouiller. Tout ce qu’il y a à faire, c’est passer en paramètre ce module au serveur, et comment le faire dépend du serveur.
Par exemple pour gunicorn, c’est le premier paramètre de la commande qu’on utilise pour lancer le serveur :
gunicorn nom_du_module
Il faut que le module soit importable (on peut passer l’option --pythonpath
pour s’en assurer.)
Pour Apache et mod_wsgi, ça se fait dans le fichier de config apache.conf:
WSGIScriptAlias / /chemin/version/module.wsgi
Bref, faire dialoguer un serveur et une application WSGI, c’est con comme la lune :
- Faire un module qui contient une variable nommée
application
contenant l’app WSGI. - Dire au serveur où se trouve le fichier
Avantages et inconvénients de WSGI
Parmi les avantages, on retrouve :
- Pas de parsing de la requête au niveau de l’application.
- Entrée de toutes les apps WSGI normalisées.
- Tous les logiciels WSGI sont compatibles entre eux.
- Le module WSGI reste chargé en mémoire une bonne fois pour toute, pas besoin de le recréer à chaque requête.
- WSGI se suffit à lui même pour écrire une application. Dans la théorie, on n’a même pas besoin de framework.
Quant aux inconvénients :
- En pratique il y a très peu de code partagé entre les applications WSGI. La communication sur WSGI s’est très mal passée et cela reste quelque chose de mystérieux pour la plupart des développeurs.
- WSGI ne gère pas les websockets, et la norme est en train d’être mise à jour pour cela. Mais cela prend du temps et les websockets sont déjà là, du coup les gens se tournent vers des solutions non WSGI (tornado, twisted, etc).
- Certains serveurs ne gèrent pas WSGI (comme nginx ou lighttpd), du coup on ne peut pas communiquer directement entre l’app et eux. Il faut un intermédiaire. C’est pour cela qu’on voit très souvent des setups comme nginx <=> gunicorn <=> django, avec un serveur WSGI qui sert d’intermédiaire. Il y a tout de même le bénéfice de pouvoir gérer parfaitement indépendamment le processus WSGI du serveur HTTP, ce qui est très pratique pour le debug, l’administration système, le déploiement, etc.
- WSGI, c’est du Python. Qui dit Python, dit import, qui dit import, dit PYTHON PATH. 90% des erreurs de mise en prod sont des erreurs d’import, et pour cette raison
sys.path
est presque toujours manipulé dans un fichier qui contient une application WSGI.
Pour la culture
Voici à quoi ressemble un hello world en WSGI :
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) yield 'Hello World\n'
Et oui, c’est très simple, et il n’y a rien à importer. La norme WSGI est une convention : il doit y avoir une variable nommée application
(ici la variable c’est le nom de la fonction), et cette variable doit contenir une application WSGI.
Mais une application WSGI, c’est juste un callable (ici une fonction) qui accepte en paramètres environ
(la requête), start_response
(une fonction qui écrit l’en-tête de la réponse) et qui retourne un générateur de string. C’est tout.
Il n’y a rien de magique. C’est une simple interface sur laquelle tout le monde s’est mis d’accord.
Quand on fait en Django:
application = get_wsgi_application()
En fait Django ne fait que fabriquer une fonction comme on vient de voir, qui derrière appelle votre application Django.
En revanche, comme on utilise souvent Django derrière nginx, le setup ressemble très souvent à ça :