#include #include #include #include #include #include #include #include #include #include #include #include #include #define BACKLOG 128 #define NB_CLIENTS 1000 #define TAILLE_BUFFER 512 #define MAXKEEPALIVE 2 #define DEBUG true #define debugPrint(X) if (DEBUG) cout << X << endl using namespace std; class SocketReader { private: /*! * @brief socket a lire */ int socketfd; /*! * @brief nombre d'octect lu a chaque tour de boucle */ char msgRead[TAILLE_BUFFER]; /*! * @brief la position du premier élément de l'intervalle de recherche de "\n" */ int start; /*! * @brief la taille de l'intervalle de recherhce de \n */ int count; public: SocketReader(int socketfd) { count = 0; this->socketfd = socketfd; } /*! * @brief La fonction readline prends le fd d'un socket et une string en * paramettre, et y met au max TAILLE_BUFFER caracteres, si les * données lues conntiennt le caractere "\n", les données sont trunc * jusqua le "\n" et les données suivantes seront disponible lors de * la prochaine lecture. * * NOTE : J'ai legerement modififé la classe pour qu'elle utilise le * socket donné au constructeur, et pour qu'elle ne retourne * pas le "\r" avant chaque "\n". * * @param[out] line : le string a remplir avec les données du socket, * qui contiendra les donnée de la position actuelle * jusqu'au prochain "\n" * */ int readLine(string &line) { line = ""; int i; while (true) { if (count == 0) { count = read(this->socketfd, msgRead, sizeof(msgRead)); if (count == -1) return -1; // cas d'erreur if (count == 0) return 0; // cas de deconnexion start = 0; } for (i = start; i < count; i++) if (msgRead[i] == '\n') break; if (i < count) { // on a trouvé /n line = line + string(msgRead, start, (i - 1) - start); if (i == count - 1) count = 0; else start = i + 1; break; } else { // on n'a pas trouvé le \n line = line + string(msgRead, start, (i - 1) - start + 1); count = 0; } } return line.size(); } };// SocketReader void exitErreur(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /*! * @brief : la fonction parseRequest permet de recuperer les elements de la * premiere ligne d'une requete HTTP, c'est a dire : * - le type de la requete (GET,POST,PUT,...) * - la ressource demandée par le client * - et la version du protocol HTTP du client * * @param[in,out] line : le string contenant la premiere ligne de la requete * @param[out] requestType : le string qui va contenir le type de la requete * @param[out] requestedRessource : le string qui va contenir la ressource * demandée par la requete * @param[out] protocolVersion : le string qui va contenir la version du * proctol HTTP demandée par le client * */ void parseRequest(string line, string &requestType, string &requestedRessource, string &protocolVersion) { string delimiter = " "; size_t pos = 0; pos = line.find(delimiter); requestType = line.substr(0, pos); debugPrint("[DEBUG] : parsed request type : " << requestType); line.erase(0, pos + delimiter.length()); pos = line.find(delimiter); requestedRessource = line.substr(0, pos); debugPrint("[DEBUG] : parsed requested ressource : " << requestedRessource); line.erase(0, pos + delimiter.length()); protocolVersion = line; debugPrint("[DEBUG] : parsed protocol version : " << protocolVersion); }// parseRequest() /*! * @brief : la fonction parseHeaders intervient apres que la premiere ligne de * la requete, elle lit et stoque les en-tetes de la requette client * dans une hashmap * * @param[in] sr : SocketReader initialisé avec le socket du client * @return : une hashmap contenant les en-tete client ainsi que leur valeurs * */ map parseHeaders(SocketReader &sr) { map headers; string delimiter = ": "; string ligne; sr.readLine(ligne); while ("" != ligne) { size_t pos = ligne.find(delimiter); string key = ligne.substr(0, pos); ligne.erase(0, pos + delimiter.length()); string value = ligne; debugPrint("[DEBUG] : parsed header : " << key << " : " << value); headers[key] = value; sr.readLine(ligne); } return headers; }// parseHeaders() /*! * @brief : la fonction sendError sert a envoyer une reponse erreur au client * @param[in] socketfd : file descriptor du socket client a qui envoyer * l'erreur * @param[in] errorCode : code erreur de la reponse a envoyer au client */ void sendError(const int socketfd, const int errorCode) { string headers; string message; debugPrint("[DEBUG] : sending error : " << errorCode); switch (errorCode) { // erreur 404 : fichier pas trouvé case 404: message = "

