On 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 :
Génial merci pour l’article ça me permet vraiment de mieux comprendre parce que c’est vrais que les explications sur ce domaine sont incompréhensible sinon pour pousser un peu, django parle aussi d’autre protocoles: https://docs.djangoproject.com/en/dev/howto/deployment/fastcgi/
Si tu as des infos clair comme pour wsgi ça m’intéresse MERCI
Oh, un article écrit rien que pour moi :P
Je sais, je l’ai déjà dit, mais vous êtes géniaux <3
Ceci dit il faut vraiment que je fasse un article de deploiment complet de django en production avec nginx, supervisor, gunicorn, et tout le bazar.
Wahou, je comprends pas comment vous faîtes, vous pondez toujours des articles au moment où j’en ai besoin!
D’ailleurs la semaine dernière je me demandais s’il était possible de faire tourner plusieurs instances d’une application CherryPy sur le même serveur WSGI.
Je voulais créer un Service Web REST et lancer plusieurs instance de l’application WSGI sur des chemins différents.
Ex:
– localhost:8080/app1/instance1
– localhost:8080/app1/instance2
Je n’ai pas réussi à le faire parce que les applications étaient crées au lancement du serveur.
Du coup CherryPy était un module partagé et global aux applications ce qui créait des conflits avec les pluggins et tools créés.
Finalement j’ai pour chaque instance je fais lance un serveur web + application sur un port différent, mais je sens que c’est un peu sale non ?
Oui, ça serait vraiment instructif, je pense. Django ou n’importe quel autre appli WSGI (bottle, flask…).
D’ailleurs, gunicorn ou uWSGI ? J’ai beau chercher, je comprend bien que l’approche est différente, mais je ne vois pas lequel est le plus performant. Sur mes premiers benchmarks, les deux sont équivalents. L’avantage de uWSGI c’est que nginx parle nativement uwsgi, et du coup on a pas à faire un proxy vers gunicorn.
Tu en penses quoi ?
Bonjour,
Merci pour vos articles.
Une précision (enfin je crois) : vous écrivez : “Apache a néanmoins besoin que le fichier s’appelle un_truc.wsgi et pas un_truc.py.”. Je ne crois pas.
Sur ma config, qui est assez ordinaire (Apache/2.2.22), mes apps, sont dans des fichiers d’extension .py. Et le fichier de config d’Apache contient des trucs comme :
WSGIScriptAlias /test /home/blabla/APPS/TEST/test.py
J’ai rien fait de spécial (enfin… je me souviens pas avoir fait qquechose de spécial :) ), et ça marche bien.
Les applis en question utilisent bottle ou cherrypy.
Bon, j’ai fumé alors. Au temps pour moi.
Finalement, c’est un peu comme CGI/FastCGI, spécialisé pour Python, non ?
Plutôt FastCGI dans le sens où il n’y a qu’une instance de l’appli pour tous les appels…
et c’est là qu’on est content d’avoir découvert ce blog pour la clairvoyance dans ses articles .
*kiff*
@Sam, du coup tu sais quoi faire dans la prochaine version de hyde ? git mv app.wsgi app.py ;)
@Krypted: il n’y a pas de routing intégré dans wsgi, mais un middleware maison peut le faire en quelques ligne. Exemple : http://mariz.org/blog/2007/06/08/simple-wsgi-middleware-dispatcher/
@maethor: j’utilise gunicorn car je le trouve plus facile à déployer, après je crois qu’à ce niveau de différence de perf, osef un peu.
@flon: oui mais la norme permet en plus de créer une application complète. On peut faire un site Web en Python en pure WSGI alors que les autres normes ne définissent que la transmission de données. Le but initial était de permettre le partage de code, notamment à travers les fameux middleware qui auraient pu s’occuper du routing, de l’authentification, etc. Dans la pratique, WSGI est utilisé exactement, et uniquement comme les normes précédentes, en plus performant.
@maethor: hyde ? Tu veux dire 0bin ?
Je suis justement entrain de me documenter un peu sur nginx, et j’avais mis de coté ce lien pour consultation ulterieure.
La derniere maj date de pres d’un an :( est ce pour ça que tu dis qu’il n’y a pas de module wsgi pour nginx?
Dans tous les cas, merci pour l’article :)
Top l’article ;-)
C’est vrai qu’on est un peux perdu au début dans ce domaine avec pas mal d’infos assez partiel et pas forcément d’actualité.
Pour ceux que ça intéresse des modèle de configs que je m’étais fait pour
– apache + passenger
https://github.com/cypx/trocr/blob/master/config-samples/apache_trocr.conf
https://github.com/cypx/trocr/blob/master/config-samples/passenger_wsgi_trocr.py
– nginx + uwsgi emperor
https://github.com/cypx/trocr/blob/master/config-samples/nginx_trocr.conf
https://github.com/cypx/trocr/blob/master/config-samples/uwsgi_trocr.ini
(trocr c’est le nom de l’application concerné mais ça fonctionnera tout aussi bien avec d’autres)
Attention à la configuration nginx par contre parce qu’elle utilise le cache, donc a adapter (ou supprimer) en fonction des éléments que vous ne voulez pas voir dans le cache nginx.
@LeMeteore: pas du tout. Je ne savais même pas que ça existait, tu me fais découvrir quelque chose. En fait, je n’avais jamais entendu parlé de quelqu’un qui utilisait quelque chose de similaire sur un site en PythoN.
@Sam, ouais, s/hyde/0bin :D
@Snarkturne, si tu as une directive WSGIScriptAlias dans la configuration d’Apache, alors tu utilises nécessairement le module mod_wsgi.
Pour ceux qui sont intéressé par le déploiement d’une application python avec le serveur Apache et le module mod_wsgi comme interface WSGI, le site du projet http://code.google.com/p/modwsgi/, et le blog du développeur où on peut trouver des infos complémentaires http://blog.dscpl.com.au.
Très bon article, le WSGI me paraît un peu moins mystique dorénavant. Mais, j’ai une question qui reste en suspens : quelle est la différence entre WSGI et CGI ?
WSGi ne fonctionne qu’avec Python, il charge le code en mémoire une bonne fois pour toute (contrairement à CGI), et il transforme tous les objets en objets Python (l’application WSGI n’a pas à faire le moindre parsing). L’application doit retourner un objet Python. Par ailleurs, WSGI d’écrire à lui seule des applications complètes, et bien que ce ne soit pas beaucoup utilisé, définie également des zones d’interfaçages supplémentaires comme les middlewares.
Attention à la configuration d’apache :
En prod dans mon ancien taf, apache était configuré (d’après un vague tuto pêcho sur le net) de tel manière que les taches WGSI n’étaient jamais redémarré mais les instances d’apache prenaient de plus en plus de mémoire jusqu’au OOM fatal (une erreur de conception de l’application n’était pas non plus à exclure)
Donc pensez à zieuter vos apache en WSGI :)
Pour commencer: Ce blog est tout simplement GENIAL ! Je vous suis depuis maintenant 1 an quand je venais de commencer en Python et c’est mon premier commentaire :D
Sinon, super article, comme d’hab ! D’ailleurs, il m’a assez inspiré (et je vous ai piqué une image) pour un article que je viens tout juste de publier sur WSGI (et 2 wrappers). Si vous voulez jeter un oeil => http://phndiaye.github.io/wsgi-kezako/
Ouaiiiii ! C’est la première fois qu’on nous pique une image !
Haha ! Un badge “Piqueur d’image” pour moi !
Pour un gros débutant dans le domaine du web en python comme moi, ce blog et cet article sont comme une pinte de bière bien fraîche offertes à un mec assoiffé ! J’avais jeté un œil a la pep wsgi mais n’avait rien capté. Merci encore pour tout ce que vous faites, vraiment des articles clairs qu’on ne trouve nulle part ailleurs !
une fois de plus, un article très clair (qui m’a fait comprendre WSGI). Bravo les gars…
J’ai quand même une question bête dont je crois deviner la réponse.
Dans la mesure ou Django vient avec son propre server pour balancer la sauce, pourquoi mettre le code dans Gunicorn ?
Question de perf j’imagine ?
Oui, le serveur de Django est conçu pour le dev. Gunicorn est conçu pour la mise en prod. Et encore, Gunicorn ne peut pas être en front, il faut mettre un truc genre nginx devant pour des raisons de sécurité.
Yep, je me promenais un peu et je suis tombé sur ton post. Très bon post, sauf que je suis gêné par un truc. Tu dis que WSGI demande à ce que ton application soit nommé “application”. Or, dans toutes les specs que j’ai lu, ce n’est pas ce qui est dit. La contrainte est simplement de faire un “callable” qui accepte deux paramètres: les variable d’environnement et la fonction “start_response”, et qui doit retourner un iterrable (la réponse au client).
D’ailleurs, regarde gunicorn (http://gunicorn.org/) l’exemple qu’ils donnent ne parle pas de “application”, il défini par contre que l’application est “main:app” (le callable “app” dans main.py). Non mais je sais que je suis chiant mais bon ce serait plus exact non ? hein dis hein ?
Le but de l’article n’est pas d’expliquer la spec, mais ce que ça fait. La plupart des serveurs WSGI cherchent par défaut un callable appelé “application” si on en précise pas un. La méthode pour préciser le nom du callable change d’un serveur à l’autre (mod_wsgi, gunicorn and uwsgi ont des syntaxes dissemblables). Pour simplifier l’explication, je vais donc simplement au plus court.
Merci beaucoup pour cet article. Même 2 ans après, ça reste l’explication la plus concrête que j’ai trouvé pour comprendre quels problèmes WSGI résoud, et comment il le fait. Continuez comme ça, vous êtes géniaux !