Deep Dive into PandApache3: Code de lancement

Mary 🇪🇺 🇷🇴 🇫🇷 - Jul 8 - - Dev Community

Vous êtes-vous déjà demandé a quoi ressemble un serveur web de l’intérieur ? Avez-vous déjà rêvé d'en créer un vous-même ? Vous êtes au bon endroit !

Bienvenue dans ce premier article technique consacré au développement de PandApache3.

Cet article et les suivants ont pour ambition de vous décrire le fonctionnement interne de PandApache3, un serveur web léger et minimaliste prêt à concurrencer Apache2 (Ici, nous sommes pour la retraite à 29 annuité).

Ces articles ne sont pas une documentation. Ils ne seront pas mis à jour au fil des évolutions de PandApache3. Leur objectif est plutôt de partager et d'expliquer du code et des choix de design.
Certaines parties du code seront simplifiées pour être plus digestes, afin de faciliter la compréhension générale de notre projet.

Avant d’aller plus loin, vous ne connaissez pas PandApache3 ? Vous pouvez si vous le souhaitez en apprendre plus en listant ce précédant article : PandApache3, le tueur d'Apache


Mais par où commencer ? Décrire un projet à partir de zéro est toujours un défi. Nous nous concentrerons sur les éléments essentiels pour débuter, en évitant de nous perdre dans des détails qui pourraient dérouter les débutants. Nous commencerons par explorer la tâche la plus fondamentale d’un service : le démarrage.


Décollage : Les premiers pas de PandApache3

Cracheur De Feu Png vectors by Lovepik.com
Que fait PandApache3 au démarrage du service ? Avant même d'accepter des requêtes HTTP et d'écouter sur un port, plusieurs tâches doivent être effectuées. Dans cette partie, nous nous concentrons sur les actions entreprises avant que la première connexion au service puisse être établie.

Notre méthode de démarrage s'appelle StartServerAsync, c'est la toute première méthode appelée lorsque notre serveur est lancé.

public static async Task StartServerAsync()
{
    Logger.Initialize();

    Server.STATUS = "PandApache3 is starting";
    Logger.LogInfo($"{Server.STATUS}");

    ServerConfiguration.Instance.ReloadConfiguration();

    _ConnectionManager = new ConnectionManager();

    TerminalMiddleware terminalMiddleware = new TerminalMiddleware();
    RoutingMiddleware routingMiddleware = new RoutingMiddleware(terminalMiddleware.InvokeAsync, fileManager);
    LoggerMiddleware loggerMiddleware = new LoggerMiddleware(authenticationMiddleware.InvokeAsync);
    Func<HttpContext, Task> pipeline = loggerMiddleware.InvokeAsync;

    await _ConnectionManagerWeb.StartAsync(pipeline);


}

Enter fullscreen mode Exit fullscreen mode

La première étape consiste à initialiser notre logger. Le logger est une classe essentielle qui enregistre toutes les actions, erreurs et messages du serveur. C'est particulièrement crucial lors du démarrage, car il doit être prêt à signaler tout problème éventuel, comme illustré par l'enregistrement du statut "is starting" sur la troisième ligne.
Les informations de logs peuvent être disponibles à deux endroits selon la configuration choisie :

  • Dans des fichiers de logs, qui est la configuration classique des services. Un fichier PandApache3.log est créé et chaque événement y est enregistré.
  • Dans la console, ce qui est très utile pour voir les logs directement sur la sortie console ou le terminal, en complément ou en alternative aux fichiers de logs.

Ces deux options peuvent également être combinées, vous permettant ainsi de choisir comment vous souhaitez gérer vos logs selon vos besoins.


Entre nous

Pourquoi opter pour du NoLog ou des logs uniquement dans la console plutôt que dans un fichier ? À première vue, cela peut sembler étrange de ne pas conserver les logs dans un fichier. Cependant, cette décision est stratégique pour PandApache3, conçu pour être PaaS-friendly. Lorsque vous gérez une plateforme en tant que service (PaaS) avec des milliers d'instances, stocker les logs sur le serveur peut poser des problèmes d'accessibilité et d'espace disque. Il est donc plus judicieux de rediriger les logs générés par l'application depuis la console vers un système dédié tel que ADX ou Elastic Search.

