Crow — микровеб-фреймворк для C++
1. Что это и зачем нужна
Crow — это легковесный, быстрый и современный микровеб-фреймворк для языка C++, вдохновлённый популярным Python-фреймворком Flask. Он предназначен для создания REST API, веб-сервисов и простых веб-приложений с минимальным количеством кода и без необходимости в сложной настройке. Crow написан на C++17/20 и использует библиотеки asio (или boost.asio) для асинхронной обработки запросов, что делает его одним из самых производительных решений в своей нише.
Зачем использовать Crow? Если вам нужно быстро развернуть HTTP-сервер на C++ для обработки JSON-запросов, отдачи статики или создания микросервиса, Crow — идеальный выбор. В отличие от тяжеловесных фреймворков вроде drogon или pistache, Crow имеет минимальный размер, простой API и не требует генерации кода или сложных макросов. Он отлично подходит для встраиваемых систем, IoT-устройств, игровых серверов и любых сценариев, где важна скорость и низкое потребление памяти.
Crow поддерживает маршрутизацию (включая динамические параметры в URL), middleware, работу с JSON (через встроенную библиотеку или nlohmann/json), WebSocket, CORS, multipart/form-data и многое другое. Фреймворк активно развивается, имеет хорошую документацию и стабильное комьюнити.
2. Установка
Crow — header-only библиотека (в большинстве случаев), поэтому установка сводится к клонированию репозитория и подключению заголовочных файлов. Однако для сборки примеров и проектов рекомендуется использовать CMake.
Способ 1: Клонирование (рекомендуется для разработки)
git clone https://github.com/CrowCpp/Crow.git
cd Crow
mkdir build
cd build
cmake ..
make
sudo make install
Способ 2: Использование vcpkg
vcpkg install crow
Способ 3: Подключение через CMake FetchContent
include(FetchContent)
FetchContent_Declare(
crow
GIT_REPOSITORY https://github.com/CrowCpp/Crow.git
GIT_TAG v1.0.0
)
FetchContent_MakeAvailable(crow)
target_link_libraries(your_project PRIVATE Crow::Crow)
Важно: Crow требует компилятор с поддержкой C++17 (gcc 8+, clang 7+, MSVC 2019+). Также необходима библиотека asio (или boost.asio). Если вы используете vcpkg, зависимости подтянутся автоматически.
3. Быстрый старт — минимальный рабочий пример
Создайте файл main.cpp со следующим содержимым:
#include "crow.h"
int main() {
crow::SimpleApp app;
// Определяем маршрут для корневого URL
CROW_ROUTE(app, "/")([](){
return "Hello, Crow!";
});
// Запускаем сервер на порту 8080
app.port(8080).multithreaded().run();
}
Скомпилируйте и запустите:
g++ -std=c++17 -pthread main.cpp -o crow_server
./crow_server
Откройте браузер и перейдите на http://localhost:8080. Вы увидите приветственное сообщение.
4. Основные методы/функции/классы
4.1. crow::SimpleApp
Сигнатура: class SimpleApp : public crow::App<Middleware...>
Что делает: Основной класс приложения. Управляет маршрутами, middleware и жизненным циклом сервера. SimpleApp — это предварительно сконфигурированная версия App с минимальным набором middleware.
crow::SimpleApp app;
app.port(8080).run();
4.2. CROW_ROUTE (макрос)
Сигнатура: CROW_ROUTE(app, url)
Что делает: Регистрирует обработчик для указанного URL. Поддерживает динамические параметры (например, /user/<int>).
CROW_ROUTE(app, "/hello/<string>")([](const std::string& name){
return crow::response("Hello, " + name);
});
4.3. crow::response
Сигнатура: response(int code, const std::string& body) или response(const std::string& body)
Что делает: Создаёт HTTP-ответ с заданным кодом и телом. Можно также устанавливать заголовки, тип контента и т.д.
CROW_ROUTE(app, "/json")([](){
crow::json::wvalue x;
x["message"] = "Hello, JSON!";
return crow::response(x);
});
4.4. crow::request
Сигнатура: const crow::request& req (передаётся как параметр в обработчик)
Что делает: Представляет входящий HTTP-запрос. Содержит URL, метод, заголовки, тело, параметры запроса и т.д.
CROW_ROUTE(app, "/info")([](const crow::request& req){
std::string method = req.method;
std::string user_agent = req.get_header_value("User-Agent");
return crow::response("Method: " + method + ", UA: " + user_agent);
});
4.5. crow::json::wvalue / crow::json::rvalue
Сигнатура: crow::json::wvalue (для записи), crow::json::rvalue (для чтения)
Что делает: Встроенная библиотека для работы с JSON. wvalue используется для создания JSON-объектов, rvalue — для парсинга входящих JSON-данных.
// Создание JSON
crow::json::wvalue json;
json["name"] = "Alice";
json["age"] = 30;
json["items"] = {"apple", "banana"};
// Чтение JSON из запроса
CROW_ROUTE(app, "/parse").methods("POST"_method)([](const crow::request& req){
auto body = crow::json::load(req.body);
if (!body) return crow::response(400, "Invalid JSON");
std::string name = body["name"].s();
return crow::response("Hello, " + name);
});
4.6. app.port() / app.bindaddr()
Сигнатура: App& port(uint16_t port) / App& bindaddr(const std::string& addr)
Что делает: Устанавливает порт и IP-адрес для прослушивания. По умолчанию — 0.0.0.0:80.
app.port(3000).bindaddr("127.0.0.1").run();
4.7. app.multithreaded()
Сигнатура: App& multithreaded()
Что делает: Включает многопоточную обработку запросов. Использует пул потоков (по умолчанию количество потоков равно числу ядер CPU).
app.port(8080).multithreaded().run();
4.8. app.loglevel()
Сигнатура: App& loglevel(crow::LogLevel level)
Что делает: Устанавливает уровень логирования. Доступные уровни: Debug, Info, Warning, Error, Critical.
app.loglevel(crow::LogLevel::Warning); // Показывать только предупреждения и ошибки
4.9. crow::mustache::load (шаблонизатор)
Сигнатура: mustache::template_t load(const std::string& filename)
Что делает: Загружает шаблон Mustache и рендерит его с переданными данными. Шаблоны должны находиться в папке templates/.
CROW_ROUTE(app, "/page")([](){
crow::mustache::template_t page = crow::mustache::load("index.html");
crow::json::wvalue ctx;
ctx["title"] = "My Page";
return page.render(ctx);
});
4.10. CROW_CATCHALL_ROUTE
Сигнатура: CROW_CATCHALL_ROUTE(app)
Что делает: Определяет обработчик для всех не найденных маршрутов (404).
CROW_CATCHALL_ROUTE(app)([](){
return crow::response(404, "Not Found");
});
5. Реальный практический пример (100-200 строк кода)
Ниже представлен полноценный REST API для управления списком задач (Todo List) с поддержкой CRUD операций, хранением в памяти и валидацией.
#include "crow.h"
#include <unordered_map>
#include <mutex>
#include <string>
struct Task {
int id;
std::string title;
bool completed;
};
class TodoAPI {
private:
std::unordered_map<int, Task> tasks;
int next_id = 1;
std::mutex mtx;
public:
TodoAPI(crow::SimpleApp& app) {
// GET /tasks — получить все задачи
CROW_ROUTE(app, "/tasks").methods("GET"_method)([this](){
std::lock_guard<std::mutex> lock(mtx);
crow::json::wvalue list;
int index = 0;
for (const auto& [id, task] : tasks) {
crow::json::wvalue item;
item["id"] = task.id;
item["title"] = task.title;
item["completed"] = task.completed;
list[index++] = std::move(item);
}
return crow::response(list);
});
// POST /tasks — создать задачу
CROW_ROUTE(app, "/tasks").methods("POST"_method)([this](const crow::request& req){
auto body = crow::json::load(req.body);
if (!body || !body.has("title")) {
return crow::response(400, "Missing 'title' field");
}
std::lock_guard<std::mutex> lock(mtx);
Task new_task{next_id++, body["title"].s(), false};
tasks[new_task.id] = new_task;
crow::json::wvalue res;
res["id"] = new_task.id;
res["title"] = new_task.title;
res["completed"] = new_task.completed;
return crow::response(201, res);
});
// GET /tasks/{id} — получить задачу по ID
CROW_ROUTE(app, "/tasks/<int>").methods("GET"_method)([this](int id){
std::lock_guard<std::mutex> lock(mtx);
auto it = tasks.find(id);
if (it == tasks.end()) {
return crow::response(404, "Task not found");
}
crow::json::wvalue res;
res["id"] = it->second.id;
res["title"] = it->second.title;
res["completed"] = it->second.completed;
return crow::response(res);
});
// PUT /tasks/{id} — обновить задачу
CROW_ROUTE(app, "/tasks/<int>").methods("PUT"_method)([this](int id, const crow::request& req){
auto body = crow::json::load(req.body);
if (!body) {
return crow::response(400, "Invalid JSON");
}
std::lock_guard<std::mutex> lock(mtx);
auto it = tasks.find(id);
if (it == tasks.end()) {
return crow::response(404, "Task not found");
}
if (body.has("title")) it->second.title = body["title"].s();
if (body.has("completed")) it->second.completed = body["completed"].b();
crow::json::wvalue res;
res["id"] = it->second.id;
res["title"] = it->second.title;
res["completed"] = it->second.completed;
return crow::response(res);
});
// DELETE /tasks/{id} — удалить задачу
CROW_ROUTE(app, "/tasks/<int>").methods("DELETE"_method)([this](int id){
std::lock_