W tym artykule opowiem o tym, jak stworzyłem szablon (cookiecutter) i skonfigurowałem środowisko do pisania usługi REST API w C++ za pomocą docker/docker-compose i menedżera pakietów Conan.
Podczas kolejnego hackathonu, w którym brałem udział jako backend developer, pojawiło się pytanie, czego użyć do napisania kolejnego mikroserwisu. Wszystko, co do tej pory napisano, zostało napisane przeze mnie i moich
Stanęliśmy więc przed zadaniem napisania usługi o dużym obciążeniu, której głównym zadaniem było wstępne przetworzenie przychodzących do niej danych i zapisanie ich do bazy danych. A po kolejnej przerwie na papierosa znajomy zasugerował, żebym jako programista C++ napisał tę usługę z wykorzystaniem profesjonalistów. Twierdzę, że będzie szybciej, bardziej produktywnie i ogólnie rzecz biorąc, jury będzie zachwycone tym, jak wiemy, jak zarządzać zasobami zespołu. Na co odpowiedziałem, że nigdy nie robiłem takich rzeczy w C++ i z łatwością mogłem poświęcić pozostałe 20+ godzin na wyszukiwanie, kompilowanie i łączenie odpowiednich bibliotek. Krótko mówiąc, stchórzyłem. Tak zdecydowaliśmy i spokojnie dokończyliśmy wszystko w Pythonie.
Teraz, podczas przymusowej izolacji, postanowiłem wymyślić, jak napisać usługi w C++. Pierwszą rzeczą do zrobienia było wybranie odpowiedniej biblioteki. Mój wybór padł
conanfile.txt
[wymaga]poco/1.9.3
libpq/11.5
i za pomocą prostego polecenia „conan install”. zainstaluj niezbędne biblioteki. Naturalnie konieczne było również dokonanie zmian w
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries(<target_name> ${CONAN_LIBS})
Następnie zacząłem szukać biblioteki do pracy z PostgreSQL, ponieważ miałem z nią niewielkie doświadczenie w pracy, a także z nią współdziałały nasze usługi Pythona. I wiesz, czego się nauczyłem? Jest w POCO! Ale Conan nie wie, że jest w POCO i nie wie jak go zbudować; w repozytorium jest nieaktualny plik konfiguracyjny (o tym błędzie pisałem już twórcom POCO). Oznacza to, że będziesz musiał poszukać innej biblioteki.
A potem mój wybór padł na mniej popularną bibliotekę
Kolejnym krokiem było napisanie szablonu usługi, który będzie mógł przetwarzać zgłoszenia.
Musimy dziedziczyć naszą klasę TemplateServerApp z Poco::Util::ServerApplication i zastąpić metodę główną.
Aplikacja serwera szablonów
#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;
}
W metodzie głównej musimy ustawić parametry: port, liczbę wątków i wielkość kolejki. A co najważniejsze, musisz określić procedurę obsługi przychodzących żądań. Odbywa się to poprzez utworzenie fabryki
Fabryka obsługi szablonów żądań
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};
W moim przypadku po prostu tworzy za każdym razem ten sam moduł obsługi - TemplateServerAppHandler. 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 << "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();
}
};
Stworzyłem także szablon klasy do pracy z PostgreSQL. Aby wykonać prosty SQL, na przykład utworzyć tabelę, istnieje metoda WykonajSQL(). W przypadku bardziej złożonych zapytań lub pobierania danych konieczne będzie uzyskanie połączenia za pośrednictwem PobierzPołączenie() i użyj interfejsu API libpg. (Być 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 połączenia z bazą danych pobierane są ze środowiska, dlatego też należy utworzyć i skonfigurować plik .env
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432
Cały kod możesz zobaczyć na stronie
Nadchodzi ostatni etap pisania pliku dockerfile i pliku docker-compose.yml. Szczerze mówiąc, zajęło to najwięcej czasu i nie tylko dlatego, że jestem noobem, ponieważ za każdym razem konieczne było przebudowywanie bibliotek, ale także z powodu pułapek Conana. Na przykład, aby Conan mógł pobrać, zainstalować i zbudować niezbędne zależności, nie wystarczy pobrać „conan install .”, musi jeszcze przekazać parametr -s kompilator.libcxx=libstdc++11, w przeciwnym razie ryzykujesz wystąpieniem wielu błędów na etapie łączenia aplikacji. Utknąłem z tym błędem przez kilka godzin i mam nadzieję, że ten artykuł pomoże innym osobom rozwiązać ten problem w krótszym czasie.
Następnie po napisaniu pliku docker-compose.yml za radą znajomego dodałem wsparcie
Mam nadzieję, że ten szablon pomoże początkującym na ich trudnej ścieżce tworzenia aplikacji REST API w świetnym i potężnym, ale tak niezgrabnym języku, jak C++.
Również gorąco polecam lekturę tutaj
Źródło: www.habr.com