Serveurs ASGI & utilisation d’Hypercorn

Pour développer ou déployer une application web asynchrone en Python, on a besoin d’un serveur ASGI. Les principales options sont Uvicorn avec Gunicorn, Daphne, Hypercorn et Granian. Après un tour d’horizon de ces différents serveurs, j’explique comment j’utilise Hypercorn.

Contexte : ASGI vs WSGI

WSGI (Web Server Gateway Interface) est une interface synchrone pour les applications web en Python (qui utilisent par exemple Flask, ou Django sans les channels). WSGI a un modèle bloquant, chaque requête monopolise un thread et doit être entièrement traitée avant que le thread puisse traiter la suivante. Ça entraîne des délais lors des requêtes vers une base de données, ou d’autres appels à des services externes.

ASGI (Asynchronous Server Gateway Interface) est une interface asynchrone, conçue pour pallier les limitations de WSGI. ASGI permet d’utiliser la syntaxe async/await, et c’est une interface qui est particulièrement adaptée pour gérer des connexions persistantes (comme les WebSockets).

Ce n’est pas le rôle du framework ASGI (FastAPI, Starlette directement, Quart, etc) de gérer les connexions réseau. Pour ça, on utilise un serveur ASGI.

Tour d’horizon

1. Uvicorn avec Gunicorn

Uvicorn est un serveur ASGI minimaliste et très rapide, conçu pour ASGI. Il s’appuie sur uvloop (un event loop très performant) et httptools (un parser HTTP bas niveau). Par défaut, il affiche un log digeste sur sa sortie standard, ce qui est un bonus notable pour le développement.

Par contre, la gestion des processus n’est pas son fort. Pour améliorer ce point, il est couramment utilisé en combinaison avec Gunicorn (un serveur WSGI historique).

2. Daphne

Daphne a été développé pour servir les channels Django. Comme il n’a pas de support pour uvloop, il est un peu moins performant qu’Uvicorn et Hypercorn. À noter que c’est le premier serveur ASGI à avoir été publié.

3. Hypercorn

Hypercorn est inspiré de Gunicorn, mais dès le départ il a été conçu pour ASGI. Il faisait initialement partie du framework Quart, avant d’être séparé pour devenir un projet autonome. Il supporte uvloop, mais aussi asyncio et trio. Il peut également servir des applications WSGI.

4. Granian

Granian est écrit en Rust (Hyper + Tokio), avec des bindings Python. Il peut servir des applications ASGI ou WSGI. Correctement configuré, Granian est très rapide, mais un paramétrage fin peut être nécessaire. Certaines applications peuvent ne pas profiter de toutes les optimisations de Granian, à cause de problèmes de compatibilité avec leurs dépendances.

Utilisation d’Hypercorn

J’ai choisi Hypercorn, parce qu’avec un seul serveur je peux gérer ASGI et WSGI, avec HTTP/2 et TLS intégrés. Mais je vais garder un œil sur Granian, car le benchmark publié par ses devs montre des performances très prometteuses.

Comme c’est un service qui sera accessible publiquement, je préfère utiliser le paquet Debian.

# apt install python3-hypercorn

Vous pouvez regarder ce tutoriel pour mettre en place des certificats Let’s Encrypt sur un système Debian.

Il n’y a pas de raison de faire tourner Hypercorn en root, donc on on lui crée un utilisateur dédié. Il faut donner à cet utilisateur les droits sur les dossiers du certificat, et sur le dossier des logs.

# adduser hypercorn
# adduser hypercorn certifs
# chgrp -R certifs /etc/letsencrypt/live/domain.tld
# chmod -R g+r /etc/letsencrypt/live/domain.tld
# chgrp -R certifs /etc/letsencrypt/archive/domain.tld
# chmod -R g+r /etc/letsencrypt/archive/domain.tld
# mkdir /var/log/hypercorn
# chown hypercorn:hypercorn /var/log/hypercorn

Il faut aussi gérer les droits après les renouvellements automatiques du certificat Let’s Encrypt. Éditez /etc/crontab pour ajouter cette ligne, en choisissant un horaire où le serveur est généralement peu chargé :

M H     3 * *   root    /usr/local/bin/certif_hypercorn.sh

Créez le fichier /usr/local/bin/certif_hypercorn.sh :

#!/bin/sh

chgrp -R certifs /etc/letsencrypt/archive/domain.tld
chmod -R g+r /etc/letsencrypt/archive/domain.tld
killall -9 hypercorn
sleep 5
/usr/local/boot.d/hypercorn.sh

Pour faire vos premiers tests, lancez cette commande :

% hypercorn -k uvloop -w 4 --bind LAN_IP:60444 --certfile /path/to/cert.pem --keyfile /path/to/key.pem --access-logfile - --reload main:app

Une fois que tout marche, il faut configurer nginx (par exemple), en créant un site qui servira de proxy inverse vers localhost:60444. Créez le fichier /etc/nginx/sites-available/domain.tld :

server {
    server_name domain.tld;
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/domain.tld/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/domain.tld/privkey.pem;

    # Serve static files directly
    location /static/ {
        alias /home/bot/harmonia/harmonia/display_history/static/;
        access_log off;
        expires 30d;
        add_header Cache-Control "public";
    }
    location /attachments/ {
        alias /home/bot/images/test_images/;
        access_log off;
        expires 30d;
        add_header Cache-Control "public";
    }

    # Everything else goes to Hypercorn
    location / {
        include proxy_params;
        proxy_pass https://127.0.0.1:60444;
    }
}

server {
    server_name domain.tld;
    listen 80;
    return 302 https://domain.tld/$request_uri;
}

On active ce nouveau site et on relance nginx :

# cd /etc/nginx/sites-enabled
# ln -s ../sites-available/domain.tld
# systemctl reload nginx.service

Ensuite, on crée une unité SystemD, afin qu’Hypercorn démarre en même temps que le système. Créez le fichier /etc/systemd/system/hypercorn.service :

[Unit]
Requires=network-online.target
After=network-online.target

[Install]
WantedBy=multi-user.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/boot.d/hypercorn.sh

Créez le fichier /usr/local/boot.d/hypercorn.sh :

#!/bin/sh

su -l hypercorn -c "cd /path/to/harmonia/display_history && hypercorn -k uvloop -w 4 --bind localhost:60444 --certfile /path/to/cert.pem --keyfile /path/to/key.pem --access-logfile /var/log/hypercorn/domain-access.log --error-logfile /var/log/hypercorn/domain-error.log Main:Display_history"

On donne les droits d’exécution aux deux scripts :

# chmod 744 /usr/local/boot.d/hypercorn.sh
# chmod 744 /usr/local/bin/certif_hypercorn.sh

Et pour finir, on active l’unité qu’on vient de créer :

systemctl enable hypercorn.service

Laisser un commentaire