对于生命,你不妨大胆一点, 因为我们始终要失去它。 --- 尼采 --- ✨✨✨项目地址在这里 ✨✨✨
✨✨✨https://gitee.com/penggli_2_0/TcpServer✨✨✨
1 前言
上一篇文章我们基本实现了高并发服务器所需的基础模块,通过TcpServer类可以快速搭建一个TCP服务器。我们的最终目的是使用这个高并发服务器去实现一些业务,那么在网络通讯中,我们就可以来实现一下HTTP服务。让欣赏器可以访问获取数据。
为了实现HTTP服务器首要的工作就是实现HTTP协议,协议是网络通讯的基础!只有确定了协议我们才能正常解析哀求报文,并组织应答报文,可以让欣赏器成功获取数据。
完成HTTP协议之后,就是设计一种报文解析模块,可以从缓冲区中获取数据,举行解析数据,得到完整哀求。
最终将这些整合为一个HTTP服务器模块,设计回调函数,实现HTTP服务器的功能!
2 Util工具类
在HTTP服务器处理处罚中,经常必要一些常用操纵,比如切分字符串,编码转换,通过状态码找到对应状态解析… Util工具类就是用来实现这些功能的类!
- SplitStr
- 功能:根据指定的分隔符 sep 将字符串 src 切分成多个子字符串,并将这些子字符串存储在 sub 向量中。
- 返回值:返回切分后的子字符串数量。
- ReadFile
- 功能:以二进制方式读取文件 filename 的内容到字符串 buf 中。
- 返回值:如果文件打开和读取成功,返回 true;否则返回 false。
- WriteFile
- 功能:以二进制方式将字符串 buf 的内容写入到文件 filename 中,如果文件已存在则覆盖。
- 返回值:如果文件打开和写入成功,返回 true;否则返回 false。
- UrlEncode
- 功能:对字符串 url 举行 URL 编码,可以选择是否将空格编码为 +。
- 返回值:返回编码后的字符串。
- HexToC
- 功能:将十六进制字符转换为对应的整数值。
- 返回值:返回转换后的整数值。
- UrlDecode
- 功能:对字符串 url 举行 URL 解码,可以选择是否将 + 解码为空格。
- 返回值:返回解码后的字符串。
- StatuDesc
- 功能:根据给定的状态码 code 返回对应的状态形貌。
- 返回值:返回状态形貌字符串,如果状态码未知,则返回 “Unkonw”。
- ExtMime
- 功能:根据 URL 的扩展名返回对应的 MIME 范例。
- 返回值:返回 MIME 范例字符串,如果扩展名未知,则返回 “application/octet-stream”。
- IsLegPath
- 功能:查抄字符串 path 是否是合法的路径,重要查抄是否存在非法的 “…” 使用。
- 返回值:如果路径合法,返回 true;否则返回 false。
- IsDir
- 功能:查抄给定的路径 dir 是否是一个目次。
- 返回值:如果是目次,返回 true;否则返回 false。
- IsRegular
- 功能:查抄给定的路径 dir 是否是一个常规文件。
- 返回值:如果是常规文件,返回 true;否则返回 false。
- // 公共方法类
- class Util
- {
- public:
- static ssize_t SplitStr(const std::string &src, const std::string &sep, std::vector<std::string> &sub)
- {
- // 根据sep分隔符切分字符串
- int offset = 0; // 偏移量
- while (offset < src.size())
- {
- size_t pos = src.find(sep, offset);
- // 没有找到sep
- if (pos == std::string::npos)
- {
- // 直接将offset后的字符串当成子串
- sub.push_back(src.substr(offset));
- break;
- }
- // 找到了sep
- else
- {
- size_t len = pos - offset;
- if (len == 0)
- {
- offset++;
- continue;
- }
- sub.push_back(src.substr(offset, len));
- offset += len; // 偏移量向后移动
- }
- }
- return sub.size();
- }
- static bool ReadFile(const std::string &filename, std::string *buf)
- {
- std::ifstream ifs(filename, std::ios::binary); // 以读方式打开文件,采取二进制读取方式
- if (ifs.is_open() == false)
- {
- LOG(ERROR, "Open %s Failed!\n", filename.c_str());
- return false;
- }
- // 获取文件大小
- ifs.seekg(0, ifs.end); // 将读取位置移动到文件末尾
- size_t n = ifs.tellg(); // 此时的偏移量即为文件大小
- ifs.seekg(0, ifs.beg); // 将读取位置移动到到文件开头
- buf->resize(n); // 将缓冲区大小设置为文件大小
- // 进行写入
- ifs.read(&(*buf)[0], n);
- // 关闭文件
- ifs.close();
- return true;
- }
- static bool WriteFile(const std::string &filename, const std::string &buf)
- {
- std::ofstream ofs(filename, std::ios::binary | std::ios::trunc); // 使用写方式打开进行二进制覆盖写
- if (ofs.is_open() == false)
- {
- LOG(ERROR, "Open %s Failed!\n", filename.c_str());
- return false;
- }
- // 进行写入
- ofs.write(&buf[0], buf.size());
- if (ofs.good() == false)
- {
- LOG(ERROR, "Write %s Failed!\n", filename.c_str());
- return false;
- }
- ofs.close();
- return true;
- }
- static std::string UrlEncode(const std::string &url, bool is_space_encode)
- {
- std::string ret;
- // 进行编码
- for (auto ch : url)
- {
- //. - _ ~ 四个字符绝对不编码
- // 字母与数字不见编码
- if (ch == '.' || ch == '-' || ch == '_' || ch == '~' || isalnum(ch))
- {
- ret += ch;
- continue;
- }
- // 空格编码为 +
- if (ch == ' ' && is_space_encode)
- {
- ret += '+';
- continue;
- }
- // 其余字符进行编码
- char buf[4]; // 编码格式 %___
- snprintf(buf, 4, "%%%02X", ch);
- ret += buf;
- }
- return ret;
- }
- // URL解码
- static char HexToC(char c)
- {
- if (c >= '0' && c <= '9')
- {
- return c - '0';
- }
- else if (c >= 'a' && c <= 'z')
- {
- return c - 'a' + 10;
- }
- else if (c >= 'A' && c <= 'Z')
- {
- return c - 'A' + 10;
- }
- return -1;
- }
- static std::string UrlDecode(const std::string &url, bool is_space_decode)
- {
- std::string res;
- // 遍历字符串 遇到%就进行解码
- for (int i = 0; i < url.size(); i++)
- {
- if (url[i] == '%')
- {
- char v1 = HexToC(url[i + 1]);
- char v2 = HexToC(url[i + 2]);
- char c = (v1 << 4) + v2;
- res += c;
- i += 2;
- continue;
- }
- else if (url[i] == '+' && is_space_decode)
- {
- res += ' ';
- continue;
- }
- else
- {
- res += url[i];
- }
- }
- return res;
- }
- // 返回状态码
- static std::string StatuDesc(int code)
- {
- auto ret = _statu_msg.find(code);
- if (ret == _statu_msg.end())
- {
- return "Unkonw";
- }
- return ret->second;
- }
- // 解析文件后缀
- static std::string ExtMime(const std::string &url)
- {
- size_t pos = url.rfind('.');
- // 没有找到返回
- if (pos == std::string::npos)
- {
- LOG(DEBUG, "没有找到'.'\n");
- return "applicantion/octet-stream";
- }
- std::string str = url.substr(pos);
- LOG(DEBUG, "文件类型:%s\n", str.c_str());
- auto it = _mime_msg.find(str);
- if (it == _mime_msg.end())
- {
- return "applicantion/octet-stream";
- }
- return it->second;
- }
- // 检查是否是合法路径
- static bool IsLegPath(const std::string &path)
- {
- // 采用计数法
- int level = 0;
- std::vector<std::string> subdir;
- int ret = SplitStr(path, "..", subdir);
- if (ret < 0)
- return false;
- for (auto &s : subdir)
- {
- if (s == "..")
- {
- level--;
- if (level < 0)
- return false;
- continue;
- }
- else
- level++;
- }
- return true;
- }
- static bool IsDir(const std::string &dir)
- {
- struct stat st;
- int n = ::stat(dir.c_str(), &st);
- if (n < 0)
- return false;
- return S_ISDIR(st.st_mode);
- }
- static bool IsRegular(const std::string &dir)
- {
- struct stat st;
- int n = ::stat(dir.c_str(), &st);
- if (n < 0)
- return false;
- return S_ISREG(st.st_mode);
- }
- };
复制代码 3 HTTP协议
3.1 HTTP哀求
http协议的哀求格式是如许的:
- 哀求行:包含哀求方法,资源路径URL,HTTP版本
- 哀求报头:以键值对的形式储存须要信息
- 空行:用于辨认正文
- 哀求正文:储存本次哀求的正文
针对这个结构我们可以搭建一个HTTP哀求的基础框架:
- class
- {
- public:
- std::string _method; // 请求方法
- std::string _path; // 查询路径
- std::string _version; // 协议版本
- std::string _body; // 请求正文
- std::smatch _matches; // 资源路径的正则提取解析
- std::unordered_map<std::string, std::string> _headers; // 请求报头
- std::unordered_map<std::string, std::string> _params; // 查询字符串
-
- };
复制代码 然后继承设置一些接口:
- 插入头部字段的接口
- 查抄哀求中是否有该头部字段
- 插入查询字符串
- 查抄哀求中是否有该查询字符串
- 获取查询字符串
- 获取正文长度
- 是否为长连接
- class HttpRequest
- {
- public:
- std::string _method; // 请求方法
- std::string _path; // 查询路径
- std::string _version; // 协议版本
- std::string _body; // 请求正文
- std::smatch _matches; // 资源路径的正则提取解析
- std::unordered_map<std::string, std::string> _headers; // 请求报头
- std::unordered_map<std::string, std::string> _params; // 查询字符串
- public:
- // 重置请求
- void Reset()
- {
- _method.clear();
- _path.clear();
- _version.clear();
- _body.clear();
- std::smatch tmp;
- _matches.swap(tmp);
- _headers.clear();
- _params.clear();
- }
- // 插入头部字段
- void SetHeader(const std::string &key, const std::string &val)
- {
- _headers.insert(std::make_pair(key, val));
- }
- // 判断是否有该头部字段
- bool HasHeader(const std::string &key) const
- {
- auto it = _headers.find(key);
- if (it == _headers.end())
- {
- return false;
- }
- return true;
- }
- // 获取头部字段
- std::string GetHeader(const std::string &key) const
- {
- auto it = _headers.find(key);
- if (it == _headers.end())
- {
- return "";
- }
- return it->second;
- }
- // 插入查询字符串
- void SetParam(const std::string &key, const std::string &val)
- {
- _params.insert(std::make_pair(key, val));
- }
- // 判断是否有该查询字符串
- bool HasParam(const std::string &key)
- {
- auto it = _params.find(key);
- if (it == _params.end())
- {
- return false;
- }
- return true;
- }
- // 获取查询字符串
- std::string GetParam(const std::string &key)
- {
- auto it = _params.find(key);
- if (it == _params.end())
- {
- return "";
- }
- return it->second;
- }
- // 获取正文长度
- size_t ContentLength()
- {
- bool ret = HasHeader("Content-Length");
- if (ret)
- {
- // 转换为长整形
- return std::stol(GetHeader("Content-Length"));
- }
- return 0;
- }
- bool Close() const
- {
- // 没有Connection字段或者Connection字段是close 就是短连接
- if (HasHeader("Connection") == true && GetHeader("Connection") == "close")
- {
- return true;
- }
- return false;
- }
- };
复制代码 如许一个基础的HTTP哀求结构就设计好了!
3.2 HTTP应答
http协议的应答格式是如许的:
- 状态行:包含HTTP版本,状态码,状态码形貌
- 应答报头:储存须要信息
- 换行符:用于辨认正文
- 正文:储存应答的正文结构
根据应答结构,我们可以搭建其应答框架:
- 设置头部字段
- 获取头部字段
- 设置正文
- 设置应答状态
- 是否是长连接
- class HttpResponse
- {
- public:
- int _statu; // 状态码
- bool _rediect_flag; // 重定向标志
- std::string _rediect_url; // 重定向的路径
- std::string _body; // 响应正文
- std::unordered_map<std::string, std::string> _headers; // 响应报头
- public:
- HttpResponse(int statu) : _statu(statu) {}
- // 重置响应
- void Reset()
- {
- }
- // 插入头部字段
- void SetHeader(const std::string &key, const std::string &val)
- {
- _headers.insert(std::make_pair(key, val));
- }
- // 判断是否有该头部字段
- bool HasHeader(const std::string &key)
- {
- auto it = _headers.find(key);
- if (it == _headers.end())
- {
- return false;
- }
- return true;
- }
- // 获取头部字段
- std::string GetHeader(const std::string &key)
- {
- auto it = _headers.find(key);
- if (it == _headers.end())
- {
- return "";
- }
- return it->second;
- }
- void SetContent(const std::string &body, const std::string &type = "text/html")
- {
- _body = body;
- SetHeader("Content-Type", type);
- }
- void SetRediret(const std::string &url, int statu = 302)
- {
- _statu = statu;
- _rediect_flag = true;
- _rediect_url = url;
- }
- bool Close()
- {
- // 没有Connection字段或者Connection字段是close 就是短连接
- if (HasHeader("Connection") == true && GetHeader("Connection") == "close")
- {
- return true;
- }
- return false;
- }
- };
复制代码 如许HTTP协议的哀求与应答我们就完成了!可以进一步举行哀求与应答的解析工作了!
4 上下文解析模块
针对应答的反序列化,我们不在协议模块中直接举行设置,因为我们无法保证连接一次就可以获取完整的报文结构,以是在一个连接中要维护一个上下文结构,可以在多次处理处罚时知道本次处理处罚应该从那边举行!
在这个上下文中起首我们就必要一个状态变量,可以标识当前应该处理处罚什么字段:
- RECV_HTTP_ERROR --- 处理出错
- RECV_HTTP_LINE --- 处理请求行
- RECV_HTTP_HEAD --- 处理头部字段
- RECV_HTTP_BODY --- 处理正文
- RECV_HTTP_OVER --- 处理完成
复制代码 每一个上下文都匹配一个哀求对象,将解析好的字段储存到这个哀求对象中:
- 处理处罚哀求行:处理处罚哀求行时使用正则表达式快速举行处理处罚,注意URL编码的转换,哀求方法的巨细写以及拆分出查询字符串!
- 处理处罚头部字段:一行一行的举行处即可,直到碰到空行!
- 处理处罚正文:从缓冲区读取出正文长度的数据,不够继承等待,够了就返回。
必要注意的是,获取数据时不一定会获取到预期的数据,一定要做好情况分类,保证正常读取!
制止出现数据过长,数据不敷等情况!
上下文每次解析都将数据及时储存到该上下文中对应的哀求对象中!
- typedef enum
- {
- RECV_HTTP_ERROR,
- RECV_HTTP_LINE,
- RECV_HTTP_HEAD,
- RECV_HTTP_BODY,
- RECV_HTTP_OVER
- } HttpRecvStatu;
- static const int MAX_SIZE = 8192;
- class HttpContext
- {
- private:
- int _resp_statu; // 响应状态码
- HttpRequest _request; // 请求信息
- HttpRecvStatu _recv_statu; // 解析状态
- private:
- bool ParseHttpLine(const std::string &line)
- {
- // 对请求行进行正则表达式解析
- // 设置解析方法: 忽略大小写!
- // std::regex re("(GET|HEAD|POST|PUT|DELETE) ([^?]+)\\?(.*) (HTTP/1\\.[01])(?:\n|\r\n)?", std::regex::icase);
- std::regex re("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?", std::regex::icase);
- //(GET|HEAD|POST|PUT|DELETE) 获取GET...请求方法
- //([^?]+) 匹配若干个 非?字符 直到? --- 获取资源路径
- //\\?(.*) \\?表示匹配原始?字符 (.*)访问到空格 ---获取请求参数
- //(HTTP/1\\.[01]) 匹配HTTP/1. 01任意一个字符
- //(?:\n|\r\n)? 匹配\n或者\r\n (?: ...)表示匹配摸个格式字符串但是不提取 .结尾的?表示前面的表达式0次或1次
- std::smatch matches;
- bool ret = std::regex_match(line, matches, re);
- if (ret == false)
- {
- LOG(ERROR, "regex_match failed\n");
- _resp_statu = 400; // Bad Reauest!
- return false;
- }
- // 0:GET /a/b/c/search?q=keyword&lang=en HTTP/1.1
- // 1:GET
- // 2:/a/b/c/search
- // 3:q=keyword&lang=en
- // 4:HTTP/1.1
- _request._method = matches[1];
- // 请求方法统一转换为大写
- std::transform(_request._method.begin(), _request._method.end(), _request._method.begin(), ::toupper);
- _request._path = Util::UrlDecode(matches[2], false);
- _request._version = matches[4];
- // 对查询字符串进行解析
- std::string str = matches[3];
- std::vector<std::string> substr;
- // 进行切分字符串
- Util::SplitStr(str, "&", substr);
- // 遍历容器
- for (auto s : substr)
- {
- // 寻找'='
- size_t pos = s.find("=");
- if (pos == std::string::npos)
- {
- LOG(ERROR, "ParseHttpLine Failed\n");
- _recv_statu = RECV_HTTP_ERROR;
- _resp_statu = 400; // BAD Resquest
- return false;
- }
- // 找到了 ‘=’
- std::string key = Util::UrlDecode(s.substr(0, pos), true);
- std::string value = Util::UrlDecode(s.substr(pos + 1), true);
- LOG(INFO, "查询字符串%s: %s\n", key.c_str(), value.c_str());
- _request.SetParam(key, value);
- }
- return true;
- }
- // 解析请求行
- bool RecvHttpLine(Buffer *buf)
- {
- if (_recv_statu != RECV_HTTP_LINE)
- return false;
- // 获取一行数据 带有\r\n
- std::string line = buf->GetLineAndPop();
- if (line.size() == 0)
- {
- // 缓存区中没有完整的一行数据 进行分类讨论
- // 如果缓冲区数据大于极限值
- if (buf->ReadAbleSize() > MAX_SIZE)
- {
- _resp_statu = 414; // URL TOO LONG
- _recv_statu = RECV_HTTP_ERROR;
- return false;
- }
- // 反之不处理
- return true;
- }
- // 一行的数据过长
- if (line.size() > MAX_SIZE)
- {
- _resp_statu = 414; // URL TOO LONG
- _recv_statu = RECV_HTTP_ERROR;
- return false;
- }
- // 进行解析
- bool ret = ParseHttpLine(line);
- if (ret == false)
- return false;
- // 请求行解析完毕 开始解析请求报头
- _recv_statu = RECV_HTTP_HEAD;
- return true;
- }
- // 解析报头
- bool RecvHttpHead(Buffer *buf)
- {
- if (_recv_statu != RECV_HTTP_HEAD)
- return false;
- // 解析请求报头直到遇到空行
- while (1)
- {
- std::string line = buf->GetLineAndPop();
- // LOG(DEBUG, "line:%s\n", line.c_str());
- if (line.size() == 0)
- {
- // 缓存区中没有完整的一行数据 进行分类讨论
- // 如果缓冲区数据大于极限值
- if (buf->ReadAbleSize() > MAX_SIZE)
- {
- // LOG(ERROR, "line too long\n");
- _resp_statu = 414; // URL TOO LONG
- _recv_statu = RECV_HTTP_ERROR;
- return false;
- }
- // 反之不处理 等待新数据到来
- // LOG(ERROR, "wait new buffer\n");
- return true;
- }
- // 一行的数据过长
- if (line.size() > MAX_SIZE)
- {
- // LOG(ERROR, "line too long\n");
- _resp_statu = 414; // URL TOO LONG
- _recv_statu = RECV_HTTP_ERROR;
- return false;
- }
- if (line == "\n" || line == "\r\n")
- {
- // LOG(ERROR, "line is empty\n");
- break;
- }
- // LOG(INFO, "line正常 进行解析处理");
- // 去除换行 \r \n
- if (line.back() == '\n')
- line.pop_back();
- if (line.back() == '\r')
- line.pop_back();
- // 进行解析
- bool ret = ParseHttpHead(line);
- if (ret == false)
- return false;
- }
- // 头部解析完成 继续解析正文
- _recv_statu = RECV_HTTP_BODY;
- return true;
- }
- bool ParseHttpHead(const std::string &line)
- {
- // 每一行都是key: val\r\n 格式
- // LOG(DEBUG, "ParseHttpHead:%s\n", line.c_str());
- // 进行解析即可
- size_t pos = line.find(": ");
- if (pos == std::string::npos)
- {
- LOG(ERROR, "ParseHttpLine Failed\n");
- _recv_statu = RECV_HTTP_ERROR;
- _resp_statu = 400; // BAD Resquest
- return false;
- }
- std::string key = line.substr(0, pos);
- std::string val = line.substr(pos + 2);
- // LOG(DEBUG, "%s: %s\n", key.c_str(), val.c_str());
- _request.SetHeader(key, val);
- return true;
- }
- bool RecvHttpBody(Buffer *buf)
- {
- if (_recv_statu != RECV_HTTP_BODY)
- return false;
- // 获取正文长度
- size_t len = _request.ContentLength();
- // 没有正文 直接读取完毕
- if (len == 0)
- {
- _recv_statu = RECV_HTTP_OVER;
- return true;
- }
- // 当前已经接受了多少数据 _request._body
- size_t relen = len - _request._body.size();
- // 接收正文放到body中 但是要考虑当前缓冲区中的数据是否是全部的报文
- // 缓冲区数据包含所有正文
- if (relen <= buf->ReadAbleSize())
- {
- // 加到_request.body的后面
- _request._body.append(buf->ReadPos(), relen);
- buf->MoveReadOffset(relen);
- _recv_statu = RECV_HTTP_OVER;
- return true;
- }
- // 缓冲区无法满足正文
- _request._body.append(buf->ReadPos(), buf->ReadAbleSize());
- buf->MoveReadOffset(buf->ReadAbleSize());
- return true;
- }
- public:
- HttpContext() : _resp_statu(200), _recv_statu(RECV_HTTP_LINE) {}
- int RespStatu() { return _resp_statu; }
- HttpRequest &Request() { return _request; }
- HttpRecvStatu RecvStatu() { return _recv_statu; }
- // 重置上下文
- void Reset()
- {
- _resp_statu = 200;
- _recv_statu = RECV_HTTP_LINE;
- _request.Reset();
- }
- void RecvhttpRequest(Buffer *buf)
- {
- // 根据不同的状态 处理不同情况
- // 处理完不要break 因为处理完 可以继续进行处理下面的数据 而不是直接退出等待新数据!
- switch (_recv_statu)
- {
- case RECV_HTTP_LINE:
- RecvHttpLine(buf);
- case RECV_HTTP_HEAD:
- RecvHttpHead(buf);
- case RECV_HTTP_BODY:
- RecvHttpBody(buf);
- }
- return;
- }
- };
复制代码 5 HTTP服务器对象
现在,HTTP协议我们实现了,可以通过协议举行通讯!如何通过缓冲区获取哀求的上下文方法我们也实现了,可以在缓冲区中读取数据,纵然一次没有发送全,下一次可以继承在原有进度上继承举行解析!
那么接下来,我们对这些功能举行一个整合封装,实现HTTP服务器的功能!
起首这个模块中有哀求方法/资源路径 与 函数指针的映射关系表,可以根据http哀求的url找到对应的资源
- 表中纪录了对于哪个哀求,应该使用哪一个函数来举行业务处理处罚
- 当服务器收到一个哀求,就要在哀求路由表中,查找是否存在对应的处理处罚函数,没有就返回404 Not Found
- 如许做的利益是用户只必要实现业务处理处罚函数,然后将哀求与函数的对应关系添加到服务器中,服务器只必要接收数据,解析数据,查找路由表映射关系,执行业务处理处罚函数!
要实现简便的搭建Http服务器,所需的要素和提供的功能有以下几项:
- GET哀求的路由映射表 — 功能性哀求的处理处罚
- POST哀求的路由映射表
- PUT哀求的路由映射表
- DELETE哀求的路由映射表
- 高性能TCP服务器 — 举行连接的IO操纵
- 静态资源相对根目次 — 实现静态资源的处理处罚
再来看服务器的处理处罚流程,只有认识了服务器处理处罚流程,才能明白代码逻辑然后举行功能实现!
- 从Socket接收数据。放到接收缓冲区
- 调用OnMessage回调函数举行业务处理处罚
- 对哀求举行解析,得到一个HttpRequest结构,包含全部的哀求要素
- 举行哀求的路由查找 — 找到对应哀求的处理处罚方法
- 静态资源哀求 — 一些实体文件资源的哀求
- 功能性哀求 — 在哀求中根据路由映射表查找处理处罚函数
- 对静态资源哀求/功能性哀求举行处理处罚完毕后,得到了一个填充了相应信息的HttpReaponse对象,组成http格式报文
成员变量:
- GET哀求的路由映射表 _get_route — 通过正则表达式映射处理处罚函数
- POST哀求的路由映射表 _post_route
- PUT哀求的路由映射表 _put_route
- DELETE哀求的路由映射表 _delete_route
- 静态资源根目次 _basedir
- TcpServer服务器 _server
私有成员函数
- 设置上下文 OnConnect(const PtrConn& conn):给连接设置空的上下文
- 缓冲区数据解析+处理处罚 OnMessage(const PtrConn& conn , Buffer *buf):只要缓冲区里有数据就持续处理处罚起首先获取上下文,通过上下文对缓冲区数据举行处理处罚得到HttpRequest对象,根据状态码>= 400判断解析结果 ,如果解析堕落 ,直接复兴堕落相应 ErrorHandler(req , rsp) 并关闭连接! 哀求解析不完整 直接return 等待下一次处理处罚。直到解析完毕 才去举行数据处理处罚。然后举行哀求路由Route(req ,&rsp) 在路由中举行数据处理处罚业务处理处罚,处理处罚后得到应答报文,对HttpResponse 举行组织发送 WriteResponse(const PtrConn& conn , req , rsp)此时重置连接的上下文!根据长短连接判断是否要关闭连接大概继承保持连接
- 路由查找 Route:对哀求举行判断,是哀求静态资源还是功能性哀求
- 静态资源哀求 :判断是否是静态资源哀求,然后举行静态资源的处理处罚
- 功能性哀求 : 通过req的哀求方法判断使用哪一个路由表,使用Dispatch举行任务派发
- 既不是静态资源一样平常是功能性哀求 就返回404!
- 判断是否是静态资源哀求 IsFileHandler:起首必须设置了静态资源根目次,哀求方法必须是GET / HEAD
,哀求的资源路径必须是合法路径,哀求的资源必须存在! 当哀求路径是"/"要补全一个初始页面 index.html,注意合并_basedir得到真正的路径!
- 静态资源的哀求处理处罚 FileHandler:将静态资源的数据读取出来,放到rsp的正文中,直接读取路径上的文件放到正文中,获取mime文件范例,添加到头部字段Content-Type!
- 功能性哀求的任务分发 Dispatcher:在对应路由表中探求是否有对应哀求的处理处罚函数,有就直接举行调用 没有就返回404。路由表中储存的是 正则表达式->处理处罚函数 的键值对。使用正则表达式举行匹配 ,匹配成功就举行执行函数
- 发送应答WriteResponse:将HttpReaponse应答按照http应答格式举行组织发送 ,起首完善头部字段 ,然后将rsp的元素按照http协议的格式举行组织,最终发送数据
- 处理处罚错误应答ErrorHandler: 提供一个错误展示页面,将页面数据当作相应正文放入rsp中
公有成员函数:
- 构造函数
- 插入关系映射到GET路由表、POST路由表、PUT路由表、DELETE路由表。
- 设置静态资源根目次
- 设置线程数量
- 启动Http服务器
- class HttpServer
- {
- private:
- using Handler = std::function<void(const HttpRequest &, HttpResponse *)>;
- using Handlers = std::vector<std::pair<std::regex, Handler>>;
- Handlers _get_route; // GET方法处理函数映射表
- Handlers _post_route; // POST方法处理函数映射表
- Handlers _delete_route; // DELETE方法处理函数映射表
- Handlers _put_route; // PUT方法处理函数映射表
- std::string _basedir;
- TcpServer _server;
- public:
- // 设置空白上下文
- void OnConnect(const PtrConn &conn)
- {
- conn->SetContext(HttpContext());
- LOG(INFO, "NEW CONNECTION :%p\n", this);
- }
- void ErrorHandler(const HttpRequest &req, HttpResponse *rsp)
- {
- // 提供一个错误展示页面
- std::string body;
- body += "<!DOCTYPE html>";
- body += "<html lang='en'>";
- body += "<head>";
- body += "<meta charset='UTF-8'>";
- body += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
- body += "<title>Error " + std::to_string(rsp->_statu) + " - Server Error</title>";
- body += "<style>";
- body += "body { background-color: #f2f2f2; color: #333; font-family: Arial, sans-serif; }";
- body += "h1 { color: #d8000c; background-color: #ffbaba; border: 1px solid #d8d8d8; padding: 10px; text-align: center; }";
- body += "div.container { max-width: 600px; margin: 50px auto; padding: 20px; background-color: #fff; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }";
- body += "</style>";
- body += "</head>";
- body += "<body>";
- body += "<div class='container'>";
- body += "<h1>";
- body += "Error " + std::to_string(rsp->_statu) + " - " + Util::StatuDesc(rsp->_statu);
- body += "</h1>";
- body += "<p>We're sorry, but something went wrong.</p>";
- body += "</div>";
- body += "</body>";
- body += "</html>";
- // 将页面数据,当作响应正文,放入rsp中
- rsp->SetContent(body, "text/html");
- }
- // 缓冲区数据解析+处理
- void OnMessage(const PtrConn &conn, Buffer *buf)
- {
- while (buf->ReadAbleSize() > 0)
- {
- // 从连接中获取上下文
- HttpContext *context = conn->GetContext()->Get<HttpContext>();
- // 从缓冲区中获取数据 处理后得到Request
- context->RecvhttpRequest(buf);
- HttpRequest req = context->Request();
- // 根据请求构建应答
- HttpResponse rsp(context->RespStatu());
- // 根据状态码判断处理结果
- // LOG(DEBUG, "res->statu :%d\n", rsp._statu);
- // 状态码大于400说明解析出错 直接退出
- if (context->RespStatu() >= 400)
- {
- // 重置上下文
- context->Reset();
- // 清空缓冲区
- buf->MoveReadOffset(buf->ReadAbleSize());
- // 获取错误响应
- ErrorHandler(req, &rsp);
- // 发送错误请求
- WriteResponse(conn, req, rsp);
- // 关闭连接
- conn->Shutdown();
- return;
- }
- // 如果解析没有完成就等待下一次处理
- if (context->RecvStatu() != RECV_HTTP_OVER)
- {
- // 退出等待新数据到来 重新进行处理
- return;
- }
- // 请求解析完成进行处理
- Route(req, &rsp);
- LOG(INFO, "%s\n", rsp._body.c_str());
- if (rsp._statu >= 400)
- {
- // 获取错误响应
- ErrorHandler(req, &rsp);
- // 发送错误请求
- WriteResponse(conn, req, rsp);
- // 重置上下文
- context->Reset();
- // 关闭连接
- conn->Shutdown();
- return;
- }
- // 获取应答
- WriteResponse(conn, req, rsp);
- // 重置上下文
- context->Reset();
- // 根据长短连接判断是否需要关闭连接
- if (rsp.Close() == true)
- conn->Shutdown();
- }
- return;
- }
- bool Route(HttpRequest &req, HttpResponse *rsp)
- {
- // 判断是否是静态资源处理
- if (IsFileHandler(req) == true)
- return FileHandler(req, rsp);
- // 判断是否实际功能性请求
- 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);
- // 不是静态请求也不是功能性请求
- else
- {
- rsp->_statu = 405; // Method Not Allowed
- return false;
- }
- }
- // 判断是否是静态资源
- bool IsFileHandler(HttpRequest &req)
- {
- // 首先_basedir必须存在
- if (_basedir.empty() == true)
- return false;
- // 请求方法必须是 GET / HEAD
- if (req._method != "GET" && req._method != "HEAD")
- return false;
- // 请求路径必须是合法路径
- if (Util::IsLegPath(req._path) == false)
- return false;
- // 请求的资源必须存在
- std::string req_path = _basedir + req._path;
- // 如果直接请求的网络根目录要补全一个初始页面
- if (req_path.back() == '/')
- req_path += "index.html";
- if (Util::IsRegular(req_path) == false)
- return false;
- // req请求路径的真正路径
- req._path = req_path;
- return true;
- }
- // 静态资源的请求处理
- bool FileHandler(HttpRequest &req, HttpResponse *rsp)
- {
- LOG(INFO, "静态资源请求:%s\n", req._path.c_str());
- // 将请求资源读取到应答正文中
- bool ret = Util::ReadFile(req._path, &rsp->_body);
- if (ret == false)
- {
- // 数据读取失败
- LOG(ERROR, "数据读取失败\n");
- return false;
- }
- // 获取文件类型mime
- std::string mime = Util::ExtMime(req._path);
- LOG(DEBUG, "Content-Type:%s\n", mime.c_str());
- // 添加到应答报头
- rsp->SetHeader("Content-Type", mime);
- return true;
- }
- // 功能性请求的任务分发
- bool Dispatcher(HttpRequest &req, HttpResponse *rsp, Handlers &handlers)
- {
- // LOG(INFO, "%s 功能性请求:%s\n", req._method.c_str(), req._path.c_str());
- // 首先根据路由表找到目标
- for (auto &handler : handlers)
- {
- const std::regex &re = handler.first;
- // 根据这个正则表达式进行解析
- bool ret = std::regex_match(req._path, req._matches, re);
- if (ret == false)
- continue;
- // 找到了就进行执行函数
- Handler Functor = handler.second;
- Functor(req, rsp);
- return true;
- }
- // 没有找到目标
- LOG(DEBUG, "404 Not Found\n");
- rsp->_statu = 404; // 设置为Not Found
- return false;
- }
- // 将HttpReaponse应答按照http应答格式进行组织发送
- void WriteResponse(const PtrConn &conn, const HttpRequest &req, HttpResponse &rsp)
- {
- // 首先先完善头部字段
- if (req.Close() == true)
- rsp.SetHeader("Connection", "close");
- else
- rsp.SetHeader("Connection", "keep-alive");
- if (rsp._body.empty() == true && rsp.HasHeader("Content-Length") == false)
- rsp.SetHeader("Content-Length", std::to_string(rsp._body.size()));
- if (rsp._body.empty() == true && rsp.HasHeader("Content-Type") == false)
- rsp.SetHeader("Content-Type", "application/octet-stream");
- if (rsp._rediect_flag == true)
- rsp.SetHeader("Location", rsp._rediect_url);
- // 将rsp组织成http格式的应答报文
- std::stringstream rsp_str;
- rsp_str << req._version << " " << std::to_string(rsp._statu) << " " << Util::StatuDesc(rsp._statu) << "\r\n";
- for (auto &it : rsp._headers)
- {
- rsp_str << it.first << ": " << it.second << "\r\n";
- }
- rsp_str << "\r\n";
- rsp_str << rsp._body << "\r\n";
- // 进行发送
- // LOG(INFO, "WriteResponse Send :%s \n", rsp_str.str().c_str());
- conn->Send(rsp_str.str().c_str(), rsp_str.str().size());
- }
- public:
- HttpServer(int port, int timeout = DEFALT_TIMEOUT) : _server(port)
- {
- _server.SetConnectCB(std::bind(&HttpServer::OnConnect, this, std::placeholders::_1));
- _server.SetMessageCB(std::bind(&HttpServer::OnMessage, this, std::placeholders::_1, std::placeholders::_2));
- _server.EnableActiveRelease(timeout); // 设置超时时间
- }
- // 插入关系映射到GET路由表
- void GET(const std::string &pattern, const Handler &func) { _get_route.push_back(std::make_pair(std::regex(pattern), func)); }
- void POST(const std::string &pattern, const Handler &func) { _post_route.push_back(std::make_pair(std::regex(pattern), func)); }
- void PUT(const std::string &pattern, const Handler &func) { _put_route.push_back(std::make_pair(std::regex(pattern), func)); }
- void DELETE(const std::string &pattern, const Handler &func) { _delete_route.push_back(std::make_pair(std::regex(pattern), func)); }
- void SetBaseDir(const std::string &dir)
- {
- assert(Util::IsDir(dir) == true);
- _basedir = dir;
- }
- // 设置服务器线程数量
- void SetThreadSize(size_t size)
- {
- _server.SetThreadSize(size);
- }
- // 启动服务器
- void Start()
- {
- _server.Start();
- }
- };
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |