目录
设计思路
首先我们要先知道HTTP的请求的流程是什么样子的,不然我们会学的很迷糊。对于HTTP请求如何到来以及去往哪里,我们应该很清楚的知道
HTTP请求在服务器系统中的传递流程是一个多层次的过程:
- 客户端发起请求 - 用户在浏览器或应用中发起HTTP请求
- 网络传输 - 请求通过网络传输到服务器
- 服务器接收 - 服务器的TCP/IP协议栈接收数据包并将其传递给操作系统
- 网络库接收 - 在muduo这样的网络库中,数据通过以下路径流动:
- 操作系统的socket接口接收原始数据
- 网络库的EventLoop检测到socket可读事件
- TcpConnection将数据读入其内部Buffer
- HTTP解析 - 数据进入HTTP解析层:
- TcpServer将连接和数据传递给HttpServer
- HttpServer创建HttpContext对象处理每个连接
- HttpContext从Buffer中读取数据并进行解析
- 解析结果存储在HttpRequest对象中
- 应用处理 - 最终HttpRequest被传递给用户注册的回调函数进行业务逻辑处理
HttpRequest通常由HttpContext创建和填充,然后传递给处理HTTP请求的回调函数或处理器。它本质上是HTTP请求信息的容器,使服务器能够方便地访问和处理客户端的请求内容。
在整个HTTP处理流程中,HttpRequest的角色是保存从网络层解析出的HTTP请求信息,供应用层使用。
HTTP请求格式
成员设计
HTTP请求中的主要要素包括:
- 请求行:
- 请求方法(GET、POST、PUT、DELETE等)
- URL,包含:
- 资源路径
- 查询参数(键值对形式)
- 协议版本(如HTTP/1.1)
- 请求头部:
- 多个键值对格式的头部字段
- 常见的如Content-Type、User-Agent、Host等
- 请求正文:
- 根据Content-Length或Transfer-Encoding确定长度
- 内容格式由Content-Type决定
首先请求行中,有请求方法,url ,协议版本,而url中又分为资源路径和参数,参数是kv的形式,所以我们需要使用一个map来保存
而请求头部中,都是一些kv格式的属性,我们也是使用一个map来保存
最后就是正文部分,正文部分是交给上层业务逻辑去处理的,我们只需要按照请求头部中的Content-Length提取出来就行了。
那么我们需要保存的就是 : 请求方法,资源路径,参数,协议版本,头部字段,正文 ,当然,由于可能会存在中间的处理过程,比如对请求行的解析,我们会使用正则表达式来进行,我们可以再存储一个 std::smatch 来保存正则提取出来的结果。
由于HttpRequest后续我们是交给 上下文模块来进行设置的,为了方便,我们就直接将成员设置为公有的了,便于直接访问。
同时,对于参数和请求头部,我们可以提供结构,用来插入kv形式的参数和请求头部,以及查询是否有某个参数或者请求头部。
再HttpRequest的头部字段中,有一个很重要的信息就是正文长度,我们可以提供一个接口用来获取正文长度。
最后再提供一个接口用于判断长短连接,长短连接后续我们会用到。
然后如果是短连接的话,我们每次接收完也需要把数据给清除了。所以也需要个接口
模块实现
代码挺简单的,跟着思路走就很容易写出来的
class HttpRequest
{
public:
string _method; //存储请求方法
string _path; //存储资源路径
string _version; //存储协议版本
string _body; //存储正文
unordered_map<string, string> _headers; //存储请求头部
unordered_map<string, string> _params; //存储查询字符串
smatch _matches; //资源路径正则提取
private:
bool HasHeader(const sting &key) const //给获取报文长度函数提供的
{
auto it = _headers.find(key);
if(it == _headers.end())
{
return false;
}
return it->second;
}
string GetHeader(const sting &key) const //给获取报文长度函数提供的
{
auto it = _headers.find(key);
if(it == _headers.end())
{
return "";
}
return it->second;
}
public:
HttpRequest()
:_version("HTTP/1.1")
{}
void clear()//清空类数据
{
_method.clear();
_path.clear();
_version("HTTP/1.1");
_body.clear();
_headers.clear();
_params.clear();
smatch matches;
_matches.swap(matches);
}
void SetHeader(const string &key, const string &val)//添加请求头部
{
_headers.insert(key, val);
}
void SetParam(const string &key, const string &val)//添加查询字符串
{
_params.insert(key, val);
}
size_t GetLength()//获取正文长度
{
bool ret = HasHeader("Content-Length");
if(ret == false)
{
return 0;
}
string len = GetHeader("Content-Length");
return stol(len);
}
//是否是短链接
bool Close() const
{
if(HasHeader("Connection") == true && GetHeader("Connection") == "keep-alive");
{
return false;
}
return true;
}
};