Ce projet est un serveur HTTP Ă©crit en C++98 (pour LINUX). Ce serveur est compatible avec le protocole HTTP/1.1.
â ïž PrĂ©requis: Fonctionne uniquement sur Linux. Vous devez avoirmake
etg++
installés sur votre machine.
- Clonez le dépÎt avec
git clone https://github.com/lxup/42Cursus-webserv.git webserv
- Accédez au dépÎt avec
cd webserv
- Exécutez
make && ./webserv config/good/webserv.conf
- Ouvrez un navigateur et allez sur
http://localhost:3434
ou utilisez curl http://localhost:3434
Vous pouvez tester de modifier le fichier de configuration, en modifiant par exemple les ports d'ecoute ou les fichiers a servir.
L'objectif est de comprendre et d'implĂ©menter les fonctionnalitĂ©s de base d'un serveur HTTP, de la rĂ©ception de requĂȘtes Ă la rĂ©ponse appropriĂ©e, en passant par la gestion des diffĂ©rents codes de statut HTTP. Il doit etre NON BLOQUANT
(en gros on doit pouvoir servir plusieurs clients en meme temps).
đ Qu'est-ce que HTTP?
HTTP (Hypertext Transfer Protocol) est le protocole utilisé pour la communication entre un client (navigateur web) et un serveur.En gros quand tu te connectes a youtube.com, tu demandes plein de fichiers a un server avec des requete et le server te les renvoit
- Programmation RĂ©seau via Sockets en C đ„
- The Method to epoll's Madness đ„
- Tutoriel VidĂ©o les mĂ©thodes HTTP đ„
- Analyser le fichier de configuration pour déterminer les blocs de serveurs et leurs directives.
- Créer une structure de données pour stocker les informations du fichier de configuration.
Sur ce schéma, on peut voir un exemple de fichier de configuration.
Le server va Ă©couter sur le port 80.
Le root du serveur est le dossier /www/site/.
Par défaut, le serveur va servir le fichier index.html.
- Créer un socket pour écouter les connexions entrantes. (listen socket)
- Créer une instance epoll pour surveiller les sockets.
- Ajouter le socket d'Ă©coute Ă l'instance epoll.
- Attendre les événements d'entrée/sortie sur les sockets. (Avec epoll_wait())
Sur ce schéma, on peut voir un exemple de fichier de configuration.
Le server va Ă©couter sur le port 80.
Le root du serveur est le dossier /www/site/.
Par défaut, le serveur va servir le fichier index.html.
Sur ce schema, on peut voir le serveur qui ecoute sur le port 80 et qui attend des connexions entrantes. Le listener socket est ajoute a la Pool de epoll et a le numero 3.
- Accepter une nouvelle connexion entrante.
- Ajouter le nouveau socket Ă l'instance epoll.
- Attendre les événements d'entrée/sortie sur les sockets. (Avec epoll_wait())
Sur ce schema, on peut voir les 3 etape de l'ajout d'un nouveau client.
1- Le client se connecte au serveur sur le listener socket
2- Le serveur accepte la connexion et cree un nouveau socket pour le client
3- Le nouveau socket est ajoute a la pool de epoll et le serveur attend des evenements sur ce socket (mais aussi toujours le listener socket)
Derniere etape (mashalla tro bo le schema)
- Un socket de connexion est present entre le client et le serveur.
Sur ce schema, les 5 etape de la gestion de requete sont present.
1- Le client envoie une requete au serveur sur le socket de connexion (le socket 4)
2- Le serveur recoit la requete et comme il s'agit d'un client deja connu, il parse la requete
3- Le serveur traite la requete, verifiant la validite de la requete, si c'est une requete chunk, un cgi, ... puis il genere une reponse
4- Le genere la reponse en recherchant le fichier demander, en executant un cgi, ...
5- Le serveur envoie la reponse au client sur le socket de connexion
Youpii c'est fini, le client a recu sa reponse et le serveur attend une nouvelle requete.
Le webserver doit ĂȘtre capable de gĂ©rer plusieurs blocs de configuration, chacun correspondant Ă un serveur virtuel. Chaque bloc de configuration contient des directives spĂ©cifiques qui dĂ©finissent le comportement du serveur.
Le fichier config.conf montre que Webserv écoute sur 3 ports différents: 80, 1313 et 9090.
Pour chaque requĂȘte entrante, Webserv doit dĂ©terminer quel bloc de configuration utiliser. Voici quelques exemples :
http://jul.com:80
â Bloc Bleu đŠ (servername et port correspondants)http://bob.com:1313
â Bloc Rouge đ„http://existepas.com:1313
â Bloc Vert đ©(bloc par dĂ©faut)http://nul.com:4321
â Pas de rĂ©ponse âŹïž (port non Ă©coutĂ©)
Pour repertorier tout ces blocs server
, on a utiliser une map avec comme cle les couple ip:port et comme valeur un vecteur de blocs server.
â Fichier de Configuration du Serveur Web
Ceci est la documentation et les rÚgles pour le fichier de configuration du serveur web. Ce sont des rÚgles grandement inspirées de nginx. Nous avons adapté quelques rÚgles à notre convenance.
- Les lignes commençant par
#
sont des commentaires. Les commentaires doivent ĂȘtre sur une ligne sĂ©parĂ©e et ne peuvent pas ĂȘtre mĂ©langĂ©s avec une directive. - Il est interdit d'avoir deux blocs
location
avec le mĂȘme chemin (path
) dans un blocserver
. - Un bloc
server
peut contenir plusieursserver_name
et plusieurslisten
(ip:port
). - Deux blocs
server
ne peuvent pas avoir le mĂȘmeserver_name
. - Deux blocs
server
peuvent partager le mĂȘmelisten
(ip:port
).
server {
...
location {
...
}
...
}
Le tableau ci-dessous résume les directives disponibles dans le fichier de configuration, y compris leur duplicabilité, le nombre de paramÚtres autorisés, et leurs valeurs par défaut.
Directive | Bloc | Duplication | Nb ParamĂštres | Valeur par DĂ©faut | Description | Exemple |
---|---|---|---|---|---|---|
server |
N/A | DUP | 0 | none | DĂ©finit un bloc de configuration pour un serveur web virtuel. | server { ... } |
location |
server |
DUP | 1 | none | Définit un bloc de configuration pour une URL spécifique. | location / { ... } |
listen |
server |
DUP | 1 | ip: 0.0.0.0 port: 80 |
DĂ©finit l'adresse IP et le port sur lequel le serveur web doit Ă©couter les requĂȘtes. | listen 80; , listen 127.0.0.1:8080; |
server_name |
server |
DUP | -1 | localhost |
Définit le(s) nom(s) de domaine (host) sur lequel le serveur web doit répondre. | server_name louis.com; |
error_page |
server |
DUP | -1 | /var/www/error.html |
Définit les pages d'erreur personnalisées. La syntaxe est stricte : error_page CODE /path/to/file; . |
error_page 404 /404.html; |
root |
server /location |
NODUP | 1 | /var/www/html |
DĂ©finit le rĂ©pertoire racine du serveur web (ou du bloc location). Dans un bloc location , cette directive surcharge celle du bloc server et ne peut pas ĂȘtre utilisĂ©e avec alias . |
root /var/www/html; |
index |
server /location |
DUP | -1 | index.html |
Définit les fichiers index par défaut (ou du bloc location). Dans un bloc location , cette directive surcharge celle du bloc server . |
index index.html index.htm; |
client_max_body_size |
server |
NODUP | 1 | 1 (Mo) |
Définit la taille maximale des données que le serveur web peut recevoir (en Mo). | client_max_body_size 10; |
alias |
location |
NODUP | 1 | none | DĂ©finit un alias pour un rĂ©pertoire. Ne peut pas ĂȘtre utilisĂ© avec root . |
alias /var/www/images/; |
return |
location |
DUP | 2 | none | Définit une rÚgle de réécriture d'URL. | return 301 https://github.com/toukoum; |
autoindex |
location |
NODUP | 1 | off |
Active ou désactive l'indexation automatique des répertoires. Ne doit pas coexister avec la directive index dans le bloc location . |
autoindex on; |
allow_methods |
location |
NODUP | 0..3 | GET DELETE POST |
Définit les méthodes HTTP autorisées. | allow_methods GET DELETE POST; |
cgi_extension |
location |
DUP | 2 | none | Définit l'extension qui sera mappée à un script CGI. | cgi_extension .php /var/www/cgi-bin/php-cgi; |
upload_path |
location |
NODUP | 1 | /var/www/upload |
Définit le répertoire de destination des fichiers uploadés. | upload_path /var/www/images; |
server {
listen 80;
server_name louis.com;
root /var/www/html;
index index.html index.htm;
error_page 404 /404.html;
location / {
root /var/www/html;
index index.html index.htm;
}
location /upload {
allow_methods POST;
upload_path /var/www/upload;
cgi_extension .php /var/www/cgi-bin/php-cgi;
}
}
â Server HTTP Ultra simplifie
Ces 50 lignes de code permettent de creer un serveur HTTP qui repond a une requete avec un message HTML. Attention, rien est protege dans ce code, il permet juste de montrer comment fonctionne les fonctions les plus importantes d'un serveur HTTP.Pour essayer ce code:
- Rendez vous dans le dossier
simpleServer
- Compilez le code avec
g++ -o server simpleServer.cpp
- Lancez le serveur avec
./server
- Ouvrez un navigateur et allez sur
http://localhost:1234
/ sinon vous pouvez utilisercurl http://localhost:1234
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 1234
#define BUFFER_SIZE 4096
int server()
{
int fdSocket = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port = htons(PORT);
address.sin_addr.s_addr = htonl(INADDR_ANY);
bind(fdSocket, (const sockaddr *)(&address), sizeof(address));
listen(fdSocket, 10);
bool active = true;
int connection;
while (active)
{
unsigned long resultLen = sizeof(sockaddr);
std::cout << "Listening on Port: " << PORT << std::endl;
connection = accept(fdSocket, (struct sockaddr *)(&address), (socklen_t *)&resultLen);
char buffer[BUFFER_SIZE];
ssize_t bytesRead = read(connection, buffer, BUFFER_SIZE);
std::cout << "Le message fait: " << bytesRead << " characteres" << std::endl;
std::cout << buffer << std::endl;
std::string content = "<h1>Bonjour, je suis un serveur HTTP tout simple!</h1>";
std::string response = "HTTP/1.1 200 OK\nContent-Type: text/html\nContent-Length: " + std::to_string(content.length()) + "\n\n" + content;
send(connection, response.c_str(), response.size(), 0);
close(connection);
}
close(fdSocket);
return (EXIT_SUCCESS);
}
int main()
{
server();
return 0;
}
Ce code permet de comprendre les principales fonctions d'un serveur HTTP. Il crĂ©e un serveur qui Ă©coute sur le port 1234 et renvoie un message HTML simple Ă chaque requĂȘte.
Il utilise notamment les fonction socket
, bind
, listen
, accept
, read
et send
pour gérer les connexions entrantes et sortantes.
â Structure d'une RequĂȘte HTTP
Une requĂȘte HTTP permet au client de demander une ressource au serveur.
-
Ligne de RequĂȘte
- Méthode: Action à réaliser sur le serveur (GET, POST, DELETE, etc.)
- URI: Adresse de la ressource demandée sur le serveur.
- Version HTTP: Version du protocole HTTP utilisée (HTTP/1.1, HTTP/1.0).
Exemple:
GET /img/logo.jpg HTTP/1.0
-
En-tĂȘtes de la RequĂȘte
Paires clĂ©-valeur fournissant des informations sur la requĂȘte ou le client.Exemple:
Host: abc.com Accept: text/html Cookie: _ga=GA1.2.407.167011
-
Corps de la RequĂȘte (pour POST et PUT uniquement)
Contient les données que le client souhaite transmettre au serveur.Exemple:
name=John+Doe&age=30&city=New+York
au format query string
â Structure d'une RĂ©ponse HTTP
La rĂ©ponse HTTP est ce que le serveur renvoie aprĂšs avoir reçu une requĂȘte.
-
Ligne de Statut
- Version HTTP
- Code de Statut: Exemples courants :
200 OK
: RequĂȘte traitĂ©e avec succĂšs.404 Not Found
: Ressource introuvable.500 Internal Server Error
: Erreur interne du serveur.
- Message: Phrase associée au code de statut.
Exemple:
HTTP/1.1 200 OK
-
En-tĂȘtes de RĂ©ponse
Paires clé-valeur fournissant des informations sur la réponse ou le serveur.Exemple:
Content-Encoding: gzip Content-Length: 342 Date: Sat, 08 Jan 2022 10:52:28 GMT
-
Corps de la RĂ©ponse
Contient la rĂ©ponse elle-mĂȘme, telle que la page HTML demandĂ©e.<html> <h1> Ceci est un page html </h1> </html>
â EPOLL fonction()
Pour notre Webserv, nous avons choisie dâutiliser la fonction epoll(). Les autres choix qui s'offrait a nous etait select() et poll() mais epoll() est de loin la plus performante (voir le medium que j'ai link en haut de la page).
đĄ fonction pour surveiller les des fd (sockets) afin de savoir quand les operation de Input/Output peuvent etre effectuees sans bloquer le programme.
-
Comment lâutiliser ?
int epoll_create(int size); // pour creer une instance et retourne le // fd associe int epoll_create1(int flags); // flags utile comme EPOLL_CLOEXEC // qui ferme automatiquement le descripteur de epoll lors de l'execution d'un processus enfant, new version de epoll_create
Une fois lâinstance creer, on peut ajouter, modifier ou supprimer des descripteurs a surveiller
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // epfd : Le descripteur de fichier de l'instance epoll. // op : L'opĂ©ration Ă effectuer (EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTL_DEL). // fd : Le descripteur de fichier Ă surveiller. // event : Une structure epoll_event qui spĂ©cifie les Ă©vĂ©nements Ă surveiller, comme EPOLLIN (donnĂ©es disponibles en lecture), EPOLLOUT (prĂȘt Ă Ă©crire), etc.
la structure dâevent a ecouter se defini comme cela:
struct epoll_event ev; ev.events = EPOLLIN; // ou EPOLLOUT out ev.data.fd = server_fd; epoll_ctl(epfd, EPOLL_CTL_ADD, server_fd, &ev);
-
Flag dâevenements
EPOLLIN â ****quand un fd est pret pour la lecture (ya des truc a lire sur le socket)
EPOLLOUT â quand un fd est pret pour lâecriture (en gros tu peux envoyer ta requete http sans que lâappel de send() soit bloquant)
EPOLLERR â quand un fd a une erreur (ex: connection reset)
EPOLLHUP â quand un fd est ferme par lâautre cote (ex: le client ferme son navigateur)
nous pour webserv on va utiliser la combinaison de EPOLLIN + EPOLLOUT
Pour recevoir une ânotificationâ quand un evenement arrive:
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout); // evlist : Un tableau pour stocker les Ă©vĂ©nements prĂȘts. // maxevents : Le nombre maximal d'Ă©vĂ©nements Ă traiter. // timeout : Temps en millisecondes pour bloquer l'attente.
epoll_wait bloquera jusqu'à ce qu'un événement se produise ou jusqu'à ce que le délai expire. Si timeout est -1, il attend indéfiniment.
-
â RequĂȘtes Chunked et DĂ©limitation
Les requĂȘtes chunked permettent d'envoyer des donnĂ©es en plusieurs morceaux de taille variable.
Fonctionnement des RequĂȘtes Chunked
Chaque chunk suit le format : `[taille du chunk en hexadĂ©cimal]\r\n[donnĂ©es du chunk]\r\n`. La fin de la transmission est indiquĂ©e par `0\r\n\r\n`. Exemple de chunk: ```http 4\r\nWiki\r\n 5\r\npedia\r\n 0\r\n\r\n ```Une requĂȘte HTTP standard se termine soit :
- Par une ligne vide aprĂšs les en-tĂȘtes (si aucun corps n'est prĂ©sent).
- Par la réception de l'intégralité des données spécifiées par
Content-Length
. - Par le chunk de fin (pour les requĂȘtes chunked).
â Codes de Statut HTTP
Les codes de statut HTTP indiquent le rĂ©sultat d'une requĂȘte HTTP.
- đ” 1xx : Informational - RequĂȘte reçue, traitement en cours.
- đą 2xx : Success - RequĂȘte reçue, comprise et acceptĂ©e avec succĂšs.
- đĄ 3xx : Redirect - Une action supplĂ©mentaire doit ĂȘtre effectuĂ©e pour complĂ©ter la requĂȘte.
- đŽ 4xx : Client Error - La requĂȘte contient une erreur qui empĂȘche le serveur de la traiter.
- â«ïž 5xx : Server Error - Le serveur a Ă©chouĂ© Ă traiter une requĂȘte apparemment valide.