This commit is contained in:
cdricms
2024-11-20 00:04:49 +01:00
parent 7b52f5de7d
commit cdae291dd8
8 changed files with 145 additions and 71 deletions

View File

@@ -1,4 +1,3 @@
code = 200 code = 200
ct = "text/html" ct = "text/html"
content = "<!DOCTYPE html><html><body><h1>Bonjour!</h1></body></html>" content = "<!DOCTYPE html><html><body><h1>Bonjour!</h1></body></html>"

View File

@@ -1,5 +1,4 @@
#include "http_request.h" #include "http_request.h"
#include "http_method.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -57,18 +56,28 @@ void http_request_set(HttpRequest *__req, char *key, char *value) {
} }
HttpRequest *handle_request(char *__req) { HttpRequest *handle_request(char *__req) {
printf("%s\n", __req); // printf("%s\n", __req);
HttpRequest *request = malloc(sizeof(HttpRequest)); HttpRequest *request = malloc(sizeof(HttpRequest));
unsigned int line_count = 0; unsigned int line_count = 0;
char *line_start = __req; char *line_start = __req;
char *line_end; char *line_end;
// Nous allons de ligne en ligne
// line_end est une chaîne de caractères allant de la nouvelle ligne trouvée
// jusqu'à la fin de la requête.
// line_start est aussi une chaîne de caractères qui va du début de la
// requête (au fil de la boucle, elle deviendra le précédent line_end)
// jusqu'à line_end.
while ((line_end = strchr(line_start, '\n')) != NULL) { while ((line_end = strchr(line_start, '\n')) != NULL) {
line_count++; line_count++;
// Nous pouvons récupérer la longueur la ligne que nous allons traiter
// en faisant cette simple opération.
size_t l_length = line_end - line_start; size_t l_length = line_end - line_start;
char line[l_length]; char line[l_length];
strncpy(line, line_start, l_length); strncpy(line, line_start, l_length);
line[l_length - 1] = 0; line[l_length - 1] = 0;
// La première ligne HTTP est toujours quelque chose suivant ce format:
// <METHOD> <PATH> HTTP/<VERSION>
if (line_count == 1) { if (line_count == 1) {
char method[10] = {0}; char method[10] = {0};
char *path = malloc(sizeof(char) * 1024); char *path = malloc(sizeof(char) * 1024);
@@ -76,22 +85,29 @@ HttpRequest *handle_request(char *__req) {
free(path); free(path);
return NULL; return NULL;
} }
// Transforme le string donné depuis la requête en int défini dans
// un enum, afin de se faciliter la vie plus tard.
request->method = get_http_method(method); request->method = get_http_method(method);
request->path = path; request->path = path;
} else { } else {
// Pour ce qui est du reste, les options fonctionnent comme un
// "key-value pair": <KEY>: <VALUE>
char key[100] = {0}; char key[100] = {0};
char *colon_pos = strchr(line, ':'); char *colon_pos = strchr(line, ':');
if (colon_pos != NULL) { if (colon_pos != NULL) {
size_t k_length = colon_pos - line; size_t k_length = colon_pos - line;
strncpy(key, line, k_length); strncpy(key, line, k_length);
// Il faut enlever le ':' + ' ' + '\r' => 3
size_t v_length = l_length - k_length - 3; size_t v_length = l_length - k_length - 3;
char *value = malloc(sizeof(char) * v_length); char *value = malloc(sizeof(char) * v_length);
// colon_pos comprend le ':' + ' ' qui sont indésirables, donc
// faut se décaler de 2, le '\r' ne sera pas pris en compte
// puisque la chaîne value est trop petite pour que ça rentre.
strcpy(value, colon_pos + 2); strcpy(value, colon_pos + 2);
http_request_set(request, key, value); http_request_set(request, key, value);
} }
} }
// TODO: Analyze line
line_start = line_end + 1; line_start = line_end + 1;
} }

View File

@@ -3,6 +3,9 @@
#include "http_content_type.h" #include "http_content_type.h"
#include "http_method.h" #include "http_method.h"
// Beaucoup d'options manques ou d'informations ne seront pas stockées.
typedef struct { typedef struct {
HttpMethod method; HttpMethod method;
char *path; char *path;
@@ -18,8 +21,13 @@ typedef struct {
char *connection; char *connection;
} HttpRequest; } HttpRequest;
// Parse la requête HTTP, afin de la manipuler facilement.
HttpRequest *handle_request(char *__req); HttpRequest *handle_request(char *__req);
// Imprime dans le stdout la requête HTTP.
void print_request(const HttpRequest *__req); void print_request(const HttpRequest *__req);
// Libère tout ce qui est en rapport avec HttpRequest.
void free_request(HttpRequest *__req); void free_request(HttpRequest *__req);
#endif #endif

View File

@@ -5,6 +5,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
bool construct_response(HttpResponse __res, char *out) { bool construct_response(HttpResponse __res, char *out) {

View File

@@ -6,28 +6,34 @@
#include "http_status.h" #include "http_status.h"
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <sys/wait.h>
typedef struct { typedef struct {
HttpStatus status_code; HttpStatus status_code; // Code HTTP, ex. 200
HttpContentType content_type; HttpContentType content_type; // ex. HTTP_CT_HTML => text/html
size_t content_length; size_t content_length; // Taille du contenu en bytes
char *body; char *body; // Contenu de la réponse
bool body_in_heap; bool body_in_heap; // Si le contenu est défini dans le heap, alors on pourra
// libérer sa mémoire
} HttpResponse; } HttpResponse;
// It will create a string respecting the Hypertext Transfer Protocol. // Cela crée une chaîne de caractères respectant le protocole HTTP selon la
// // réponse donnée.
// To then be sent to the client.
bool construct_response(HttpResponse __res, char *out); bool construct_response(HttpResponse __res, char *out);
// Respond a http response to the client (clientfd); // Envoie la réponse au client.
void http_respond(HttpResponse *__res, int clientfd); void http_respond(HttpResponse *__res, int clientfd);
// Read a file from a specified path
// Lit un fichier et renvoie son contenu.
char *read_file(const char *__path); char *read_file(const char *__path);
// Given a path will return a HttpResponse
// Lit un fichier à un chemin donné et forme une réponse HTTP.
HttpResponse *from_file(const char *__path); HttpResponse *from_file(const char *__path);
// Libère tout ce qui est nécessaire en rapport avec la structure HttpResponse.
void free_response(HttpResponse *__res); void free_response(HttpResponse *__res);
// Execute un programme python à un chemin donné et envoie son STDOUT au socket
// client.
HttpServerRunStatus cgi(const char *__path, int clientfd); HttpServerRunStatus cgi(const char *__path, int clientfd);
#endif #endif

View File

@@ -3,17 +3,15 @@
#include "http_request.h" #include "http_request.h"
#include "http_response.h" #include "http_response.h"
#include "http_status.h" #include "http_status.h"
#include <asm-generic/socket.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
HttpServerRunStatus http_server_setup(HttpServer *s) { HttpServerRunStatus http_server_setup(HttpServer *s) {
int opt = 1; int opt = 1;
s->pid = 0;
if ((s->server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) if ((s->server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return HTTP_SRS_SOCKET_FAILED; return HTTP_SRS_SOCKET_FAILED;
@@ -33,7 +31,7 @@ HttpServerRunStatus http_server_setup(HttpServer *s) {
0) 0)
return HTTP_SRS_BIND_FAILED; return HTTP_SRS_BIND_FAILED;
if (listen(s->server_fd, s->workers) < 0) if (listen(s->server_fd, s->backlog) < 0)
return HTTP_SRS_LISTEN_FAILED; return HTTP_SRS_LISTEN_FAILED;
return HTTP_SRS_SETUP; return HTTP_SRS_SETUP;
@@ -49,54 +47,85 @@ HttpServerRunStatus http_server_run(HttpServer *s) {
accept(s->server_fd, (struct sockaddr *)s->address, &addrlen)) < 0) accept(s->server_fd, (struct sockaddr *)s->address, &addrlen)) < 0)
return HTTP_SRS_ACCEPT_FAILED; return HTTP_SRS_ACCEPT_FAILED;
char request[BUFSIZ] = {0}; // Après chaque acceptation, nous créons un processus enfant pour gérer la
// requête, afin de pouvoir répondre à plusieurs requêtes simultanément.
if ((valread = recv(client_fd, request, 1024 - 1, MSG_PEEK)) < 0) s->pid = fork();
return HTTP_SRS_READ_FAILED; if (s->pid < 0) {
return HTTP_SRS_FORK_FAILED;
HttpRequest *req = handle_request(request); }
if (req == NULL)
return HTTP_SRS_HANDLE_REQUEST_FAILED; if (s->pid == 0) {
HttpResponse *res; char request[BUFSIZ] = {0};
if (!strcmp(req->path, "/")) { if ((valread = recv(client_fd, request, 1024 - 1, MSG_PEEK)) < 0)
res = from_file("./" DEFAULT_HTML); exit(HTTP_SRS_READ_FAILED);
} else if (!strncmp(req->path, "/cgi/", strlen("/cgi/"))) {
return cgi(req->path, client_fd); // L'objectif est de parser la requête dans une structure afin de non
} else { // seulement récuperer les informations utiles, mais aussi pour les
char path[] = "."; // manipuler aisément.
strcat(path, req->path); HttpRequest *req = handle_request(request);
res = from_file(path); if (req == NULL)
} exit(HTTP_SRS_HANDLE_REQUEST_FAILED);
// print_request(req);
// free_request(req); HttpResponse *res;
if (res == NULL) {
char *body = malloc(sizeof(char) * 128); // Si le chemin est la racine, nous retournons le fichier par défaut
body = "<!DOCTYPE html><html><body><h1>404 Not " // HTML.
"Found</h1></body></html>"; if (!strcmp(req->path, "/")) {
HttpResponse __res = { res = from_file("./" DEFAULT_HTML);
.status_code = HTTP_NOT_FOUND, }
.content_length = strlen(body), // Si on cherche à faire tourner un script python, nous pouvons le faire
.content_type = HTTP_CT_HTML, // en allant à http://<host>:<port>/cgi/<script>.py
.body = body, else if (!strncmp(req->path, "/cgi/", strlen("/cgi/"))) {
.body_in_heap = false, // Lorsqu'on essaie de libérer le body exit(cgi(req->path, client_fd));
// sur cette instance, le server crash. }
// Alors, on ne le libère pas et ça cause // Sinon l'url donnée peut correspondre à un fichier se trouvant dans
// des memory leaks. // l'arborescence du "current working directory".
}; else {
res = malloc(sizeof(__res)); char path[] = ".";
*res = __res; strcat(path, req->path);
res = from_file(path);
}
// Si jamais le fichier n'a pas été trouvé, alors nous retournons un 404
// NOT FOUND.
if (res == NULL) {
char *body = malloc(sizeof(char) * 128);
body = "<!DOCTYPE html><html><body><h1>404 Not "
"Found</h1></body></html>";
HttpResponse __res = {
.status_code = HTTP_NOT_FOUND,
.content_length = strlen(body),
.content_type = HTTP_CT_HTML,
.body = body,
.body_in_heap = false, // Lorsqu'on essaie de libérer le body
// sur cette instance, le server crash.
// Alors, on ne le libère pas et ça cause
// des memory leaks.
};
res = malloc(sizeof(__res));
*res = __res;
}
// Nous envoyons la réponse au client.
http_respond(res, client_fd);
free_response(res);
exit(HTTP_SRS_RUNNING);
} }
http_respond(res, client_fd);
free_response(res);
close(client_fd); close(client_fd);
return HTTP_SRS_RUNNING; return HTTP_SRS_RUNNING;
} }
HttpServerRunStatus http_server_stop(HttpServer *s) { HttpServerRunStatus http_server_stop(HttpServer *s) {
// Nous attendons que tous les processus enfant se termine avant de quitter
// le programme.
if (s->pid > 0) {
wait(NULL);
}
// Puis on ferme tout ce qui est nécessaire de fermer.
close(s->server_fd); close(s->server_fd);
if (s->address != NULL) if (s->address != NULL)

View File

@@ -1,9 +1,9 @@
#ifndef HTTP_SERVER_H #ifndef HTTP_SERVER_H
#define HTTP_SERVER_H #define HTTP_SERVER_H
// #include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/types.h>
#ifndef DEFAULT_HTML #ifndef DEFAULT_HTML
#define DEFAULT_HTML "index.html" #define DEFAULT_HTML "index.html"
@@ -24,15 +24,25 @@ typedef enum {
HTTP_SRS_WONT_HANDLE, HTTP_SRS_WONT_HANDLE,
} HttpServerRunStatus; } HttpServerRunStatus;
// Structure ayant pour objectif de stocker tout ce qui est nécessaire au bon
// fonctionnement du serveur.
typedef struct { typedef struct {
int port; int port; // Port du host
int server_fd; int server_fd; // File descriptor du server
unsigned int workers; unsigned int backlog; // Le nombre de clients possibles pouvant être en
struct sockaddr_in *address; // attente.
struct sockaddr_in *address; // L'adresse décrivant une socket internet.
pid_t pid; // Le pid du parent en attente de requêtes.
} HttpServer; } HttpServer;
// On setup le server, avec son file descriptor, ses options, le bind et le
// listen.
HttpServerRunStatus http_server_setup(HttpServer *s); HttpServerRunStatus http_server_setup(HttpServer *s);
// Si tout est ok, on peut lancer le server, tant qu'il tourne, il peut
// recevoir des clients.
HttpServerRunStatus http_server_run(HttpServer *s); HttpServerRunStatus http_server_run(HttpServer *s);
// Si pour quelques raisons il s'arrête, on free tout ce qu'il doit être
// free.
HttpServerRunStatus http_server_stop(HttpServer *s); HttpServerRunStatus http_server_stop(HttpServer *s);
#endif #endif

17
main.c
View File

@@ -1,18 +1,23 @@
#include "http/http_server.h" #include "http/http_server.h"
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
int main() { int main() {
HttpServer server = {.port = 8080, .workers = 3}; // Nous créons une variable server contenant certaines informations
if (http_server_setup(&server) != HTTP_SRS_SETUP) // sur le server HTTP que nous voulons utiliser, notamment son port.
exit(1); HttpServer server = {.port = 8080, .backlog = 3};
// On setup le server, avec son file descriptor, ses options, le bind et le
// listen.
HttpServerRunStatus status; HttpServerRunStatus status;
if ((status = http_server_setup(&server)) != HTTP_SRS_SETUP)
exit(status);
// Si tout est ok, on peut lancer le server, tant qu'il tourne, il peut
// recevoir des clients.
while ((status = http_server_run(&server)) == HTTP_SRS_RUNNING) while ((status = http_server_run(&server)) == HTTP_SRS_RUNNING)
; ;
printf("Status: %d\n", status);
// Si pour quelques raisons il s'arrête, on free tout ce qu'il doit être
// free.
http_server_stop(&server); http_server_stop(&server);
return 0; return 0;