
În acest articol, voi povesti despre cum am creat un șablon (cookiecutter) și am configurat un mediu pentru a scrie un serviciu REST API în C++ folosind docker/docker-compose și managerul de pachete conan.
În timpul unui hackathon la care am participat ca dezvoltator backend, s-a pus problema pe ce să scriem următorul microserviciu. Tot ce fusese scris până în acel moment fusese realizat de mine și de în limbajul Python, deoarece colegul meu era specialist în acest domeniu și se ocupa profesional de dezvoltarea backend-urilor, în timp ce eu eram, în general, dezvoltator pentru sisteme încorporate și scriam în marele și teribilul C++, pe când Python îl învățasem doar la universitate.
Așadar, noi aveam sarcina de a scrie un serviciu cu încărcare ridicată, a cărui principala responsabilitate era preprocesarea datelor ce îi erau transmise și salvarea acestora în baza de date. După o pauză, colegul meu mi-a sugerat, ca dezvoltator C++, să scriu acest serviciu în C++. Argumentând că astfel va fi mai rapid, mai eficient și că juriul va fi impresionat de modul în care ne folosim de resursele echipei. La care eu am răspuns că nu m-am ocupat niciodată de astfel de lucruri în C++ și că pot petrece cu ușurință cele 20+ de ore restante căutând, compilând și legând biblioteci potrivite. Cu alte cuvinte, mi-a fost frică. Așa că am decis să continuăm să scriem totul în Python.
Acum, în timpul autoizolării forțate, am decis să mă familiarizăm cu modul de a scrie servicii în C++. Primul lucru pe care trebuia să-l fac era să aleg o bibliotecă potrivită. Alegerea mea a fost , deoarece era scrisă într-un stil orientat pe obiecte și putea să se laude cu o documentație decentă. De asemenea, s-a pus problema alegerii unui sistem de construire. Până în acel moment lucrasem doar cu Visual Studio, IAR și „makefile-uri goale”. Și niciunul dintre aceste sisteme nu mă atrăgea, deoarece intenționam să rulez întregul serviciu într-un container docker. Atunci m-am decis să învăț despre cmake și interesantul manager de pachete . Acest manager de pachete permitea să definim toate dependențele într-un singur fișier
conanfile.txt
[requires]
poco/1.9.3
libpq/11.5
[generators]
cmake
și, cu ajutorul unei comenzi simple „conan install .”, să instalăm bibliotecile necesare. Bineînțeles, au fost necesare și modificări în
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries( ${CONAN_LIBS})
După aceasta, am început să caut o bibliotecă pentru a lucra cu PostgreSQL, deoarece aveam ceva experiență cu aceasta, iar serviciile noastre pe Python interacționau cu ea. Și știți ce am descoperit? Este în POCO! Dar conan nu știe că este în POCO și nu știe cum să o compileze, în repository se află un fișier de configurare învechit (am scris deja despre această eroare creatorilor POCO). Asta înseamnă că va trebui să caut o altă bibliotecă.
Și astfel, alegerea mea a căzut pe o bibliotecă mai puțin populară . Și am avut o mare noroc, ea era deja în conan și chiar se compila și se lega.
Următorul pas a fost scrierea unui șablon de serviciu, capabil să gestioneze cereri.
Trebuie să moștenim clasa noastră TemplateServerApp de la Poco::Util::ServerApplication și să suprascriem metoda main.
TemplateServerApp
#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 &)
{
HTTPServerParams* pParams = new HTTPServerParams;
pParams->setMaxQueued(100);
pParams->setMaxThreads(16);
HTTPServer s(new TemplateRequestHandlerFactory, ServerSocket(8000), pParams);
s.start();
cerr << "Server started" << endl;
waitForTerminationRequest(); // așteaptă CTRL-C sau kill
cerr << "Shutting down..." << endl;
s.stop();
return Application::EXIT_OK;
}În metoda main trebuie să stabilim parametrii: portul, numărul de thread-uri și dimensiunea cozii. Iar cel mai important, trebuie să stabilim handler-ul pentru cererile de intrare. Acest lucru se face prin crearea unei fabrici
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};În cazul meu, ea creează pur și simplu de fiecare dată același handler — TemplateServerAppHandler. Aici putem plasa logica noastră de afaceri.
TemplateServerAppHandler
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();
}
};De asemenea, am creat un șablon de clasă pentru a lucra cu PostgreSQL. Pentru a executa un SQL simplu, cum ar fi crearea unei tabele, există metoda ExecuteSQL(). Pentru interogări mai complexe sau pentru a obține date, va trebui să obțineți conexiunea prin GetConnection() și să folosiți API-ul libpg. (Poate că voi corecta mai târziu această nedreptate).
Bază de date
#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;
};Toate parametrii pentru conectarea la baza de date sunt preluate din mediu, așa că va trebui să creați și să configurați un fișier .env
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432Puteți vedea tot codul pe

Și a venit ultima etapă, scrierea dockerfile-ului și a docker-compose.yml. Voi fi sincer, acest proces a consumat cea mai mare parte din timp, și nu doar pentru că sunt novice, că era necesar să recompilăm bibliotecile de fiecare dată, ci din cauza capcanelor ascunse ale conan. De exemplu, pentru a permite lui conan să descarce, să instaleze și să compileze dependențele necesare, nu este suficient să ranseze 'conan install .', trebuie de asemenea să transmiteți parametrul -s compiler.libcxx=libstdc++11, altfel riscați să obțineți o mulțime de erori în etapa de legare a aplicației dumneavoastră. Am stat cu această eroare câteva ore și sper că acest articol va ajuta alte persoane să rezolve această problemă mai repede.
Apoi, după ce am scris docker-compose.yml, la sfatul unui prieten, am adăugat suport pentru și acum puteți obține un șablon complet pentru un serviciu REST API în C++, cu un mediu configurat și PostgreSQL ridicat, introducând în consolă 'cookiecutter ' Apoi 'docker-compose up --build'.
Sper că acest șablon va ajuta începătorii în calea lor dificilă de a dezvolta aplicații REST API în marele și puternicul, dar atât de greoi limbaj C++.
De asemenea, recomand cu căldură să citiți acest articol. Acolo se explică în detaliu cum să lucrați cu POCO și să scrieți propriul serviciu REST API.
Sursa: habr.com
