
In questo articolo parlerò di come ho creato un template (cookiecutter) e impostato l'ambiente per scrivere un servizio REST API in C++ utilizzando Docker/Docker Compose e il gestore di pacchetti Conan.
Durante un hackathon a cui ho partecipato come sviluppatore backend, è emerso il problema di quale sarebbe stata la tecnologia da usare per scrivere il prossimo microservizio. Tutto ciò che era stato scritto fino a quel momento era frutto del lavoro mio e del mio in Python, poiché il mio collega era uno specialista in questo campo e si occupava professionalmente dello sviluppo backend, mentre io ero uno sviluppatore di sistemi embedded che scriveva in C++, un linguaggio che consideravo sia grande che terribile, mentre ho solo imparato un po' di Python all'università.
Quindi, ci siamo trovati di fronte alla sfida di sviluppare un servizio ad alta richiesta, il cui compito principale era il pre-processing dei dati in ingresso e la loro registrazione nel database. Durante una delle pause, un compagno mi ha proposto, come sviluppatore C++, di scrivere questo servizio in C++. Argomentando che sarebbe stata più veloce, più efficiente, e che giurerei che la giuria sarebbe stata entusiasta di come gestivamo le risorse del team. A cui ho risposto che non avevo mai affrontato cose simili in C++ e che avrei potuto facilmente dedicare le restanti 20+ ore alla ricerca, compilazione e collegamento delle librerie appropriate. In parole povere, ho avuto paura. Così abbiamo deciso di scrivere tutto in Python.
Adesso, durante questa auto-isolamento forzato, mi sono deciso a capire come scrivere servizi in C++. La prima cosa da fare era scegliere una libreria adatta. La mia scelta è ricaduta su , poiché era scritta in uno stile orientato agli oggetti e presentava una documentazione adeguata. Si pose anche il problema della scelta del sistema di build. Fino a quel momento avevo lavorato solo con Visual Studio, IAR e makefile 'nudi'. Nessuno di questi sistemi mi ispirava, dato che intendevo eseguire l'intero servizio in un contenitore Docker. Pertanto, decisi di provare a capire cmake e un interessante gestore di pacchetti . Questo gestore di pacchetti consentiva di specificare tutte le dipendenze in un unico file
conanfile.txt
[requires]
poco/1.9.3
libpq/11.5
[generators]
cmake
e con il semplice comando 'conan install .' installare le librerie necessarie. Naturalmente, era anche necessario apportare modifiche a
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries( ${CONAN_LIBS})
Dopo di che, ho iniziato a cercare una libreria per lavorare con PostgreSQL, dato che avevo una piccola esperienza con essa e i nostri servizi in Python interagivano proprio con questa. E sapete cosa ho scoperto? È presente in POCO! Ma Conan non sa che esiste in POCO e non riesce a compilarla, nel repository c'è un file di configurazione obsoleto (ho già segnalato questo errore ai creatori di POCO). Quindi, dovrò cercare un'altra libreria.
E così ho scelto una libreria meno popolare . Sono stato estremamente fortunato, era già presente in conan e persino compilata e linkata.
Il passo successivo è stato scrivere un modello di servizio in grado di gestire le richieste.
Dobbiamo ereditare la nostra classe TemplateServerApp da Poco::Util::ServerApplication e sovrascrivere il metodo main.
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 started" << endl;
waitForTerminationRequest(); \/\/ wait for CTRL-C or kill
cerr << "Shutting down..." << endl;
s.stop();
return Application::EXIT_OK;
}Nel metodo main, dobbiamo impostare i parametri: porta, numero di thread e dimensione della coda. E la cosa più importante, dobbiamo definire il gestore delle richieste in arrivo. Questo avviene creando una fabbrica.
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};Nel mio caso, essa crea semplicemente lo stesso gestore ogni volta: TemplateServerAppHandler. Qui possiamo posizionare la nostra logica di business.
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();
}
};Ho anche creato un modello di classe per lavorare con PostgreSQL. Per eseguire una semplice query SQL, come la creazione di una tabella, c'è un metodo ExecuteSQL(). Per query più complesse o per ottenere dati, dovrai ottenere la connessione tramite GetConnection() e utilizzare l'API libpg. (Forse in futuro correggerò questa ingiustizia).
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;
};Tutti i parametri per la connessione al database vengono presi dall'ambiente, quindi è necessario creare e configurare un file .env
è destinato a leggere le registrazioni dal file
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432Puoi vedere tutto il codice su

Siamo quindi giunti all'ultimo passaggio della scrittura del dockerfile e del docker-compose.yml. Devo essere onesto, gran parte del mio tempo è stato dedicato a questo, non solo perché sono un principiante e ho dovuto ricompilare le librerie ogni volta, ma anche a causa delle insidie di conan. Ad esempio, affinché conan scarichi, installi e compili le dipendenze necessarie, non basta eseguire "conan install .", ma è necessario anche passare il parametro -s compiler.libcxx=libstdc++11; altrimenti, si rischia di ricevere un sacco di errori nella fase di collegamento della propria applicazione. Ho passato ore su questo errore e spero che questo articolo aiuti altre persone a risolvere il problema in meno tempo.
Successivamente, dopo aver scritto il docker-compose.yml, su consiglio di un mio amico, ho aggiunto il supporto per e ora puoi ottenere un modello completo per un servizio REST API in C++, con l'ambiente configurato e PostgreSQL avviato, semplicemente digitando in console "cookiecutter " E poi "docker-compose up --build">.
Spero che questo modello aiuti i neofiti nel loro difficile percorso di sviluppo di applicazioni REST API in un linguaggio tanto grande e potente, quanto ingombrante come il C++.
Inoltre, consiglio vivamente di leggere questo l'articolo. Esso offre ulteriori dettagli su come lavorare con POCO e scrivere il proprio servizio REST API.
Fonte: habr.com
