更多 Python 文章见《有容乃大(Python集萃)》专栏
Crow 是一个轻量、极速、头文件仅需的 C++ 微型 Web 框架,Flask 风格路由、类型安全、内置 JSON/WebSocket/ 模板引擎,适合快速构建高性能 HTTP 服务。
主要为解决:
- 路由参数类型不安全:通过
black_magic::get_parameter_tag在编译期生成参数签名 Tag,将 URL 模板与 handler 参数类型做静态校验 - 多线程模型复杂:封装了 Asio
io_service池 + Round-Robin 调度,开发者无需手动管理线程模型 - 中间件扩展困难:通过
std::tuple<Middlewares...>+ 模板递归实现编译期中间件管线,零虚函数开销
场景匹配:
| 能力 | 适用场景 | 不适用场景 |
|---|---|---|
| Trie 前缀树路由 | 中等规模路由表(<1000 条),路径参数多 | 超大规模路由表(>10k),需正则路由 |
| 编译期类型安全 handler | API 服务,参数类型明确 | 动态路由注册(运行时才确定路由) |
| Asio 异步 I/O | 高并发短连接 HTTP 服务 | CPU 密集型长连接推送 |
| WebSocket | 实时双向通信(聊天、推送) | 高吞吐流式数据(应选专用协议) |
| Header-Only | 快速原型、嵌入式集成 | 大型项目编译时间敏感 |
| Blueprint 模块化 | 多版本 API 分组、插件式架构 | 单体极简服务 |
整体架构与执行流程
架构图

核心模块职责
| 模块 | 核心职责 | 输入 | 输出 |
|---|---|---|---|
Crow<Middlewares...> |
应用生命周期管理、配置聚合、路由注册入口 | 路由规则、端口/线程数等配置 | 运行中的 Server 实例 |
Router |
URL → Rule 映射、Trie 查找、Blueprint 解析 | request.url + request.method |
routing_handle_result(rule_index + routing_params) |
Trie |
前缀树 URL 匹配,支持 <int> <str> <path> 等参数类型 |
URL 字符串 | 匹配的 rule_index + 解析后的参数 |
Server |
多线程 io_service 池管理、accept 调度、信号处理 | 新连接 fd | 分配到最空闲 io_service 的 Connection |
Connection |
单个 HTTP 连接完整生命周期:解析→路由→中间件→handler→响应 | TCP socket 数据流 | HTTP 响应字节流 |
HTTPParser |
将原始字节流解析为结构化 request |
TCP buffer 字节流 | 填充完整的 request 对象 |
Middleware Pipeline |
编译期递归展开 before_handle/after_handle 调用链 | request + response + context | 修改后的 request/response |
task_timer |
连接超时调度,基于秒级 tick 的延迟任务队列 | 超时秒数 + 回调函数 | 超时触发时执行回调(关闭连接) |
核心执行时序

