Dans cet article, je vais parler de la façon dont j'ai créé un template (cookiecutter) et mis en place un environnement pour écrire un service API REST en C++ en utilisant docker/docker-compose et le gestionnaire de package conan.
Lors du prochain hackathon, auquel j'ai participé en tant que développeur back-end, la question s'est posée de savoir comment écrire le prochain microservice. Tout ce qui a été écrit à ce jour a été écrit par moi et mon
Nous étions donc confrontés à la tâche d'écrire un service très chargé, dont la tâche principale était de prétraiter les données qui lui parvenaient et de les écrire dans la base de données. Et après une autre pause cigarette, un ami m'a suggéré, en tant que développeur C ++, d'écrire ce service sur les avantages. Arguant que ce sera plus rapide, plus productif, et en général, le jury sera ravi de la façon dont nous savons gérer les ressources de l'équipe. À quoi j'ai répondu que je n'avais jamais fait de telles choses en C ++ et que je pouvais facilement consacrer les 20 heures restantes à trouver, compiler et lier des bibliothèques appropriées. Bref, j'ai dégonflé. Là-dessus, ils ont décidé et ont calmement tout ajouté en Python.
Maintenant, pendant l'auto-isolement forcé, j'ai décidé de comprendre comment écrire des services en C ++. La première chose à faire était de choisir une bibliothèque appropriée. Mon choix s'est porté sur
conanfile.txt
[nécessite]poco/1.9.3
libpq/11.5
et avec une simple commande "conan install ." installez les bibliothèques requises. Naturellement, il était également nécessaire d'apporter des modifications à
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries(<target_name> ${CONAN_LIBS})
Après cela, j'ai commencé à chercher une bibliothèque pour travailler avec PostgreSQL, car c'est avec elle que j'avais peu d'expérience, et c'est aussi avec elle que nos services Python interagissaient. Et tu sais ce que j'ai découvert ? Elle est dans POCO! Mais conan ne sait pas qu'il est dans POCO et ne sait pas comment le construire, le référentiel contient un fichier de configuration obsolète (j'ai déjà écrit à propos de cette erreur aux créateurs de POCO). Donc, vous devez chercher une autre bibliothèque.
Et puis mon choix s'est porté sur une bibliothèque moins populaire
L'étape suivante consistait à écrire un modèle de service capable de traiter les demandes.
Nous devons dériver notre classe TemplateServerApp de Poco::Util::ServerApplication et remplacer la méthode main.
ModèleServeurApp
#pragma once
#include <string>
#include <vector>
#include <Poco/Util/ServerApplication.h>
class TemplateServerApp : public Poco::Util::ServerApplication
{
protected:
int main(const std::vector<std::string> &);
};
int TemplateServerApp::main(const vector<string> &)
{
HTTPServerParams* pParams = new HTTPServerParams;
pParams->setMaxQueued(100);
pParams->setMaxThreads(16);
HTTPServer s(new TemplateRequestHandlerFactory, ServerSocket(8000), pParams);
s.start();
cerr << "Server started" << endl;
waitForTerminationRequest(); // wait for CTRL-C or kill
cerr << "Shutting down..." << endl;
s.stop();
return Application::EXIT_OK;
}
Dans la méthode principale, nous devons définir les paramètres : port, nombre de threads et taille de la file d'attente. Et surtout, vous devez définir le gestionnaire pour les demandes entrantes. Cela se fait en créant une usine
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};
Dans mon cas, il crée simplement le même gestionnaire à chaque fois - TemplateServerAppHandler. C'est là que nous pouvons placer notre logique métier.
Gestionnaire de modèles de serveurs d'applications
class TemplateServerAppHandler : public HTTPRequestHandler
{
public:
void handleRequest(HTTPServerRequest &req, HTTPServerResponse &resp)
{
URI uri(req.getURI());
string method = req.getMethod();
cerr << "URI: " << uri.toString() << endl;
cerr << "Method: " << req.getMethod() << endl;
StringTokenizer tokenizer(uri.getPath(), "/", StringTokenizer::TOK_TRIM);
HTMLForm form(req,req.stream());
if(!method.compare("POST"))
{
cerr << "POST" << endl;
}
else if(!method.compare("PUT"))
{
cerr << "PUT" << endl;
}
else if(!method.compare("DELETE"))
{
cerr << "DELETE" << endl;
}
resp.setStatus(HTTPResponse::HTTP_OK);
resp.setContentType("application/json");
ostream& out = resp.send();
out << "{"hello":"heh"}" << endl;
out.flush();
}
};
J'ai également créé un modèle de classe pour travailler avec PostgreSQL. Afin d'effectuer des requêtes SQL simples, telles que la création d'une table, il existe une méthode ExécuterSQL(). Pour des requêtes plus complexes ou pour obtenir des données, vous devrez obtenir la connexion via ObtenirConnexion() et utilisez l'API libpg. (Peut-être que je corrigerai cette injustice plus tard).
Base de données
#pragma once
#include <memory>
#include <mutex>
#include <libpq-fe.h>
class Database
{
public:
Database();
std::shared_ptr<PGconn> GetConnection() const;
bool ExecuteSQL(const std::string& sql);
private:
void establish_connection();
void LoadEnvVariables();
std::string m_dbhost;
int m_dbport;
std::string m_dbname;
std::string m_dbuser;
std::string m_dbpass;
std::shared_ptr<PGconn> m_connection;
};
Tous les paramètres de connexion à la base de données sont extraits de l'environnement, vous devez donc également créer et configurer le fichier .env
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432
Vous pouvez voir tout le code sur
Et la dernière étape de l'écriture du dockerfile et du docker-compose.yml est arrivée. Pour être honnête, cela a pris la plupart du temps, et pas seulement parce que je suis un noob, qu'il fallait reconstruire les bibliothèques à chaque fois, mais à cause des pièges de conan. Ainsi par exemple, pour que conan télécharge, installe et construise les dépendances nécessaires, il ne lui suffit pas de télécharger "conan install.", il doit aussi passer le paramètre -s compiler.libcxx=libstdc++11, sinon, vous risquez d'obtenir un tas d'erreurs lors de l'étape de liaison de votre application. Je suis bloqué avec cette erreur depuis plusieurs heures et j'espère que cet article aidera d'autres personnes à résoudre ce problème plus rapidement.
De plus, après avoir écrit docker-compose.yml, sur les conseils de mon ami, j'ai ajouté le support pour
J'espère que ce modèle aidera les débutants sur leur chemin difficile de développement d'applications API REST dans un langage génial et puissant, mais aussi maladroit que C ++.
Aussi, je recommande fortement de lire ici
Source: habr.com