Drogon для C++: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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);                    

Рекомендаций