Cette approche permet également d'obtenir rapidement des retours d'informations lors du développement de l'application.

Enfin, la possibilité d'utiliser NoLog avec PandApache3 (en désactivant l'écriture de logs à la fois dans le fichier et dans la console) est une conséquence directe de la flexibilité offerte par le service.


Plongée dans la configuration:

Fantasy Fantasy Sea Creature Png Télécharger Png vectors by Lovepik.com
Comme tout service, PandApache3 est configurable. Après l’initialisation des logs, le chargement de la configuration devient donc la seconde étape obligatoire à réaliser. Cette configuration, disponible sous la forme du fichier PandApache3.conf sur la machine, joue un rôle essentiel dans le comportement et les fonctionnalités de PandApache3.

public void ReloadConfiguration()
{
    string fullPath = Path.Combine(_configurationPath, "PandApache3.conf");
    if (!File.Exists(fullPath))
    {
        throw new FileNotFoundException("The configuration file didn't exist", fullPath);
    }

    try
    {
        foreach (var line in File.ReadLines(fullPath))
        {
            if (string.IsNullOrWhiteSpace(line) || line.Trim().StartsWith("#"))
            {
                continue;
            }
            else
            {
                var parts = line.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length == 2)
                {
                    var key = parts[0].Trim();
                    var value = parts[1].Trim();

                    MapConfiguration(key, value);
                }
            }

        }
        Logger.LogInfo("Configuration reloaded");
    }
    catch (Exception ex)
    {
        throw new Exception($"Error during configuration reload: {ex.Message}");
    }
}

public void MapConfiguration(string key, string value)
{
    var actionMap = new Dictionary<string, Action<string>>
    {
        ["servername"] = v => ServerName = v,
        ["serverip"] = v =>
        {
            if (IPAddress.TryParse(v, out var parsedIPAddress))
                ServerIP = parsedIPAddress;
            else
                Logger.LogWarning("Server IP invalid");
        },
        ["serverport"] = v => TrySetIntValue(v, val => ServerPort = val, "Server port invalid"),

    };

    if (actionMap.TryGetValue(key.ToLower(), out var action))
    {
        action(value);
    }
    else
    {
        Logger.LogWarning($"Unknown configuration key: {key}");
    }
}
Enter fullscreen mode Exit fullscreen mode

Cette fonction ReloadConfigurationcharge chaque ligne du fichier PandApache3.conf (hors commentaire), en associant chaque clé à une valeur. Puis, la fonction MapConfigurationdispose (Comme la constitution 🤓) d’un dictionnaire (actionMap) qui va permettre de mapper chaque clé, sur une action à réaliser avant d’associer la valeur a la variable de la classe.

Par exemple pour la ligne : ["servername"] = v => ServerName = v,
La clé du dictionnaire est servername et l’action associée est v => ServerName = v, où v représente la valeur. L’action est une fonction lambda qui affecte cette valeur à la propriété ServerName.

Maintenant équipé des informations nécessaires, notre serveur est prêt à démarrer selon les spécifications fournis et à nous renvoyer des retours d'informations en cas de problème. Passons à la prochaine étape : la gestion des connexions !


Entre nous

Une erreur de paramètre dans la configuration n’est pas bloquante ; une alerte sera émise, mais le service démarrera tout de même ! En cas d’absence de fichier de configuration, les paramètres par défaut de l’application seront utilisés.

Toujours entre nous

Pourquoi avoir choisi un fichier .conf au format texte plutôt que JSON ou YAML ? D’abord pour sa simplicité : rien de plus facile que d’écrire un premier fichier de configuration au format texte, contrairement à l’édition de JSON ou YAML qui peut poser problème sans un bon éditeur. De plus, le format texte accepte les commentaires, ce qui est très pratique pour l’auto-documentation du fichier de configuration. À l’avenir, il n’est pas exclu de supporter plusieurs formats de fichier pour gérer la configuration.


Le Cœur de PandApache3 : Le Gestionnaire de Connexions

Logo En Forme De Coeur Png vectors by Lovepik.com
Le cœur de notre serveur PandApache3 réside dans son gestionnaire de connexion, représenté par l'objet ConnectionManager.

