V tem članku bom govoril o tem, kako sem ustvaril predlogo (cookiecutter) in nastavil okolje za pisanje storitve REST API v C++ z uporabo docker/docker-compose in upravitelja paketov conan.
Med naslednjim hackathonom, na katerem sem sodeloval kot backend razvijalec, se je pojavilo vprašanje, s čim napisati naslednjo mikrostoritev. Vse, kar je bilo do sedaj napisano, sem napisal jaz in moji
Tako smo bili postavljeni pred nalogo pisanja visoko obremenjene storitve, katere glavna naloga je bila predobdelava podatkov, ki prihajajo vanjo, in njihovo pisanje v bazo podatkov. In po še enem premoru mi je prijatelj predlagal, naj kot razvijalec C++ napišem to storitev s pomočjo profesionalcev. To trdijo tako, da bo hitrejše, bolj produktivno, na splošno pa bo žirija navdušena nad tem, kako znamo upravljati z viri ekipe. Na kar sem odgovoril, da še nikoli nisem počel takšnih stvari v C++ in bi zlahka posvetil preostalih 20+ ur iskanju, prevajanju in povezovanju ustreznih knjižnic. Preprosto povedano, obupal sem. Tako smo se odločili in vse mirno dokončali v Pythonu.
Zdaj, med prisilno samoizolacijo, sem se odločil ugotoviti, kako pisati storitve v C++. Najprej se je bilo treba odločiti za primerno knjižnico. Moja izbira je padla na
conanfile.txt
[zahteva]poco/1.9.3
libpq/11.5
in s preprostim ukazom "conan install ." namestite potrebne knjižnice. Seveda je bilo treba spremeniti tudi v
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries(<target_name> ${CONAN_LIBS})
Po tem sem začel iskati knjižnico za delo s PostgreSQL, saj je bila tista, s katero sem imel malo izkušenj, in je bila tudi tista, s katero so sodelovale naše storitve Python. In veš, kaj sem se naučil? V POCO je! Toda conan ne ve, da je v POCO in ne ve, kako ga zgraditi; v repozitoriju je zastarela konfiguracijska datoteka (o tej napaki sem že pisal ustvarjalcem POCO). To pomeni, da boste morali poiskati drugo knjižnico.
In potem je moja izbira padla na manj priljubljeno knjižnico
Naslednji korak je bil pisanje storitvene predloge, ki lahko obdeluje zahteve.
Naš razred TemplateServerApp moramo podedovati od Poco::Util::ServerApplication in preglasiti glavno metodo.
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;
}
V glavni metodi moramo nastaviti parametre: vrata, število niti in velikost čakalne vrste. In kar je najpomembnejše, določiti morate upravljavca za dohodne zahteve. To se naredi z ustvarjanjem tovarne
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};
V mojem primeru preprosto vsakič ustvari isti upravljalnik - TemplateServerAppHandler. Tu lahko umestimo svojo poslovno logiko.
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();
}
};
Ustvaril sem tudi predlogo razreda za delo s PostgreSQL. Za izvajanje preprostega SQL, kot je ustvarjanje tabele, obstaja metoda ExecuteSQL(). Za zahtevnejše poizvedbe ali pridobivanje podatkov boste morali pridobiti povezavo prek GetConnection() in uporabite API libpg. (Morda kasneje popravim to krivico).
Baze podatkov
#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;
};
Vsi parametri za povezavo z bazo podatkov so vzeti iz okolja, zato morate ustvariti in konfigurirati tudi datoteko .env
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432
Vso kodo si lahko ogledate na
In zdaj prihaja zadnja faza pisanja dockerfile in docker-compose.yml. Če sem iskren, je to vzelo večino časa, pa ne samo zato, ker sem noob, ker je bilo treba vsakič znova zgraditi knjižnice, ampak zaradi pasti conana. Na primer, da bi conan prenesel, namestil in zgradil potrebne odvisnosti, ni dovolj, da prenese »conan install .«, temveč mora posredovati tudi parameter -s compiler.libcxx=libstdc++11, sicer tvegate, da boste v fazi povezovanja vaše aplikacije dobili kup napak. Ta napaka se mi pojavlja že nekaj ur in upam, da bo ta članek pomagal drugim rešiti to težavo v krajšem času.
Nato sem po pisanju docker-compose.yml po nasvetu prijatelja dodal podporo
Upam, da bo ta predloga pomagala začetnikom na njihovi težki poti razvijanja aplikacij REST API v odličnem in močnem, a tako okornem jeziku, kot je C++.
Prav tako toplo priporočam branje tukaj
Vir: www.habr.com