From 31e42739033878630877798001f7b7e45eb4d6bd Mon Sep 17 00:00:00 2001 From: HugoDrl Date: Sun, 28 Dec 2025 14:12:31 +0100 Subject: [PATCH] transfer local --- .gitignore | 1 + README.md | 1 + go.mod | 3 ++ main.go | 55 +++++++++++++++++++++++ src/parser/main.go | 66 ++++++++++++++++++++++++++++ src/router/definition.go | 27 ++++++++++++ src/router/init.go | 35 +++++++++++++++ src/router/main.go | 95 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 283 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 go.mod create mode 100644 main.go create mode 100644 src/parser/main.go create mode 100644 src/router/definition.go create mode 100644 src/router/init.go create mode 100644 src/router/main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1e32c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.git/* \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e601e59 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +salut diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..066db91 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module httpRouter + +go 1.25.4 diff --git a/main.go b/main.go new file mode 100644 index 0000000..1970e82 --- /dev/null +++ b/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "httpRouter/src/router" + "net" + "time" +) + +func main() { + //Création du socket d'écoute + server, err := net.Listen("tcp", "localhost:4000") + + if err != nil { + fmt.Printf("Erreur : %s", err.Error()) + } + defer server.Close() + + //Initialisation du routeur + router := router.NewRouter() + router.Register("GET", "/hello", sendHello) + router.Register("GET", "/time", sendTime) + router.Register("GET", "/time/{time}", hello) + router.Register("GET", "/time/{day}/{hour}/{minute}", hello) + + //Écoute et gestion des réception + for { + //Accepter une nouvelle connexion + conn, err := server.Accept() + + //Gestion des erreurs s'il y en a (stop total) + if err != nil { + fmt.Printf("Erreur : %s\n", err.Error()) + break + } + + fmt.Printf("Nouvelle connection acceptée !\n") + + go router.HandleClient(conn) + } + +} + +func sendHello(method, path string, args map[string]string, queries map[string]string) (int, string) { + return 200, "Hello World !" +} + +func sendTime(method, path string, args map[string]string, queries map[string]string) (int, string) { + return 200, fmt.Sprintf("%q", time.Now()) +} + +func hello(method, path string, args map[string]string, queries map[string]string) (int, string) { + fmt.Printf("%w\n", queries) + return 200, fmt.Sprintf("%w", args) +} diff --git a/src/parser/main.go b/src/parser/main.go new file mode 100644 index 0000000..05b2f2e --- /dev/null +++ b/src/parser/main.go @@ -0,0 +1,66 @@ +package parser + +import ( + "bufio" + "fmt" + "strings" +) + +func ReadRequest(reader *bufio.Reader) ([]string, string, error) { + var message []byte + + buffer := make([]byte, 8) + + for { + if reader.Size() == 0 { + break + } + charsRed, err := reader.Read(buffer) + if err != nil { + fmt.Printf("Erreur : %s\n", err.Error()) + break + } + + message = append(message, buffer[:charsRed]...) + + if charsRed < 8 { + break + } + } + + parts := strings.Split(string(message), "\r\n\r\n") + + headerLines := strings.Split(parts[0], "\r\n") + body := parts[1] + + if len(parts) != 2 { + return nil, "", fmt.Errorf("Wrong") + } + + return headerLines, body, nil +} + +func ParseURL(path string) (string, map[string]string, error) { + //Décomposition du chemin et des paramètres + url := strings.Split(path, "?") + + //Gestion d'une URL invalide + if len(url) == 0 || len(url) > 2 || url[0] == "" { + return "BAD REQUEST", nil, fmt.Errorf("Mauvaise URL") + } + + parameters := make(map[string]string) + + //Gestion des paramètres s'il y en a + if len(url) > 1 { + slices := strings.Split(url[1], "&") + for _, slice := range slices { + elements := strings.Split(slice, "=") + if len(elements) != 2 || elements[0] == "" || elements[1] == "" { + return "BAD REQUEST", nil, fmt.Errorf("Mauvaise gestion des paramètres") + } + parameters[elements[0]] = elements[1] + } + } + return url[0], parameters, nil +} diff --git a/src/router/definition.go b/src/router/definition.go new file mode 100644 index 0000000..f1d67f8 --- /dev/null +++ b/src/router/definition.go @@ -0,0 +1,27 @@ +package router + +import ( + "sync" +) + +// Interface de fonction pour les routes statiques +type HandlerFunc func( + method string, + path string, + args map[string]string, + queries map[string]string, +) (int, string) + +type dynamicRoute struct { + method string + path string + elements []string + function HandlerFunc +} + +// Définition du routeur +type Router struct { + lock sync.RWMutex + staticRoutes map[string]HandlerFunc + dynamicRoutes []dynamicRoute +} diff --git a/src/router/init.go b/src/router/init.go new file mode 100644 index 0000000..2df09a5 --- /dev/null +++ b/src/router/init.go @@ -0,0 +1,35 @@ +package router + +import ( + "fmt" + "strings" +) + +// Création d'un routeur +func NewRouter() *Router { + return &Router{ + staticRoutes: make(map[string]HandlerFunc), + dynamicRoutes: make([]dynamicRoute, 0), + } +} + +// Ajout de nouvelles routes +func (router *Router) Register(method, path string, handler HandlerFunc) { + //Vérouille en écriture la map + router.lock.Lock() + defer router.lock.Unlock() + + //On vérifie si le chemin est dynamique, si tel est le cas on push dans dynamicRoutes + if strings.Contains(path, "{") && strings.Contains(path, "}") { + router.dynamicRoutes = append(router.dynamicRoutes, dynamicRoute{ + method: method, + path: path, + elements: strings.Split(path, "/"), + function: handler, + }) + //Si le chemin ne contient pas de paramètres, alors on l'ajoute dans staticRoutes + } else { + router.staticRoutes[fmt.Sprintf("%s %s", method, path)] = handler + } + +} diff --git a/src/router/main.go b/src/router/main.go new file mode 100644 index 0000000..830660a --- /dev/null +++ b/src/router/main.go @@ -0,0 +1,95 @@ +package router + +import ( + "bufio" + "fmt" + "httpRouter/src/parser" + "net" + "strings" +) + +func (router *Router) HandleClient(conn net.Conn) error { + //Création du lecteur + reader := bufio.NewReader(conn) + + requestHeader, _, err := parser.ReadRequest(reader) + + if err != nil { + return err + } + + response := buildResponse(router.Handle(strings.Split(requestHeader[0], " ")[0], strings.Split(requestHeader[0], " ")[1])) + + conn.Write([]byte(response)) + + conn.Close() + return nil +} + +// Recherche et éxécution des routes sauvegardées +func (router *Router) Handle(method, path string) (int, string) { + + endpoint, parameters, err := parser.ParseURL(path) + + if err != nil { + return 400, endpoint + } + + //Vérouille en lecture la map + router.lock.RLock() + defer router.lock.RUnlock() + + fmt.Printf("%w\n", router.staticRoutes) + fmt.Printf("%s - %s", method, path) + //Vérification de l'existence de la route dans les routes statiques enregistrées + Handler, exists := router.staticRoutes[fmt.Sprintf("%s %s", method, endpoint)] + if exists { + return Handler(method, path, make(map[string]string), parameters) + } + + elements := strings.Split(endpoint, "/") + + //Vérification de la cohérence du chemin avec chaque route dynamique (O(n)) + for _, route := range router.dynamicRoutes { + if len(elements) == len(route.elements) && route.method == method { + //On récupère les sous parties du chemin dynamique pour les comparer avec le chemin découpé + dynPath := route.elements + args := make(map[string]string) + //Pour chacun des sous ensembles on compare un a un les éléments + for index, item := range dynPath { + //S'il n'y a pas égalité stricte et que l'élément n'est pas un paramètre, on passe directement au prochain chemin dynamique + if elements[index] != item && (item[0] != '{' || item[len(item)-1] != '}') { + break + //Sinon si le chemin est un paramètre alors on ajoute dans le dictionnaire des paramètres la valeur du sous élément d'entrée + } else if len(item) > 0 && item[0] == '{' && item[len(item)-1] == '}' { + args[item[1:len(item)-1]] = elements[index] + } + //Si on a pas échappé à la boucle et qu'on est arrivés à la fin, c'est que c'était le bon chemin, on exécute la fonction associée + if index == len(elements)-1 { + return route.function(method, path, args, parameters) + } + } + } else { + continue + } + } + //Si aucun chemin n'a été validé, c'est qu'il n'est pas renseigné dans le routeur + return 404, "NOT FOUND" +} + +// Logique de code +func buildResponse(code int, message string) string { + var codes = map[int]string{ + 200: "OK", + 400: "WRONG REQUEST", + 404: "NOT FOUND", + } + + response := fmt.Sprintf("HTTP/1.1 %d %s", code, codes[code]) + response += "Content-Type: text/plain\r\n" + response += fmt.Sprintf("Content-Length: %d\r\n", len(message)) + response += "\r\n" + response += message + + return response +}