Construire un Framework Web Minimaliste en Python

Un framework web agit comme un intermédiaire entre l'interface utilisateur et la base de données. Dans les applications web courantes, une seule page d'accueil n'est pas suffisante ; des URL avec des chemins différents permettent d'accéder à des sections distinctes de l'application.

Implémentation avec des Sockets

On peut utiliser un serveur socket pour écouter les requêtes HTTP. En analysant la première ligne de la requête, on extrait le chemin demandé pour servir le contenu approprié.

Exemple de requêtes comparées :


# Page d'accueil : http://127.0.0.1:8080
GET / HTTP/1.1
Host: 127.0.0.1:8080
Accept-Language: fr-FR

# Chemin /index : http://127.0.0.1:8080/index
GET /index HTTP/1.1
Host: 127.0.0.1:8080
Accept-Language: fr-FR

Le serveur socket traite les connexions et envoie des réponses HTTP basées sur le chemin extrait.


import socket

serveur_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serveur_tcp.bind(('localhost', 8080))
serveur_tcp.listen(10)

while True:
    connexion_client, info_client = serveur_tcp.accept()
    donnees_brutes = connexion_client.recv(2048)
    connexion_client.send(b'HTTP/1.1 200 OK\r\n\r\n')
    
    texte_requete = donnees_brutes.decode('utf-8')
    chemin_demande = texte_requete.split(' ')[1]
    
    if chemin_demande == '/index':
        with open('template_index.html', 'rb') as fichier_html:
            contenu = fichier_html.read()
            connexion_client.send(contenu)
    elif chemin_demande == '/login':
        connexion_client.send(b'Page de connexion')
    else:
        connexion_client.send(b'Bienvenue sur le site')

Cette approche manque de modularité et devient difficile à maintenir avec de nombreuses routes.

Optimisation avec le Module wsgiref

Le module intégré wsgiref simplifie la création de serveurs WSGI. Il encapsule les opérations socket et structure les données de requête dans un dictionnaire.

Fonctionnalités clés :

Élément Description
request Dictionnaire contenant les données de requête HTTP.
response Fonction pour configurer les en-têtes de réponse.
make_server(ip, port, app) Crée un serveur HTTP qui appelle la fonction app pour chaque requête.
.serve_forever() Démarre l'écoute du serveur.

Implémentation de base :


from wsgiref.simple_server import make_server

def application(environ, start_response):
    """Fonction WSGI pour traiter les requêtes."""
    start_response('200 OK', [('Content-Type', 'text/html')])
    chemin = environ.get('PATH_INFO')
    
    if chemin == '/index':
        return [b'Contenu de la page index']
    elif chemin == '/login':
        return [b'Contenu de la page login']
    return [b'Page par d\u00e9faut']

if __name__ == '__main__':
    serveur_wsgi = make_server('localhost', 8080, application)
    serveur_wsgi.serve_forever()

Pour éviter les conditions if-elif répétitives, on externalise les routes et les templates.

Structure Modulaire

On sépare la logique en fichiers distincts : urls.py pour les routes, templates/ pour les fichiers HTML, et app.py pour le code principal.


# app.py
from wsgiref.simple_server import make_server
import os
from urls import ROUTES

DOSSIER_TEMPLATES = os.path.join(os.path.dirname(__file__), 'templates')

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    chemin = environ.get('PATH_INFO', '/')
    
    fichier_template = 'defaut.html'
    for route, template in ROUTES:
        if route == chemin:
            fichier_template = template
            break
    
    chemin_complet = os.path.join(DOSSIER_TEMPLATES, fichier_template)
    with open(chemin_complet, 'rb') as f:
        return [f.read()]

if __name__ == '__main__':
    serveur = make_server('localhost', 8080, application)
    serveur.serve_forever()

# urls.py
ROUTES = [
    ('/index', 'index.html'),
    ('/login', 'login.html'),
]

Les fichiers HTML dans le dossier templates contiennent le contenu statique.

Pages Statiques vs Dynamiques

Les pages statiques ont un contenu fixe, tandis que les pages dynamiques génèrent du HTML à partir de données côté serveur, souvent issues d'une base de données.

Intégration de Données avec Jinja2

Jinja2 permet d'injecter des données Python dans les templates HTML, facilitant la création de pages dynamiques.

Fonction Rôle
Template() Charge un template depuis une chaîne de caractères.
render() Passe des variables au template pour le rendu.
{{ }} Dans le template, insère des expressions Python.

Exemple d'utilisation :


# app.py avec Jinja2
from wsgiref.simple_server import make_server
from jinja2 import Template
import os

def generer_page(modele, contexte):
    """Génère le HTML final avec Jinja2."""
    template = Template(modele)
    return template.render(contexte).encode('utf-8')

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    chemin = environ.get('PATH_INFO')
    
    donnees_utilisateur = {'nom': 'Alice', 'role': 'admin'}
    liste_elements = [10, 20, 30]
    
    chemin_template = os.path.join('templates', 'exemple.html')
    with open(chemin_template, 'r', encoding='utf-8') as f:
        contenu_modele = f.read()
    
    page_generee = generer_page(contenu_modele, {
        'utilisateur': donnees_utilisateur,
        'elements': liste_elements
    })
    return [page_generee]

if __name__ == '__main__':
    serveur = make_server('localhost', 8080, application)
    serveur.serve_forever()



<html>
<head>
    <title>Page Dynamique</title>
</head>
<body>
    <h1>Informations Utilisateur</h1>
    <p>Nom : {{ utilisateur.nom }}</p>
    <p>Rôle : {{ utilisateur.role }}</p>
    <h2>Liste des Éléments</h2>
    <ul>
        {% for item in elements %}
            <li>Élément : {{ item }}</li>
        {% endfor %}
    </ul>
</body>
</html>

Étiquettes: Socket HTTP wsgiref Python jinja2

Publié le 10 juin à 04h09