En ĉi tiu artikolo mi parolos pri kiel mi kreis ŝablonon (cookiecutter) kaj starigis medion por verki REST-API-servon en C++ uzante docker/docker-compose kaj la pakaĵadministrilon conan.
Dum la sekva hackathon, en kiu mi partoprenis kiel backend-programisto, la demando ekestis pri kion uzi por skribi la sekvan mikroservon. Ĉio, kio estis skribita ĝis nun, estis skribita de mi kaj mia
Do, ni alfrontis la taskon verki altŝarĝan servon, kies ĉefa tasko estis antaŭprilabori la datumojn venantajn al ĝi kaj skribi ĝin al la datumbazo. Kaj post alia fumpaŭzo, amiko sugestis, ke mi, kiel C++-programisto, verku ĉi tiun servon uzante la avantaĝojn. Argumentante ĉi tion estas, ke ĝi estos pli rapida, pli produktiva, kaj ĝenerale, la ĵurio ĝojos pri kiel ni scias kiel administri la rimedojn de la teamo. Al kio mi respondis, ke mi neniam faris tiajn aferojn en C++ kaj povas facile dediĉi la ceterajn 20+ horojn al serĉado, kompilo kaj ligado de taŭgaj bibliotekoj. Simple dirite, mi kokidiĝis. Tion ni decidis kaj trankvile kompletigis ĉion en Python.
Nun, dum la devigita mem-izolado, mi decidis eltrovi kiel skribi servojn en C++. La unua afero estis decidi pri taŭga biblioteko. Mia elekto falis
conanfile.txt
[postulas]poco/1.9.3
libpq/11.5
kaj per simpla komando "conan install ." instali la necesajn bibliotekojn. Nature, ankaŭ necesis fari ŝanĝojn al
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries(<target_name> ${CONAN_LIBS})
Post tio, mi komencis serĉi bibliotekon por labori kun PostgreSQL, ĉar ĝi estis tiu kun kiu mi havis malmulte da sperto laboranta, kaj ĝi ankaŭ estis tiu kun kiu niaj Python-servoj interagis. Kaj ĉu vi scias, kion mi lernis? Ĝi estas en POCO! Sed conan ne scias, ke ĝi estas en POCO kaj ne scias kiel konstrui ĝin; estas malaktuala agorda dosiero en la deponejo (mi jam skribis pri ĉi tiu eraro al la kreintoj de POCO). Ĉi tio signifas, ke vi devos serĉi alian bibliotekon.
Kaj tiam mia elekto falis sur malpli popularan bibliotekon
La sekva paŝo estis verki servoŝablonon, kiu povas procesi petojn.
Ni devas heredi nian TemplateServerApp klason de Poco::Util::ServerApplication kaj superregi la ĉefan metodon.
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;
}
En la ĉefa metodo ni devas agordi la parametrojn: haveno, nombro da fadenoj kaj vostograndeco. Kaj plej grave, vi devas specifi prizorganton por envenantaj petoj. Ĉi tio estas farita kreante fabrikon
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};
En mia kazo, ĝi simple kreas la saman prizorganton ĉiufoje - TemplateServerAppHandler. Ĉi tie ni povas meti nian komercan logikon.
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();
}
};
Mi ankaŭ kreis klasŝablonon por labori kun PostgreSQL. Por plenumi simplan SQL, kiel krei tabelon, ekzistas metodo ExecuteSQL (). Por pli kompleksaj demandoj aŭ reakiro de datumoj, vi devos akiri konekton per GetConnection () kaj uzu la libpg API. (Eble poste mi korektos ĉi tiun maljustaĵon).
Datumbazo
#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;
};
Ĉiuj parametroj por konektiĝi al la datumbazo estas prenitaj el la medio, do vi ankaŭ devas krei kaj agordi la .env-dosieron
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432
Vi povas vidi la tutan kodon ĉe
Kaj nun venas la fina etapo de verkado de la dockerfile kaj docker-compose.yml. Verdire, ĉi tio daŭris la plej grandan parton de la tempo, kaj ne nur ĉar mi estas novulo, ĉar necesis ĉiufoje rekonstrui la bibliotekojn, sed pro la faŭltoj de konano. Ekzemple, por ke conan elŝutu, instalu kaj konstruu la necesajn dependecojn, ne sufiĉas ke ĝi elŝutu "conan install .", ĝi ankaŭ bezonas pasi la parametron -s compiler.libcxx=libstdc++11, alie. vi riskas ricevi multajn erarojn ĉe la ligofazo de via aplikaĵo. Mi restis kun ĉi tiu eraro dum pluraj horoj kaj mi esperas, ke ĉi tiu artikolo helpos aliajn homojn solvi ĉi tiun problemon en malpli da tempo.
Poste, skribinte docker-compose.yml, laŭ la konsilo de mia amiko, mi aldonis subtenon
Mi esperas, ke ĉi tiu ŝablono helpos komencantojn sur ilia malfacila vojo evoluigi REST-API-aplikaĵojn en la bonega kaj potenca, sed tiel mallerta lingvo kiel C++.
Ankaŭ mi tre rekomendas legi ĉi tie
fonto: www.habr.com