Ouvrir la navigation secondaire
Précédent Utiliser cssnext vite fait bien fait
Suivant Docker, Django et le Mac

Déploiement d'une application web Python

Plusieurs solutions se sont succédées au fil du temps pour déployer du code Python pour le web et ça fait un bout de temps que je voulais mettre mes notes sur ce sujet à plat.

Alors c'est parti pour le show un petit tour d'horizon.

CGI - The Common Gateway Interface

CGI (The Common Gateway Interface) est une interface permettant à un serveur web de déléguer les requêtes HTTP à des applications.

Les informations reçues dans une requête HTTP par un serveur web sont passées à un programme via des variables d'environnement et l'entrée standard (STDIN), et les données en sortie du programme sont passées via la sortie standard (STDOUT). Les tubes sont utilisés pour l'IPC.

Petit clin d'œil à Perl pour son histoire fascinante dans l'apparition du web dynamique :)

La simplicité de CGI est neutralisée par ses nombreux désavantages.

Un problème de fond concerne la performance : pour chaque requête HTTP, il faut fourcher un nouveau processus, ce qui signifie dans le cas d'un langage dynamique la charge supplémentaire d'avoir à lancer aussi à chaque fois un interpréteur, une connexion à une base de donnée etc.

Même si CGI c'est le passé pour les sites à fort trafic, c'est important de comprendre comment ça fonctionne car tout ce qui a suivi en est inspiré :

  • le serveur web crée des variables d'environnement à partir des informations de la requête
  • le serveur web invoque un programme CGI avec cet environnement
  • le serveur web passe le corps de la requête au programme via STDIN
  • le programme se base sur les variables d'environnement et sur l'entrée standard pour savoir quoi faire
  • le programme retourne le statut et les en-têtes HTTP suivis d'un séparateur et du corps de la réponse via STDOUT
  • le serveur web réceptionne les informations et retourne une réponse HTTP

mod_python

mod_python de Gregory Trubetskoy a permis entre autres choses d'intégrer l'interpréteur Python à l'intérieur même d'Apache pour tacler le problème de performance de CGI.

Il fut donné à la fondation Apache en 2002, puis abandonné en 2010. Il y a eu un petit sursaut en 2013 et des commits en 2017.

Le fait d'être complètement lié à un serveur web spécifique est devenu un désagrément quand Apache a commencé à perdre la cote face à des nouveaux challengers comme Nginx.

On lui trouve aussi d'autres désavantages sérieux.

SCGI et FastCGI

SCGI et FastCGI sont des variantes de l'interface CGI.

Ce sont des protocoles qui règlent le problème de performance de CGI en maintenant des daemons (des processus persistants en arrière-plan).

Les requêtes HTTP sont transférées par le serveur web aux daemons par IPC via des sockets Unix ou TCP/IP, ce qui permet aussi de pouvoir placer les applications n'importe où sur le réseau.

Avec FastCGI par exemple, un pool de daemons (des worker processes) qui embarquent l'interpréteur Python est créé et maintenu par un serveur FastCGI. Quand le serveur web a besoin de traiter une requête, il se connecte au serveur FastCGI via une socket. Le serveur FastCGI délègue ensuite la requête à un worker process libre. La réponse est renvoyée au serveur web via la même socket.

SCGI a été élaboré par la communauté Python pour être plus simple a implémenter que FastCGI.

WSGI (The Web Server Gateway Interface)

WSGI (2003 et 2010) est une interface inspirée de CGI et spécifique à Python qui décrit comment un serveur web peut communiquer avec une application Python.

WSGI propose une solution au problème d'interopérabilité rencontré par les frameworks Python de l'époque qui devaient fournir une flopée d'adaptateurs pour utiliser les différents protocoles (CGI, mod_python, FastCGI, SCGI etc.).

Techniquement, l'interface WSGI comprend 2 parties : le serveur (ou passerelle), et l'application (ou framework). Une application WSGI doit fournir en guise de point d'entrée un callable qui prend deux arguments (un dictionnaire environ et une fonction start_response). Ensuite ce callable doit, dans son propre corps, invoquer la fonction start_response (avec en paramètre un statut et des en-têtes HTTP), puis retourner un iterable contenant le body de la réponse. Enfin, c'est le serveur WSGI qui invoque le callable de l'application WSGI une fois pour chaque requête HTTP. Il est possible d'ajouter là dessus un ou plusieurs middlewares.

Vous pouvez avoir un aperçu ici et du fonctionnement de l'interface.

Il nous faut donc un serveur WSGI capable de transformer la requête HTTP. Parfois c'est le serveur web lui même qui fait le travail (avec mod_wsgi par exemple), mais la plupart du temps on ajoute un serveur WSGI indépendant dans la pile. Le serveur web fait alors office de proxy et transmet la requête HTTP au serveur WSGI. Les protocoles de communication derrière le serveur WSGI pour parler avec l'application dépendent de la solution retenue. Le plus souvent on se retrouve avec un modèle similaire à l'architecture FastCGI/SCGI : un pool de worker processes appartenant au serveur WSGI et embarquant l'interpréteur Python.

Petit résumé pour bien sentir l'inspiration CGI :

  • le server WSGI crée un dictionnaire environ à partir des informations de la requête
  • le server WSGI invoque l'application WSGI en lui passant environ et un callable
  • l'application WSGI se base sur environ pour savoir quoi faire
  • l'application WSGI invoque le callable avec le statut et les en-têtes HTTP
  • l'application WSGI produit (elle yield) le corps de la réponse
  • le serveur WSGI réceptionne les informations et retourne une réponse HTTP

Les critiques et l'après WSGI

Le design de la fonction start_response de WSGI est souvent un motif de plainte.

Le fait que WSGI soit tellement inspiré de CGI fait parfois passer le développement web Python pour une antiquité mais il faut bien assurer la rétrocompatibilité :

WSGI's curious insistence on compatibility with CGI also means that [...] the Python web-development world still hasn't been able to significantly improve on 1997's application programming model.

Le plus gros problème de WSGI est d'être fortement lié au protocole HTTP et à son modèle de requête/réponse. Difficile de l'utiliser avec d'autres protocoles.

Pendant longtemps, ceux qui voulaient faire des WebSockets utilisaient d'autres solutions, comme socket.io dans une boucle Node.js à côté d'une stack Django avec un pont en REST pour communiquer.

Du coup, Django Channels a développé ASGI, un successeur spirituel de WSGI qui supporte d'autres protocoles.

Les nouveaux frameworks web Python asynchrones se basent sur asyncio avec un déploiement qui me semble plus simple, à la Node.js.

Ces deux derniers sujets sont très intéressants mais je ne les connais pas encore assez pour en parler correctement.

En attendant, on sait dérouler du Django teenager et le faire scaler :)