The requested URL was Not " "Found

\r\n\r\n"; headers = "HTTP/1.1 404 Not Found\nServer: Djalim's Server (it uses arch " "btw)\nContent-Length: " + to_string(message.size()) + "\r\n\r\n"; write(socketfd, headers.c_str(), headers.size()); write(socketfd, message.c_str(), message.size()); break; // erreur 501 : fonctionnalité pas implementée case 501: message = "

Not Implemented

\r\n\r\n"; headers = "HTTP/1.1 501 Not Implemented\nServer: Djalim's Server (it uses " "arch btw)\nContent-Length: " + to_string(message.size()) + "\r\n\r\n"; write(socketfd, headers.c_str(), headers.size()); write(socketfd, message.c_str(), message.size()); break; // erreur 505 : version HTTP incompatible/ pas supportée case 505: message = "

unsupported HTTP Version

\r\n\r\n"; headers = "HTTP/1.1 505 Version Not Supported\nServer: Djalim's Server (it " "uses arch btw)\nContent-Length: " + to_string(message.size()) + "\r\n\r\n"; write(socketfd, headers.c_str(), headers.size()); write(socketfd, message.c_str(), message.size()); break; // TODO : ajouter les autres erreurs existantes }; }// sendError() /*! * @brief : la fonction sendFile sert a envoyer un fichier au client, cette fonction part du principe que le fichier a envoyer existe deja, il faut donc verifier l'existance du fichier avant l'appel de cette fonction. * @param[in] socketfd : file descriptor du socket client a qui envoyer * le fichier * @param[in] filePath : chemin du fichier a envoyer * @param[in] fileStat : structure stat associé au fichier a envoyer * @param[in] sendContent : Le booleen sendContent permet d'indiquer a cette * fonction si elle doit envoyer uniquement l'en-tete * ou non * */ void sendFile(const int socketfd, const string filePath, const struct stat &fileStat, const bool sendContent) { debugPrint("[DEBUG] : sending file : " << filePath); int file = open(filePath.c_str(), O_RDONLY); char *fileContent; string responseHeaders; responseHeaders = "HTTP/1.1 200 OK\nServer: Djalim's Server (it uses arch " "btw)\nContent-Length: " + to_string(fileStat.st_size) + "\r\n\r\n"; write(socketfd, responseHeaders.c_str(), responseHeaders.size()); if (sendContent) { read(file, &fileContent, fileStat.st_size); write(socketfd, &fileContent, fileStat.st_size); write(socketfd, "\r\n\r\n", 5); } close(file); }// sendFile() /*! * @brief : La fonction getHandler gere les requetes GET recue par le serveur * elle verifie l'existance de la ressource demandée et renvoie * la reponse appropriée. * @param[in] socketfd : socket du client a qui envoyer la reponse * @param[in,out] requestedRessource : ressource demandée par le client, elle * peut etre affecté a "/index" si la * ressource demandée et "/" * @param[in] requestHeaders : en-tetes de la requette du client * */ void getHandler(const int socketfd, string requestedRessource, const map requestHeaders) { debugPrint("[DEBUG] : handling a GET request"); if ("/" == requestedRessource) requestedRessource = "/index.html"; string filePath = "." + requestedRessource; struct stat fileStat; debugPrint("[DEBUG] : Searching for file " << filePath); if (-1 == stat(filePath.c_str(), &fileStat)) { debugPrint("[ERROR] : could not stat file"); sendError(socketfd, 404); return; } sendFile(socketfd, filePath, fileStat, true); } // getHandler() /*! * @brief : La fonction getHandler gere les requetes HEAD recue par le serveur * elle verifie l'existance de la ressource demandée et renvoie * la reponse appropriée. * @param[in] socketfd : socket du client a qui envoyer la reponse * @param[in,out] requestedRessource : ressource demandée par le client, elle * peut etre affecté a "/index" si la * ressource demandée et "/" * @param[in] requestHeaders : en-tetes de la requette du client * */ void headHandler(const int socketfd, string requestedRessource, const map requestHeaders) { debugPrint("[DEBUG] : handling a HEAD request"); if ("/" == requestedRessource) requestedRessource = "/index.html"; string filePath = "." + requestedRessource; struct stat fileStat; debugPrint("[DEBUG] : Searching for file " << filePath); if (-1 == stat(filePath.c_str(), &fileStat)) { debugPrint("[ERROR] : could not stat file"); sendError(socketfd, 404); return; } sendFile(socketfd, filePath, fileStat, false); }// headHandler() /*! * @brief : La fonction getHandler gere les requetes POST recue par le serveur, * actuellement pas implémenté, cette fonction retourne une erreur * 501 au client * @param[in] socketfd : socket du client a qui envoyer la reponse * */ void postHandler(const int socketfd) { debugPrint("[DEBUG] : handling a POST request"); sendError(socketfd, 501); } /*! * @brief : La fonction getHandler gere les requetes PUT recue par le serveur, * actuellement pas implémenté, cette fonction retourne une erreur * 501 au client * @param[in] socketfd : socket du client a qui envoyer la reponse * */ void putHandler(const int socketfd) { debugPrint("[DEBUG] : handling a PUT request"); sendError(socketfd, 501); } /*! * @brief : La fonction getHandler gere les requetes DELETE recue par le * serveur, actuellement pas implémenté, cette fonction retourne une * erreur 501 au client * @param[in] socketfd : socket du client a qui envoyer la reponse * */ void deleteHandler(const int socketfd) { debugPrint("[DEBUG] : handling a DELETE request"); sendError(socketfd, 501); } int main(int argc, char *argv[]) { if (argc != 2) exitErreur("[FATAL ERROR] : missing port"); int sock_serveur = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in sockaddr_serveur; sockaddr_serveur.sin_family = AF_INET; sockaddr_serveur.sin_port = htons(atoi(argv[1])); //TODO faire en sorte que l'ip peut etre choisi en paramettre du serveur inet_aton("127.0.0.1", &sockaddr_serveur.sin_addr); int yes = 1; if (setsockopt(sock_serveur, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) exitErreur("[FATAL ERROR] : setsockopt"); if (bind(sock_serveur, (struct sockaddr *)&sockaddr_serveur, sizeof(sockaddr_in)) == -1) exitErreur("[FATAL ERROR] : bind"); if (listen(sock_serveur, BACKLOG) == -1) exitErreur("[FATAL ERROR] : listen"); int sock_client; string msgRecu(""); debugPrint("[DEBUG] : Started server"); char* ip; debugPrint("[DEBUG] : listening on "<< inet_ntoa(sockaddr_serveur.sin_addr)); const map types; for (int i = 1; i <= NB_CLIENTS; i++) { // Le serveur attend un client struct sockaddr_in sockaddr_client; socklen_t a; sock_client = accept(sock_serveur, (struct sockaddr*)&sockaddr_client, &a); if (sock_client == -1) exitErreur("[FATAL ERROR] : accept"); int p = fork(); if (!p) { // processus fils debugPrint("[DEBUG] : "<< inet_ntoa(sockaddr_client.sin_addr) << " connected" ); close(sock_serveur); SocketReader sr(sock_client); for (int i = 1; i <= MAXKEEPALIVE; i++) { string ligne; int n = sr.readLine(ligne); // client s'est déconnecté if (!n) { debugPrint("[DEBUG] : Client deconnected"); break; } // cas d'erreur if (n == -1){ debugPrint("[FATAL ERROR] : Could not read line"); break; } string requestType, requestedRessource, protocolVersion; if ("" != ligne) parseRequest(ligne, requestType, requestedRessource, protocolVersion); map headers = parseHeaders(sr); //TODO faire un truc avec les en-tete parsées if ("HTTP/1.1" != protocolVersion) { sendError(sock_client, 505); } // pas tres elegant mais fonctionnel :) if ("GET" == requestType) getHandler(sock_client, requestedRessource, headers); else if ("HEAD" == requestType) headHandler(sock_client, requestedRessource, headers); else if ("POST" == requestType) postHandler(sock_client); else if ("PUT" == requestType) putHandler(sock_client); else if ("DELETE" == requestType) deleteHandler(sock_client); if (MAXKEEPALIVE == i) debugPrint("[DEBUG] : reached max keep alive requests"); } debugPrint("[DEBUG] : closing connection with client"); close(sock_client); exit(EXIT_SUCCESS); } close(sock_client); } debugPrint("[DEBUG] : Max client reached, terminating server"); close(sock_serveur); return EXIT_SUCCESS; }// main()