
In dit artikel vertel ik hoe ik een sjabloon (cookiecutter) heb gemaakt en een omgeving heb ingesteld voor het schrijven van een REST API-service in C++ met behulp van docker/docker-compose en de pakketbeheerder conan.
Tijdens een hackathon, waarbij ik deelnam als backend-ontwikkelaar, kwam de vraag naar voren in welke taal we de volgende microservice zouden schrijven. Alles wat tot nu toe was geschreven, was door mij en mijn in de taal Python, aangezien mijn collega een specialist op dit gebied was en professioneel backend-ontwikkeling deed, terwijl ik eigenlijk een ontwikkelaar voor embedded systemen was en schreef in het grote en vreselijke C++, terwijl ik Python slechts op de universiteit had geleerd.
Dus stond de taak voor ons om een hoogbelastbare service te schrijven, waarvan de belangrijkste taak het voorverwerken van de binnenkomende gegevens en het opslaan ervan in een database was. Na weer een pauze stelde mijn kameraad voor, als C++-ontwikkelaar, deze service in C++ te schrijven. Hij argumenteerde dit door te zeggen dat het sneller en efficiƫnter zou zijn, en dat de jury onder de indruk zou zijn van hoe we de middelen van het team konden benutten. Waarop ik antwoordde dat ik nog nooit zulke dingen in C++ had gedaan en makkelijk de resterende 20+ uur zou kunnen besteden aan het zoeken naar, het compileren van en het linken van geschikte bibliotheken. Kortom, ik het was bang. Zo besloten we het en schreven alles gewoon in Python.
Nu, tijdens de gedwongen zelfisolatie, besloot ik me te verdiepen in hoe ik services in C++ moest schrijven. Het eerste wat ik moest doen, was een geschikte bibliotheek kiezen. Mijn keuze viel op , aangezien deze in objectgeoriƫnteerde stijl was geschreven en bovendien goede documentatie had. Ook kwam de vraag over het kiezen van een build-systeem naar voren. Tot dat moment had ik alleen met Visual Studio, IAR en 'blote' makefiles gewerkt. Geen van deze systemen trok me aan, aangezien ik van plan was om de hele service in een docker-container te draaien. Toen besloot ik om cmake en de interessante pakketbeheerder te onderzoeken. Deze pakketbeheerder stelde me in staat om alle afhankelijkheden in ƩƩn bestand te beschrijven,
conanfile.txt
[requires]
poco/1.9.3
libpq/11.5
[generators]
cmake
en met behulp van een eenvoudige opdracht āconan install .ā de benodigde bibliotheken te installeren. Uiteraard waren er ook wijzigingen nodig in de
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries( ${CONAN_LIBS})
Daarna begon ik te zoeken naar een bibliotheek voor het werken met PostgreSQL, omdat ik daar wat ervaring mee had en onze diensten op Python daarmee samenwerkten. En weet je wat ik ontdekte? Het is beschikbaar in POCO! Maar conan weet niet dat het in POCO zit en kan het niet bouwen; in de repository ligt een verouderd configuratiebestand (ik heb de makers van POCO al over deze fout geschreven). Dus ik moest een andere bibliotheek zoeken.
En zo koos ik voor een minder populaire bibliotheek . En ik had het geluk dat het al in conan zat en zelfs samen kon worden gesteld.
De volgende stap was het schrijven van een sjabloonservice die in staat is om verzoeken te verwerken.
We moeten onze klasse TemplateServerApp laten afleiden van Poco::Util::ServerApplication en de hoofdmethode overschrijven.
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 gestart" << endl;
waitForTerminationRequest(); // wacht op CTRL-C of kill
cerr << "Afsluiten..." << endl;
s.stop();
return Application::EXIT_OK;
}In de hoofdmethode moeten we parameters invoeren: poort, aantal threads en grootte van de wachtrij. Belangrijker nog, we moeten een verwerkingshandler voor binnenkomende verzoeken instellen. Dit gebeurt door een fabriek te maken.
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};In mijn geval creĆ«ert deze elke keer dezelfde handler ā TemplateServerAppHandler. Hier kunnen we onze businesslogica onderbrengen.
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 << "Methode: " << 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();
}
};Ook heb ik een klasse-sjabloon gemaakt voor het werken met PostgreSQL. Om een eenvoudige SQL-opdracht uit te voeren, zoals het creƫren van een tabel, is er de methode ExecuteSQL(). Voor complexere queries of het ophalen van gegevens moet je een verbinding verkrijgen via GetConnection() en de libpg API gebruiken. (Misschien zal ik deze onrechtvaardigheid later corrigeren).
Database
#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;
};Alle parameters voor de databaseverbinding worden uit de omgeving gehaald, dus je moet ook een .env-bestand aanmaken en configureren.
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432Je kunt de volledige code bekijken op

En nu is de laatste fase van het schrijven van de dockerfile en docker-compose.yml aangebroken. Ik zal eerlijk zijn, hier ging het grootste deel van de tijd in zitten, en niet alleen omdat ik een noob ben, die elke keer de bibliotheken opnieuw moest samenstellen, maar vanwege de valkuilen van conan. Bijvoorbeeld, om conan de benodigde afhankelijkheden te laten downloaden, installeren en builden, is het niet genoeg om 'conan install .' te downloaden; het is ook nodig om de parameter -s compiler.libcxx=libstdc++11 door te geven, anders loop je het risico op een hoop fouten tijdens het linken van je applicatie. Ik heb enkele uren met deze fout gezeten en ik hoop dat dit artikel andere mensen helpt om dit probleem sneller op te lossen.
Daarna, na het schrijven van de docker-compose.yml, heb ik op advies van een vriend ondersteuning toegevoegd voor en nu kun je een volledige sjabloon krijgen voor een REST API-service in C++, met een geconfigureerde omgeving en draaiende PostgreSQL, door simpelweg in de console "cookiecutter " in te voeren. En daarna "docker-compose up --build".
Ik hoop dat deze sjabloon beginners helpt op hun moeilijk pad in de ontwikkeling van REST API-toepassingen in de grote en machtige, maar zo onhandige taal zoals C++.
Bovendien raad ik ten zeerste aan om dit artikel te lezen. Daarin wordt uitgebreider uitgelegd hoe je met POCO werkt en je eigen REST API-service schrijft.
Bron: habr.com
