
W tym artykule opowiem o tym, jak stworzyłem szablon (cookiecutter) i skonfigurowałem środowisko do pisania usługi REST API w C++ z wykorzystaniem docker/docker-compose oraz menedżera pakietów conan.
Podczas kolejnego hackathonu, w którym brałem udział jako programista backendu, pojawiło się pytanie, na czym napisać kolejny mikroserwis. Wszystko, co do tej pory napisałem, zostało stworzone przeze mnie i mojego w języku Python, ponieważ mój kolega był specjalistą w tej dziedzinie i zawodowo zajmował się programowaniem backendów, podczas gdy ja byłem programistą zajmującym się systemami wbudowanymi i pisałem w wielkim i strasznym C++, a Pythona nauczyłem się jedynie na uniwersytecie.
Otóż przed nami stanęło zadanie stworzenia usługi o wysokim obciążeniu, której głównym celem było wstępne przetwarzanie otrzymywanych danych i zapis ich w bazie danych. Po kolejnej przerwie mój kolega zaproponował mi, jako programiście C++, napisanie tej usługi w C++. Argumentował to tym, że będzie to szybsze, wydajniejsze, a jury będzie zachwycone tym, jak potrafimy zarządzać zasobami zespołu. Na co odpowiedziałem, że nigdy nie zajmowałem się takimi rzeczami w C++ i z łatwością mogę poświęcić pozostałe ponad 20 godzin na poszukiwania, kompilację i łączenie odpowiednich bibliotek. Krótko mówiąc, spanikowałem. Na tym się skończyło i spokojnie dokończyliśmy wszystko w Pythonie.
Teraz, podczas przymusowej izolacji, postanowiłem dowiedzieć się, jak pisać usługi w C++. Pierwszą rzeczą, którą musiałem zrobić, to określić odpowiednią bibliotekę. Mój wybór padł na , ponieważ była napisana w stylu obiektowym i mogła pochwalić się dobrą dokumentacją. Pojawił się również problem wyboru systemu budowania. Do tego momentu pracowałem tylko z Visual Studio, IAR i "golymi" makefile. Żaden z tych systemów mnie nie pociągał, ponieważ planowałem uruchomić całą usługę w kontenerze docker. Wtedy postanowiłem spróbować zmierzyć się z cmake i interesującym menedżerem pakietów . Ten menedżer pakietów umożliwiał zapisanie wszystkich zależności w jednym pliku
conanfile.txt
[requires]
poco/1.9.3
libpq/11.5
[generators]
cmake
i za pomocą prostej komendy „conan install .” zainstalować potrzebne biblioteki. Oczywiście, należało również wprowadzić zmiany w
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries( ${CONAN_LIBS})
Po tym zacząłem szukać biblioteki do pracy z PostgreSQL, ponieważ miałem z nią niewielkie doświadczenie, a nasze usługi w Pythonie również interagowały z nią. I wiecie, co odkryłem? Jest w POCO! Ale conan nie wie, że jest w POCO i nie potrafi jej zbudować, w repozytorium znajduje się przestarzały plik konfiguracyjny (już napisałem o tym błędzie twórcom POCO). Oznacza to, że będę musiał szukać innej biblioteki.
I wtedy mój wybór padł na mniej popularną bibliotekę . I miałem ogromne szczęście, była już w conan i nawet była kompilowana i łączona.
Następnym krokiem było napisanie wzorca usługi, która potrafi obsługiwać zapytania.
Musimy dziedziczyć naszą klasę TemplateServerApp z Poco::Util::ServerApplication i nadpisać metodę 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 << "Serwer uruchomiony" << endl;
waitForTerminationRequest(); // czekaj na CTRL-C lub kill
cerr << "Zamykanie..." << endl;
s.stop();
return Application::EXIT_OK;
}W metodzie main musimy ustawić parametry: port, liczbę wątków i rozmiar kolejki. A co najważniejsze, musimy ustawić obsługę przychodzących zapytań. Robi się to za pomocą utworzenia fabryki
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};W moim przypadku za każdym razem tworzy ona ten sam obsługiwacz — TemplateServerAppHandler. To właśnie tutaj możemy umieścić naszą logikę biznesową.
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 << "Metoda: " << 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();
}
};Stworzyłem również szablon klasy do pracy z PostgreSQL. Aby wykonać proste zapytanie SQL, takie jak stworzenie tabeli, użyj metody ExecuteSQL(). W przypadku bardziej skomplikowanych zapytań lub pozyskiwania danych konieczne będzie uzyskanie połączenia przez GetConnection() i użycie API libpg. (Może później naprawię tę niesprawiedliwość).
Baza danych
#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;
};Wszystkie parametry do połączenia z bazą danych są pobierane z otoczenia, więc musisz również stworzyć i skonfigurować plik .env
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432Możesz zobaczyć cały kod na

I nadszedł ostatni etap pisania dockerfile i docker-compose.yml. Powiem szczerze, to zajęło większość czasu, i to nie tylko dlatego, że jestem nowicjuszem i musiałem za każdym razem przestać budować biblioteki, ale również z powodu pułapek związanych z conanem. Na przykład, aby conan pobrał, zainstalował i zbudował wymagane zależności, nie wystarczy tylko wykonać „conan install .”, musi również przekazać parametr -s compiler.libcxx=libstdc++11, w przeciwnym razie ryzykujesz otrzymanie multitude błędów na etapie kompilacji Twojej aplikacji. Spędziłem kilka godzin z tym błędem, i mam nadzieję, że ten artykuł pomoże innym osobom rozwiązać ten problem w krótszym czasie.
Następnie, po napisaniu docker-compose.yml, na radę mojego kolegi dodałem wsparcie i teraz możesz uzyskać pełnoprawny szablon dla serwisu REST API w C++, z skonfigurowanym otoczeniem i uruchomioną PostgreSQL, po prostu wpisując w konsolę „cookiecutter ”. A następnie „docker-compose up —build”.
Mam nadzieję, że ten szablon pomoże nowicjuszom w ich trudnej drodze do rozwoju aplikacji REST API w wielkim i potężnym, ale tak nieporęcznym języku, jak C++.
Ponadto bardzo polecam przeczytać tę artykuł. Zawiera bardziej szczegółowe informacje na temat pracy z POCO i pisania własnego serwisu REST API.
Źródło: habr.com