底层原理
Crow 用编译期计算换取运行时安全。通过 get_parameter_tag 将 URL 模板的参数类型编码为编译期常量,再与 handler 签名做静态校验,将一类常见的运行时错误(参数类型不匹配)前移到编译期。通过牺牲编译时间和错误信息的可读性,换来生产环境中更少的运行时故障。
抽象与机制
Trie 前缀树路由
Crow 的路由匹配核心是一棵按 HTTP Method 分组的 Trie(前缀树)。每个 HTTP Method 拥有独立的 PerMethod 结构,包含 rules 向量和 Trie 实例。Trie 节点 (Node) 的关键字段:
rule_index:匹配到该节点时对应的规则索引(0 表示无规则)param:参数类型(INT/UINT/DOUBLE/STRING/PATH/MAX,MAX 表示无参数)children:子节点向量
匹配算法是递归下降:从根节点出发,逐字符匹配静态片段,遇到参数节点时用 strtoll/strtod 等解析并压入 routing_params,回溯时 pop 参数。optimize() 方法将单字符链合并为多字符节点,减少递归深度。
编译期参数签名 (Black Magic)
CROW_ROUTE(app, "/users/<int>/posts/<string>") 宏展开时,get_parameter_tag 在编译期扫描 URL 字符串字面量,生成一个 6 进制编码的 Tag 值(int=1, uint=2, double=3, string=4, path=5)。该 Tag 作为模板参数传入 Router::new_rule_tagged<Tag>,在编译期与 handler 的参数类型列表做兼容性校验。不兼容时直接编译错误,而非运行时崩溃。
io_service 池多线程模型
Server 启动时创建 concurrency_ - 1 个 worker 线程,每个线程运行独立的 io_service 实例。主线程运行 acceptor 的 io_service。新连接通过 pick_io_service_idx() 选择队列最短的 io_service,然后 post 到该线程执行。这种模型下:
- 每个 io_service 的事件循环在单线程中运行,无锁
- 连接的整个生命周期(读→解析→处理→写)固定在同一个 io_service 线程
task_queue_length_pool_使用std::atomic<unsigned int>跟踪各线程负载
Scatter-Gather 零拷贝响应
prepare_buffers() 构建响应时,不将所有数据拼接为单一字符串,而是将状态行、每个 Header 键值对、Date/Server 行分别作为 asio::const_buffer 存入 buffers_ 向量。do_write() 一次性将所有 buffer 交给 Asio 的 async_write,由内核做 gather-write,避免用户态内存拷贝。对于超过 stream_threshold_(默认 1MiB)的响应体,切换为同步分块写入,绕过超时计时器。
中间件管线模板递归
中间件调用链通过 middleware_call_helper<CallCriteria, N, Context, Container> 实现。N 从 0 递增到 sizeof...(Middlewares) - 1,依次调用每个中间件的 before_handle。如果任一中间件调用 res.end()(短路),则立即反向调用已执行中间件的 after_handle。after_handlers_call_helper 从 N 递减到 0,保证 LIFO 顺序。CallCriteria 策略控制哪些中间件被调用(全局 vs 局部)。
源码地图
bash
crow/include/
├── crow.h # 聚合头文件,引入所有模块
├── crow/
│ ├── app.h # [核心] Crow<App> 主类,服务生命周期、配置、run()
│ ├── routing.h # [核心] Router + Trie + BaseRule/TaggedRule/DynamicRule + Blueprint
│ ├── http_connection.h # [核心] Connection 模板,请求完整生命周期、scatter-gather 响应
│ ├── http_server.h # [核心] Server 模板,io_service 池、accept 调度、信号处理
│ ├── middleware.h # [核心] 中间件管线模板递归、ILocalMiddleware 接口
│ ├── parser.h # HTTPParser,封装 http-parser 回调驱动解析
│ ├── http_request.h # request 结构体
│ ├── http_response.h # response 结构体、静态文件、重定向
│ ├── websocket.h # WebSocket Connection,RFC6455 帧解析
│ ├── middleware_context.h # context<Middlewares...>,编译期中间件上下文
│ ├── utility.h # black_magic 命名空间:const_str、parameter_tag、编译期校验
│ ├── task_timer.h # 秒级 tick 定时器,连接超时管理
│ ├── socket_adaptors.h # SocketAdaptor / SSLAdaptor,Asio socket 抽象
│ ├── compression.h # zlib 压缩(deflate/gzip)
│ ├── json.h # 轻量 JSON 读写
│ ├── mustache.h # Mustache 模板引擎
│ ├── ci_map.h # 大小写不敏感 map(header 存储)
│ ├── query_string.h # URL query string 解析
│ ├── http_parser_merged.h # nodejs/http-parser C 库内嵌
│ ├── logging.h # 日志系统(LogLevel + ILogHandler)
│ ├── settings.h # 编译配置宏
│ ├── version.h # 版本号
│ └── middlewares/
│ ├── cookie_parser.h # Cookie 读写中间件
│ ├── cors.h # CORS 中间件
│ ├── session.h # Session 中间件
│ └── utf-8.h # UTF-8 校验中间件
API 介绍
常用 API 表格
| API | 参数 | 说明 |
|---|---|---|
CROW_ROUTE(app, url) |
app: Crow 实例; url: 路由模板 |
注册编译期类型安全路由,返回 TaggedRule |
app.route_dynamic(url) |
url: 路由字符串 |
注册运行时动态路由,返回 DynamicRule(无编译期校验) |
app.port(uint16_t) |
端口号 | 设置监听端口,返回 self_t& 支持链式调用 |
app.multithreaded() |
无 | 使用所有硬件线程,等价于 concurrency(hardware_concurrency) |
app.concurrency(uint16_t) |
线程数 | 设置工作线程数(最小 2) |
app.timeout(uint8_t) |
秒数 | 设置连接超时(默认 5s) |
app.loglevel(LogLevel) |
日志级别 | 设置日志级别(Debug/Info/Warning/Error/Critical) |
app.bindaddr(string) |
IP 地址 | 绑定地址(默认 "0.0.0.0") |
app.register_blueprint(bp) |
Blueprint 引用 | 注册蓝图模块 |
app.tick(duration, func) |
时间间隔 + 回调 | 设置定时 tick 回调 |
app.stream_threshold(size_t) |
字节数 | 设置响应体流式写入阈值(默认 1MiB) |
app.validate() |
无 | 校验所有路由规则(run() 自动调用) |
app.run() |
无 | 阻塞启动服务 |
app.run_async() |
无 | 非阻塞启动,返回 std::future<void> |
app.stop() |
无 | 停止服务 |
rule.methods(HTTPMethod...) |
HTTP 方法枚举 | 设置路由允许的 HTTP 方法 |
rule.name(string) |
路由名称 | 为路由命名(调试用) |
CROW_BP_ROUTE(bp, url) |
Blueprint + URL | 在蓝图内注册路由 |
CROW_WEBSOCKET_ROUTE(app, url) |
App + URL | 注册 WebSocket 路由 |
ws.onopen/onmessage/onclose/onerror(f) |
回调函数 | 设置 WebSocket 事件回调 |
res.end() / res.end(body_part) |
可选 body 片段 | 结束响应并发送 |
res.set_header(key, value) |
头键值 | 设置响应头 |
res.redirect(location) |
目标 URL | 307 临时重定向 |
res.set_static_file_info(path) |
文件路径 | 返回静态文件响应 |
样例
ruby
#include "crow.h"
#include <spdlog/spdlog.h>
int main()
{
crow::App<crow::CookieParser, crow::CORSHandler> app;
CROW_ROUTE(app, "/health")
.methods(crow::HTTPMethod::Get)([]() {
return crow::response(200, "{"status":"ok"}");
});
CROW_ROUTE(app, "/users/<int>")
.methods(crow::HTTPMethod::Get)([](const crow::request& req, crow::response& res, int user_id) {
if (user_id <= 0) {
res = crow::response(400, "invalid user_id");
res.end();
return;
}
crow::json::wvalue result;
result["id"] = user_id;
result["name"] = "user_" + std::to_string(user_id);
res = crow::response(result);
res.set_header("Content-Type", "application/json");
res.end();
});
CROW_ROUTE(app, "/echo")
.methods(crow::HTTPMethod::Post)([](const crow::request& req, crow::response& res) {
if (req.body.empty()) {
res = crow::response(400, "empty body");
res.end();
return;
}
SPDLOG_INFO("echo request body size={}", req.body.size());
res = crow::response(200, req.body);
res.set_header("Content-Type", "application/octet-stream");
res.end();
});
auto& cors = app.get_middleware<crow::CORSHandler>();
cors.global().methods("GET,POST,OPTIONS");
app.loglevel(crow::LogLevel::Info)
.port(8080)
.concurrency(4)
.timeout(10);
SPDLOG_INFO("starting crow server on 0.0.0.0:8080 with 4 threads");
app.run();
return 0;
}
日志追踪
替换默认 CerrLogHandler,实现自定义 ILogHandler 将日志桥接到 spdlog。为每个请求生成 X-Request-Id,在中间件 before_handle 中注入,贯穿全链路日志。
arduino
class SpdlogHandler : public crow::ILogHandler {
public:
void log(std::string message, crow::LogLevel level) override {
switch (level) {
case crow::LogLevel::Debug: SPDLOG_DEBUG("{}", message); break;
case crow::LogLevel::Info: SPDLOG_INFO("{}", message); break;
case crow::LogLevel::Warning: SPDLOG_WARN("{}", message); break;
case crow::LogLevel::Error: SPDLOG_ERROR("{}", message); break;
case crow::LogLevel::Critical: SPDLOG_CRITICAL("{}", message); break;
}
}
};