# La documentation de Webserv --- Github : [https://github.com/lobbyra/webserv](https://github.com/lobbyra/webserv) Miro : [https://miro.com/welcomeonboard/ltD8UP…](https://miro.com/welcomeonboard/ltD8UPsekNhA1L9WsxGPy3vCndZh9BXrFTPrwFmvTQlJbt27zbwJorcdTcBezT1f) **Ressources normes RFC :** [7230 : Message Syntax and Routing](https://tools.ietf.org/html/rfc7230) [7231 : Semantics and Content](https://tools.ietf.org/html/rfc7231) [7232 : Conditional Requests](https://tools.ietf.org/html/rfc7232) [7233 : Range Requests](https://tools.ietf.org/html/rfc7233) [7234 : Caching](https://tools.ietf.org/html/rfc7234) [7235 : Authentication](https://tools.ietf.org/html/rfc7235) [Socket Programming in C/C++ (Geeks For Geeks)](https://www.geeksforgeeks.org/socket-programming-cc/) [Nginx keywords](http://nginx.org/en/docs/http/ngx_http_core_module.html) Introduction : *Ce document a pour but de rassembler des explications et ressources utiles dans la bonne réalisation de ce projet de groupe.* # Quels besoins notre projet doit il remplir ? ## C'est quoi HTTP ? <p style='text-align: justify;'> HTTP est l'acronyme de Hypertext Transfer Protocol ou Protocol de transfert d'Hypertexte. C'est un protocole de communication informatique basé sur le modèle client-serveur. Ce protocole est le fondement du World Wide Web que l'on connaît aujourd'hui. Il permet une communication externe (Internet) ou interne (intranet) entre plusieurs ordinateurs. </p> <p style='text-align: justify;'> Ce protocole permet de transporter toutes sortes de données. Des pages web à sa création puis au transfert de fichier via FTP jusqu'à aujourd'hui où il permet le streaming de vidéo et de musique. Son développement s'est fait en parallèle des architecture réseau permettant toujours plus de débit et donc de contenu plus lourd. </p> ## C'est quoi un serveur HTTP ? Un serveur HTTP est un serveur informatique répondant à des requêtes sous la forme de relation client-serveur. Il peut prendre la forme d'un ordinateur (ex illustration première page) contenant un logiciel qui va répondre à des requêtes de protocole HTTP. Ce terme est utilisé majoritairement pour classifier ce genre de logiciel. Nginx et Apache sont des serveurs HTTP. Ici on parle de serveur HTTP sous la forme de logiciel. Il est pensé pour être disponible en permanence, à recevoir et répondre à des requêtes simultanément venant des clients. ## Pourquoi avons-nous créé HTTP au cours de l'histoire ? Ce protocole a été pensé pour créer le World Wide Web. Ce réseau a pour but de connecter et de communiquer avec des gens et des ressources du monde entier. Les premières institutions à mettre en place cette communication sont l'armée Américaine et les grandes universités du monde pour partager des études. ## Quelles sont les différences entre HTTP d'aujourd'hui et HTTP à sa création ? Le plus grand changement a été de créer une surcouche sécurisée créant nouveau protocole nommé HTTPS. Il permet de chiffrer les communications entre le client et le serveur pour protéger contre l'analyse des communications contenant des informations sensibles comme les mots de passe ou codes bancaires. Mais plus globalement c'est l'utilisation que l'on en fait qui a changé. Passé du simple échange de page HTML (comme ca) à du streaming de film en 4K. ## Apache et Nginx, c’est quoi du coup ? Ce sont les fameux logiciels cités plutôt qui sont la partie software d'un serveur HTTP. Ils attendent des requêtes de clients et peuvent y répondre simultanément. Apache et Nginx sont les deux logiciels les plus utilisés en tant que serveur HTTP. Apache partageant 55% de la globalité des sites sur internet mais Nginx est le plus utilisé dans les 1000 sites les plus actifs d'internet. En gros Nginx > Apache mais cet article est à lire pour saisir les différences. Il explique très bien l'historique et les différences techniques de ces deux logiciels. Ici, le but de ce projet est plutôt de se rapprocher d'Nginx. C'est pourquoi, pour les comportements non spécifiés dans le sujet, nous nous baserons sur le comportement d'Nginx. Il en est de même pour la manière de configurer le serveur, la syntaxe et les mot-clés d'Nginx seront utilisés. ## Grossièrement, comment se décompose un serveur web ? En premier lieu il y a le fichier de configuration qui va décrire sur quels ports écouter et comment réagir aux requêtes. Vous avez vu à peu près comment ce fichier se décompose dans l'article. Une fois ce fichier de configuration en mémoire, le programme va se mettre en pause dans l'attente d'une requête. Lorsqu'une requête est reçue, l'URL de cette requête va être comparée aux différents blocs de location pour déduire les paramètres à appliquer sur la gestion de cette requête. Enfin quand la requête est traité ou est en attente indépendamment du serveur web, retour au point de départ dans l'attente d'une nouvelle requête. # Réalisation du projet ## Documentation (Ce que vous êtes en train de lire) ## Preuves de concepts Cette partie n'est pas une application stricte de la notion de POC car ici il n'y a pas d'innovation. Mais dans tous les cas ces notions sont nouvelles, c'est pourquoi on parle de POC ici. Les preuves de concept sont des codes que nous allons faire pour essayer une notion que l'on ne connaît pas en montrant concrètement comment la notion en question fonctionne. Cela a pour but de servir au dev qui réalise la POC pour apprendre et synthétiser la nouvelle notion et d'expliquer en montrant une démonstration de la notion concrètement. Enfin les POC vont nous servir à avoir de l'avance dans la construction de la structure du code. Mieux savoir quand et comment on va implémenter la notion. Il y a cinq POC, voici leurs themes et leurs liens Github : - [Dossiers](https://github.com/lobbyra/webserv/tree/main/docs/poc/directories) : Les fonctions de gestions de dossiers. - [Mini client HTTP](https://github.com/lobbyra/webserv/tree/main/docs/poc/mini_client) : Réaliser un mini client pour découvrir les sockets et l'envoie de requêtes en C. - [Mini serveur HTTP](https://github.com/lobbyra/webserv/tree/main/docs/poc/mini_serv) : Réaliser un mini server pour découvrir comment attendre une requête sur un socket et comment y répondre. - [Fonction select\(\)](https://github.com/lobbyra/webserv/tree/main/docs/poc/select) : Découverte de la programmation asynchrone. - [strtime](https://github.com/lobbyra/webserv/tree/main/docs/poc/strtime) : Decouverte des fonctions de parsing du temps. (string vers mémoire et inversement) ## Découpage logique Structurer, coder, recommencer. Avancement progressif dans le code : - On essaye de voir quelles choses sont a faire en premier dans ce que nous avons pas fais. - On fais une structure logique ( [Miro](https://miro.com/) ) de ce que l'on veut coder. - On choisi qui va le coder. - On le code. - On recommence. # Notions abordées *Liste non exhaustive de toutes les notions abordées.* Ecrire de la documentation nous sert à nous auto-expliquer la notion que nous avons étudié. Cela sert aussi d’archive pour se remémorer une notion bien après l’avoir appliquée si jamais on a besoin de revenir dessus au cours du projet ou pour l’exam. Merci d'accompagner, le plus possible, ces explications de ressources utiles (Sites, Video YT, post Stack Overflow, etc..) ## Partie client ### Comment un programme fait une requête à un serveur HTTP ? Voici un exemple écrit StackOverflow expliqué d'une requête HTTP en C : ICI Un exemple vidéo expliqué par Jacob Sorber (<- Dieu vivant) : Vidéo De quoi est composée une requête HTTP ? Sources : [RFC_CLIENT_MESSAGING](https://tools.ietf.org/html/rfc7230#section-2.1), [RFC_MESSAGE_FORMAT](https://tools.ietf.org/html/rfc7230#section-3) Prenons cette illustration : ![](https://imgur.com/hzfHIrp.jpg) **Request line :** La request line comporte, dans l'ordre, les éléments suivants : GET : Méthode, nature de la requête. /doc/test.html : Chemin de la ressource à accéder. HTTP/1.1 : Version du protocole à utiliser dans la communication. **Request Headers :** La partie des headers n'applique pas d'obligations spécifiques en genre et en nombre. Il y a néanmoins deux type d'headers. Le header HTTP et les autres. Un header HTTP est un header connu et ayant une signification spécifique dans les normes du protocole. Un header non reconnu est simplement ignoré car il peut être utilisé dans le cadre d'échanges entre applications spécifiques. **Request message body ou payload :** La présence d'un body est signalée par la présence du header Content-Length ou Transfer-Encoding. Sa présence dépend aussi de la méthode de la requête. Cette troisième et dernière partie contient des données qui peuvent par exemple être des paramètres ou des identifiants dans la requête d'une ressource en particulier. Dans l'exemple ci-dessus, on peut voir que la requête contient l'identifiant d'un livre et son auteur. ### Quelles sont les types de requêtes à prendre en compte dans le cadre de ce projet ? Toutes les requêtes HTTP 1.1 (refs. RFC en 1ère page). Elles sont spécifiées plus loin dans ce document. ## Partie serveur ### Comment un programme attend une requête HTTP ? Exemple vidéo : [ICI](https://www.youtube.com/watch?v=esXw4bdaZkc&list=PL9IEJIKnBJjH_zM5LnovnoaKlXML5qh17&index=3) ### Comment on traite plusieurs requêtes de plus utilisateurs simultanément ? #### Intro / Ressources utiles Voici un cours d'initiation à cette nouvelle façon de programmer nommé asynchrone : [ICI](http://p-fb.net/fileadmin/_migrated/content_uploads/programmation_asynchrone_utf8.pdf) Un comparatif détaillé sur cette méthode dans le cas d'Nginx et Apache : [ICI](https://djangodeployment.com/2016/11/15/why-nginx-is-faster-than-apache-and-why-you-neednt-necessarily-care/) #### Approches techniques dans le cadre de notre projet Nous allons utiliser la fonction select pour gérer les appels comme des événements pour ne pas bloquer le programme qui attend que l'appel en question soit fini. ### De quoi est composée une réponse HTTP ? prenons cette illustration : ![](https://imgur.com/ocAyLPk.jpg) **Status Line :** La status line comporte, dans l'ordre, les éléments suivants : - HTTP/1.1 : HTTP version : Représente la version HTTP utilisée dans la communication. - 200 : Status code : C'est un élément de trois chiffres décrivant le résultat de la requête menant à cette réponse. - OK : reason-phrase : Ne délivre aucune information supplémentaire par rapport au status code. Sert juste de description textuelle au status code. **Responses Headers :** Similaire aux headers de requête, malgré que certains headers ne sont disponibles uniquement pour l'un ou l'autre. Vous trouverez plus loin la liste des headers avec leurs catégories. (General | Requête | Réponse) **Message Body :** Similaire à une requête. ## Relation client-serveur ### TCP / UDP, un lien avec ce projet ou transparent ? En reprenant ce bon vieu schéma TCP/IP : ![](https://imgur.com/sfwbbmA.jpg) Pour rappel TCP et UDP sont des protocoles de transport de données. La couche transport est complètement gérée par la fonction socket(), nous choisissons uniquement le protocole que socket va utiliser à la création de celui-ci (3ème argument de la fonction). ### Comment assurer la transmission client-server ? La gestion d'erreur des différents code de retour des appels systèmes devrait suffir à assurer la connexion et le bon comportement en cas de problèmes de communication. ### Negotiation client-server ## Méthode de requêtes Source : https://tools.ietf.org/html/rfc7231#section-4 **On peut classer certaines méthodes par catégories :** - Il y a les Safe Methods, ce sont les méthodes considérées comme sûres car elle ne demande pas de modifications à l'intérieur du serveur. On peut aussi dire qu'elles sont read-only. GET, HEAD, OPTIONS et TRACE sont des Safe Methods. - Les Idempotent Methods (def. idempotent) inclus les Safe Methods mais aussi les méthodes qui modifient le serveur en ayant le même résultat si elles sont appelées plusieurs fois de suite. - Enfin, les Cacheable Methods sont les méthodes qui peuvent être gardées en mémoire pour être renvoyées si jamais ces mêmes requêtes sont demandées plusieurs fois de suite. - GET ( section-4.3.1 ) Cette requête est celle que nous faisons le plus. C'est la requête qui va être faite pour demander un contenu à un serveur web. Par exemple, quand vous entrer http://google.com/ dans votre navigateur, celui-ci fait une requête GET / vers l'ip de google.com pour demander la page web principale ("/" pour la racine, root). Ainsi les serveurs de google vous répondent avec un code 200 OK avec en body la page web demandé. Enfin, votre navigateur affiche en HTML la page demandé et PAF vous avez Google. - HEAD ( section-4.3.2 ) Exactement la même fonction que GET. Sauf que HEAD n’envoie pas le contenu (body) de la requête demandé. Les headers liés au body (section-3.3) sont donc soit pas présents soit ignorés. Ce type de requête a deux utilités majeures. Premièrement, elle sert à tester la validité de l’accès à une ressource. Cela permet de tester si une requête GET est possible en réduisant la charge appliquée au serveur si celle-ci est valide. Aussi, si l’on a besoin uniquement de certaines métadonnées présentes dans les headers, la méthode HEAD s'y prête mieux. Par exemple, si on veut avoir un fichier volumineux en particulier tout le temps à jour. On peut faire une requête HEAD pour comparer la Content-Length ou la dernière date de modification pour re-télécharger ou non ce fichier. - POST ( section-4.3.3 ) Ce type de requête permet d'envoyer des données au serveur HTTP. Ce genre de requête n'a pas pour but de modifier le serveur mais ce sont plutôt des données en liaison avec un back-end ou une page HTML. Dans ces requêtes, on envoie généralement des formulaires HTML. - PUT ( section-4.3.4 ) Les requêtes PUT servent à modifier les fichiers locaux du serveur. En corp de requête, une ressource sera envoyé, à écrire au chemin fournis dans la requête. Cela peut être une image ou une page HTML par exemple. En cas de succès, la réponse à un PUT est soit un 201 Created si la ressource demandée a été créée ou 200 OK ou 204 si la ressource a été modifiée. En cas d'erreur, renvoie un code d'erreur. - DELETE ( section-4.3.5 ) Cette méthode est un équivalent de la commande UNIX rm. Elle permet de demander la suppression d'une ressource au chemin indiqué. - CONNECT ( section-4.3.6 ) Cette méthode sert à créer un tunnel TCP entre deux machines. Dans la requête, le client spécifie une machine de destination à laquelle il veut faire le pont. Ensuite toutes les données que le client enverra seront directement retransmises au destinataire via le serveur HTTP. Par exemple, on peut avoir un serveur HTTP de proxy qui va donner accès à plusieurs machines dans un réseau privé. Celui-ci va s'occuper de faire l'authentification puis de connecter le client avec la bonne machine. - OPTIONS ( section-4.3.7 ) Cette méthode sert à demander au serveur distant des informations concernant la façon de communiquer. Dans le cadre de notre projet. Cette méthode se concentre surtout sur le Header Allow. A la suite d'une requête OPTIONS Webserv va répondre avec le Header Allow en indiquant quelles méthodes un client peut faire sur celui-ci. - TRACE Toujours la même réponse. Nous nous basons sur Nginx et son comportement est de répondre 405 (not allowed) dans tout les cas. Ceci n'est pas configurable, depuis une certaine version c'est *hardcodé*. ## Types de headers Source : https://tools.ietf.org/html/rfc7231#section-5 **General Headers :** Date **Request Headers :** - Accept-Charset - Accept-Language - Authorization - Content-Language - Host - Referer - User-Agent **Response Headers :** - Allow - Content-Length - Content-Location - Content-Type - Last-Modified - Location - Retry-After - Server - Transfer-Encoding - WWW-Authenticate ## Types de réponses *Ce qui est demandé ici est une très courte description des codes d'erreurs.* Source : https://tools.ietf.org/html/rfc7231#section-6 1xx (Informational) link : |code | reason-phrase |description| |:----|:----------------|:---------:| |100 |Continue | | |101 |Switching Protols| | 2xx (Successful) link : |code | reason-phrase |description| |:----|:----------------------------|:---------:| |200 |OK | | |201 |Created | | |202 |Accepted | | |203 |Non-Authoritative Information| | |204 |No Content | | |205 |Reset Content | | |206 |Partial Content | | 3xx (Redirection) link : |code | reason-phrase |description| |:----|:----------------------------|:---------:| |301 |Moved Permanently | | |301 |Moved Permanently | | |302 |Found | | |303 |See Other | | |304 |Not Modified | | |305 |Use Proxy | | |307 |Temporary Redirect | | 4xx (Client Error) link : | code | reason-phrase | description | |:---- |:----------------------------- |:-----------:| | 400 | Bad Request | | | 401 | Unauthorized | | | 402 | Bad Request | | | 403 | Forbidden | | | 404 | Not Found | | | 405 | Method Not Allowed | | | 406 | Not Acceptable | | | 407 | Proxy Authentication Required | | | 408 | Request Timeout | | | 409 | Conflict | | | 410 | Gone | | | 411 | Length Required | | | 412 | Precondition Failed | | | 413 | Payload Too Large | | | 414 | URI Too Long | | | 415 | Unsupported Media Type | | | 416 | Range Not Satisfiable | | | 417 | Expectation Failed | | | 426 | Upgrade Required | | |code | reason-phrase |description| |:----|:----------------------------|:---------:| |500 |Internal Server Error | | |501 |Not Implemented | | |502 |Bad Gateway | | |503 |Service Unavailable | | |504 |Gateway Timeout | | |505 |HTTP Version Not Supported | | ## Fichier de configuration **Ressources :** - listen : [link](http://nginx.org/en/docs/http/request_processing.html) - server_name : [link](http://nginx.org/en/docs/http/server_names.html) - global : [link](https://www.digitalocean.com/community/tutorials/understanding-the-nginx-configuration-file-structure-and-configuration-contexts) - autoindex : [link](https://www.keycdn.com/support/nginx-directory-index#:~:text=By%20default%2C%20Nginx%20tries%20to,is%20if%20it%20has%20permissions).) Visiblement, nous n'avons que le contexte de server {} à implémenter dans le serveur. Chemin de test de comparaison avec nginx : /etc/nginx/conf.d/default.conf ## Les mots clés à implémenter : - listen - server_name - error_page - client_max_body_size - root - index - autoindex - location (location n'étant pas vraiment un mot clé de cl) - autoindex - index - fastcgi_param - fastcgi_pass |mot-clé | value type | |:----:|:-------:| |listen|std::string| |server_name|std::list\<std::string\>| |error_page|std::map<int, std::string>| |client_max_body_size|int (en octet)| |root|std::string| |autoindex|std::string ("on"|"off")| |index|std::list\<std::string\>| |fastcgi_param|std::map<std::string, std::string>| ## CGI **C'est quoi CGI?** CGI est une interface permettant de déléguer la distribution de la ressource demandée par le client. C'est un processus enfant que Nginx va créer en lançant ce binaire CGI. Un exemple de CGI est php-fpm qui permet de distribuer des pages HTML générées par un moteur php. PHP étant un langage que le serveur web n'interprète pas. Cette couche d'abstraction permet donc d'appeler des moteurs de génération de page de n'importe quel langage tant qu'il y a cette interface CGI à exécuter. Nginx va donc lancer une instance du binaire CGI, envoyer la requête au CGI demandé et écouter la réponse du logiciel en question puis la délivrer au client. **C'est quoi la différence entre CGI et FastCGI ?** Le fastCGI apporte majoritairement le dynamisme des pages. Mais la différence est flou entre les deux. **Comment un CGI se matérialise-t-il en code ?** C'est un fichier binaire qui va être exécuté en processus fils. Ce processus fils aura pour entrée et sortie STDIN et STDOUT respectivement. Ce processus prendra en variable d'environnement les paramètres que vous voulez lui affecter. **C'est quoi les Méta-variables et comment les gérer ?** Ce sont les paramètres avec lesquels vous allez affecter votre binaire CGI. **Doit-on envoyer les headers en entrée du CGI ou juste le body ?** AUTH_TYPE CONTENT_LENGTH CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO PATH_TRANSLATED QUERY_STRING REMOTE_ADDR REMOTE_IDENT REMOTE_USER REQUEST_METHOD REQUEST_URI SCRIPT_NAME SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE | Param | Config ou Requête |description| |:---------------:|:-----------------:|:---------:| | AUTH_TYPE | requête || |CONTENT_LENGTH | requête || |CONTENT_TYPE | requête || |GATEWAY_INTERFACE| requête || |PATH_INFO | requête || |PATH_TRANSLATED | requête || |QUERY_STRING | requête || |REMOTE_ADDR | requête || |REMOTE_INDENT | requête || |REMOTE_USER | requête || |REQUEST_METHOD | requête || |REQUEST_URI | requête || |SCRIPT_NAME | requête || |SERVER_NAME | requête || |SERVER_PORT | requête || |SERVER_PROTOCOL | requête || |SERVER_SOFTWARE | requête || # Organisation de groupe ## Communication & Structure **Communication** Nous communiquerons via Discord qui permet facilement de séparer les discussions par sujet, de pouvoir partager son écran et d’épingler des messages plus importants que d’autres. **Structure** Nous réaliserons la structure logique du code sur le logiciel Miro qui est un logiciel de mindmap collaboratif a l’instar de google docs. Voici une illustration du rendu à partir d'un autre projet : ## Code ### Github Nous utilisons Github comme dépôt et support de travail. Nous allons travailler chacun sur une branche personnelle. Nous mergerons au fur et à mesure du projet l'avancement de nos parties de code. La branche principale, nommé Main, devra respecter les conditions suivantes : - Compiler avec -Wall -Wextra -Werror -std=c++98. - Être en accord avec le format de cpplint. - Être testé avec -fsanitize=address. - Ne pas avoir de fuites. - Contenir uniquement du code utile et de commentaire censé. (CaD pas de log.txt ou .o en trop) ### Noms de fichiers **Extensions :** Les fichiers source ne comprenant pas de préprocesseurs seront suffixé de .cpp. Les fichiers ne contenant uniquement du préprocesseur seront suffixé de .hpp. **Noms :** Tout en minuscule et séparé par des underscores. Voici un exemple : ![](https://imgur.com/bxb1sNQ.jpg) On a la catégorie de code "redirectors", donc ce sur quoi le code source porte puis un sous groupe divisant pour organiser le code et avoir des code sources organisés et pas trop long. ### Compilation utilisée Ce projet sera compilé et compilable sur linux et macOS par le compilateur clang++ accompagné des flags -Wall -Wextra -Werror -std=c++98. Nous ferons des tests plus précis avec le flags -fsanitize=address pour les heap-overflow, stack-overflow et autre. Nous inspecterons les fuites de mémoire à l'aide de Valgrind (linux) et de Leaks (macOS). # Formatage du code Notes diverses des conventions de nommages ou de structure de codes. Nombre maximal de caractères par ligne : **80** *Nous savons que ce n'est pas forcément le meilleurs choix mais dans le cas où cette condition est demandée dans un projet futur, il est important de savoir faire du bon code dans cette restriction et donc de se creuser la tête pour trouver de bonnes syntaxes.* Préfixage des types de données personnalisées : typedef -> t_ ( exemple : t_location ) struct -> s_ ( exemple : s_location ) class -> c_ ( exemple : c_location ) Pointeur sur fonction f_ (exemple f_parser) Utilisation des attributs privés de nos classes : Prefixage par _ ( exemple -> au lien de this->name -> _name ) Les fonctions doivent (quand c'est possible) se prefixer par une action. Exemples : - Une fonction qui va chercher une donnée peut se prefixer par get_. (get_name()) - Une fonction qui retourne un booléen peut se prefixer par is_. (is_odd())