Implémenter notre API

Comment implémenter facilement notre API ? La réponse ici !

Cloner le repository de notre API

Avant de commencer à implémenter notre API, vous trouverez son code source sur GitHub. Vous pouvez le clonner comme ceci :

$ git clone git@github.com:bref/bref-api.git
$ cd bref-api
$ git checkout stable

Utiliser le repository git en submodule

Le repository de notre api est prévu pour être utilisé en tant que submodule et c'est que nous faisons dans notre propre Zia. Par exemple nous voulons l'utiliser dans le dossier ./src/api, par rapport à la racine de notre repository :

$ git submodule add git@github.com:bref/bref-api.git src/api
$ cd src/api
$ git checkout stable
$ cd ../..
$ git add src/api
$ git commit -m "Add bref API"

Plus d'infos sur les submodules »

Le logger est représenté sous 2 formes :

L'interface ILogger

C'est une interface classique pour un logger.

On peut mettre un niveau de log général au logger et logger des messages d'une certaine sévérité grâce à la méthode log(severity, msg).

Seul les messages d'une sévérité supérieur ou égale à celle du logger seront réellement logger.

void example(ILogger *logger)
{
  // On dit au logger de ne logger qu'à partir d'une sévérité
  // de type ILogger::Error ou supérieur
  logger->setSeverity(ILogger::Error);

  // Debug étant un niveau inférieur à Error, le logger va l'ignorer
  logger->log(ILogger::Debug, "un message de debug");

  // C'est une erreur, le logger log le message
  logger->log(ILogger::Error, "un message d'erreur");
}

Il vous reste donc qu'à implémenter votre classe de log héritant de l'interface ILogger.

ScopedLogger et macros

Les macros de log sont construites sur le modèle de :

#define LOG(logger, sev) bref::ScopedLogger(logger, sev).log()
#define LOG_DEBUG(logger) LOG(logger, bref::ILogger::Debug)

En réalité, afin de réduire l'utilisation de ressources en log ignoré, la macro LOG teste le niveau de log avant de construire un ScopedLogger et de l'utilisation de streams.

La classe ScopedLogger est déjà implémentée, sont utlisation couplée au macros est très simple, même transparente.

void myHandler(ILogger *logger)
{
  logger->setSeverity(ILogger::Warning);
  // Grâce à la macro aucune stringstream n'est crée ici
  LOG_DEBUG(logger) << "Un message de debug";
  // Ici la stringstream est crée et le message loggé
  LOG_WARN(logger) << "Un message de warning";
}

La méthode log de votre future classe de log sera appelée à la destruction du ScopedLogger, avec en paramètre le niveau de log fourni ainsi que le message à logger.

Nous somme partis du fait qu'il nous fallait un conteneur pour notre configuration YAML, nous avons donc créé celui-ci en pensant à l'arborescence de celle-ci, c'est pourquoi celui-ci intègre à la fois deux types : BrefValueList et BrefValueArray, respectivement une std::list et une std::map de BrefValue. Au final ce conteneur est

Types

Notre conteneur générique permet de stocker les types suivants :

  • std::string ;
  • bool ;
  • int ;
  • BrefValueList : correspondant à une std::list<bref::BrefValue> ;
  • BrefValueArray : correspondant à une std::map<std::string, bref::BrefValue>.

Implémentation

Pour l'implémenter, rien de plus simple, développez :

  • Les méthodes de modification de contenu :
    • Les setters, par exemple : void setNull() ;
    • Une méthode pour pusher dans la BrefValueList : void push(const bref::BrefValue &node) ;
    • Un operator[] pour le BrefValueArray ;
  • Les méthodes de test du type contenu, par exemple : bool isNull() const ;
  • les méthodes de récuperation de contenu, par exemple : bool asBool() const.

Avoir un conteneur générique, c'est bien, mais implémenter des méthodes pour le parcourir à chaque fois qu'on en a besoin, ça l'est moins. C'est pourquoi nous avons pensé inclure des méthodes d'aide au parcours de nos BrefValue pour le corps du serveur, mais aussi les modules.

Exemple

Pour mieux comprendre, voici un exemple simple : vous voulez récupérer une variable DocumentRoot dans votre configuration.

Nous avons, ici un extrait de configuration (et oui, en YAML, parcequ'on aime ça) :

DocumentRoot: /var/www
VirtualHosts:
  - ServerName: example.com
    DocumentRoot: /home/vhosts/example.com
  - ServerName: example.fr
    DocumentRoot: /home/vhosts/example.fr
  - ServerName: example.net

En fonction du VirtualHost où que l'on utilise, la racine de celui-ci n'est pas la même, il nous faut donc des méthodes pour récupérer la valeur la plus pertinente, par exemple, le DocumentRoot du VitualHost pour l'host example.com (/home/vhosts/example.com) ou le "général" pour l'host example.net (/var/www).

Utilisation

Cherchant soit une variable de configuration générique, soit une variablde configuration dépendant du contexte d'une requête HTTP, nous avons donc deux fonctions dans notre interface IConfHelper :

virtual const BrefValue & findValue(std::string const & key) = 0;
virtual const BrefValue & findValue(std::string const & key,
                                    HttpRequest const & request) = 0;

Nous pouvons par exemple récupérer les deux DocumentRoot de l'exemple ci-contre : notre fonction findValue utilisera le header HTTP Host de la requête et la variable de configuration ServerName afin de déterminer quel DocumentRoot retourner.

Ayant un BrefValue dans notre API, nous avons tout simplement créer deux classes, HttpRequest et HttpResponse, héritant toutes les deux d'une map de BrefValue, et incluant des getters et setters spécifiques à une requête et une réponse HTTP.