Vitrine
Bannière + journal d'accès sous trafic réel (visuel hero)

06 — Programme système

localhost

Le web par le bas : un serveur HTTP en Rust, piloté comme nginx.

2026 · Cursus Zone01

Contexte

Serveur HTTP/1.1 from scratch (epoll, CGI, uploads, hôtes virtuels) avec une CLI d'administration : check, journal d'accès, statistiques et tableau de bord en direct.

RôleCœur epoll et routeur écrits pendant le cursus ; refonte de la CLI d'administration en novembre 2026.

Ce que j’ai construit

  • localhost run : bannière de démarrage (version, config validée, pid, sockets d'écoute, hôtes virtuels), journal d'accès coloré une ligne par requête (heure, méthode, statut, chemin, taille, durée mesurée, client, hôte), événements CGI (pid, code de sortie, durée), expirations
  • Ligne de statistiques périodique (débit, taux de succès, p95, connexions actives) et arrêt propre sur Ctrl+C / SIGTERM avec bilan de session
  • localhost check : équivalent de nginx -t (syntaxe, résumé des blocs et routes, avertissements, erreurs bloquantes)
  • localhost status [--watch] : tableau de bord en direct (débit en sparkline, répartition des statuts, percentiles, méthodes, transferts, CGI) lu sur /__localhost/status (JSON réservé aux clients locaux)
  • Page d'accueil servie avec statistiques en direct, listing de dossier (tailles, dates, échappement HTML), pages 404/500
  • Tout est réel : les chiffres viennent des compteurs de la boucle d'événements ; scripts/traffic.py génère du vrai trafic pour les démos

Défis techniques

  • CGI bloqué : environnement sans PATH (le shebang #!/usr/bin/env python3 échouait) et EPOLLHUP ignoré → la connexion restait pendue jusqu'au timeout. Désormais : CGI GET/POST fonctionnels, 502 si le script échoue, processus tué si le client expire (0 zombie)
  • Corps tronqués : la requête était traitée dès réception des en-têtes, sans attendre Content-Length ni la fin du chunked → uploads et POST aléatoirement incomplets (400). Ajout de 100 Continue
  • Uploads binaires corrompus : le multipart passait par une conversion texte. Analyse désormais au niveau des octets (fichiers identiques au bit près, vérifié)
  • Hôtes virtuels : deux blocs server sur le même host:port faisaient échouer le démarrage ; sélection désormais par l'en-tête Host. Un port déclaré deux fois dans un même bloc est refusé à la validation
182
tests Rust
24/24
tests d'audit
p50 ≈ 50 µs
1
seule dépendance

Qualité et tests

  • Suite d'audit tests/run_tests.py : 24/24 (le test CGI passe maintenant réellement : « executed → 200 OK » au lieu de « -1 »)
  • 182 tests Rust : 158 tests unitaires répartis sur 10 modules et 24 tests de bout en bout (le vrai binaire, démarré sur un port libre et interrogé en HTTP brut), dont les cas limites (corps fragmentés, chunked, 100-continue, multipart binaire et assainissement du nom, validation de config, statistiques, lecture du JSON de statut)
  • Trafic réel : ~650 requêtes mixtes sur 30 s, 0 erreur serveur hors cas volontaires, 0 processus zombie ; latence p50 ≈ 50 µs (fichiers statiques)

Stack

Rust (édition 2021) · libc (epoll, sockets, fork/execve, signaux) · Python (scripts CGI, générateur de trafic)

Galerie

Liens