Implémenter un module

Nous allons voir commenter implémenter facilement un module dans cette section.

Introduction

Les différents types de modules

Il existe 6 types de modules :

  • Core
  • Logger
  • Config Loader
  • Network
  • File Serve
  • HTTP Module

Ils dérivent tous d'une interface / abstract. Une section sera dédiée pour l'implémentation de chacun de ces modules.

Core

#ifndef API_V2_ICORE_HPP
#define API_V2_ICORE_HPP

#include <vector>
#include "IModule.hpp"

class IModuleLoader;
class IConfig;

namespace Module {

     class ICore {
     public:
         virtual bool Run(std::vector<std::string> const &opts, IModuleLoader *moduleLoader) = 0;

         virtual IConfig const &GetConfig() const = 0;

     public:
         virtual IModule *Get(std::string const &name) const = 0;

         virtual IModule *Get(IModule::Type type) const = 0;

         };
     }
    #endif //API_V2_ICORE_HPP

Le module Core représente le coeur du programme. Il va être le premier module chargé, et s'occuper de lancer tous les modules obligatoires. Il va aussi se charger de lancer le module Config Loader.

Run()
Permet de lancer le fonctionnement du Core. Il requiert les arguments du main, ainsi qu'un Module Loader.

GetConfig()
Permet de récupérer la configuration actuelle du Core.

Get()
Permet de récupérer un module du Core. Il est possible de retrouver un module par son nom, ou encore son Enum.

Logger

#ifndef API_V2_ILOGGING_HPP
#define API_V2_ILOGGING_HPP

#include <string>

 namespace Module {
     class ILogger {
     public:
         enum Type {
             Emergency,
             Alert,
             Critic,
             Error,
             Warning,
             Notice,
             Info,
             Debug
         };

         virtual void Log(std::string const &msg, Type type = Type::Info) const = 0;

    };
}

#endif //API_V2_ILOGGING_HPP

Ce module permet de tracer l'activité de chaque module couramment chargé par le Core.

enum Type
Permet de spécifier le degré d'importance des messages affichés (Debug étant de basse priorité, et Emergency de niveau critique).

Log()
Affiche des messages, en prenant en compte leur priorité définie précédement.

Config Loader

#ifndef API_V2_ICONFIGLOADER_HPP
#define API_V2_ICONFIGLOADER_HPP

#include <string>

class IConfig {};

namespace Module {
    class IConfigLoader {
    public:
        virtual IConfig *LoadConfig(std::string const &path) = 0;
    };
}

#endif //API_V2_ICONFIGLOADER_HPP

Ce module va s'occuper de charger la configuration par défault, ainsi que celle du fichier s'il est fourni.

LoadConfig()
Charge la configuration située au chemin passé en argument, et renvoie un handler sur celle-ci.

Network

#ifndef API_V2_INETWORK_HPP
#define API_V2_INETWORK_HPP

namespace HTTP {
    class Request;

    class Response;

    class ProcessingList;
}

namespace Module {
    class INetwork {
    public:
        virtual bool Start(HTTP::Request *req, HTTP::Response *res, HTTP::ProcessingList *pl) = 0;

	virtual void Poll() = 0;

	virtual void Run() = 0;
    };
}

#endif //API_V2_INETWORK_HPP

Ce module va permettre toute la gestion des connexions réseau.

Start()
Permet de lancer le réseau.

Poll()
Gestion du réseau de manière non bloquante.

Run()
Gestion du réseau de manière bloquante.

File Serve

#ifndef API_V2_IFILESERVE_HPP
#define API_V2_IFILESERVE_HPP

#include <string>
#include <fstream>

namespace Module {
    class IFileServe {
    public:
        virtual unsigned int GetFileSize(std::string const &path) const = 0;

        virtual std::ifstream &GetFile(std::string const &path) const = 0;

        virtual char *GetFileByRange(unsigned int offset, unsigned int size) const = 0;
    };
}
#endif //API_V2_IFILESERVE_HPP

Ce module va chercher des fichiers en fonction des chemins qui lui sont fournis.

GetFileSize()
Récupère la taille du fichier situé au chemin donné en paramètre.

GetFile()
Récupère le fichier situé au chemin donné en paramètre, sous la forme d'un stream.

GetFileByRange()
Renvoie un ensemble de caractères défini par un début et une taille.

HTTP Handle

#ifndef API_V2_IMODULE_HPP
#define API_V2_IMODULE_HPP

#include <string>

namespace Module {
    class ICore;

