From c9f07482d17ee39ed234494a0ceddfebe4abd410 Mon Sep 17 00:00:00 2001 From: Jyzdcs Date: Mon, 15 Jun 2026 15:43:03 +0200 Subject: [PATCH 1/7] feat: implement core server components including Client, PollManager, Socket, and Server classes - Added Client class to manage TCP connection state and data handling. - Introduced PollManager for managing file descriptors and polling events. - Created Socket class for encapsulating server socket operations. - Developed Server class to orchestrate connections and handle client interactions. - Updated README with usage instructions and project structure. - Removed unnecessary .gitignore entries in the server directory. --- include/server/.gitignore | 4 - include/server/Client.hpp | 166 +++++++++++++++++++++++++++++ include/server/PollManger.hpp | 101 ++++++++++++++++++ include/server/README.md | 44 ++++++++ include/server/Server.hpp | 140 ++++++++++++++++++++++++ include/server/Socket.hpp | 82 ++++++++++++++ include/server/server-checklist.md | 70 ++++++++++++ src/server/.gitignore | 4 - 8 files changed, 603 insertions(+), 8 deletions(-) create mode 100644 include/server/Client.hpp create mode 100644 include/server/PollManger.hpp create mode 100644 include/server/README.md create mode 100644 include/server/Server.hpp create mode 100644 include/server/Socket.hpp create mode 100644 include/server/server-checklist.md diff --git a/include/server/.gitignore b/include/server/.gitignore index 5e7d273..e69de29 100644 --- a/include/server/.gitignore +++ b/include/server/.gitignore @@ -1,4 +0,0 @@ -# Ignore everything in this directory -* -# Except this file -!.gitignore diff --git a/include/server/Client.hpp b/include/server/Client.hpp new file mode 100644 index 0000000..9a1b323 --- /dev/null +++ b/include/server/Client.hpp @@ -0,0 +1,166 @@ +#ifndef CLIENT_HPP +#define CLIENT_HPP + +#include + +/* +** Client +** ------ +** Goal: Représenter l'état d'UNE connexion TCP active. C'est l'objet +** central qui fait le pont entre Core Server et HTTP Layer. +** +** Cette classe ne fait AUCUN appel système réseau elle-même (pas de +** read/write directement ici idéalement — voir note ci-dessous). +** Elle stocke juste l'état : ce qu'on a reçu, ce qu'on doit envoyer, +** et où on en est dans le cycle de vie de la requête. +** +** C'est l'objet que Server manipule à chaque tour de boucle : +** - Server fait read() → range les octets dans client.read_buffer +** - HTTP Layer lit client.read_buffer → remplit client.write_buffer +** - Server fait write() → vide client.write_buffer +** +** Note: selon ton design, tu peux mettre les appels read()/write() +** directement dans des méthodes de Client (receiveData/sendData) — +** c'est acceptable et même conseillé pour garder Server léger. +** Le prototype ci-dessous part sur cette option. +*/ + +// État du cycle de vie d'une requête — utile pour savoir +// "où on en est" sans tout recalculer à chaque tour de poll(). +enum ClientState +{ + READING_REQUEST, // on accumule read_buffer, requête pas encore complète + PROCESSING, // requête complète, HTTP Layer doit la traiter + SENDING_RESPONSE, // write_buffer prêt, on envoie au client + CGI_RUNNING, // en attente de la sortie d'un processus CGI + DONE, // réponse envoyée — fermer ou attendre keep-alive + CLOSING // erreur ou déconnexion — à nettoyer +}; + +class Client +{ + public: + /* + ** Goal: Initialiser un client à partir du fd retourné par accept(). + ** server_port permet de retrouver le bon ServerConfig (multi-port) + ** sans avoir à le chercher à chaque requête. + */ + Client(int fd, int server_port); + ~Client(); + + // --- Accès au fd --- + + /* + ** Goal: Retourne le fd de ce client. + ** Utilisé par Server pour PollManager::isReadable(getFd()) etc. + */ + int getFd() const; + + // --- Lecture (déclenché par PollManager::isReadable) --- + + /* + ** Goal: Appelle read() sur le fd et accumule dans read_buffer. + ** Retourne : + ** > 0 : nombre d'octets lus (normal, continuer) + ** == 0 : le client a fermé la connexion (EOF) → state = CLOSING + ** < 0 : erreur read() → state = CLOSING (ne PAS check errno + ** pour décider du comportement HTTP, juste fermer proprement) + ** + ** Après chaque appel réussi, vérifie isRequestComplete() et passe + ** state à PROCESSING si c'est le cas. + */ + int receiveData(); + + /* + ** Goal: Vérifie si read_buffer contient une requête HTTP complète. + ** - Sans body : présence de "\r\n\r\n" + ** - Avec body : présence de "\r\n\r\n" ET + ** read_buffer.size() - header_end >= Content-Length + ** + ** Cette logique est dupliquée/déléguée avec RequestParser — + ** à toi de choisir : soit Client appelle un helper de + ** RequestParser::isComplete(), soit Client a sa propre version + ** légère. Recommandation : déléguer à RequestParser pour éviter + ** la duplication de logique de parsing. + */ + bool isRequestComplete() const; + + // --- Écriture (déclenché par PollManager::isWritable) --- + + /* + ** Goal: Appelle write() avec write_buffer et avance _write_offset. + ** Gère le "partial write" : write() peut envoyer moins d'octets + ** que demandé, il NE FAUT PAS considérer ça comme une erreur. + ** + ** Retourne : + ** > 0 : octets envoyés (continuer au prochain poll si incomplet) + ** == 0 : rien envoyé (peu probable si POLLOUT était set, mais + ** ne pas crasher) + ** < 0 : erreur → state = CLOSING + ** + ** Quand _write_offset == write_buffer.size() → state = DONE, + ** et PollManager doit repasser ce fd en POLLIN seulement + ** (ou removeFd si pas de keep-alive). + */ + int sendData(); + + /* + ** Goal: Indique si write_buffer a été entièrement envoyé. + ** Utilisé par Server pour savoir quand basculer + ** updateEvents(fd, POLLIN) (retirer POLLOUT). + */ + bool isResponseFullySent() const; + + // --- État et accesseurs --- + + ClientState getState() const; + void setState(ClientState state); + + /* + ** Goal: Accès en lecture/écriture au buffer de requête brut. + ** L'HTTP Layer (RequestParser) lit ce buffer pour produire + ** un HttpRequest. Après parsing réussi, le buffer est vidé + ** (clear) ou avancé (pour gérer le pipelining keep-alive — + ** optionnel selon votre ambition). + */ + std::string& getReadBuffer(); + + /* + ** Goal: Définit la réponse complète à envoyer. + ** Appelé par HTTP Layer (ResponseBuilder) une fois la réponse + ** assemblée. Remet _write_offset à 0 et state = SENDING_RESPONSE. + */ + void setWriteBuffer(const std::string& response); + + /* + ** Goal: Port du serveur sur lequel ce client s'est connecté. + ** Permet au Router de retrouver le bon ServerConfig + ** (cas multi-port avec configs différentes). + */ + int getServerPort() const; + + /* + ** Goal: Timestamp de la dernière activité (read ou write réussi). + ** Utilisé par Server pour détecter et fermer les connexions + ** inactives (anti "hang indéfiniment", règle du sujet). + */ + long getLastActivity() const; + void updateLastActivity(); + + private: + int _fd; + int _server_port; + ClientState _state; + + std::string _read_buffer; + std::string _write_buffer; + size_t _write_offset; + + long _last_activity; + + // Non copyable : un Client possède un fd unique + Client(const Client& other); + Client& operator=(const Client& other); +}; + +#endif \ No newline at end of file diff --git a/include/server/PollManger.hpp b/include/server/PollManger.hpp new file mode 100644 index 0000000..5f7fa53 --- /dev/null +++ b/include/server/PollManger.hpp @@ -0,0 +1,101 @@ +#ifndef POLLMANAGER_HPP +#define POLLMANAGER_HPP + +#include +#include + +/* +** PollManager +** ----------- +** Goal: Centraliser la liste des file descriptors surveillés par poll() +** et exposer une interface simple pour les ajouter/retirer/interroger. +** +** Cette classe NE SAIT PAS ce qu'est un client HTTP, un CGI ou un socket +** d'écoute. Elle manipule uniquement des entiers (fd) et des flags +** (POLLIN, POLLOUT). C'est la frontière la plus basse du serveur. +** +** Règle absolue : un seul poll() pour tout le programme. Cette classe +** EST ce poll() unique. +*/ +class PollManager +{ + private: + std::vector _fds; + + /* + ** Goal: Trouver l'index dans _fds correspondant à un fd donné. + ** Retourne -1 si non trouvé. + ** Interne uniquement — utilisé par updateEvents/removeFd/isReadable... + */ + int findIndex(int fd) const; + + // Non copyable : PollManager possède une ressource unique (la liste fds) + PollManager(const PollManager& other); + PollManager& operator=(const PollManager& other); + + public: + PollManager(); + ~PollManager(); + + /* + ** Goal: Ajouter un fd à la liste surveillée par poll(). + ** events = POLLIN, POLLOUT, ou POLLIN | POLLOUT + ** Appelé quand : un nouveau client se connecte (accept), + ** un CGI démarre (pipe), etc. + */ + void addFd(int fd, short events); + + /* + ** Goal: Retirer un fd de la liste. + ** Appelé quand : un client se déconnecte, un CGI a fini, + ** une erreur fatale sur ce fd. + ** Important : NE FAIT PAS le close() du fd. C'est la responsabilité + ** de l'appelant (Server/Client) de fermer le fd lui-même. + */ + void removeFd(int fd); + + /* + ** Goal: Modifier les events surveillés pour un fd existant. + ** Cas d'usage typique : un Client a fini de lire (POLLIN suffit) + ** puis a une réponse à envoyer (passer à POLLIN | POLLOUT). + ** Si fd non trouvé : ne fait rien (no-op silencieux). + */ + void updateEvents(int fd, short events); + + /* + ** Goal: Appeler poll() sur la liste actuelle. + ** timeout en millisecondes (ex: 1000 = 1s). Utiliser un timeout + ** fini (pas -1) pour pouvoir détecter les clients inactifs + ** (cf. gestion des timeouts côté Server). + ** Retourne : nombre de fds prêts (>= 0), ou -1 en cas d'erreur + ** (errno EINTR à gérer : ne pas crasher, juste re-looper). + */ + int poll(int timeout_ms); + + /* + ** Goal: Après un poll() réussi, indique si ce fd est prêt en lecture. + ** Utilisé par Server pour décider d'appeler read() sur ce fd. + */ + bool isReadable(int fd) const; + + /* + ** Goal: Idem mais pour l'écriture (POLLOUT déclenché). + ** Utilisé par Server pour décider d'appeler write() sur ce fd. + */ + bool isWritable(int fd) const; + + /* + ** Goal: Détecte une erreur/hangup sur ce fd (POLLHUP, POLLERR, POLLNVAL). + ** Si true, le fd doit être nettoyé (close + removeFd) sans tenter + ** read/write supplémentaire. + */ + bool hasError(int fd) const; + + /* + ** Goal: Exposer la liste complète des fds actuellement surveillés. + ** Utile pour itérer dans Server::run() après chaque poll(). + */ + const std::vector& getFds() const; +}; + +#endif \ No newline at end of file diff --git a/include/server/README.md b/include/server/README.md new file mode 100644 index 0000000..1d27fe6 --- /dev/null +++ b/include/server/README.md @@ -0,0 +1,44 @@ +## Comment lire ces fichiers + +**PollManager** — la brique la plus basse. Encapsule `poll()` et le +tableau de `pollfd`. Tu codes ça en premier, et tu peux le tester tout +seul avec un petit `main()` qui ajoute `stdin` (fd 0) et vérifie que +`isReadable(0)` devient vrai quand tu tapes au clavier. + +**Socket** — un wrapper sur `socket`/`bind`/`listen`/`accept`. Une +instance par port à écouter. Le commentaire détaille l'ordre exact des +syscalls et pourquoi (notamment `SO_REUSEADDR` et `O_NONBLOCK`, qui sont +des pièges classiques si oubliés). + +**Client** — c'est le cœur de ton travail. C'est l'objet qui porte +l'état d'une connexion. J'ai volontairement mis `receiveData()` et +`sendData()` dans cette classe (et pas dans `Server`) pour que la +logique de "partial read/write" et de buffers soit localisée à un seul +endroit — c'est là que tu vas passer le plus de temps et où les edge +cases (EOF, partial write, requête incomplète) se concentrent. + +**Server** — orchestrateur. La boucle `run()` est décrite en +pseudo-code dans le commentaire — c'est le squelette que tu dois +remplir. Les 4 handlers privés (`handleNewConnection`, +`handleClientRead`, `handleClientWrite`, `closeClient`) sont les seuls +endroits où tu touches aux fds. + +## Core server order + +1. **`src/server/PollManager.cpp`** + Implémente les 8 méthodes. + Teste avec `tests/test_poll_manager.cpp` + (ajoute `stdin`, vérifie `isReadable(0)`). + +2. **`src/server/Socket.cpp`** + Implémente le constructeur + (les 5 étapes du commentaire) + et `acceptConnection()`. + +3. **`src/server/Client.cpp`** + `receiveData()` et `sendData()` sont les plus importants. + Commence par une version simple, + ajoute le _partial write_ ensuite. + +4. **`src/server/Server.cpp`** + Remplis `run()` en suivant le pseudo‑code du header. diff --git a/include/server/Server.hpp b/include/server/Server.hpp new file mode 100644 index 0000000..eb6d997 --- /dev/null +++ b/include/server/Server.hpp @@ -0,0 +1,140 @@ +#ifndef SERVER_HPP +#define SERVER_HPP + +#include +#include +#include "Socket.hpp" +#include "Client.hpp" +#include "PollManger.hpp" +#include "../../config/default.conf" + +/* +** Server +** ------ +** Goal: Chef d'orchestre. Possède tous les Socket d'écoute, tous les +** Client connectés, et LE PollManager unique. Contient la boucle +** principale run(). +** +** C'est la SEULE classe qui doit connaître à la fois : +** - les sockets d'écoute (Socket) +** - les clients connectés (Client) +** - le poll() (PollManager) +** - et qui DOIT appeler l'HTTP Layer (via un point d'entrée unique, +** ex: HttpDispatcher::handle(client, configs) — à définir avec +** l'équipe HTTP Layer comme LE point de contact entre les 2 modules) +** +** Tout le reste (Socket, Client, PollManager) ne sait rien des autres +** classes du Core Server — c'est Server qui les fait collaborer. +*/ +class Server +{ + public: + Server(); + ~Server(); + + /* + ** Goal: Enregistrer une config serveur (= un ou plusieurs + ** host:port à écouter). Appelé depuis main() après le parsing + ** de la config, AVANT run(). + ** Crée en interne le(s) Socket correspondant(s) et les ajoute + ** au PollManager (POLLIN, car on attend des connexions). + */ + void addServerConfig(const ServerConfig& config); + + /* + ** Goal: LA boucle principale. Ne retourne jamais sauf signal + ** d'arrêt (ex: SIGINT proprement géré) ou erreur fatale. + ** + ** Pseudo-code de la boucle (1 tour = 1 itération) : + ** + ** while (running) { + ** poll_manager.poll(TIMEOUT_MS); + ** + ** for each fd in poll_manager.getFds(): + ** if fd == un des listening sockets: + ** if isReadable(fd): handleNewConnection(fd); + ** else: + ** if hasError(fd): closeClient(fd); + ** else if isReadable(fd): handleClientRead(fd); + ** else if isWritable(fd): handleClientWrite(fd); + ** + ** checkTimeouts(); // déconnecte les clients inactifs + ** } + ** + ** Règle absolue : TOUT read/write passe par les handlers privés + ** ci-dessous, jamais d'appel direct dans run(). + */ + void run(); + + /* + ** Goal: Demande l'arrêt propre de la boucle (ex: appelé depuis + ** un signal handler SIGINT). Met _running = false. + ** La boucle actuelle finit son tour puis sort de run(). + */ + void stop(); + + private: + std::vector _listening_sockets; + std::map _clients; // fd -> Client* + std::map _configs_by_port; // port -> config (multi-port) + PollManager _poll_manager; + bool _running; + + /* + ** Goal: Un socket d'écoute a un événement POLLIN → accept(). + ** Crée un nouveau Client, l'ajoute à _clients et au PollManager + ** (POLLIN seulement au début — on n'a rien à écrire encore). + */ + void handleNewConnection(int listening_fd); + + /* + ** Goal: Un client a des données à lire. + ** Appelle client->receiveData(). + ** Si state == CLOSING (déconnexion/erreur) → closeClient(fd). + ** Si state == PROCESSING (requête complète) → appelle le point + ** d'entrée HTTP Layer pour obtenir la réponse, puis + ** client->setWriteBuffer(response) et + ** poll_manager.updateEvents(fd, POLLIN | POLLOUT). + */ + void handleClientRead(int fd); + + /* + ** Goal: Un client est prêt à recevoir des données. + ** Appelle client->sendData(). + ** Si isResponseFullySent() : + ** - si keep-alive : updateEvents(fd, POLLIN), state = READING_REQUEST, + ** vider read_buffer pour la prochaine requête sur ce fd + ** - sinon : closeClient(fd) + ** Si state == CLOSING → closeClient(fd). + */ + void handleClientWrite(int fd); + + /* + ** Goal: Fermeture propre d'un client. + ** Fait : close(fd), poll_manager.removeFd(fd), + ** delete _clients[fd], _clients.erase(fd). + ** Centraliser ici évite les fuites de fd dispersées dans le code. + */ + void closeClient(int fd); + + /* + ** Goal: Parcourt _clients et ferme ceux dont + ** getLastActivity() dépasse un seuil (ex: 60s sans activité). + ** Implémente la règle "une requête ne doit jamais hang indéfiniment". + ** Appelé une fois par tour de boucle, après le traitement des fds. + */ + void checkTimeouts(); + + /* + ** Goal: Retrouve le ServerConfig correspondant au port sur lequel + ** le client est connecté. Utilisé pour passer la bonne config + ** (routes, error pages, max body size) au Router de l'HTTP Layer. + */ + const ServerConfig& getConfigForClient(const Client* client) const; + + // Non copyable + Server(const Server& other); + Server& operator=(const Server& other); +}; + +#endif \ No newline at end of file diff --git a/include/server/Socket.hpp b/include/server/Socket.hpp new file mode 100644 index 0000000..0c28458 --- /dev/null +++ b/include/server/Socket.hpp @@ -0,0 +1,82 @@ +#ifndef SOCKET_HPP +#define SOCKET_HPP + +#include + +/* +** Socket +** ------ +** Goal: Encapsuler la création d'un socket d'écoute serveur : +** socket() + setsockopt() + bind() + listen(). +** +** Une instance = un socket d'écoute pour une combinaison host:port +** définie dans la config. Si la config définit 3 ports, tu crées +** 3 instances de Socket. +** +** Cette classe NE GÈRE PAS les clients connectés (c'est Client) et +** NE FAIT PAS de poll() elle-même (c'est PollManager). Elle fournit +** juste un fd prêt à être surveillé. +*/ +class Socket +{ + public: + /* + ** Goal: Construire et configurer le socket d'écoute. + ** host = "0.0.0.0" ou une IP spécifique (ex: "127.0.0.1") + ** port = ex 8080 + ** + ** Fait, dans l'ordre : + ** 1. socket(AF_INET, SOCK_STREAM, 0) + ** 2. setsockopt(SO_REUSEADDR) — évite "Address already in use" + ** au redémarrage rapide du serveur + ** 3. fcntl(fd, F_SETFL, O_NONBLOCK) — OBLIGATOIRE, le sujet + ** interdit tout fd bloquant + ** 4. bind() sur host:port + ** 5. listen() avec un backlog raisonnable (ex: 128) + ** + ** En cas d'échec à une étape : throw une exception ou retourne + ** un état d'erreur — à toi de choisir la stratégie d'erreur, + ** mais le programme ne doit JAMAIS crash (cf. règles générales). + */ + Socket(const std::string& host, int port); + ~Socket(); + + /* + ** Goal: Retourner le fd du socket d'écoute. + ** Utilisé pour : PollManager::addFd(getFd(), POLLIN) + ** et pour comparer fds[i].fd == getFd() dans Server::run() + ** afin de savoir "est-ce un nouveau client ou un client existant ?" + */ + int getFd() const; + + /* + ** Goal: Accepter une nouvelle connexion entrante. + ** Doit être appelé UNIQUEMENT quand PollManager::isReadable(getFd()) + ** est true (sinon violation de la règle "jamais sans poll()"). + ** + ** Retourne : le fd du nouveau client (>= 0), ou -1 si accept() + ** échoue (ex: EAGAIN car un autre thread/process l'a pris avant — + ** improbable ici mais à gérer sans crash). + ** + ** Le fd retourné doit être mis en O_NONBLOCK avant d'être utilisé. + */ + int acceptConnection() const; + + /* + ** Goal: Retourne le port sur lequel ce socket écoute. + ** Utile pour le logging / debug et pour faire correspondre + ** une connexion entrante à son ServerConfig (multi-port). + */ + int getPort() const; + + private: + int _fd; + std::string _host; + int _port; + + // Non copyable : un Socket possède un fd unique (ressource OS) + Socket(const Socket& other); + Socket& operator=(const Socket& other); +}; + +#endif \ No newline at end of file diff --git a/include/server/server-checklist.md b/include/server/server-checklist.md new file mode 100644 index 0000000..05a94ff --- /dev/null +++ b/include/server/server-checklist.md @@ -0,0 +1,70 @@ +# Webserv — Checklist complète du projet + +--- + +## 🟪 Module Core Server (event loop) + +### 1 — PollManager + +- [ ] `addFd(fd, events)` +- [ ] `removeFd(fd)` +- [ ] `updateEvents(fd, events)` +- [ ] `poll(timeout_ms)` — gérer le retour -1 avec EINTR sans crash +- [ ] `isReadable(fd)` +- [ ] `isWritable(fd)` +- [ ] `hasError(fd)` — détecter POLLHUP / POLLERR / POLLNVAL +- [ ] `getFds()` +- [ ] Test isolé : ajouter `stdin` (fd 0), taper au clavier, vérifier `isReadable(0)` + +### 2 — Socket + +- [ ] Constructeur : `socket()` +- [ ] `setsockopt(SO_REUSEADDR)` +- [ ] Passage en non-bloquant (`fcntl` + `O_NONBLOCK`) +- [ ] `bind()` sur host:port +- [ ] `listen()` avec backlog +- [ ] Gestion d'erreur sur chaque étape (pas de crash, message clair) +- [ ] `getFd()` +- [ ] `getPort()` +- [ ] `acceptConnection()` — retourne le fd du nouveau client, le passe en non-bloquant +- [ ] Test : `./webserv` démarre et écoute (vérifiable avec `lsof -i :8080` ou `netstat`) + +### 3 — Client + +- [ ] Constructeur (fd, server_port) +- [ ] `getFd()` +- [ ] `receiveData()` — read() + accumulation dans read_buffer +- [ ] Gérer read() qui retourne 0 → EOF → state = CLOSING +- [ ] Gérer read() qui retourne -1 → erreur → state = CLOSING (sans check errno pour la logique) +- [ ] `isRequestComplete()` — détecte `\r\n\r\n` + vérifie Content-Length si présent +- [ ] `sendData()` — write() avec gestion du **partial write** (offset) +- [ ] `isResponseFullySent()` +- [ ] `getState()` / `setState()` +- [ ] `getReadBuffer()` / `setWriteBuffer()` +- [ ] `getServerPort()` +- [ ] `getLastActivity()` / `updateLastActivity()` +- [ ] Destructeur (pas de close() ici si géré par Server::closeClient) + +### 4 — Server (boucle principale) + +- [ ] `addServerConfig()` — crée les `Socket` correspondants, les ajoute au PollManager (POLLIN) +- [ ] `run()` : boucle infinie avec `poll_manager.poll(timeout)` +- [ ] Distinguer "fd = socket d'écoute" vs "fd = client existant" +- [ ] `handleNewConnection()` — accept() + création Client + ajout PollManager +- [ ] `handleClientRead()` — receiveData() + appel HTTP Layer si requête complète +- [ ] `handleClientWrite()` — sendData() + gestion fin d'envoi (keep-alive ou close) +- [ ] `closeClient()` — close(fd) + removeFd + delete + erase (centralisé, aucune fuite de fd) +- [ ] `checkTimeouts()` — fermer les clients inactifs depuis > X secondes +- [ ] `getConfigForClient()` — retrouver le bon ServerConfig selon le port +- [ ] `stop()` — arrêt propre (SIGINT géré sans crash) +- [ ] **Vérifier : aucun read/write n'est appelé sans passage par poll() avant** +- [ ] **Vérifier : un seul poll() pour tout le programme (listen + clients + CGI pipes)** + +### 5 — Tests Core Server + +- [ ] Test manuel telnet : connexion acceptée, log affiché +- [ ] Test manuel telnet : déconnexion détectée proprement (pas de crash) +- [ ] Test manuel telnet : requête envoyée en plusieurs morceaux → bien accumulée +- [ ] Test : 2 clients connectés simultanément, chacun reçoit sa propre réponse +- [ ] Test : client qui n'envoie rien pendant longtemps → timeout déclenché et connexion fermée +- [ ] Test : grosse requête (plusieurs Mo) → pas de crash, lue en plusieurs read() diff --git a/src/server/.gitignore b/src/server/.gitignore index 5e7d273..e69de29 100644 --- a/src/server/.gitignore +++ b/src/server/.gitignore @@ -1,4 +0,0 @@ -# Ignore everything in this directory -* -# Except this file -!.gitignore From b4f4404bb908fda13c14d85b5e666993f9bcb4fd Mon Sep 17 00:00:00 2001 From: Jyzdcs Date: Tue, 16 Jun 2026 11:56:28 +0200 Subject: [PATCH 2/7] feat: implement PollManager class for managing file descriptors - Added PollManager class to centralize the management of file descriptors monitored by poll(). - Implemented methods for adding, removing, and querying file descriptors. - Created main.cpp to demonstrate PollManager functionality with basic operations. - Included necessary headers and defined class methods in PollManager.cpp. --- .../{PollManger.hpp => PollManager.hpp} | 3 + main.cpp | 13 ++++ src/server/PollManager.cpp | 68 +++++++++++++++++++ 3 files changed, 84 insertions(+) rename include/server/{PollManger.hpp => PollManager.hpp} (98%) create mode 100644 src/server/PollManager.cpp diff --git a/include/server/PollManger.hpp b/include/server/PollManager.hpp similarity index 98% rename from include/server/PollManger.hpp rename to include/server/PollManager.hpp index 5f7fa53..4781681 100644 --- a/include/server/PollManger.hpp +++ b/include/server/PollManager.hpp @@ -2,6 +2,7 @@ #define POLLMANAGER_HPP #include +#include #include /* @@ -37,6 +38,8 @@ class PollManager PollManager(); ~PollManager(); + int getSize() const; + /* ** Goal: Ajouter un fd à la liste surveillée par poll(). ** events = POLLIN, POLLOUT, ou POLLIN | POLLOUT diff --git a/main.cpp b/main.cpp index e69de29..bc1e66e 100644 --- a/main.cpp +++ b/main.cpp @@ -0,0 +1,13 @@ +#include "include/server/PollManager.hpp" + +int main(void) +{ + PollManager pollManager; + + std::cout << pollManager.getSize() << std::endl; + pollManager.addFd(1, POLLIN); + pollManager.addFd(2, POLLIN); + std::cout << pollManager.getSize() << std::endl; + pollManager.removeFd(2); + std::cout << pollManager.getSize() << std::endl; +} diff --git a/src/server/PollManager.cpp b/src/server/PollManager.cpp new file mode 100644 index 0000000..3f9c9d3 --- /dev/null +++ b/src/server/PollManager.cpp @@ -0,0 +1,68 @@ +#include "../../include/server/PollManager.hpp" + +PollManager::PollManager() { + _fds.reserve(5); + std::cout << "[PollManager] ctor called\n"; +}; + +PollManager::PollManager(const PollManager& other) { +}; + +PollManager& PollManager::operator=(const PollManager& other) { + // return *this; +}; + +PollManager::~PollManager() { + std::cout << "[PollManager] dtor called\n"; +}; + +// ---------------------- PRIVATE ---------------------- + +int PollManager::findIndex(int fd) const { + std::size_t i = 0; + while (_fds[i].fd != fd) { + i++; + }; + return i; +}; + +// ---------------------- PUBLIC ---------------------- + +int PollManager::getSize() const { + return _fds.size(); +}; + +void PollManager::addFd(int fd, short events) { + struct pollfd newFd; + + newFd.fd = fd; + newFd.events = events; + _fds.push_back(newFd); +}; + +void PollManager::removeFd(int fd) { + int idx = findIndex(fd); + if (idx < _fds.size()) { + _fds.erase(_fds.begin() + idx); + } +}; + +void PollManager::updateEvents(int fd, short events) { + +}; + +int PollManager::poll(int timeout_ms) { + +}; + +bool PollManager::isReadable(int fd) const { + +}; + +bool PollManager::isWritable(int fd) const { + +}; + +bool PollManager::hasError(int fd) const { + +}; From bb03073b7ce2f2e62b1e9df9476fb94d7f8e90be Mon Sep 17 00:00:00 2001 From: Jyzdcs Date: Tue, 16 Jun 2026 16:19:52 +0200 Subject: [PATCH 3/7] refactor: simplify PollManager by removing copy constructor and assignment operator - Removed the non-copyable constructor and assignment operator from PollManager as they were not implemented. - Renamed the poll() method to pollEngine() for clarity. - Added a new test file for PollManager to validate its methods using stdin as a mock event source. - Updated .gitignore to include a.out and .vscode directories. --- .gitignore | 4 +- include/server/PollManager.hpp | 6 +- src/server/PollManager.cpp | 37 ++++-- tests/server/test_poll_manager.cpp | 182 +++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+), 17 deletions(-) create mode 100644 tests/server/test_poll_manager.cpp diff --git a/.gitignore b/.gitignore index baa38f8..9e8aeb5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ .DS_Store -.vscode \ No newline at end of file +.vscode + +a.out \ No newline at end of file diff --git a/include/server/PollManager.hpp b/include/server/PollManager.hpp index 4781681..d457437 100644 --- a/include/server/PollManager.hpp +++ b/include/server/PollManager.hpp @@ -30,10 +30,6 @@ class PollManager */ int findIndex(int fd) const; - // Non copyable : PollManager possède une ressource unique (la liste fds) - PollManager(const PollManager& other); - PollManager& operator=(const PollManager& other); - public: PollManager(); ~PollManager(); @@ -73,7 +69,7 @@ class PollManager ** Retourne : nombre de fds prêts (>= 0), ou -1 en cas d'erreur ** (errno EINTR à gérer : ne pas crasher, juste re-looper). */ - int poll(int timeout_ms); + int pollEngine(int timeout_ms); /* ** Goal: Après un poll() réussi, indique si ce fd est prêt en lecture. diff --git a/src/server/PollManager.cpp b/src/server/PollManager.cpp index 3f9c9d3..efe805d 100644 --- a/src/server/PollManager.cpp +++ b/src/server/PollManager.cpp @@ -5,13 +5,6 @@ PollManager::PollManager() { std::cout << "[PollManager] ctor called\n"; }; -PollManager::PollManager(const PollManager& other) { -}; - -PollManager& PollManager::operator=(const PollManager& other) { - // return *this; -}; - PollManager::~PollManager() { std::cout << "[PollManager] dtor called\n"; }; @@ -48,21 +41,43 @@ void PollManager::removeFd(int fd) { }; void PollManager::updateEvents(int fd, short events) { - + int idx = findIndex(fd); + _fds[idx].events = events; }; -int PollManager::poll(int timeout_ms) { - +int PollManager::pollEngine(int timeout_ms) { + int poll_count = poll(&_fds[0], _fds.size(), timeout_ms); + + return poll_count; }; bool PollManager::isReadable(int fd) const { + int idx = findIndex(fd); + if (_fds[idx].revents & POLLIN) { + return true; + } + return false; }; bool PollManager::isWritable(int fd) const { + int idx = findIndex(fd); + if (_fds[idx].revents & POLLOUT) { + return true; + } + return false; }; bool PollManager::hasError(int fd) const { - + int idx = findIndex(fd); + + if (_fds[idx].revents & (POLLHUP | POLLNVAL | POLLERR)) { + return true; + } + return false; }; + +const std::vector& PollManager::getFds() const { + return _fds; +}; \ No newline at end of file diff --git a/tests/server/test_poll_manager.cpp b/tests/server/test_poll_manager.cpp new file mode 100644 index 0000000..4343cfa --- /dev/null +++ b/tests/server/test_poll_manager.cpp @@ -0,0 +1,182 @@ +/* +** test_poll_manager.cpp +** ---------------------- +** Goal: Valider chaque méthode de PollManager AVANT de coder Socket +** et Server. On utilise stdin (fd 0) comme "fausse source +** d'événements" — pas besoin de sockets pour ce test. +** +** Compilation : +** c++ -Wall -Wextra -Werror -std=c++98 -I ../include \ +** test_poll_manager.cpp ../src/server/PollManager.cpp \ +** -o test_poll_manager +** +** Exécution : +** ./test_poll_manager +** -> puis tape n'importe quoi au clavier + Entrée +** -> le programme doit afficher "stdin is readable!" et quitter +** proprement si tu tapes "exit" +** +** Ce que ce test prouve, étape par étape : +** 1. addFd() ajoute bien fd 0 à la liste +** 2. poll() ne bloque pas indéfiniment (timeout visible toutes les 2s) +** 3. isReadable() détecte correctement quand stdin a des données +** 4. hasError() ne se déclenche jamais sur un fd normal +** 5. updateEvents() ne casse rien quand on l'appelle +** 6. removeFd() retire bien le fd (plus aucun event après) +*/ + +#include "../../include/server/PollManager.hpp" +#include +#include +#include + +// Petite aide visuelle pour rendre le test lisible dans le terminal +static void printStep(const std::string& step) +{ + std::cout << "\n=== " << step << " ===" << std::endl; +} + +int main() +{ + PollManager poll_manager; + + // ------------------------------------------------------------ + // ETAPE 1 : addFd() — on surveille stdin en lecture + // ------------------------------------------------------------ + printStep("Etape 1 : addFd(STDIN_FILENO, POLLIN)"); + poll_manager.addFd(STDIN_FILENO, POLLIN); + + assert(poll_manager.getFds().size() == 1); + assert(poll_manager.getFds()[0].fd == STDIN_FILENO); + std::cout << "OK : stdin (fd " << STDIN_FILENO + << ") ajoute a la liste. Taille liste = " + << poll_manager.getFds().size() << std::endl; + + // ------------------------------------------------------------ + // ETAPE 2 : poll() avec timeout — ne doit jamais bloquer pour rien + // ------------------------------------------------------------ + printStep("Etape 2 : poll() avec timeout de 2000ms"); + std::cout << "Tape quelque chose au clavier puis Entree." << std::endl; + std::cout << "(si tu ne tapes rien, le programme affichera" + " 'timeout, rien de pret' toutes les 2s)" << std::endl; + std::cout << "Tape 'exit' + Entree pour quitter proprement.\n" << std::endl; + + bool running = true; + int loop_count = 0; + + while (running) + { + int ready = poll_manager.pollEngine(2000); // 2000ms = 2s de timeout + + loop_count++; + + // -------------------------------------------------------- + // Cas A : poll() retourne -1 -> erreur + // -------------------------------------------------------- + if (ready < 0) + { + std::cout << "poll() a retourne une erreur (ready=-1)." + << " Si c'est EINTR, c'est normal (signal recu)." + << " On continue sans crasher." << std::endl; + continue; + } + + // -------------------------------------------------------- + // Cas B : poll() retourne 0 -> timeout, personne n'est pret + // -------------------------------------------------------- + if (ready == 0) + { + std::cout << "[tour " << loop_count << "] timeout, rien de pret" + << std::endl; + continue; + } + + // -------------------------------------------------------- + // Cas C : ready > 0 -> au moins un fd est pret, on inspecte + // -------------------------------------------------------- + std::cout << "[tour " << loop_count << "] poll() dit : " + << ready << " fd(s) pret(s)" << std::endl; + + // ETAPE 3 : isReadable() + if (poll_manager.isReadable(STDIN_FILENO)) + { + char buffer[256]; + ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer) - 1); + + if (n > 0) + { + buffer[n] = '\0'; + std::string input(buffer); + + // Retirer le \n final pour la comparaison + if (!input.empty() && input[input.size() - 1] == '\n') + input.erase(input.size() - 1); + + std::cout << "OK : stdin is readable! Tu as tape : \"" + << input << "\"" << std::endl; + + if (input == "exit") + { + std::cout << "Mot 'exit' detecte, on arrete le test." + << std::endl; + running = false; + } + } + } + + // ETAPE 4 : hasError() — ne doit jamais se déclencher ici + if (poll_manager.hasError(STDIN_FILENO)) + { + std::cout << "ATTENTION : hasError() est true sur stdin," + << " ce qui est inattendu dans ce test simple." + << std::endl; + } + } + + // ------------------------------------------------------------ + // ETAPE 5 : updateEvents() — verifie que ca ne crash pas + // ------------------------------------------------------------ + printStep("Etape 5 : updateEvents(STDIN_FILENO, POLLIN)"); + poll_manager.updateEvents(STDIN_FILENO, POLLIN); + std::cout << "OK : updateEvents() appele sans crash." << std::endl; + + // Test sur un fd qui n'existe pas dans la liste -> doit etre un no-op + poll_manager.updateEvents(9999, POLLIN); + std::cout << "OK : updateEvents() sur un fd inexistant (9999)" + << " ne crash pas (no-op attendu)." << std::endl; + + // ------------------------------------------------------------ + // ETAPE 6 : removeFd() — la liste doit redevenir vide + // ------------------------------------------------------------ + printStep("Etape 6 : removeFd(STDIN_FILENO)"); + poll_manager.removeFd(STDIN_FILENO); + + assert(poll_manager.getFds().empty()); + std::cout << "OK : stdin retire. Taille liste = " + << poll_manager.getFds().size() << std::endl; + + // removeFd sur un fd deja absent -> ne doit pas crash non plus + poll_manager.removeFd(STDIN_FILENO); + std::cout << "OK : removeFd() une seconde fois sur le meme fd" + << " ne crash pas (no-op attendu)." << std::endl; + + std::cout << "\n=== TOUS LES TESTS PollManager SONT PASSES ===" + << std::endl; + + return 0; +} + +// Ce test verifie si la detection isReadable fonctionne ainsi que le fait de pouvoir switch pour ecouter le writtable + +// int main(void) +// { +// PollManager pollManager; + +// // std::cout << pollManager.getSize() << std::endl; +// pollManager.addFd(0, POLLIN); +// int pollNum = pollManager.pollEngine(-1); +// std::cout << pollManager.isReadable(0) << std::endl; +// pollManager.updateEvents(0, POLLOUT); +// pollNum = pollManager.pollEngine(-1); +// std::cout << pollManager.isWritable(0) << std::endl; +// } \ No newline at end of file From feae0aec000d1f26de87820cc06d9faa269fe365 Mon Sep 17 00:00:00 2001 From: Jyzdcs Date: Thu, 18 Jun 2026 11:11:25 +0200 Subject: [PATCH 4/7] feat: enhance PollManager functionality with isReadable and isWritable tests - Updated main.cpp to test the isReadable and isWritable methods of PollManager. - Added functionality to switch between listening for readable and writable events. - Cleaned up commented-out code for better readability. --- include/server/Client.hpp | 244 +++++++++++++++++++------------------- main.cpp | 15 ++- 2 files changed, 131 insertions(+), 128 deletions(-) diff --git a/include/server/Client.hpp b/include/server/Client.hpp index 9a1b323..a855c7f 100644 --- a/include/server/Client.hpp +++ b/include/server/Client.hpp @@ -39,128 +39,128 @@ enum ClientState class Client { - public: - /* - ** Goal: Initialiser un client à partir du fd retourné par accept(). - ** server_port permet de retrouver le bon ServerConfig (multi-port) - ** sans avoir à le chercher à chaque requête. - */ - Client(int fd, int server_port); - ~Client(); - - // --- Accès au fd --- - - /* - ** Goal: Retourne le fd de ce client. - ** Utilisé par Server pour PollManager::isReadable(getFd()) etc. - */ - int getFd() const; - - // --- Lecture (déclenché par PollManager::isReadable) --- - - /* - ** Goal: Appelle read() sur le fd et accumule dans read_buffer. - ** Retourne : - ** > 0 : nombre d'octets lus (normal, continuer) - ** == 0 : le client a fermé la connexion (EOF) → state = CLOSING - ** < 0 : erreur read() → state = CLOSING (ne PAS check errno - ** pour décider du comportement HTTP, juste fermer proprement) - ** - ** Après chaque appel réussi, vérifie isRequestComplete() et passe - ** state à PROCESSING si c'est le cas. - */ - int receiveData(); - - /* - ** Goal: Vérifie si read_buffer contient une requête HTTP complète. - ** - Sans body : présence de "\r\n\r\n" - ** - Avec body : présence de "\r\n\r\n" ET - ** read_buffer.size() - header_end >= Content-Length - ** - ** Cette logique est dupliquée/déléguée avec RequestParser — - ** à toi de choisir : soit Client appelle un helper de - ** RequestParser::isComplete(), soit Client a sa propre version - ** légère. Recommandation : déléguer à RequestParser pour éviter - ** la duplication de logique de parsing. - */ - bool isRequestComplete() const; - - // --- Écriture (déclenché par PollManager::isWritable) --- - - /* - ** Goal: Appelle write() avec write_buffer et avance _write_offset. - ** Gère le "partial write" : write() peut envoyer moins d'octets - ** que demandé, il NE FAUT PAS considérer ça comme une erreur. - ** - ** Retourne : - ** > 0 : octets envoyés (continuer au prochain poll si incomplet) - ** == 0 : rien envoyé (peu probable si POLLOUT était set, mais - ** ne pas crasher) - ** < 0 : erreur → state = CLOSING - ** - ** Quand _write_offset == write_buffer.size() → state = DONE, - ** et PollManager doit repasser ce fd en POLLIN seulement - ** (ou removeFd si pas de keep-alive). - */ - int sendData(); - - /* - ** Goal: Indique si write_buffer a été entièrement envoyé. - ** Utilisé par Server pour savoir quand basculer - ** updateEvents(fd, POLLIN) (retirer POLLOUT). - */ - bool isResponseFullySent() const; - - // --- État et accesseurs --- - - ClientState getState() const; - void setState(ClientState state); - - /* - ** Goal: Accès en lecture/écriture au buffer de requête brut. - ** L'HTTP Layer (RequestParser) lit ce buffer pour produire - ** un HttpRequest. Après parsing réussi, le buffer est vidé - ** (clear) ou avancé (pour gérer le pipelining keep-alive — - ** optionnel selon votre ambition). - */ - std::string& getReadBuffer(); - - /* - ** Goal: Définit la réponse complète à envoyer. - ** Appelé par HTTP Layer (ResponseBuilder) une fois la réponse - ** assemblée. Remet _write_offset à 0 et state = SENDING_RESPONSE. - */ - void setWriteBuffer(const std::string& response); - - /* - ** Goal: Port du serveur sur lequel ce client s'est connecté. - ** Permet au Router de retrouver le bon ServerConfig - ** (cas multi-port avec configs différentes). - */ - int getServerPort() const; - - /* - ** Goal: Timestamp de la dernière activité (read ou write réussi). - ** Utilisé par Server pour détecter et fermer les connexions - ** inactives (anti "hang indéfiniment", règle du sujet). - */ - long getLastActivity() const; - void updateLastActivity(); - - private: - int _fd; - int _server_port; - ClientState _state; - - std::string _read_buffer; - std::string _write_buffer; - size_t _write_offset; - - long _last_activity; - - // Non copyable : un Client possède un fd unique - Client(const Client& other); - Client& operator=(const Client& other); + private: + int _fd; + int _server_port; + ClientState _state; + + std::string _read_buffer; + std::string _write_buffer; + size_t _write_offset; + + long _last_activity; + + // Non copyable : un Client possède un fd unique + Client(const Client& other); + Client& operator=(const Client& other); + + public: + /* + ** Goal: Initialiser un client à partir du fd retourné par accept(). + ** server_port permet de retrouver le bon ServerConfig (multi-port) + ** sans avoir à le chercher à chaque requête. + */ + Client(int fd, int server_port); + ~Client(); + + // --- Accès au fd --- + + /* + ** Goal: Retourne le fd de ce client. + ** Utilisé par Server pour PollManager::isReadable(getFd()) etc. + */ + int getFd() const; + + // --- Lecture (déclenché par PollManager::isReadable) --- + + /* + ** Goal: Appelle read() sur le fd et accumule dans read_buffer. + ** Retourne : + ** > 0 : nombre d'octets lus (normal, continuer) + ** == 0 : le client a fermé la connexion (EOF) → state = CLOSING + ** < 0 : erreur read() → state = CLOSING (ne PAS check errno + ** pour décider du comportement HTTP, juste fermer proprement) + ** + ** Après chaque appel réussi, vérifie isRequestComplete() et passe + ** state à PROCESSING si c'est le cas. + */ + int receiveData(); + + /* + ** Goal: Vérifie si read_buffer contient une requête HTTP complète. + ** - Sans body : présence de "\r\n\r\n" + ** - Avec body : présence de "\r\n\r\n" ET + ** read_buffer.size() - header_end >= Content-Length + ** + ** Cette logique est dupliquée/déléguée avec RequestParser — + ** à toi de choisir : soit Client appelle un helper de + ** RequestParser::isComplete(), soit Client a sa propre version + ** légère. Recommandation : déléguer à RequestParser pour éviter + ** la duplication de logique de parsing. + */ + bool isRequestComplete() const; + + // --- Écriture (déclenché par PollManager::isWritable) --- + + /* + ** Goal: Appelle write() avec write_buffer et avance _write_offset. + ** Gère le "partial write" : write() peut envoyer moins d'octets + ** que demandé, il NE FAUT PAS considérer ça comme une erreur. + ** + ** Retourne : + ** > 0 : octets envoyés (continuer au prochain poll si incomplet) + ** == 0 : rien envoyé (peu probable si POLLOUT était set, mais + ** ne pas crasher) + ** < 0 : erreur → state = CLOSING + ** + ** Quand _write_offset == write_buffer.size() → state = DONE, + ** et PollManager doit repasser ce fd en POLLIN seulement + ** (ou removeFd si pas de keep-alive). + */ + int sendData(); + + /* + ** Goal: Indique si write_buffer a été entièrement envoyé. + ** Utilisé par Server pour savoir quand basculer + ** updateEvents(fd, POLLIN) (retirer POLLOUT). + */ + bool isResponseFullySent() const; + + // --- État et accesseurs --- + + ClientState getState() const; + void setState(ClientState state); + + /* + ** Goal: Accès en lecture/écriture au buffer de requête brut. + ** L'HTTP Layer (RequestParser) lit ce buffer pour produire + ** un HttpRequest. Après parsing réussi, le buffer est vidé + ** (clear) ou avancé (pour gérer le pipelining keep-alive — + ** optionnel selon votre ambition). + */ + std::string& getReadBuffer(); + + /* + ** Goal: Définit la réponse complète à envoyer. + ** Appelé par HTTP Layer (ResponseBuilder) une fois la réponse + ** assemblée. Remet _write_offset à 0 et state = SENDING_RESPONSE. + */ + void setWriteBuffer(const std::string& response); + + /* + ** Goal: Port du serveur sur lequel ce client s'est connecté. + ** Permet au Router de retrouver le bon ServerConfig + ** (cas multi-port avec configs différentes). + */ + int getServerPort() const; + + /* + ** Goal: Timestamp de la dernière activité (read ou write réussi). + ** Utilisé par Server pour détecter et fermer les connexions + ** inactives (anti "hang indéfiniment", règle du sujet). + */ + long getLastActivity() const; + void updateLastActivity(); }; #endif \ No newline at end of file diff --git a/main.cpp b/main.cpp index bc1e66e..27c1f7b 100644 --- a/main.cpp +++ b/main.cpp @@ -1,13 +1,16 @@ #include "include/server/PollManager.hpp" +// Ce test vite si la detection isReadable fonctionne ainsi que le fait de pouvoir switch pour ecouter le writtable + int main(void) { PollManager pollManager; - std::cout << pollManager.getSize() << std::endl; - pollManager.addFd(1, POLLIN); - pollManager.addFd(2, POLLIN); - std::cout << pollManager.getSize() << std::endl; - pollManager.removeFd(2); - std::cout << pollManager.getSize() << std::endl; + // std::cout << pollManager.getSize() << std::endl; + pollManager.addFd(0, POLLIN); + int pollNum = pollManager.pollEngine(-1); + std::cout << pollManager.isReadable(0) << std::endl; + pollManager.updateEvents(0, POLLOUT); + pollNum = pollManager.pollEngine(-1); + std::cout << pollManager.isWritable(0) << std::endl; } From 224363e4e29bbf38cf1efb8610cb7b87168fbb18 Mon Sep 17 00:00:00 2001 From: Jyzdcs Date: Thu, 18 Jun 2026 11:13:09 +0200 Subject: [PATCH 5/7] chore: remove main.cpp test code for PollManager - Deleted main.cpp which contained test code for PollManager's isReadable and isWritable methods. - Cleaned up the repository by removing unused test code. --- main.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/main.cpp b/main.cpp index 27c1f7b..e69de29 100644 --- a/main.cpp +++ b/main.cpp @@ -1,16 +0,0 @@ -#include "include/server/PollManager.hpp" - -// Ce test vite si la detection isReadable fonctionne ainsi que le fait de pouvoir switch pour ecouter le writtable - -int main(void) -{ - PollManager pollManager; - - // std::cout << pollManager.getSize() << std::endl; - pollManager.addFd(0, POLLIN); - int pollNum = pollManager.pollEngine(-1); - std::cout << pollManager.isReadable(0) << std::endl; - pollManager.updateEvents(0, POLLOUT); - pollNum = pollManager.pollEngine(-1); - std::cout << pollManager.isWritable(0) << std::endl; -} From ca22ba2791a28e2f902ef0a6d71fd6995ae822ef Mon Sep 17 00:00:00 2001 From: Jyzdcs Date: Thu, 18 Jun 2026 11:41:51 +0200 Subject: [PATCH 6/7] fix(PollManager): change index type from int to size_t in removeFd method - Updated the removeFd method to use size_t for the index variable, improving type safety and consistency with the vector size type. --- src/server/PollManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/PollManager.cpp b/src/server/PollManager.cpp index efe805d..933c3a7 100644 --- a/src/server/PollManager.cpp +++ b/src/server/PollManager.cpp @@ -34,7 +34,7 @@ void PollManager::addFd(int fd, short events) { }; void PollManager::removeFd(int fd) { - int idx = findIndex(fd); + size_t idx = findIndex(fd); if (idx < _fds.size()) { _fds.erase(_fds.begin() + idx); } From 8a331e91758d3604a082865795a726643adb84e6 Mon Sep 17 00:00:00 2001 From: Jyzdcs Date: Thu, 18 Jun 2026 11:47:05 +0200 Subject: [PATCH 7/7] refactor(tests): clean up test_poll_manager.cpp by commenting out unused code - Commented out the PollManager includes and the printStep function to improve readability. - Maintained the structure of the main function while ensuring that the test logic remains intact for future use. --- tests/server/test_poll_manager.cpp | 279 +++++++++++++++-------------- 1 file changed, 142 insertions(+), 137 deletions(-) diff --git a/tests/server/test_poll_manager.cpp b/tests/server/test_poll_manager.cpp index 4343cfa..eed5d83 100644 --- a/tests/server/test_poll_manager.cpp +++ b/tests/server/test_poll_manager.cpp @@ -25,147 +25,152 @@ ** 6. removeFd() retire bien le fd (plus aucun event après) */ -#include "../../include/server/PollManager.hpp" -#include -#include -#include - -// Petite aide visuelle pour rendre le test lisible dans le terminal -static void printStep(const std::string& step) -{ - std::cout << "\n=== " << step << " ===" << std::endl; -} - int main() { - PollManager poll_manager; - - // ------------------------------------------------------------ - // ETAPE 1 : addFd() — on surveille stdin en lecture - // ------------------------------------------------------------ - printStep("Etape 1 : addFd(STDIN_FILENO, POLLIN)"); - poll_manager.addFd(STDIN_FILENO, POLLIN); - - assert(poll_manager.getFds().size() == 1); - assert(poll_manager.getFds()[0].fd == STDIN_FILENO); - std::cout << "OK : stdin (fd " << STDIN_FILENO - << ") ajoute a la liste. Taille liste = " - << poll_manager.getFds().size() << std::endl; - - // ------------------------------------------------------------ - // ETAPE 2 : poll() avec timeout — ne doit jamais bloquer pour rien - // ------------------------------------------------------------ - printStep("Etape 2 : poll() avec timeout de 2000ms"); - std::cout << "Tape quelque chose au clavier puis Entree." << std::endl; - std::cout << "(si tu ne tapes rien, le programme affichera" - " 'timeout, rien de pret' toutes les 2s)" << std::endl; - std::cout << "Tape 'exit' + Entree pour quitter proprement.\n" << std::endl; - - bool running = true; - int loop_count = 0; - - while (running) - { - int ready = poll_manager.pollEngine(2000); // 2000ms = 2s de timeout - - loop_count++; - - // -------------------------------------------------------- - // Cas A : poll() retourne -1 -> erreur - // -------------------------------------------------------- - if (ready < 0) - { - std::cout << "poll() a retourne une erreur (ready=-1)." - << " Si c'est EINTR, c'est normal (signal recu)." - << " On continue sans crasher." << std::endl; - continue; - } - - // -------------------------------------------------------- - // Cas B : poll() retourne 0 -> timeout, personne n'est pret - // -------------------------------------------------------- - if (ready == 0) - { - std::cout << "[tour " << loop_count << "] timeout, rien de pret" - << std::endl; - continue; - } - - // -------------------------------------------------------- - // Cas C : ready > 0 -> au moins un fd est pret, on inspecte - // -------------------------------------------------------- - std::cout << "[tour " << loop_count << "] poll() dit : " - << ready << " fd(s) pret(s)" << std::endl; - - // ETAPE 3 : isReadable() - if (poll_manager.isReadable(STDIN_FILENO)) - { - char buffer[256]; - ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer) - 1); - - if (n > 0) - { - buffer[n] = '\0'; - std::string input(buffer); - - // Retirer le \n final pour la comparaison - if (!input.empty() && input[input.size() - 1] == '\n') - input.erase(input.size() - 1); - - std::cout << "OK : stdin is readable! Tu as tape : \"" - << input << "\"" << std::endl; - - if (input == "exit") - { - std::cout << "Mot 'exit' detecte, on arrete le test." - << std::endl; - running = false; - } - } - } - - // ETAPE 4 : hasError() — ne doit jamais se déclencher ici - if (poll_manager.hasError(STDIN_FILENO)) - { - std::cout << "ATTENTION : hasError() est true sur stdin," - << " ce qui est inattendu dans ce test simple." - << std::endl; - } - } - - // ------------------------------------------------------------ - // ETAPE 5 : updateEvents() — verifie que ca ne crash pas - // ------------------------------------------------------------ - printStep("Etape 5 : updateEvents(STDIN_FILENO, POLLIN)"); - poll_manager.updateEvents(STDIN_FILENO, POLLIN); - std::cout << "OK : updateEvents() appele sans crash." << std::endl; - - // Test sur un fd qui n'existe pas dans la liste -> doit etre un no-op - poll_manager.updateEvents(9999, POLLIN); - std::cout << "OK : updateEvents() sur un fd inexistant (9999)" - << " ne crash pas (no-op attendu)." << std::endl; - - // ------------------------------------------------------------ - // ETAPE 6 : removeFd() — la liste doit redevenir vide - // ------------------------------------------------------------ - printStep("Etape 6 : removeFd(STDIN_FILENO)"); - poll_manager.removeFd(STDIN_FILENO); - - assert(poll_manager.getFds().empty()); - std::cout << "OK : stdin retire. Taille liste = " - << poll_manager.getFds().size() << std::endl; - - // removeFd sur un fd deja absent -> ne doit pas crash non plus - poll_manager.removeFd(STDIN_FILENO); - std::cout << "OK : removeFd() une seconde fois sur le meme fd" - << " ne crash pas (no-op attendu)." << std::endl; - - std::cout << "\n=== TOUS LES TESTS PollManager SONT PASSES ===" - << std::endl; - - return 0; + return 0; } +// #include "../../include/server/PollManager.hpp" +// #include +// #include +// #include + +// // Petite aide visuelle pour rendre le test lisible dans le terminal +// static void printStep(const std::string& step) +// { +// std::cout << "\n=== " << step << " ===" << std::endl; +// } + +// int main() +// { +// PollManager poll_manager; + +// // ------------------------------------------------------------ +// // ETAPE 1 : addFd() — on surveille stdin en lecture +// // ------------------------------------------------------------ +// printStep("Etape 1 : addFd(STDIN_FILENO, POLLIN)"); +// poll_manager.addFd(STDIN_FILENO, POLLIN); + +// assert(poll_manager.getFds().size() == 1); +// assert(poll_manager.getFds()[0].fd == STDIN_FILENO); +// std::cout << "OK : stdin (fd " << STDIN_FILENO +// << ") ajoute a la liste. Taille liste = " +// << poll_manager.getFds().size() << std::endl; + +// // ------------------------------------------------------------ +// // ETAPE 2 : poll() avec timeout — ne doit jamais bloquer pour rien +// // ------------------------------------------------------------ +// printStep("Etape 2 : poll() avec timeout de 2000ms"); +// std::cout << "Tape quelque chose au clavier puis Entree." << std::endl; +// std::cout << "(si tu ne tapes rien, le programme affichera" +// " 'timeout, rien de pret' toutes les 2s)" << std::endl; +// std::cout << "Tape 'exit' + Entree pour quitter proprement.\n" << std::endl; + +// bool running = true; +// int loop_count = 0; + +// while (running) +// { +// int ready = poll_manager.pollEngine(2000); // 2000ms = 2s de timeout + +// loop_count++; + +// // -------------------------------------------------------- +// // Cas A : poll() retourne -1 -> erreur +// // -------------------------------------------------------- +// if (ready < 0) +// { +// std::cout << "poll() a retourne une erreur (ready=-1)." +// << " Si c'est EINTR, c'est normal (signal recu)." +// << " On continue sans crasher." << std::endl; +// continue; +// } + +// // -------------------------------------------------------- +// // Cas B : poll() retourne 0 -> timeout, personne n'est pret +// // -------------------------------------------------------- +// if (ready == 0) +// { +// std::cout << "[tour " << loop_count << "] timeout, rien de pret" +// << std::endl; +// continue; +// } + +// // -------------------------------------------------------- +// // Cas C : ready > 0 -> au moins un fd est pret, on inspecte +// // -------------------------------------------------------- +// std::cout << "[tour " << loop_count << "] poll() dit : " +// << ready << " fd(s) pret(s)" << std::endl; + +// // ETAPE 3 : isReadable() +// if (poll_manager.isReadable(STDIN_FILENO)) +// { +// char buffer[256]; +// ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer) - 1); + +// if (n > 0) +// { +// buffer[n] = '\0'; +// std::string input(buffer); + +// // Retirer le \n final pour la comparaison +// if (!input.empty() && input[input.size() - 1] == '\n') +// input.erase(input.size() - 1); + +// std::cout << "OK : stdin is readable! Tu as tape : \"" +// << input << "\"" << std::endl; + +// if (input == "exit") +// { +// std::cout << "Mot 'exit' detecte, on arrete le test." +// << std::endl; +// running = false; +// } +// } +// } + +// // ETAPE 4 : hasError() — ne doit jamais se déclencher ici +// if (poll_manager.hasError(STDIN_FILENO)) +// { +// std::cout << "ATTENTION : hasError() est true sur stdin," +// << " ce qui est inattendu dans ce test simple." +// << std::endl; +// } +// } + +// // ------------------------------------------------------------ +// // ETAPE 5 : updateEvents() — verifie que ca ne crash pas +// // ------------------------------------------------------------ +// printStep("Etape 5 : updateEvents(STDIN_FILENO, POLLIN)"); +// poll_manager.updateEvents(STDIN_FILENO, POLLIN); +// std::cout << "OK : updateEvents() appele sans crash." << std::endl; + +// // Test sur un fd qui n'existe pas dans la liste -> doit etre un no-op +// poll_manager.updateEvents(9999, POLLIN); +// std::cout << "OK : updateEvents() sur un fd inexistant (9999)" +// << " ne crash pas (no-op attendu)." << std::endl; + +// // ------------------------------------------------------------ +// // ETAPE 6 : removeFd() — la liste doit redevenir vide +// // ------------------------------------------------------------ +// printStep("Etape 6 : removeFd(STDIN_FILENO)"); +// poll_manager.removeFd(STDIN_FILENO); + +// assert(poll_manager.getFds().empty()); +// std::cout << "OK : stdin retire. Taille liste = " +// << poll_manager.getFds().size() << std::endl; + +// // removeFd sur un fd deja absent -> ne doit pas crash non plus +// poll_manager.removeFd(STDIN_FILENO); +// std::cout << "OK : removeFd() une seconde fois sur le meme fd" +// << " ne crash pas (no-op attendu)." << std::endl; + +// std::cout << "\n=== TOUS LES TESTS PollManager SONT PASSES ===" +// << std::endl; + +// return 0; +// } + // Ce test verifie si la detection isReadable fonctionne ainsi que le fait de pouvoir switch pour ecouter le writtable // int main(void)