Drogon для C++: полное руководство с примерами
Добро пожаловать в мир высокопроизводительной веб-разработки на C++ с фреймворком Drogon. Если вы привыкли к асинхронным возможностям Node.js, удобству Flask или Django, но при этом вам критична производительность на уровне нативного кода, Drogon — это то, что вам нужно. В этой статье мы подробно разберём, как установить Drogon, написать первое приложение, использовать его ключевые методы и избежать типичных ошибок.
1. Что это и зачем нужна
Drogon — это высокопроизводительный, кроссплатформенный веб-фреймворк для языка C++. В отличие от многих других решений, он построен на асинхронной модели ввода-вывода (IO) с использованием собственного event loop. Это позволяет обрабатывать тысячи одновременных соединений без создания отдельного потока на каждое, что кардинально снижает накладные расходы на память и переключение контекста.
Зачем использовать Drogon? Во-первых, это скорость. Благодаря компилируемому C++ и неблокирующему IO, Drogon способен обрабатывать запросы с задержкой в микросекундах, что делает его идеальным выбором для высоконагруженных REST API, микросервисов и real-time приложений. Во-вторых, это богатая функциональность "из коробки": ORM для работы с базами данных, поддержка WebSocket, JSON, SSL/TLS, middleware, шаблонизатор (ctemplate) и встроенный HTTP-клиент.
Фреймворк активно развивается, имеет понятную документацию и дружелюбное сообщество. Если вам нужно создать backend, который будет работать на пределе возможностей железа, и при этом вы не хотите жертвовать выразительностью кода — Drogon станет вашим надёжным инструментом.
2. Установка
Drogon не устанавливается через пакетный менеджер вроде pip или npm. Его нужно собрать из исходников. Однако процесс максимально автоматизирован с помощью CMake и vcpkg.
Способ 1: Установка через vcpkg (рекомендуется)
# Клонируем vcpkg, если ещё не сделали этого
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh # или bootstrap-vcpkg.bat на Windows
# Устанавливаем Drogon
./vcpkg install drogon
Способ 2: Сборка из исходников вручную
# Устанавливаем зависимости (Ubuntu/Debian)
sudo apt-get install git cmake build-essential libssl-dev libjsoncpp-dev libboost-all-dev libsqlite3-dev libmariadb-dev libpq-dev
# Клонируем репозиторий
git clone https://github.com/drogonframework/drogon.git
cd drogon
# Создаём папку для сборки
mkdir build && cd build
# Конфигурируем и собираем
cmake ..
make -j$(nproc)
# Устанавливаем в систему
sudo make install
После установки вы сможете подключать Drogon в своих CMake-проектах:
find_package(Drogon REQUIRED)
target_link_libraries(my_app PRIVATE Drogon::Drogon)
3. Быстрый старт — минимальный рабочий пример
Давайте напишем простейший HTTP-сервер, который отвечает "Hello, World!" на любой GET-запрос.
// main.cpp
#include <drogon/drogon.h>
int main() {
// Создаём экземпляр приложения
drogon::app().registerHandler("/", [](const drogon::HttpRequestPtr& req,
std::function<void (const drogon::HttpResponsePtr&)> &&callback) {
// Создаём ответ
auto resp = drogon::HttpResponse::newHttpResponse();
resp->setBody("Hello, World!");
callback(resp);
});
// Запускаем сервер на порту 8080
drogon::app().addListener("0.0.0.0", 8080).run();
return 0;
}
Сборка и запуск:
g++ -std=c++17 -o hello main.cpp -ldrogon -ljsoncpp -lssl -lcrypto -lpthread
./hello
Теперь откройте браузер и перейдите на http://localhost:8080. Вы увидите заветное "Hello, World!".
4. Основные методы/функции/классы
Рассмотрим ключевые компоненты Drogon, которые вы будете использовать в каждом проекте.
4.1. drogon::app()
Сигнатура: static drogon::App &drogon::app()
Что делает: Возвращает глобальный синглтон объекта приложения. Через него настраиваются все параметры сервера: порт, логи, количество потоков, middleware и т.д.
Пример:
drogon::app()
.addListener("0.0.0.0", 8080)
.setThreadNum(4) // Использовать 4 потока для обработки
.enableLogging(true)
.run();
4.2. registerHandler()
Сигнатура: void registerHandler(const std::string &path, const HttpMethod &method, Callback callback) (и перегрузки)
Что делает: Регистрирует обработчик для определённого пути и HTTP-метода. Можно использовать лямбды, функции или методы классов.
Пример:
drogon::app().registerHandler("/api/data", drogon::Get, [](auto req, auto callback) {
auto resp = drogon::HttpResponse::newHttpJsonResponse({{"key", "value"}});
callback(resp);
});
4.3. drogon::HttpRequest
Сигнатура: class HttpRequest : public ObjectBase
Что делает: Представляет входящий HTTP-запрос. Позволяет получить параметры, заголовки, тело запроса, cookies и т.д.
Пример:
app().registerHandler("/user", [](const HttpRequestPtr& req, auto callback) {
auto name = req->getParameter("name"); // GET параметр
auto json = req->getJsonObject(); // JSON тело
auto token = req->getHeader("Authorization"); // Заголовок
LOG_INFO << "User: " << name;
callback(HttpResponse::newHttpResponse());
});
4.4. drogon::HttpResponse
Сигнатура: static HttpResponsePtr newHttpResponse() и static HttpResponsePtr newHttpJsonResponse(const Json::Value &data)
Что делает: Создаёт HTTP-ответ. Можно задать статус, тело, заголовки, cookies.
Пример:
auto resp = drogon::HttpResponse::newHttpResponse();
resp->setStatusCode(drogon::k200OK);
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
resp->setBody(R"({"status": "ok"})");
resp->addHeader("X-Custom", "value");
callback(resp);
4.5. drogon::HttpClient
Сигнатура: static HttpClientPtr newHttpClient(const std::string &baseUrl)
Что делает: Создаёт асинхронный HTTP-клиент для отправки запросов к внешним сервисам. Работает в том же event loop, что и сервер.
Пример:
auto client = drogon::HttpClient::newHttpClient("http://api.example.com");
auto req = drogon::HttpRequest::newHttpRequest();
req->setMethod(drogon::Get);
req->setPath("/data");
client->sendRequest(req, [](ReqResult result, const HttpResponsePtr& response) {
if (result == ReqResult::Ok) {
LOG_INFO << "Response: " << response->getBody();
}
});
4.6. drogon::orm::DbClient
Сигнатура: static DbClientPtr newPgClient(const std::string &connInfo) (и для MySQL, SQLite)
Что делает: Создаёт пул соединений к базе данных. Поддерживает PostgreSQL, MySQL, SQLite. Работает асинхронно.
Пример:
auto db = drogon::orm::DbClient::newPgClient("host=localhost port=5432 dbname=test user=user password=pass", 4);
db->execSqlAsync("SELECT * FROM users WHERE id = $1", [](auto result) {
for (auto &row : result) {
LOG_INFO << "User: " << row["name"].as<std::string>();
}
}, [](const drogon::orm::DrogonDbException &e) {
LOG_ERROR << e.base().what();
}, 1);
4.7. drogon::WebSocketController
Сигнатура: class WebSocketController : public HttpSimpleController<WebSocketController>
Что делает: Позволяет обрабатывать WebSocket-соединения. Можно переопределить методы handleNewMessage, handleNewConnection, handleConnectionClosed.
Пример:
class Chat : public drogon::WebSocketController<Chat> {
public:
void handleNewMessage(const WebSocketConnectionPtr& wsConn, std::string &&message) override {
wsConn->send("Echo: " + message);
}
void handleNewConnection(const HttpRequestPtr& req, const WebSocketConnectionPtr& wsConn) override {
LOG_INFO << "New WebSocket connection";
}
};
4.8. drogon::Middleware
Сигнатура: class Middleware : public ObjectBase (необходимо реализовать virtual void invoke(...))
Что делает: Позволяет встраивать промежуточные обработчики (логирование, аутентификация, CORS) в цепочку обработки запроса.
Пример:
class LoggingMiddleware : public drogon::HttpMiddleware<LoggingMiddleware> {
public:
void invoke(const HttpRequestPtr& req, MiddlewareNextCallback&& nextCb, MiddlewareCallback&& mcb) override {
LOG_INFO << "Request: " << req->getPath();
nextCb(mcb);
}
};
// Регистрация
drogon::app().registerMiddleware(std::make_shared<LoggingMiddleware>());
5. Реальный практический пример (100-200 строк кода)
Создадим полноценный REST API для управления списком задач (TODO list) с использованием PostgreSQL и Drogon ORM.
// main.cpp
#include <drogon/drogon.h>
#include <json/json.h>
using namespace drogon;
// Модель задачи
struct Task {
int id;
std::string title;
bool completed;
};
// Контроллер для задач
class TaskController {
public:
static void getAllTasks(const HttpRequestPtr& req,
std::function<void (const HttpResponsePtr&)> &&callback) {
auto db = app().getDbClient();
db->execSqlAsync("SELECT id, title, completed FROM tasks ORDER BY id",
[callback](const orm::Result &result) {
Json::Value tasks(Json::arrayValue);
for (auto &row : result) {
Json::Value task;
task["id"] = row["id"].as<int>();
task["title"] = row["title"].as<std::string>();
task["completed"] = row["completed"].as<bool>();
tasks.append(task);
}
auto resp = HttpResponse::newHttpJsonResponse(tasks);
callback(resp);
},
[callback](const orm::DrogonDbException &e) {
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
resp->setBody(e.base().what());
callback(resp);
});
}
static void createTask(const HttpRequestPtr& req,
std::function<void (const HttpResponsePtr&)> &&callback) {
auto json = req->getJsonObject();
if (!json || !(*json)["title"].isString()) {
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
auto db = app().getDbClient();
db->execSqlAsync("INSERT INTO tasks (title, completed) VALUES ($1, false) RETURNING id, title, completed",
[callback](const orm::Result &result) {
auto &row = result[0];
Json::Value task;
task["id"] = row["id"].as<int>();
task["title"] = row["title"].as<std::string>();
task["completed"] = row["completed"].as<bool>();
auto resp = HttpResponse::newHttpJsonResponse(task);
resp->setStatusCode(k201Created);
callback(resp);
},
[callback](const orm::DrogonDbException &e) {
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
resp->setBody(e.base().what());
callback(resp);
},
(*json)["title"].asString());
}
};
int main() {
// Настройка базы данных
app().createDbClient("postgresql", "localhost", 5432, "testdb", "user", "password", 4);
// Регистрация маршрутов
app().registerHandler("/api/tasks", Get, TaskController::getAllTasks);