_ConnectionManager = new ConnectionManager();

Enter fullscreen mode Exit fullscreen mode

Cet objet relativement simple possède deux attributs clés : le TcpListener et le pipeline.

public TcpListener Listener { get; set; }
private Func<HttpContext, Task> _pipeline;

Enter fullscreen mode Exit fullscreen mode

Le TcpListenerest un composant fondamental qui permet aux clients de se connecter à notre serveur via le protocole TCP. Quant à notre variable _pipeline, elle représente une fonction asynchrone qui prend en paramètre un contexte HTTP (HttpContext) et retourne une tâche (Task). De manière imagée, notre pipeline est une série d'actions que nous souhaitons exécuter sur chaque requête HTTP. Chaque action est exécutée par ce que l'on appelle un Middleware.

Justement, dans la suite de notre code nous mettons en place les middlewares à utiliser pour chaque requête HTTP reçue :

TerminalMiddleware terminalMiddleware = new TerminalMiddleware();
RoutingMiddleware routingMiddleware = new RoutingMiddleware(terminalMiddleware.InvokeAsync);
LoggerMiddleware loggerMiddleware = new LoggerMiddleware(authenticationMiddleware.InvokeAsync);
Func<HttpContext, Task> pipeline = loggerMiddleware.InvokeAsync;
Enter fullscreen mode Exit fullscreen mode

Nous avons donc trois middlewares ici :

  • TerminalMiddleware
  • RoutingMiddleware
  • LoggerMiddleware

Chaque middleware appelle le suivant dans une chaîne bien définie (Logger appelle Routing, puis Routing appelle Terminal). Cette chaîne de middlewares (notre pipeline) est affectée à notre gestionnaire de connexion (ConnectionManager).

Maintenant que tout est en place, nous pouvons démarrer notre gestionnaire de connexion :

await _ConnectionManagerWeb.StartAsync(pipeline);

Enter fullscreen mode Exit fullscreen mode

La fonction StartAsyncconfigure simplement notre TcpListenerpour écouter sur le port et l'adresse IP définis dans la configuration, puis le met en marche :

public async Task StartAsync(Func<HttpContext, Task> pipeline)
{
    Listener = new TcpListener(ServerConfiguration.Instance.ServerIP, ServerConfiguration.Instance.ServerPort);
    Logger.LogInfo($"Web server listening on {ServerConfiguration.Instance.ServerIP}:{ServerConfiguration.Instance.ServerPort}");
    Listener.Start();
    _pipeline = pipeline;
}

Enter fullscreen mode Exit fullscreen mode

Voila, notre serveur est désormais démarré et prêt à recevoir des connexions entrantes.


Entre nous

Ce que font les middlewares n'est pas crucial pour le moment. Ce qu'il faut retenir, c'est que notre ConnectionManager, chargé de traiter les connexions reçues sur son TCP listener, les fera passer toutes à travers cette chaîne de middlewares et dans cet ordre.
Cependant, les noms sont assez explicites et vous pouvez deviner le rôle de chaque middleware :

  • Logger : Enregistre la requête entrante dans les logs.
  • Routing : Dirige la requête vers la bonne ressource.
  • Terminal : Le dernier middleware dans la chaîne, qui ne fait rien de particulier mais qui est là.

Toujours entre nous

Une requête qui traverse les middlewares le fait à l'aller, mais aussi au retour (dans le sens inverse). Dans notre exemple, cela signifie que la requête est d'abord loggée par le premier middleware, puis la réponse obtenue est également loggée par ce même middleware devenu le dernier dans la chaîne.


Merci infiniment d'avoir exploré les coulisses de PandApache3 avec moi ! Vos réflexions et votre soutien sont essentiels pour faire évoluer ce projet. 🚀
N'hésitez pas à partager vos idées et impressions dans les commentaires ci-dessous. Je suis impatient d'échanger avec vous !

Suivez mes aventures sur Twitter @pykpyky pour rester à jour sur toutes les nouvelles.

Vous pouvez également découvrir le projet complet sur GitHub et me rejoindre lors de sessions de codage en direct sur Twitch pour des sessions passionnantes et interactives. À bientôt derrière l'écran !


. . . . . . . . . . . . . . . . . . . . . . . . . .