Skip to content
Merged
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
.DS_Store

.vscode

a.out
# Personal concept notes (learn-concept skill)
concepts/
166 changes: 166 additions & 0 deletions include/server/Client.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#ifndef CLIENT_HPP
#define CLIENT_HPP

#include <string>

/*
** 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
{
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
100 changes: 100 additions & 0 deletions include/server/PollManager.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#ifndef POLLMANAGER_HPP
#define POLLMANAGER_HPP

#include <vector>
#include <iostream>
#include <poll.h>

/*
** 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<struct pollfd> _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;

public:
PollManager();
~PollManager();

int getSize() const;

/*
** 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 pollEngine(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<struct pollfd>& getFds() const;
};

#endif
44 changes: 44 additions & 0 deletions include/server/README.md
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading