In questo articolo parlerò di come ho creato un modello (cookiecutter) e impostato un ambiente per scrivere un servizio API REST in C++ utilizzando docker/docker-compose e il gestore pacchetti conan.
Durante il successivo hackathon, al quale ho partecipato come sviluppatore backend, è sorta la domanda su cosa utilizzare per scrivere il prossimo microservizio. Tutto quello che è stato scritto finora è stato scritto da me e dal mio
Quindi, ci siamo trovati di fronte al compito di scrivere un servizio ad alto carico, il cui compito principale era preelaborare i dati in arrivo e scriverli nel database. E dopo un'altra pausa per fumare, un amico mi ha suggerito, come sviluppatore C++, di scrivere questo servizio utilizzando i professionisti. Sostenere questo è che sarà più veloce, più produttivo e, in generale, la giuria sarà contenta di come sappiamo gestire le risorse del team. Al che ho risposto che non avevo mai fatto cose del genere in C++ e avrei potuto facilmente dedicare le restanti 20+ ore alla ricerca, compilazione e collegamento di librerie adatte. In poche parole, mi sono tirato indietro. Questo è ciò che abbiamo deciso e con calma abbiamo completato tutto in Python.
Ora, durante l'autoisolamento forzato, ho deciso di capire come scrivere servizi in C++. La prima cosa da fare era decidere una biblioteca adatta. La mia scelta è caduta
conanfile.txt
[richiede]poco/1.9.3
libpq/11.5
e con un semplice comando "conan install". installare le librerie necessarie. Naturalmente è stato necessario apportare modifiche anche a
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries(<target_name> ${CONAN_LIBS})
Successivamente, ho iniziato a cercare una libreria con cui lavorare con PostgreSQL, poiché era quella con cui avevo poca esperienza di lavoro, ed era anche quella con cui interagivano i nostri servizi Python. E sai cosa ho imparato? È in POCO! Ma conan non sa che è in POCO e non sa come costruirlo; nel repository c'è un file di configurazione obsoleto (ho già scritto di questo errore ai creatori di POCO). Ciò significa che dovrai cercare un'altra libreria.
E poi la mia scelta è caduta su una libreria meno frequentata
Il passo successivo è stato scrivere un modello di servizio in grado di elaborare 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<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;
}
Nel metodo principale dobbiamo impostare i parametri: porta, numero di thread e dimensione della coda. E, cosa più importante, devi specificare un gestore per le richieste in arrivo. Questo viene fatto creando una fabbrica
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};
Nel mio caso, crea semplicemente ogni volta lo stesso gestore: TemplateServerAppHandler. È qui che possiamo collocare la nostra logica aziendale.
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 un semplice SQL, come la creazione di una tabella, esiste un metodo EseguiSQL(). Per interrogazioni più complesse o recupero dati sarà necessario collegarsi tramite OttieniConnessione() e utilizzare l'API libpg. (Forse più tardi correggerò questa ingiustizia).
Banca Dati
#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 anche il file .env
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432
Puoi vedere tutto il codice su
E ora arriva la fase finale della scrittura del dockerfile e del docker-compose.yml. Ad essere onesti, questo ha richiesto la maggior parte del tempo, e non solo perché sono un noob, perché era necessario ricostruire le librerie ogni volta, ma a causa delle insidie di Conan. Ad esempio, affinché conan possa scaricare, installare e creare le dipendenze necessarie, non è sufficiente scaricare “conan install .”, deve anche passare il parametro -s compiler.libcxx=libstdc++11, altrimenti rischi di ricevere una serie di errori nella fase di collegamento della tua applicazione. Sono rimasto bloccato con questo errore per diverse ore e spero che questo articolo possa aiutare altre persone a risolvere questo problema in meno tempo.
Successivamente, dopo aver scritto docker-compose.yml, su consiglio del mio amico, ho aggiunto il supporto
Spero che questo modello aiuti i principianti nel loro difficile percorso di sviluppo di applicazioni API REST nel linguaggio fantastico e potente, ma così goffo come C++.
Inoltre, consiglio vivamente di leggere qui
Fonte: habr.com