Implémentation de micro-service et RESTAPI en C ++

Cet article n’est pas parfait, il contiendrait des fautes d’ortographe et erreurs dans les codes, priez de me contacter pour la correction.

De nos jours, il y a beaucoup de buzz autour de node.js dans le domaine des micro-services en raison de sa productivité et de la grande communauté qui le supporte, et en effet node.js est vraiment une plate-forme puissante pour le développement du cloud computing, mais n’oubliez pas que toute sa productivité relais sur différentes couches d’abstraction qui reduissent sa performance. Mais je ne veux pas commencer ici une polémique sur lequel est le meilleur, cet article concerne l’utilisation du C ++ moderne, si vous êtes comme moi qui adore le C ++ et ses excellentes performances grâce à son niveau zéro d’abstractions, alors cet article est pour vous.

Pour implémenter le micro-service en C ++ avec moins de code, nous utiliserons une excellente bibliothèque de Microsoft appelée le SDK C ++ REST , il s’agit d’une bibliothèque multiplateforme ce qui signifie qu’elle fonctionne sur Linux, macOS et Windows, cette bibliothèque utilise des C ++ 11 et fournit tous les avantages d’E/ S de thread et de réseau dont nous avons besoin pour implémenter un micro-service complet qui expose une interface REST.

Pour écrire et déboguer le code, nous utiliserons Visual Studio Code (VSC), mais vous pouvez utiliser Xcode sur macOS ou Visual Studio sur Windows, vim, Emacs, etc. tout ce que vous voulez car le projet ne dépend d’aucun IDE .

Conditions préalables

$ brew update 
$ brew install cmake openssl boost
/ usr / local / Cellar / boost / 1.60.0_1
/ usr / local / Cellar / cmake / 3.4.2
/ usr / local / Cellar / openssl / 1.0.2f

À propos du REST SDK C ++

Il s’agit d’une bibliothèque open source multi-plateforme de Microsoft qui vous permet de créer des clients REST asynchrones au-dessus du runtime d’accès concurrentiel sous namespace pplx, mais cela vous permet également de créer des serveurs REST. Cette bibliothèque est adaptative, ce qui signifie qu’en fonction du système d’exploitation que vous compilez, elle prend en charge les implémentations natives.Par exemple, la classe http_listener est implémentée au-dessus des ports d’achèvement IO (IOCP), ce qui sur macOS, cela se fait via kqueue appel système, sous Linux, cela se fait via l’ appel système epoll et sous Windows via CreateIoCompletionPort mais vous n’avez pas besoin de tout savoir sur ces API IO car le SDK C ++ REST s’en charge car il repose en outre fortement sur la bibliothèque boost asio via une seule classe appelée http_listener , mais ce n’est qu’un exemple de l’adaptabilité du bibliothèque, la même chose s’applique pour les threads, les sockets, l’encodage de caractères, etc.

Nous utilisons le SDK C ++ REST pour créer un serveur multi-thread qui expose une API REST et qui peut parfaitement fonctionner comme base d’un micro-service complet, capable d’être utilisé sur toute implémentation backend d’architecture de micro-service sérieuse, en particulier sur les micro-services critiques et de haute performance.

Implémentation de micro-services

L’architecture de base:

où un contrôleur fournit essentiellement les gestionnaires pour chacune des méthodes HTTP suivantes:

pour plus d’informations sur chaque méthode, veuillez consulter ici .

Le contrôleur utilise en interne la classe web :: http :: experimental :: listener :: http_listener qui permet essentiellement au service d’écouter les requêtes HTTP.

Une fois qu’une demande est reçue, le http_listener vérifie est une demande HTTP valide et que la méthode HTTP est prise en charge par le service, si tel est le cas, traite la demande au contrôleur pour un traitement ultérieur dans un thread différent de celui qui démultiplexe la demande d’origine , cela vous permet d’exécuter la logique de la requête dans un thread d’exécution distinct, laving http_listener prêt à accepter plus de requête (s).