    class IHTTPHandle {
    public:
        virtual bool Handle(HTTP::Request *req, HTTP::Response *res, HTTP::ProcessingList *pl) = 0;
    };

}

#endif //API_V2_IMODULE_HPP

Il s'occupe de la gestion des modules liés au protocole HTTP.

Handle()
Il s'agit du callback appelé à chaque fois que le module est invoqué par la processing list.

Exemple d'implémentation

Maintenant que vous connaissez les caractériques de chaque module, voyons à présent une implémentation concrète d'un module avec la SaltAPI.

Tout d'abord, créons une classe HelloWord qui nous servira d'exemple. Son but va être de modifier le header en ajoutant le contenu Content-type: text/plain, changer le body par "HelloWorld", et finalement changer le status code à 200.

Étant sur Windows, il ne faut pas oublier deux choses: nous allons créer des librairies dynamiques, ce qui implique d'ajouter un extern "C" afin de correctement récupérer les symboles, et ajouter EXPORT en incluant le fichier "DllExport.hpp".

#include "Module/IHTTPHandle.hpp"
#include "Module/AModule.hpp"
#include "DllExport.hpp"

extern "C"
{
    class EXPORT HelloWorld : public Module::IHTTPHandle, public Module::AModule
    {
    public:
        HelloWorld(Module::ICore &core);

        virtual ~HelloWorld();
    };

    Module::IModule	*LoadModule(Module::ICore &core);
}

Une étape importante est l'implémentation du contructeur de la classe parent, Module::AModule. En effet il va à la fois déterminer le rôle de notre module, ainsi que son nom afin de plus facilement le retrouver. Le notre interviendra en tant que module HTTP, et s'appelera "HelloWorld".

HelloWorld::HelloWorld(Module::ICore &core): Module::AModule(core, HTTP, "HelloWorld") {}

Implémentons maintenant la méthode bool Handle(HTTP::Request *req, HTTP::Response *res, HTTP::ProcessingList *pl); (implémentée dans la classe Module::IHTTPModule). Celle-ci est très succinte :

bool HelloWorld::Handle(HTTP::Request *req, HTTP::Response *res, HTTP::ProcessingList *pl) {
    res->GetHeader().Add("Content-Type", "text/plain");
    res->SetBody("HelloWorld");
    res->SetStatusCode(200);
    return true;
}

Seule une étape nous sépare maintenant d'un module fonctionnel ! Il nous manque la méthode capable de cloner notre module, dont la signature Module::IModule *GetModule(Module::ICore &core) const; est implémentée dans Module::AModule. Copiez les valeurs nécessaires à la construction (dans l'exemple nous n'avons pas d'attributs).

Module::IModule* HelloWorld::GetModule(Module::ICore &core) const {
    return new HelloWorld(core);
}

Félicitations ! Votre module est désormais fonctionnel. Un petit récapitulatif :

HelloWorld.hpp

#ifndef API_V2_HELLOWORLD_HPP
#define API_V2_HELLOWORLD_HPP

#include "Module/IHTTPHandle.hpp"
#include "Module/AModule.hpp"
#include "DllExport.hpp"

extern "C"
{
    class EXPORT HelloWorld : public Module::IHTTPHandle, public Module::AModule
    {
    public:
        HelloWorld(Module::ICore &core);

        virtual ~HelloWorld();

    public:
        bool Handle(HTTP::Request *req, HTTP::Response *res, HTTP::ProcessingList *pl);

    public:
        Module::IModule *GetModule(Module::ICore &core) const override;
    };

    Module::IModule	*LoadModule(Module::ICore &core);
}

#endif //API_V2_HELLOWORLD_HPP

HelloWorld.cpp

#include "HelloWorld.hpp"
#include "HTTP/Request.hpp"
#include "HTTP/Response.hpp"
#include "HTTP/ProcessingList.hpp"

HelloWorld::HelloWorld(Module::ICore &core): Module::AModule(core, HTTP, "HelloWorld") {

}

HelloWorld::~HelloWorld() {

}

bool HelloWorld::Handle(HTTP::Request *req, HTTP::Response *res, HTTP::ProcessingList *pl) {
    res->GetHeader().Add("Content-Type", "text/plain");
    res->SetBody("HelloWorld");
    res->SetStatusCode(200);
    return true;
}

Module::IModule* HelloWorld::GetModule(Module::ICore &core) const {
    return new HelloWorld(core);
}

Module::IModule	*LoadModule(Module::ICore &core) {
	return new HelloWorld(core);
}