446 lines
14 KiB
C++
446 lines
14 KiB
C++
#include <arpa/inet.h>
|
|
#include <fcntl.h>
|
|
#include <ios>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <string>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#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<string, string> parseHeaders(SocketReader &sr) {
|
|
map<string, string> 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 = "<html><body><h1>The requested URL was Not "
|
|
"Found</h1></body></html>\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 = "<html><body><h1>Not Implemented</h1></body></html>\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 =
|
|
"<html><body><h1>unsupported HTTP Version</h1></body></html>\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<string, string> 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<string, string> 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<string, int> 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<string, string> 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()
|