Pour que http_listener sache quel gestionnaire appeler sur le contrôleur, vous devez enregistrer vos gestionnaires avec http_listener . Pour cela, nous avons la classe MicroserviceController qui dérive de BasicController et ControllerBasicController fournit un wrapper autour de http_listener et des méthodes pratiques pour lire la chaîne de point de terminaison et décider si le service doit déterminer l’adresse IP de l’hôte, que ce soit IP4 ou IP6, ou vous peut transmettre un nom de domaine ou une adresse IP fixe; et le contrôleur fournit une interface que le contrôleur concret dans ce casMicroserviceController implémente pour chaque méthode HTTP, alors ouvrez le fichier microsvc_controller.hpp et vous verrez le code suivant:

#pragma once#include <basic_controller.hpp>using namespace cfx;class MicroserviceController : public BasicController, Controller {
public:
MicroserviceController() : BasicController() {}
~MicroserviceController() {}
void handleGet(http_request message) override;
void handlePut(http_request message) override;
void handlePost(http_request message) override;
void handlePatch(http_request message) override;
void handleDelete(http_request message) override;
void initRestOpHandlers() override;
};

vous pouvez voir dans le code ci-dessus que MicroserviceController remplace la méthode BasicController :: initRestOpHandlers () et toutes les méthodes de l’ interface Controller . Maintenant, si vous ouvrez le fichier * .cpp correspondant, vous verrez le code qui enregistre les gestionnaires sur l’ interface du contrôleur avec le http_listener représenté par la variable membre _listener :

#pragma once#include <basic_controller.hpp>using namespace cfx;
class MicroserviceController : public BasicController, Controller {
public:
MicroserviceController() : BasicController() {}
~MicroserviceController() {}
void handleGet(http_request message) override;
void handlePut(http_request message) override;
void handlePost(http_request message) override;
void handlePatch(http_request message) override;
void handleDelete(http_request message) override;
void initRestOpHandlers() override;
};

la magie liaison du binding est effectué par le std :: bind modèle de fonction qui génère une enveloppe d’appel de renvoi pour une fonction membre non-statique, où , pour toutes les fonctions membres qu’un seul argument est nécessaire, d’ où le std::placeholders::_1 ‘ argument à la fin de chaque déclaration.

Une fois correctement enregistrés nos gestionnaires plus loin dans le code sur le même fichier microscv_controller.cpp vous pouvez voir l’implémentation de chaque méthode:

void MicroserviceController::handleGet(http_request message) {
auto path = requestPath(message);
if (!path.empty()) {
if (path[0] == “service” && path[1] == “test”) {
auto response = json::value::object();
response[“version”] = json::value::string(“0.1.1”);
response[“status”] = json::value::string(“ready!”);
message.reply(status_codes::OK, response);
}
}
message.reply(status_codes::NotFound);
}

dans le code ci-dessus, nous pouvons voir l’implémentation du gestionnaire pour la méthode HTTP GET. Chaque fois qu’une requête GET est envoyée au service, http_listener appellera cette méthode de classe sur notre classe MicroserviceController , et la même chose pour chaque gestionnaire que nous enregistrons avec http_listener.

Le code ci-dessus passe le message d’ entrée via la méthode de classe requestPath qui extrait essentiellement le chemin de la requête du message et renvoie un std :: vector <string> qui contient chaque section du chemin afin que nous puissions évaluer l’API REST demandée et son entrée paramètres, dans ce cas, la requête devrait être quelque chose comme ceci:

curl -k -i -X GET -H "Content-Type:application/json" http://<host_ip>:6502/v1/ivmero/api/service/test

Sachez que <host_ip> n’est qu’un espace réservé, plus loin dans cet article, je parlerai de la façon de spécifier la chaîne de point de terminaison afin que le micro-service puisse résoudre automatiquement l’adresse IP de l’hôte.

et renvoie une réponse JSON comme celle-ci, si la requête aboutit:

{ 
"Status": "ready!",
"Version": "0.1.1"
}

