initial commit
This commit is contained in:
commit
01712b8d1b
445
ultraOverEngenieeredHTTPserver.cxx
Normal file
445
ultraOverEngenieeredHTTPserver.cxx
Normal file
@ -0,0 +1,445 @@
|
||||
#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()
|
Loading…
Reference in New Issue
Block a user