/* ************************************************************************** */ /* */ /* ::: :::::::: */ /* Server.cpp :+: :+: :+: */ /* +:+ +:+ +:+ */ /* By: lkantzer <lkantzer@student.42.fr> +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2025/12/12 13:29:50 by lkantzer #+# #+# */ /* Updated: 2025/12/15 09:27:52 by lkantzer ### ########.fr */ /* */ /* ************************************************************************** */
Webserv
Écrire le serveur HTTP au lieu de l'installer
Un serveur HTTP/1.1 en C++98, sans bibliothèque : socket non bloquante, boucle poll(), requête analysée à la main et réponse construite octet par octet.
- GET · POST · DELETE
- méthodes
- poll()
- multiplexage
- 13
- codes gérés
La boucle du serveur
Un serveur, c'est six appels système dans le bon ordre — et un seul d'entre eux a le droit d'attendre.
socket → setsockopt(SO_REUSEADDR) → fcntl(O_NONBLOCK) → bind → listen → poll
- 01Ouvrir
Une socket TCP, SO_REUSEADDR pour pouvoir relancer le serveur sans attendre la fin du TIME_WAIT, puis O_NONBLOCK.
- 02Attacher
bind sur l'hôte et le port de la configuration, puis listen avec une file d'attente de dix connexions.127.0.0.1:8080
- 03Attendre
poll() sur tout le tableau de descripteurs, sans délai maximum. C'est le seul point du programme qui dort.
- 04Accepter
Quand c'est la socket d'écoute qui est prête, accept rend un client, qu'on passe lui aussi en non bloquant et qu'on ajoute au tableau.
- 05Servir
Quand c'est un client, on lit sa requête, on l'analyse, on renvoie la réponse, puis on ferme et on le retire du tableau.4 096 octets lus
Un seul poll() pour toutes les connexions : rien dans le programme n'attend jamais un client en particulier.
Ce que le serveur lit et écrit
HTTP est un protocole en texte : tout tient dans un découpage de chaîne, et c'est justement ce que le sujet interdit de déléguer.
GET /index.html HTTP/1.1\r\n Host: localhost\r\n \r\n
- Ligne de requête
- Méthode, URI et version, séparées par des espaces. Ce qui ne se lit pas en trois mots est une requête invalide, et vaut un 400.
- En-têtes
- Une clé, deux points, une valeur — jusqu'à la ligne vide. Espaces de tête et retour chariot final retirés, le reste rangé dans une map.
- Corps
- Tout ce qui suit la ligne vide, gardé tel quel en attendant la limite de taille annoncée par la configuration.
- Réponse
- Ligne de statut, en-têtes, Content-Length calculé sur le corps, ligne vide, corps. Dans cet ordre, et avec des .
La configuration, telle qu'elle est écrite
Le fichier suit la grammaire de NGINX : c'est lui qui décide ce que le serveur écoute, sert et refuse.
- server
- Un bloc par serveur virtuel : port d'écoute, hôte, noms, racine des fichiers et page d'index.
- client_max_body_size
- La taille maximale d'un corps de requête, au-delà de laquelle la réponse est un 413.1 048 576 octets
- error_page
- Une page à servir par code d'erreur, plutôt que le message par défaut du serveur.
- location
- Un chemin, les méthodes qu'on y autorise, et selon les cas l'autoindex, la redirection, le dossier d'upload ou l'extension CGI.
- 01Socket d'écoute et clients passés en O_NONBLOCK, une seule boucle poll() pour tout le monde
- 02Requête découpée à la main : ligne de requête, en-têtes, corps
- 03Treize codes de statut rendus avec leur message, du 200 au 505
- 04Fichier de configuration à la NGINX : serveurs, locations, méthodes autorisées
Un serveur ne s'écrit pas comme un programme
Rien ne bloque jamais : ni accept, ni recv, ni send. Tout passe par un seul poll() qui rend la main sur le premier descripteur prêt, et c'est cette inversion qui commande toute l'architecture.