
В этой статье я расскажу о том, как создал шаблон (cookiecutter) и настроил окружение для написания REST API сервиса на С++ с использованием docker/docker-compose и пакетного менеджера conan.
Во время очередного хакатона, в котором я участвовал в качестве бекенд-разработчика, встал вопрос о том, на чем писать очередной микросервис. Все что было написано на текущий момент, писалось мной и моим товарищем на языке Python, так как мой коллега был специалистом в этой области и профессионально занимался разработкой бекендов, в то время как я вообще являлся разработчиком под встроенные системы и писал на великом и ужасном С++, а Python просто подучил в университете.
Так вот, перед нами встала задача написать высоконагруженный сервис, основной задачей которого был препроцессинг поступающих к нему данных и запись их в БД. И после очередного перекура товарищ предложил мне, как С++ разработчику, написать этот сервис на плюсах. Аргументируя это тем, что так будет быстрее, производительнее, да и вообще, жюри будут в восторге от того, как мы умеем распоряжаться ресурсами команды. На что я ответил, что никогда не занимался такими вещами на С++ и с легкостью могу оставшиеся 20+ часов посвятить поиску, компиляции и компоновке подходящих библиотек. Проще говоря, я струсил. На том и порешили и спокойно дописали все на Python.
Сейчас же, во время вынужденной самоизоляции я решился разобраться в том, как писать сервисы на С++. Первое, что нужно было сделать, это определиться с подходящей библиотекой. Мой выбор пал на POCO, так как она была написана в объектно-ориентированном стиле, а также могла похвастаться нормальной документацией. Также, встал вопрос о выборе системы сборки. Я до этого момента работал только с Visual Studio, IAR и «голыми» makefile. И ни одна из этих систем меня не прельщала, так как я планировал запускать весь сервис в docker-контейнере. Тогда я решил попробовать разобраться с cmake и интересным пакетным менеджером conan. Этот пакетный менеджер позволял прописать все зависимости в одном файле
poco/1.9.3
libpq/11.5
[generators]
cmake
и с помощью простой команды «conan install .» установить необходимые библиотеки. Естественно, также требовалось внести изменения в
include(build/conanbuildinfo.cmake) conan_basic_setup() target_link_libraries(<target_name> ${CONAN_LIBS})
После этого я начал искать библиотеку для работы с PostgreSQL, так как именно с ней у меня имелся небольшой опыт работы, а также именно с ней взаимодействовали наши сервисы на Python. И знаете, что я узнал? Она есть в POCO! Но conan не знает, что она есть в POCO и не умеет ее билдить, в репозитории лежит устаревший конфигурационный файл (я уже написал об этой ошибке создателям POCO). А значит, придется искать другую библиотеку.
И тогда мой выбор пал на менее популярную библиотеку libpg. И мне несказанно повезло, она уже была в conan и даже собиралась и компоновалась.
Следующим шагом было написание шаблона сервиса, умеющего обрабатывать запросы.
Мы должны унаследовать наш класс TemplateServerApp от Poco::Util::ServerApplication и переопределить метод main.
#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; }
В методе main мы должны задать параметры: порт, количество потоков и размер очереди. А самое главное, должны задать обработчик входящих запросов. Делается это посредством создания фабрики
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory { public: virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request) { return new TemplateServerAppHandler; } };
В моем случае она просто каждый раз создает один и тот же обработчик — 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(); } };
Также я создал шаблон класса для работы с PostgreSQL. Для того, чтобы выполнить простой SQL, например создать таблицу, есть метод ExecuteSQL(). Для более сложных запросов или получения данных придется получать connection через GetConnection() и использовать API libpg. (Возможно потом я исправлю эту несправедливость).
#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; };
Все параметры для подключения к базе данных берутся из окружения, так что вам также нужно создать и настроить файл .env
DATABASE_NAME=template DATABASE_USER=user DATABASE_PASSWORD=password DATABASE_HOST=postgres DATABASE_PORT=5432
Вы можете посмотреть весь код на гитхабе.

И настал последний этап написания dockerfile и docker-compose.yml. Скажу честно, на это ушла большая часть времени, и не только потому, что я нуб, что необходимо было каждый раз пересобирать библиотеки, а из-за подводных камней conan. Так например, для того, чтобы conan скачал, установил и побилдил необходимые зависимости, ему мало скачать «conan install .», ему также необходимо передать параметр -s compiler.libcxx=libstdc++11, иначе вы рискуете получить кучу ошибок на этапе компоновки вашего приложения. Я просидел с этой ошибкой несколько часов, и надеюсь, что эта статья поможет другим людям решить эту проблему за более короткое время.
Далее, после написания docker-compose.yml, по совету своего товарища я добавил поддержку cookiecutter и теперь вы можете получить себе полноценный шаблон для REST API сервиса на С++, c настроенным окружением, и поднятой PostgreSQL, просто введя в консоль «cookiecutter https://github.com/KovalevVasiliy/cpp_rest_api_template.git». А затем «docker-compose up --build».
Надеюсь, данный шаблон поможет новичкам на их нелегком пути разработки REST API приложений на великом и могучем, но таком неповоротливом языке, как С++.
Также, я очень рекомендую прочитать вот эту статью. В ней подробнее объясняется как работать с POCO и написать свой REST API сервис.