ou un code d’état HTTP 404 (non trouvé) si le chemin de la requête ne correspond à aucune API REST prise en charge par le service:

HTTP / 1.1 404 introuvableContent-Length: 0

La réponse est renvoyée à l’appelant via l’ instance de message d’ entrée de type http_request qui a une référence à l’appelant.

Initialisation du service

L’initialisation est située sur le fichier main.cpp alors parcourons le code:

#include <iostream>#include <usr_interrupt_handler.hpp>
#include <runtime_utils.hpp>#include “microsvc_controller.hpp”using namespace web;
using namespace cfx;int main(int argc, const char * argv[]) {
InterruptHandler::hookSIGINT();
MicroserviceController server;
server.setEndpoint(“http://host_auto_ip4:6502/v1/ivmero/api");

try {
// wait for server initialization…
server.accept().wait();
std::cout << “Copyright © ivmeroLabs 2016. ... now listening for requests at: “ << server.endpoint() << ‘\n’;

InterruptHandler::waitForUserInterrupt(); server.shutdown().wait();
}
catch(std::exception & e) {
std::cerr << “erreur! :(“ << ‘\n’;
}
catch(…) {
RuntimeUtils::printStackTrace();
} return 0;
}

il existe quelques fichiers d’en-tête usr_interrupt_handler.hpp qui contiennent du code à gérer lorsque l’utilisateur tape Ctrl + C sur le terminal où le service est en cours d’exécution et runtime_utils.hpp qui contient du code pour imprimer la trace de la pile lorsqu’une exception inattendue se produit. Ces fichiers se trouvent sous / micro-service / foundation / include.

Dans la première ligne de code, nous accrochons l’interruption SIGINT pour gérer Ctrl + C.

InterruptHandler::hookSIGINT();

Ensuite, nous créons et initialisons le service avec le point de terminaison où il écoutera les demandes:

MicroserviceController server;
server.setEndpoint(“http://host_auto_ip4:6502/v1/ivmero/api");

si vous passez host_auto_ip4 ou host_auto_ip6 signifie que le service doit détecter automatiquement l’adresse IP de l’hôte, si vous avez plus d’une carte réseau, il utilisera la première qu’il trouve dans la liste des adresses IP disponibles. Vient ensuite le numéro de port et enfin l’adresse de base de l’API REST dans ce cas est / v1 / ivmero / api.

Ensuite, nous initialisons le pool de threads utilisé par le runtime de concurrence pplx et commençons à accepter les connexions réseau:

server.accept().wait();

après cet appel, si vous exécutez le débogueur, vous verrez une quarantaine de threads sur le processus de service, ce qui constitue le pool de threads hébergé par le planificateur pplx. Ces threads sont pré-engendrés pour éviter les retards lorsqu’un thread est requis et ils sont réutilisés pendant la durée de vie. Donc rien à craindre. :)

IMPORTANT: la méthode accept () retourne une pplx :: task, mais pour lui permettre d’effectuer la création du pool de threads et d’initialiser l’accepteur nous devons attendre la fin de la tâche d’où l’appel à la méthode wait () .

Dès que l’ appel wait () revient, nous devons configurer une barrière pour éviter que le processus ne finisse de laisser l’accepteur mourir avant que toute demande ne puisse être traitée, nous appelons donc:

InterruptHandler :: waitForUserInterrupt ();

qui est essentiellement un appel bloquant qui gère le SIGINT et se termine dès que l’utilisateur tape Ctrl + C sur le terminal.

Enfin, si l’utilisateur met fin au processus, nous arrêtons simplement le service et attendons que tous les threads se terminent:

server.shutdown().wait();

Conclusion

Comme vous pouvez le voir, le code est assez simple à implémenter un micro-service en C ++, aussi simple qu’un serveur node.js qui expose une interface REST.
Cet article peut contenir des fautes d’ortographes et autres, priez de me contacter pour la correction.

Cet article est une traduction de l’original ici.

Tech nerd. South Korea

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store