Systèmes2025TerminéDépôt privé
/* ************************************************************************** */
/*                                                                            */
/*                                                        :::      ::::::::   */
/*   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       */
/*                                                                            */
/* ************************************************************************** */
Created : ouverture du dépôt · Updated : dernier commit

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.

C++98Sockets BSDpoll()HTTP/1.1
GET · POST · DELETE
méthodes
poll()
multiplexage
13
codes gérés
Comment ça marche

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
  1. 01Ouvrir

    Une socket TCP, SO_REUSEADDR pour pouvoir relancer le serveur sans attendre la fin du TIME_WAIT, puis O_NONBLOCK.

  2. 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

  3. 03Attendre

    poll() sur tout le tableau de descripteurs, sans délai maximum. C'est le seul point du programme qui dort.

  4. 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.

  5. 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.
En bref
  • 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
Le point dur

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.