在前面的文章中,我们已经实现了一个基础的 HTTP 服务,可以接收请求、解析 HTTP 头并执行对应的业务逻辑。但随着功能逐渐增多,我很快发现一个问题:
很多逻辑其实是通用逻辑,并不属于某一个具体业务。例如:
- 请求日志记录
- Cookie 解析
- Session 管理
- 参数解析
- Content-Type 处理
- 跨域 CORS 处理
如果这些逻辑全部写在业务代码里,代码会迅速变得臃肿,例如:
处理请求
├─ 打印日志
├─ 解析 Cookie
├─ 创建 Session
├─ 解析参数
├─ 业务逻辑
└─ 构建响应
随着功能增加,代码会越来越难维护。
因此,大多数 Web 框架都会提供一种机制:Middleware(中间件)。
在很多流行框架中,例如 Express.js、Koa 或 Gin,中间件都是核心设计之一。
本篇是手写 C++ Web 服务器系列第四篇,本篇我们来实现一套简单的中间件系统,源码地址:https://github.com/Fall-Rain/asio_web_service
一、中间件的核心思想
中间件本质上是一条处理链。
一个请求进入服务器后,会依次经过多个处理阶段:
Client Request
│
▼
Middleware 1
│
▼
Middleware 2
│
▼
Middleware 3
│
▼
Business Handler
│
▼
Response
每个中间件都可以:
- 处理请求
- 修改请求
- 决定是否继续执行下一个中间件
- 在业务执行之后再处理响应
这种设计通常称为:
责任链模式(Chain of Responsibility)
二、中间件接口设计
首先我们定义一个统一的中间件接口:
class middleware {
public:
virtual ~middleware() = default;
virtual void handle(std::shared_ptr<Session> session,
std::function<void()> next) = 0;
};
这里有两个关键参数:
session
用于访问:
- request
- response
- socket
- session_id
next()
用于执行下一个中间件。
如果某个中间件不调用 next(),请求链就会在这里终止。
三、中间件执行机制
在 Session 中,我们维护了一个中间件列表:
std::vector<std::shared_ptr<middleware>> middlewares_;
服务器初始化时注册中间件:
Session::Session(boost::asio::ip::tcp::socket &socket)
: client_socket_(std::move(socket)) {
add_middleware(std::make_shared<process_params_middleware>());
add_middleware(std::make_shared<process_content_type_middleware>());
add_middleware(std::make_shared<log_middleware>());
add_middleware(std::make_shared<cros_middleware>());
add_middleware(std::make_shared<cookie_middleware>());
add_middleware(std::make_shared<session_middleware>());
}
当请求解析完成后,会执行:
run_middlewares();
核心逻辑如下:
void Session::run_next(size_t index) {
if (index < middlewares_.size()) {
middlewares_[index]->handle(
shared_from_this(),
[this, index] {
run_next(index + 1);
});
} else {
response = business_logic::process_request(request);
}
}
执行流程其实非常简单:
middleware[0]
↓
middleware[1]
↓
middleware[2]
↓
business_logic
每个中间件调用 next() 时,才会进入下一个。
四、中间件的执行顺序
当前服务器注册的中间件顺序如下:
process_params_middleware
process_content_type_middleware
log_middleware
cros_middleware
cookie_middleware
session_middleware
请求流程:
HTTP Request
│
▼
解析 URL 参数
│
解析 Content-Type
│
打印请求日志
│
CORS 处理
│
Cookie 解析
│
Session 管理
│
业务处理
│
Response
五、日志中间件
日志中间件用于记录请求和响应。
class log_middleware : public middleware {
void handle(std::shared_ptr<Session> session, std::function<void()> next) override {
std::cout << "请求:" << std::endl
<< session->request.to_http_string()
<< std::endl; next(); std::cout << "应答:" << std::endl
<< session->response.to_http_string()
<< std::endl;
}
};
注意这里的执行顺序:
打印请求
↓
next()
↓
打印响应
这样就可以同时记录:
- 请求内容
- 返回结果
六、Cookie 中间件
Cookie 中间件负责解析请求中的 Cookie。
auto header_cookie = session->request.headers.find("Cookie");
如果存在 Cookie:
Cookie: session=abc123; user=fallrain
则解析为:
session -> abc123
user -> fallrain
并写入:
request.cookie
响应阶段,如果有 Cookie 需要返回:
Set-Cookie: session=xxxx
七、Session 中间件
Session 中间件负责:
- 检查是否存在 session
- 如果没有则创建
- 将 session_id 写入 request
核心逻辑:
auto it = session->request.cookie.find("session");
如果没有:
create_session_map()
如果有但失效:
重新创建 session
最后将 session 写入响应:
Set-Cookie: session=xxxx
八、Content-Type 处理
HTTP 请求体可能是多种格式:
application/json
application/xml
multipart/form-data
中间件会自动解析这些内容:
JSON:
boost::property_tree::read_json
XML:
boost::property_tree::read_xml
FormData:
解析 multipart boundary。
最终解析结果写入:
request.ptree
request.form_data
业务代码可以直接使用。
九、中间件设计的优势
引入中间件后,服务器架构变得非常清晰:
HTTP解析
│
▼
Middleware Chain
│
▼
Business Logic
优点:
- 模块解耦
- 逻辑复用
- 可插拔设计
- 代码结构清晰
新增功能只需要新增一个中间件。
例如:
- JWT 鉴权
- 限流
- API 日志
- 监控
十、总结
在这个 C++ Web 服务器中,中间件系统成为整个框架的核心扩展机制。
通过简单的接口设计和责任链模式,我们实现了一个非常灵活的请求处理流程。
最终形成了这样的架构:
TCP Socket
│
HTTP Parser
│
Middleware Chain
│
Business Logic
│
HTTP Response
文章评论
这个确实可以学习一下 感谢大佬分享
@陈祈星 推荐你看一下最新的一篇模板化的中间件链设计,这个更符合C++的风格