目录
[1.1 HttpRequest](#1.1 HttpRequest)
[1.2 HttpResponse](#1.2 HttpResponse)
[1.3 HttpContext](#1.3 HttpContext)
[1.4 HttpServer](#1.4 HttpServer)
[2.1 构造服务器](#2.1 构造服务器)
[2.2 设置自定义协议](#2.2 设置自定义协议)
[2.3 设置路由](#2.3 设置路由)
[2.4 收到消息处理流程](#2.4 收到消息处理流程)
[2.4.1 初始状态](#2.4.1 初始状态)
[2.4.2 反序列化(重点)](#2.4.2 反序列化(重点))
[2.4.3 路由分发](#2.4.3 路由分发)
[2.4.4 响应发送](#2.4.4 响应发送)
[3.1 测试环境](#3.1 测试环境)
[3.2 测试结果](#3.2 测试结果)
一、业务相关模块
1.1 HttpRequest
cpp
class HttpRequest
{
public:
std::string _method; // 请求方法 (GET/POST/PUT/DELETE)
std::string _path; // 请求路径
std::string _version; // HTTP版本
std::string _body; // 请求体
std::smatch _matches; // 正则匹配结果
std::unordered_map<std::string, std::string> _headers; // 请求头
std::unordered_map<std::string, std::string> _params; // 查询参数
};
作用:存储客户端发送的HTTP请求信息
示例 :https://blog.csdn.net/m0_68617924?spm=1010.2135.3001.5343
解析后的结果:
-
method: GET -
path: /m0_68617924 -
version: HTTP/1.1 -
headers: Host: blog.csdn.net, User-Agent: Mozilla/5.0, Accept: text/html -
params: spm=1010.2135.3001.5343
1.2 HttpResponse
cpp
class HttpResponse
{
public:
int _statu; // 状态码 (200, 404, 500等)
bool _redirect_flag; // 是否重定向
std::string _redirect_url; // 重定向URL
std::string _body; // 响应体
std::unordered_map<std::string, std::string> _headers; // 响应头
};
作用:存放HttpRequest经过路由函数处理后的结果
路由函数统一格式:
cpp
void Func(const HttpRequest& req, HttpResponse* rsp)
该函数支持:
-
重定向功能
-
各种格式的响应体
1.3 HttpContext
cpp
class HttpContext
{
private:
int _resp_statu; // 响应状态
HttpRecvStatu _recv_statu; // 接收状态
HttpRequest _request; // 请求对象
};
作用:管理HTTP协议的解析状态。该模块需要和HttpServer一起理解。
1.4 HttpServer
cpp
class HttpServer
{
using Handler = std::function<void(const HttpRequest&, HttpResponse*)>;
using Handlers = std::vector<std::pair<std::regex, Handler>>;
private:
Handlers _get_route; // GET路由表
Handlers _post_route; // POST路由表
Handlers _put_route; // PUT路由表
Handlers _delete_route; // DELETE路由表
std::string _basedir; // 基础目录
TcpServer _server; // TCP服务器
};
作用:核心模块,负责:
-
将缓冲区数据反序列化成HttpRequest
-
调用回调函数处理成HttpResponse
-
将响应发送给客户端浏览器
二、处理过程
2.1 构造服务器
cpp
HttpServer(int port, int timeout = 10) : _server(port)
{
_server.EnableInactiveRelease(timeout);
_server.SetConnectedCallBack(std::bind(&HttpServer::OnConnected,
this, std::placeholders::_1));
_server.SetMessageCallBack(std::bind(&HttpServer::OnMessage,
this, std::placeholders::_1,
std::placeholders::_2));
}
功能:
-
启动服务器并监听端口
-
获取用户连接
-
设置底层回调函数
2.2 设置自定义协议
cpp
void OnConnected(const PtrConnection& conn)
{
conn->SetContext(HttpContext());
}
作用 :有连接进来时调用,将连接中的any类型设置为HttpContext,用于存储反序列化草稿,实现自定义协议。
2.3 设置路由
cpp
server.SetBaseDir("./wwwroot/");
server.Get("/hello", Hello);
server.Post("/login", Login);
server.Put("/1234.txt", PutFile);
server.Delete("/1234.txt", DelFile);
业务处理示例(复读机):
cpp
std::string RequestStr(const HttpRequest& req)
{
std::stringstream ss;
ss << req._method << " " << req._path << " "
<< req._version << "\r\n";
for (auto& it : req._params)
{
ss << it.first << ": " << it.second << "\r\n";
}
ss << "\r\n";
ss << req._body;
return ss.str();
}
void Hello(const HttpRequest& req, HttpResponse* rsp)
{
rsp->SetContent(RequestStr(req), "text/plain");
}
说明 :上层业务处理就是告诉服务器如何将req转为rsp,这些函数会被存入HttpServer对应的路由表中。
2.4 收到消息处理流程
2.4.1 初始状态
-
连接的写入缓冲区有数据
-
草稿区(any)已被初始化为HttpContext,但未开始反序列化,内容为空
-
发送缓冲区为空(还没有路由处理函数)
2.4.2 反序列化(重点)
cpp
void RecvHttpRequest(buffer* buf)
{
switch (_recv_statu)
{
case RECV_HTTP_LINE:
RecvHttpLine(buf);
case RECV_HTTP_HEAD:
RecvHttpHead(buf);
case RECV_HTTP_BODY:
RecvHttpBody(buf);
}
}
状态枚举:
cpp
typedef enum
{
RECV_HTTP_ERROR, // 错误状态
RECV_HTTP_LINE, // 解析请求行
RECV_HTTP_HEAD, // 解析请求头
RECV_HTTP_BODY, // 解析请求体
RECV_HTTP_OVER // 解析完成
} HttpRecvStatu;
关键点:
-
初始状态为
RECV_HTTP_LINE -
状态和草稿都会保存在
any中 -
下次反序列化时继续上次操作,无需分类讨论
获取请求行:
cpp
bool RecvHttpLine(buffer* buf)
{
if (_recv_statu != RECV_HTTP_LINE)
return 0;
std::string line = buf->GetLineAndPop();
bool ret = ParseHttpLine(line);
if (!ret)
return 0;
_recv_statu = RECV_HTTP_HEAD;
return 1;
}
正则表达式切割:
cpp
bool ParseHttpLine(const std::string& line)
{
std::smatch matches;
std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\\n|\\r\\n)?",
std::regex::icase);
bool ret = std::regex_match(line, matches, e);
if (ret == 0)
{
_recv_statu = RECV_HTTP_ERROR;
_resp_statu = 400;
return 0;
}
_request._method = matches[1];
_request._path = Util::UrlDecode(matches[2], 0);
_request._version = matches[4];
// 解析查询参数
std::string query = matches[3];
std::vector<std::string> query_array;
Util::Split(query, "&", &query_array);
for (auto& str : query_array)
{
size_t pos = str.find("=");
std::string key = Util::UrlDecode(str.substr(0, pos), 1);
std::string val = Util::UrlDecode(str.substr(pos + 1), 1);
_request.SetParam(key, val);
}
return 1;
}
2.4.3 路由分发
cpp
void Route(HttpRequest& req, HttpResponse* rsp)
{
// 1. 检查是否为静态文件请求
if (IsFileHandler(req))
return FileHandler(req, rsp);
// 2. 根据请求方法分发
if (req._method == "GET" || req._method == "HEAD")
{
return Dispatcher(req, rsp, _get_route);
}
else if (req._method == "POST")
{
return Dispatcher(req, rsp, _post_route);
}
else if (req._method == "PUT")
{
return Dispatcher(req, rsp, _put_route);
}
else if (req._method == "DELETE")
{
return Dispatcher(req, rsp, _delete_route);
}
// 3. 未找到匹配路由
rsp->_statu = 404;
}
设计特点:
-
每个HTTP方法对应一个路由数组
-
使用正则表达式匹配路径,支持灵活的路由规则
-
遍历数组查找匹配项,未找到则返回404
2.4.4 响应发送
cpp
void WriteResponse(const PtrConnection& conn, HttpRequest& req, HttpResponse& rsp)
{
// 1. 设置Connection头
if (req.Close() == 1)
rsp.SetHeader("Connection", "close");
else
rsp.SetHeader("Connection", "keep-alive");
// 2. 设置Content-Length
if (!rsp._body.empty() && !rsp.HasHeader("Content-Length"))
rsp.SetHeader("Content-Length", std::to_string(rsp._body.size()));
// 3. 设置Content-Type
if (!rsp._body.empty() && !rsp.HasHeader("Content-Type"))
rsp.SetHeader("Content-Type", "application/octet-stream");
// 4. 处理重定向
if (rsp._redirect_flag == 1)
rsp.SetHeader("Location", rsp._redirect_url);
// 5. 序列化响应
std::stringstream rsp_str;
rsp_str << req._version << " "
<< std::to_string(rsp._statu) << " "
<< Util::StatuDesc(rsp._statu) << "\r\n";
for (auto& head : rsp._headers)
rsp_str << head.first << ": " << head.second << "\r\n";
rsp_str << "\r\n";
rsp_str << rsp._body;
// 6. 发送
conn->Send(rsp_str.str().c_str(), rsp_str.str().size());
}
三、压力测试
3.1 测试环境
-
服务器配置:2核心8G云服务器
-
测试工具:webbench-v1.5
-
并发量:5000
-
测试时长:12小时
-
测试方式:客户端与服务器在同一台机器运行
3.2 测试结果
cpp
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.
Benchmarking: GET http://127.0.0.1:8085/hello
5000 clients, running 43200 sec.
Speed=171204 pages/min, 7057 bytes/sec.
Requests: 123267172 succeed, 0 failed.
性能指标:
-
平均请求数:171,204次/分钟
-
成功请求:123,267,172次
-
失败请求:0次
