V tomto článku budem hovoriť o tom, ako som vytvoril šablónu (cookiecutter) a nastavil prostredie na písanie služby REST API v C++ pomocou docker/docker-compose a správcu balíkov conan.
Pri ďalšom hackathone, ktorého som sa zúčastnil ako backend developer, vyvstala otázka, čo použiť na napísanie ďalšej mikroslužby. Všetko, čo bolo doteraz napísané, som napísal ja a môj
Stáli sme teda pred úlohou napísať vysoko zaťaženú službu, ktorej hlavnou úlohou bolo predspracovať do nej prichádzajúce dáta a zapísať ich do databázy. A po ďalšej dymovej prestávke mi priateľ navrhol, aby som ako vývojár C++ napísal túto službu pomocou profesionálov. Argumentujúc tým, že to bude rýchlejšie, produktívnejšie a vo všeobecnosti bude porota potešená tým, ako vieme spravovať zdroje tímu. Na to som odpovedal, že takéto veci som v C++ nikdy nerobil a zvyšných 20+ hodín by som pokojne mohol venovať vyhľadávaniu, kompilovaniu a prepájaniu vhodných knižníc. Jednoducho povedané, vykašlal som sa. To je to, pre čo sme sa rozhodli a pokojne dokončili všetko v Pythone.
Teraz, počas nútenej samoizolácie, som sa rozhodol prísť na to, ako písať služby v C++. Ako prvé bolo potrebné rozhodnúť sa pre vhodnú knižnicu. Moja voľba padla
conanfile.txt
[vyžaduje] poco/1.9.3
libpq/11.5
a jednoduchým príkazom "conan install." nainštalovať potrebné knižnice. Prirodzene, bolo potrebné vykonať aj zmeny
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries(<target_name> ${CONAN_LIBS})
Potom som začal hľadať knižnicu na prácu s PostgreSQL, pretože to bola knižnica, s ktorou som nemal veľa skúseností, a tiež to bola tá, s ktorou interagovali naše služby Python. A viete, čo som sa naučil? Je to v POCO! Conan však nevie, že je v POCO a nevie, ako ho zostaviť, v úložisku je zastaraný konfiguračný súbor (o tejto chybe som už písal tvorcom POCO). To znamená, že budete musieť hľadať inú knižnicu.
A potom moja voľba padla na menej obľúbenú knižnicu
Ďalším krokom bolo napísanie šablóny služby, ktorá dokáže spracovať požiadavky.
Musíme zdediť našu triedu TemplateServerApp od Poco::Util::ServerApplication a prepísať hlavnú metódu.
Aplikácia 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 hlavnej metóde musíme nastaviť parametre: port, počet vlákien a veľkosť frontu. A čo je najdôležitejšie, musíte zadať obsluhu pre prichádzajúce požiadavky. To sa deje vytvorením továrne
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};
V mojom prípade jednoducho vždy vytvorí rovnaký obslužný program - TemplateServerAppHandler. Tu môžeme umiestniť našu obchodnú 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();
}
};
Vytvoril som tiež šablónu triedy na prácu s PostgreSQL. Na vykonanie jednoduchého SQL, ako je vytvorenie tabuľky, existuje metóda ExecuteSQL(). Pre zložitejšie otázky alebo získavanie údajov budete musieť získať spojenie cez GetConnection() a použite libpg API. (Snáď neskôr túto nespravodlivosť napravím).
databázy
#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;
};
Všetky parametre pre pripojenie k databáze sú prevzaté z prostredia, takže je potrebné vytvoriť a nakonfigurovať aj súbor .env
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432
Celý kód môžete vidieť na
A teraz prichádza posledná fáza písania súboru dockerfile a docker-compose.yml. Úprimne povedané, zabralo to väčšinu času, a to nielen preto, že som noob, pretože bolo potrebné zakaždým prestavať knižnice, ale aj kvôli nástrahám conanu. Napríklad, aby si conan stiahol, nainštaloval a vytvoril potrebné závislosti, nestačí mu stiahnuť „conan install .“, ale musí odovzdať aj parameter -s kompilátor.libcxx=libstdc++11, inak riskujete, že vo fáze prepojenia vašej aplikácie dostanete veľa chýb. S touto chybou som sa zasekol niekoľko hodín a dúfam, že tento článok pomôže ostatným vyriešiť tento problém za kratší čas.
Ďalej, po napísaní docker-compose.yml som na radu môjho priateľa pridal podporu
Dúfam, že táto šablóna pomôže začiatočníkom na ich náročnej ceste vývoja aplikácií REST API vo skvelom a výkonnom, ale takom nemotornom jazyku, akým je C++.
Tiež veľmi odporúčam prečítať si tu
Zdroj: hab.com