在上一篇文章中,我们搭建了 C++ Web 服务器的基础骨架,包括 TCP 监听、异步 Session 管理、HTTP 请求解析以及简单响应发送。这些是服务器的基础能力,但真正的 Web 服务并不仅仅是“接收请求-返回响应”。一个成熟的服务器必须能够:
- 处理复杂业务逻辑
- 管理用户状态(如登录、购物车等)
- 保证请求可追踪性和安全性
- 支持多种内容类型和请求体格式
本篇文章将详细介绍我在开源项目 asio_web_service 中实现的 业务逻辑处理、Cookie 与 Session 管理机制,让服务器能够支持真实 Web 应用的功能。
一、业务逻辑框架设计
为了让服务器能够处理多样化业务请求,我设计了一个 集中式业务逻辑管理框架:
std::map<HttpMethod, std::map<std::string, FunctionPtr>> business_logic::function_map = {
{HttpMethod::GET, {}},
{HttpMethod::POST, {}},
...
};
这里使用了 静态映射表,根据 HTTP 方法(GET、POST 等)和 URI 路径映射到对应的处理函数。
1. 为什么用静态映射表?
- 可扩展性:新增业务逻辑时,只需要注册一个函数,而不改核心框架
- 解耦核心和业务:服务器核心只负责网络和 IO,业务逻辑完全独立
- 统一调度:请求到来时只需根据方法和 URI 查表即可执行对应函数,效率高
注册业务逻辑非常简单,例如:
business_logic::register_handle(HttpMethod::GET, "/hello", [](const http_request_struct &req
{
return http_response_struct("Hello World", ContentType::TEXT_PLAIN);
});
这行代码就完成了一个 /hello GET 请求的业务处理函数注册。
二、Session 管理与 Cookie
1. Session 的概念
Web 是无状态协议,每次 HTTP 请求都是独立的。为了跟踪用户状态,我们必须在服务器端维护一个 Session。
在 business_logic 中,我使用:
std::map<std::string, std::map<std::string, std::string >> business_logic::session_map;
- key:
session_id(UUID 字符串) - value:用户数据映射,如登录状态、临时信息等
2. 创建 Session
当客户端首次访问服务器时,如果没有携带 session Cookie,服务器会创建新的 Session:
std::string business_logic::create_session_map() {
boost::uuids::uuid uuid = boost::uuids::random_generator()();
std::string uuidStr = boost::uuids::to_string(uuid);
session_map[uuidStr] = {}; // 初始化空的 session 数据
return uuidStr;
}
这里使用 Boost UUID 保证每个 Session 唯一,防止冲突。新 Session 会在响应中通过 Set-Cookie 返回给客户端,客户端保存 Cookie 后,下次请求即可带上 Session ID。
3. 获取 Session
如果客户端请求中已经带了 Cookie,服务器直接使用对应 Session:
if (cookie_map.find("session") == cookie_map.end()) {
session_id = business_logic::create_session_map();
} else {
session_id = cookie_map["session"];
}
这样,每个客户端的请求都能访问自己独立的 Session 数据,确保状态隔离。
三、Cookie 的处理
1. 解析客户端 Cookie
HTTP 请求头中包含 Cookie,我们需要解析并存储到映射表:
auto header_cookie = headers.find("Cookie");
if (header_cookie != headers.end()) {
std::vector<string> cookie_vec;
boost::split(cookie_vec, header_cookie->second, boost::is_any_of(";"));
for (const auto &item : cookie_vec) {
std::vector<string> cookie;
boost::split(cookie, item, boost::is_any_of("="));
cookie_map[cookie[0]] = cookie[1];
}
}
- 支持多 Cookie
- 每个 Cookie 会被拆解为 key-value,并存入
cookie_map
2. 设置 Cookie
如果服务器生成了新的 Session,需要在响应中设置 Set-Cookie:
if (self->request.cookie.find("session") == cookie_map.end()) {
self->response.cookie["session"] = session_id;
}
然后在写入 HTTP 响应时拼接:
if (!cookie.empty()) {
std::ostringstream oss;
for (const auto &item: cookie) {
oss << item.first << "=" << item.second << ";";
}
oss.str().pop_back(); // 去掉最后的分号
headers["Set-Cookie"] = oss.str();
}
浏览器收到后自动存储 Cookie,下一次请求时带上,服务器即可识别用户身份。
四、请求体解析与业务处理
1. 根据 Content-Type 解析请求体
不同业务可能发送 JSON、XML 或表单数据。Session 在接收请求后,会根据 Content-Type 自动解析:
switch (contentType) {
case ContentType::APPLICATION_JSON:
boost::property_tree::read_json(stringstream, self->request.ptree);
break;
case ContentType::APPLICATION_XML:
boost::property_tree::read_xml(stringstream, self->request.ptree);
break;
}
2. 支持 multipart/form-data
对于文件上传或复杂表单数据,解析逻辑如下:
self->request.form_data[field_name] = value;
这样,无论是文本字段还是文件字段,都能被统一存储在 form_data 映射中,业务函数直接读取即可。
3. 调用业务函数
解析完请求参数和内容后,交给 business_logic 统一处理:
self->response = business_logic::process_request(self->request);
- 如果 URI 对应函数存在,直接调用
- 否则尝试返回静态文件(GET 请求)
- 不存在则返回 404
五、写响应与 Cookie 返回
在 Session::do_write() 中:
- 构建响应头
- 拼接 Set-Cookie
- 写入客户端
boost::asio::async_write(client_socket_, boost::asio::buffer(response_str),
[self = shared_from_this()](std::error_code ec, std::size_t length) {
if (!ec) {
} else {
std::cerr << "write to remote server error: " << ec.message() << std::endl;
}
});
异步写入保证了高并发性能。
六、设计哲学与总结
- 解耦:业务逻辑完全与核心框架隔离
- 可扩展:添加新路由或业务逻辑无需修改底层网络代码
- 状态管理:Session + Cookie 实现跨请求用户状态
- 统一接口:GET、POST、HEAD 等请求都可统一处理
- 多内容支持:JSON、XML、表单数据、文件上传
这一套机制保证了服务器不仅能处理静态请求,也能承载动态 Web 应用。
文章评论