在前几篇文章中,我们已经实现了一个基础的 HTTP 服务器:
- 通过 Boost.Asio 处理 TCP 连接
- 解析 HTTP 请求头和请求体
- 构造
http_request_struct - 执行业务逻辑并返回
http_response_struct
随着功能越来越多,我开始思考一个问题:
服务器应该如何组织请求处理逻辑?
在很多 Web 框架中,一个请求往往需要经过多个处理步骤,例如:
请求参数解析
↓
Content-Type 处理
↓
日志记录
↓
CORS 处理
↓
Cookie 解析
↓
Session 管理
↓
业务逻辑
如果这些逻辑全部写在一个函数里,代码会迅速变得非常混乱。
因此,大多数 Web 框架都会引入一种机制:
Middleware(中间件)
每个中间件负责一件事情,然后按顺序执行。
本篇是 手写 C++ Web 服务器系列的第五篇。
在这一篇中,我们将详细讲解 C++ 模板展开(Template Expansion)与模板特化(Template Specialization) 在框架中的实现方式,并分析其在路由处理与类型适配中的作用。源码地址:https://github.com/Fall-Rain/asio_web_service
一、最初的中间件实现
在最开始,我使用的是一种比较常见的设计:
std::vector<std::shared_ptr<middleware>> middlewares;
每个 middleware 继承自一个基类:
class middleware {
public:
virtual void handle(Session&, std::function<void()> next) = 0;
};
执行流程:
middleware1
↓
middleware2
↓
middleware3
↓
business logic
这种设计其实就是经典的:
责任链模式(Chain of Responsibility)
但这种实现方式存在几个问题:
1 虚函数调用
每个中间件调用:
virtual dispatch
这会带来额外开销。
2 动态内存分配
每个 middleware 都需要:
new middleware
shared_ptr
3 运行时调度
所有中间件是在 运行时 决定顺序。
但在我的 Web 框架中:
中间件顺序其实是固定的。
既然如此,我们可以把它提前到 编译期。
二、模板中间件链
于是我尝试用 模板参数包(Template Parameter Pack) 来实现 middleware。
先定义一个模板类:
template<typename... middlewares>
class middleware_chain;
这里的:
middlewares...
就是 模板参数包。
例如:
middleware_chain<
process_params_middleware,
process_content_type_middleware,
log_middleware,
cros_middleware,
websocket_middlesare,
cookie_middleware,
session_middleware
>
编译器会把这些类型打包成:
middlewares...
三、模板递归展开
真正的关键在这里:
template<typename First, typename... Rest>
class middleware_chain<First, Rest...>
{
public:
template<typename Session>
static void run(Session session)
{
First middleware;
middleware.handle(session, [session](){
middleware_chain<Rest...>::run(session);
});
}};
这里使用了 模板偏特化:
middleware_chain<First, Rest...>
意思是:
第一个类型
+
剩余类型
例如:
middleware_chain<A,B,C>
会被解析为:
First = A
Rest = B,C
执行流程变成:
A
↓
middleware_chain<B,C>
然后继续展开:
B
↓
middleware_chain<C>
最终:
C
↓
middleware_chain<>
这就是 模板递归展开(Template Recursion)。
四、模板特化(递归终止)
模板递归必须有一个 终止条件。
因此我写了一个 完全特化:
template<>
class middleware_chain<>
{
public:
static void run(std::shared_ptr<Session> session)
{
try
{
session->response =
business_logic::process_request(session->request);
}
catch(const std::exception& e)
{
session->response.body = e.what();
}
}};
这里:
middleware_chain<>
表示:
模板参数为空
也就是:
没有 middleware 了
此时执行最终逻辑:
business_logic::process_request()
整个调用链变成:
middleware1
↓
middleware2
↓
middleware3
↓
business_logic
五、在 Session 中执行
现在在 Session 中只需要一行代码:
void Session::run_middlewares()
{
middleware_chain<
process_params_middleware,
process_content_type_middleware,
log_middleware,
cros_middleware,
websocket_middlesare,
cookie_middleware,
session_middleware
>::run(shared_from_this());
}
执行顺序:
process_params_middleware
↓
process_content_type_middleware
↓
log_middleware
↓
cros_middleware
↓
websocket_middlesare
↓
cookie_middleware
↓
session_middleware
↓
business_logic
六、编译期调用链
和传统 middleware 最大的不同在于:
调用链已经在 编译期确定。
编译器看到代码后会展开成类似:
process_params_middleware.handle()
process_content_type_middleware.handle()
log_middleware.handle()
cros_middleware.handle()
websocket_middleware.handle()
cookie_middleware.handle()
session_middleware.handle()
business_logic()
甚至在 O2 / O3 优化下:
很多函数会 直接 inline。
这意味着:
几乎没有额外调用开销
七、这种设计的优势
1 没有虚函数
避免:
virtual dispatch
2 没有动态内存
不需要:
shared_ptr<middleware>
3 编译期优化
整个调用链可以被:
inline
loop unroll
constant propagation
4 类型安全
如果 middleware 类型写错:
编译期报错
而不是运行时错误。
八、完整请求流程
现在整个 Web 框架的请求流程是:
Client
│
▼
TCP Accept
│
▼
HTTP Header 解析
│
▼
HTTP Body 读取
│
▼
Middleware Chain
│
├ 参数解析
├ Content-Type解析
├ 日志
├ CORS
├ WebSocket检测
├ Cookie
└ Session
│
▼
Business Logic
│
▼
HTTP Response
九、总结
通过模板递归和模板特化,我们实现了一套 编译期中间件系统。
核心技术包括:
- 模板参数包
- 模板递归展开
- 模板特化
- 编译期调用链
本版中间件无运行时开销,符合 C++ zero-cost abstraction
在保证结构清晰的同时,也能获得接近手写代码的性能。
文章评论