U ovom ću članku govoriti o tome kako sam stvorio predložak (cookiecutter) i postavio okruženje za pisanje REST API usluge u C++ koristeći docker/docker-compose i upravitelj paketa conan.
Tijekom sljedećeg hackathona, na kojem sam sudjelovao kao backend developer, postavilo se pitanje čime napisati sljedeći mikroservis. Sve što je do sada napisano napisala sam ja i moji
Dakle, suočili smo se sa zadatkom pisanja visokoopterećene usluge, čija je glavna zadaća bila pretprocesirati podatke koji dolaze i upisati ih u bazu podataka. I nakon još jedne pauze, prijatelj mi je predložio da, kao C++ programer, napišem ovu uslugu koristeći profesionalce. Tvrdi se da će biti brži, produktivniji i općenito, žiri će biti oduševljen time kako znamo kako upravljati resursima tima. Na što sam odgovorio da nikada nisam radio takve stvari u C++-u i lako bih mogao posvetiti preostalih 20+ sati traženju, kompajliranju i povezivanju odgovarajućih biblioteka. Jednostavno rečeno, uplašio sam se. To je ono za što smo se odlučili i mirno završili sve u Pythonu.
Sada, tijekom prisilne samoizolacije, odlučio sam smisliti kako pisati usluge u C++. Prvo što je trebalo učiniti bilo je odlučiti se za odgovarajuću knjižnicu. Moj izbor je pao na
conanfile.txt
[zahtijeva]poco/1.9.3
libpq/11.5
i jednostavnom naredbom "conan install ." instalirajte potrebne biblioteke. Naravno, također je bilo potrebno napraviti promjene u
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries(<target_name> ${CONAN_LIBS})
Nakon toga sam počeo tražiti biblioteku za rad s PostgreSQL-om, budući da je to bila ona s kojom sam imao malo iskustva u radu, a to je bila i ona s kojom su naši Python servisi komunicirali. I znate li što sam naučio? U POCO je! Ali conan ne zna da je u POCO-u i ne zna kako ga izgraditi; u repozitoriju postoji zastarjela konfiguracijska datoteka (o ovoj pogrešci sam već pisao kreatorima POCO-a). To znači da ćete morati potražiti drugu knjižnicu.
A onda je moj izbor pao na manje popularnu knjižnicu
Sljedeći korak bio je napisati predložak usluge koji može obraditi zahtjeve.
Moramo naslijediti našu klasu TemplateServerApp od Poco::Util::ServerApplication i nadjačati glavnu metodu.
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<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;
}
U glavnoj metodi moramo postaviti parametre: port, broj niti i veličinu reda čekanja. I što je najvažnije, morate navesti rukovatelja za dolazne zahtjeve. To se postiže stvaranjem tvornice
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};
U mom slučaju, jednostavno svaki put stvara isti rukovatelj - TemplateServerAppHandler. Ovdje možemo smjestiti svoju poslovnu logiku.
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();
}
};
Također sam izradio predložak klase za rad s PostgreSQL-om. Za izvođenje jednostavnog SQL-a, kao što je stvaranje tablice, postoji metoda IzvršiSQL(). Za složenije upite ili dohvaćanje podataka, morat ćete uspostaviti vezu putem GetConnection() i koristite libpg API. (Možda kasnije ispravim ovu nepravdu).
Baza podataka
#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;
};
Svi parametri za povezivanje s bazom preuzeti su iz okruženja, pa je potrebno izraditi i konfigurirati i .env datoteku
.NS
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432
Sve kodove možete vidjeti na
A sada dolazi posljednja faza pisanja dockerfilea i docker-compose.yml. Da budem iskren, ovo je oduzelo većinu vremena, i to ne samo zato što sam noob, jer je bilo potrebno svaki put iznova graditi biblioteke, već zbog zamki conana. Na primjer, da bi conan mogao preuzeti, instalirati i izgraditi potrebne ovisnosti, nije dovoljno da preuzme “conan install .”, također treba proslijediti parametar -s compiler.libcxx=libstdc++11, inače riskirate da dobijete hrpu pogrešaka u fazi povezivanja svoje aplikacije. Zaglavio sam s ovom pogreškom nekoliko sati i nadam se da će ovaj članak pomoći drugima da riješe ovaj problem u kraćem roku.
Zatim, nakon što sam napisao docker-compose.yml, na savjet svog prijatelja, dodao sam podršku
Nadam se da će ovaj predložak pomoći početnicima na njihovom teškom putu razvoja REST API aplikacija u sjajnom i moćnom, ali tako nespretnom jeziku kao što je C++.
Također, toplo preporučujem čitanje ovdje
Izvor: www.habr.com