【项目日记】仿mudou的高并发服务器 --- 实现HTTP服务器 ...

打印 上一主题 下一主题

主题 554|帖子 554|积分 1662


   对于生命,你不妨大胆一点,      因为我们始终要失去它。        --- 尼采 ---       
   ✨✨✨项目地址在这里 ✨✨✨
  ✨✨✨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。

  1. // 公共方法类
  2. class Util
  3. {
  4. public:
  5.     static ssize_t SplitStr(const std::string &src, const std::string &sep, std::vector<std::string> &sub)
  6.     {
  7.         // 根据sep分隔符切分字符串
  8.         int offset = 0; // 偏移量
  9.         while (offset < src.size())
  10.         {
  11.             size_t pos = src.find(sep, offset);
  12.             // 没有找到sep
  13.             if (pos == std::string::npos)
  14.             {
  15.                 // 直接将offset后的字符串当成子串
  16.                 sub.push_back(src.substr(offset));
  17.                 break;
  18.             }
  19.             // 找到了sep
  20.             else
  21.             {
  22.                 size_t len = pos - offset;
  23.                 if (len == 0)
  24.                 {
  25.                     offset++;
  26.                     continue;
  27.                 }
  28.                 sub.push_back(src.substr(offset, len));
  29.                 offset += len; // 偏移量向后移动
  30.             }
  31.         }
  32.         return sub.size();
  33.     }
  34.     static bool ReadFile(const std::string &filename, std::string *buf)
  35.     {
  36.         std::ifstream ifs(filename, std::ios::binary); // 以读方式打开文件,采取二进制读取方式
  37.         if (ifs.is_open() == false)
  38.         {
  39.             LOG(ERROR, "Open %s Failed!\n", filename.c_str());
  40.             return false;
  41.         }
  42.         // 获取文件大小
  43.         ifs.seekg(0, ifs.end);  // 将读取位置移动到文件末尾
  44.         size_t n = ifs.tellg(); // 此时的偏移量即为文件大小
  45.         ifs.seekg(0, ifs.beg);  // 将读取位置移动到到文件开头
  46.         buf->resize(n); // 将缓冲区大小设置为文件大小
  47.         // 进行写入
  48.         ifs.read(&(*buf)[0], n);
  49.         // 关闭文件
  50.         ifs.close();
  51.         return true;
  52.     }
  53.     static bool WriteFile(const std::string &filename, const std::string &buf)
  54.     {
  55.         std::ofstream ofs(filename, std::ios::binary | std::ios::trunc); // 使用写方式打开进行二进制覆盖写
  56.         if (ofs.is_open() == false)
  57.         {
  58.             LOG(ERROR, "Open %s Failed!\n", filename.c_str());
  59.             return false;
  60.         }
  61.         // 进行写入
  62.         ofs.write(&buf[0], buf.size());
  63.         if (ofs.good() == false)
  64.         {
  65.             LOG(ERROR, "Write %s Failed!\n", filename.c_str());
  66.             return false;
  67.         }
  68.         ofs.close();
  69.         return true;
  70.     }
  71.     static std::string UrlEncode(const std::string &url, bool is_space_encode)
  72.     {
  73.         std::string ret;
  74.         // 进行编码
  75.         for (auto ch : url)
  76.         {
  77.             //. - _ ~ 四个字符绝对不编码
  78.             // 字母与数字不见编码
  79.             if (ch == '.' || ch == '-' || ch == '_' || ch == '~' || isalnum(ch))
  80.             {
  81.                 ret += ch;
  82.                 continue;
  83.             }
  84.             // 空格编码为 +
  85.             if (ch == ' ' && is_space_encode)
  86.             {
  87.                 ret += '+';
  88.                 continue;
  89.             }
  90.             // 其余字符进行编码
  91.             char buf[4]; // 编码格式 %___
  92.             snprintf(buf, 4, "%%%02X", ch);
  93.             ret += buf;
  94.         }
  95.         return ret;
  96.     }
  97.     // URL解码
  98.     static char HexToC(char c)
  99.     {
  100.         if (c >= '0' && c <= '9')
  101.         {
  102.             return c - '0';
  103.         }
  104.         else if (c >= 'a' && c <= 'z')
  105.         {
  106.             return c - 'a' + 10;
  107.         }
  108.         else if (c >= 'A' && c <= 'Z')
  109.         {
  110.             return c - 'A' + 10;
  111.         }
  112.         return -1;
  113.     }
  114.     static std::string UrlDecode(const std::string &url, bool is_space_decode)
  115.     {
  116.         std::string res;
  117.         // 遍历字符串 遇到%就进行解码
  118.         for (int i = 0; i < url.size(); i++)
  119.         {
  120.             if (url[i] == '%')
  121.             {
  122.                 char v1 = HexToC(url[i + 1]);
  123.                 char v2 = HexToC(url[i + 2]);
  124.                 char c = (v1 << 4) + v2;
  125.                 res += c;
  126.                 i += 2;
  127.                 continue;
  128.             }
  129.             else if (url[i] == '+' && is_space_decode)
  130.             {
  131.                 res += ' ';
  132.                 continue;
  133.             }
  134.             else
  135.             {
  136.                 res += url[i];
  137.             }
  138.         }
  139.         return res;
  140.     }
  141.     // 返回状态码
  142.     static std::string StatuDesc(int code)
  143.     {
  144.         auto ret = _statu_msg.find(code);
  145.         if (ret == _statu_msg.end())
  146.         {
  147.             return "Unkonw";
  148.         }
  149.         return ret->second;
  150.     }
  151.     // 解析文件后缀
  152.     static std::string ExtMime(const std::string &url)
  153.     {
  154.         size_t pos = url.rfind('.');
  155.         // 没有找到返回
  156.         if (pos == std::string::npos)
  157.         {
  158.             LOG(DEBUG, "没有找到'.'\n");
  159.             return "applicantion/octet-stream";
  160.         }
  161.         std::string str = url.substr(pos);
  162.         LOG(DEBUG, "文件类型:%s\n", str.c_str());
  163.         auto it = _mime_msg.find(str);
  164.         if (it == _mime_msg.end())
  165.         {
  166.             return "applicantion/octet-stream";
  167.         }
  168.         return it->second;
  169.     }
  170.     // 检查是否是合法路径
  171.     static bool IsLegPath(const std::string &path)
  172.     {
  173.         // 采用计数法
  174.         int level = 0;
  175.         std::vector<std::string> subdir;
  176.         int ret = SplitStr(path, "..", subdir);
  177.         if (ret < 0)
  178.             return false;
  179.         for (auto &s : subdir)
  180.         {
  181.             if (s == "..")
  182.             {
  183.                 level--;
  184.                 if (level < 0)
  185.                     return false;
  186.                 continue;
  187.             }
  188.             else
  189.                 level++;
  190.         }
  191.         return true;
  192.     }
  193.     static bool IsDir(const std::string &dir)
  194.     {
  195.         struct stat st;
  196.         int n = ::stat(dir.c_str(), &st);
  197.         if (n < 0)
  198.             return false;
  199.         return S_ISDIR(st.st_mode);
  200.     }
  201.     static bool IsRegular(const std::string &dir)
  202.     {
  203.         struct stat st;
  204.         int n = ::stat(dir.c_str(), &st);
  205.         if (n < 0)
  206.             return false;
  207.         return S_ISREG(st.st_mode);
  208.     }
  209. };
复制代码
3 HTTP协议

3.1 HTTP哀求

http协议的哀求格式是如许的:

  • 哀求行:包含哀求方法,资源路径URL,HTTP版本
  • 哀求报头:以键值对的形式储存须要信息
  • 空行:用于辨认正文
  • 哀求正文:储存本次哀求的正文

    针对这个结构我们可以搭建一个HTTP哀求的基础框架:
  1. class
  2. {
  3. public:
  4.         std::string _method;                                   // 请求方法
  5.     std::string _path;                                     // 查询路径
  6.     std::string _version;                                  // 协议版本
  7.     std::string _body;                                     // 请求正文
  8.     std::smatch _matches;                                  // 资源路径的正则提取解析
  9.     std::unordered_map<std::string, std::string> _headers; // 请求报头
  10.     std::unordered_map<std::string, std::string> _params;  // 查询字符串
  11.        
  12. };
复制代码
然后继承设置一些接口:

  • 插入头部字段的接口
  • 查抄哀求中是否有该头部字段
  • 插入查询字符串
  • 查抄哀求中是否有该查询字符串
  • 获取查询字符串
  • 获取正文长度
  • 是否为长连接
  1. class HttpRequest
  2. {
  3. public:
  4.     std::string _method;                                   // 请求方法
  5.     std::string _path;                                     // 查询路径
  6.     std::string _version;                                  // 协议版本
  7.     std::string _body;                                     // 请求正文
  8.     std::smatch _matches;                                  // 资源路径的正则提取解析
  9.     std::unordered_map<std::string, std::string> _headers; // 请求报头
  10.     std::unordered_map<std::string, std::string> _params;  // 查询字符串
  11. public:
  12.     // 重置请求
  13.     void Reset()
  14.     {
  15.         _method.clear();
  16.         _path.clear();
  17.         _version.clear();
  18.         _body.clear();
  19.         std::smatch tmp;
  20.         _matches.swap(tmp);
  21.         _headers.clear();
  22.         _params.clear();
  23.     }
  24.     // 插入头部字段
  25.     void SetHeader(const std::string &key, const std::string &val)
  26.     {
  27.         _headers.insert(std::make_pair(key, val));
  28.     }
  29.     // 判断是否有该头部字段
  30.     bool HasHeader(const std::string &key) const
  31.     {
  32.         auto it = _headers.find(key);
  33.         if (it == _headers.end())
  34.         {
  35.             return false;
  36.         }
  37.         return true;
  38.     }
  39.     // 获取头部字段
  40.     std::string GetHeader(const std::string &key) const
  41.     {
  42.         auto it = _headers.find(key);
  43.         if (it == _headers.end())
  44.         {
  45.             return "";
  46.         }
  47.         return it->second;
  48.     }
  49.     // 插入查询字符串
  50.     void SetParam(const std::string &key, const std::string &val)
  51.     {
  52.         _params.insert(std::make_pair(key, val));
  53.     }
  54.     // 判断是否有该查询字符串
  55.     bool HasParam(const std::string &key)
  56.     {
  57.         auto it = _params.find(key);
  58.         if (it == _params.end())
  59.         {
  60.             return false;
  61.         }
  62.         return true;
  63.     }
  64.     // 获取查询字符串
  65.     std::string GetParam(const std::string &key)
  66.     {
  67.         auto it = _params.find(key);
  68.         if (it == _params.end())
  69.         {
  70.             return "";
  71.         }
  72.         return it->second;
  73.     }
  74.     // 获取正文长度
  75.     size_t ContentLength()
  76.     {
  77.         bool ret = HasHeader("Content-Length");
  78.         if (ret)
  79.         {
  80.             // 转换为长整形
  81.             return std::stol(GetHeader("Content-Length"));
  82.         }
  83.         return 0;
  84.     }
  85.     bool Close() const
  86.     {
  87.         // 没有Connection字段或者Connection字段是close 就是短连接
  88.         if (HasHeader("Connection") == true && GetHeader("Connection") == "close")
  89.         {
  90.             return true;
  91.         }
  92.         return false;
  93.     }
  94. };
复制代码
如许一个基础的HTTP哀求结构就设计好了!
3.2 HTTP应答

http协议的应答格式是如许的:

  • 状态行:包含HTTP版本,状态码,状态码形貌
  • 应答报头:储存须要信息
  • 换行符:用于辨认正文
  • 正文:储存应答的正文结构

根据应答结构,我们可以搭建其应答框架:

  • 设置头部字段
  • 获取头部字段
  • 设置正文
  • 设置应答状态
  • 是否是长连接
  1. class HttpResponse
  2. {
  3. public:
  4.     int _statu;                                            // 状态码
  5.     bool _rediect_flag;                                    // 重定向标志
  6.     std::string _rediect_url;                              // 重定向的路径
  7.     std::string _body;                                     // 响应正文
  8.     std::unordered_map<std::string, std::string> _headers; // 响应报头
  9. public:
  10.     HttpResponse(int statu) : _statu(statu) {}
  11.     // 重置响应
  12.     void Reset()
  13.     {
  14.     }
  15.     // 插入头部字段
  16.     void SetHeader(const std::string &key, const std::string &val)
  17.     {
  18.         _headers.insert(std::make_pair(key, val));
  19.     }
  20.     // 判断是否有该头部字段
  21.     bool HasHeader(const std::string &key)
  22.     {
  23.         auto it = _headers.find(key);
  24.         if (it == _headers.end())
  25.         {
  26.             return false;
  27.         }
  28.         return true;
  29.     }
  30.     // 获取头部字段
  31.     std::string GetHeader(const std::string &key)
  32.     {
  33.         auto it = _headers.find(key);
  34.         if (it == _headers.end())
  35.         {
  36.             return "";
  37.         }
  38.         return it->second;
  39.     }
  40.     void SetContent(const std::string &body, const std::string &type = "text/html")
  41.     {
  42.         _body = body;
  43.         SetHeader("Content-Type", type);
  44.     }
  45.     void SetRediret(const std::string &url, int statu = 302)
  46.     {
  47.         _statu = statu;
  48.         _rediect_flag = true;
  49.         _rediect_url = url;
  50.     }
  51.     bool Close()
  52.     {
  53.         // 没有Connection字段或者Connection字段是close 就是短连接
  54.         if (HasHeader("Connection") == true && GetHeader("Connection") == "close")
  55.         {
  56.             return true;
  57.         }
  58.         return false;
  59.     }
  60. };
复制代码
如许HTTP协议的哀求与应答我们就完成了!可以进一步举行哀求与应答的解析工作了!
4 上下文解析模块

针对应答的反序列化,我们不在协议模块中直接举行设置,因为我们无法保证连接一次就可以获取完整的报文结构,以是在一个连接中要维护一个上下文结构,可以在多次处理处罚时知道本次处理处罚应该从那边举行!
在这个上下文中起首我们就必要一个状态变量,可以标识当前应该处理处罚什么字段:
  1.     RECV_HTTP_ERROR --- 处理出错
  2.     RECV_HTTP_LINE --- 处理请求行
  3.     RECV_HTTP_HEAD --- 处理头部字段
  4.     RECV_HTTP_BODY --- 处理正文
  5.     RECV_HTTP_OVER --- 处理完成
复制代码
每一个上下文都匹配一个哀求对象,将解析好的字段储存到这个哀求对象中:

  • 处理处罚哀求行:处理处罚哀求行时使用正则表达式快速举行处理处罚,注意URL编码的转换,哀求方法的巨细写以及拆分出查询字符串!
  • 处理处罚头部字段:一行一行的举行处即可,直到碰到空行!
  • 处理处罚正文:从缓冲区读取出正文长度的数据,不够继承等待,够了就返回。
必要注意的是,获取数据时不一定会获取到预期的数据,一定要做好情况分类,保证正常读取!
制止出现数据过长,数据不敷等情况!
上下文每次解析都将数据及时储存到该上下文中对应的哀求对象中!
  1. typedef enum
  2. {
  3.     RECV_HTTP_ERROR,
  4.     RECV_HTTP_LINE,
  5.     RECV_HTTP_HEAD,
  6.     RECV_HTTP_BODY,
  7.     RECV_HTTP_OVER
  8. } HttpRecvStatu;
  9. static const int MAX_SIZE = 8192;
  10. class HttpContext
  11. {
  12. private:
  13.     int _resp_statu;           // 响应状态码
  14.     HttpRequest _request;      // 请求信息
  15.     HttpRecvStatu _recv_statu; // 解析状态
  16. private:
  17.     bool ParseHttpLine(const std::string &line)
  18.     {
  19.         // 对请求行进行正则表达式解析
  20.         // 设置解析方法: 忽略大小写!
  21.         // std::regex re("(GET|HEAD|POST|PUT|DELETE) ([^?]+)\\?(.*) (HTTP/1\\.[01])(?:\n|\r\n)?", std::regex::icase);
  22.         std::regex re("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?", std::regex::icase);
  23.         //(GET|HEAD|POST|PUT|DELETE) 获取GET...请求方法
  24.         //([^?]+) 匹配若干个 非?字符 直到? --- 获取资源路径
  25.         //\\?(.*) \\?表示匹配原始?字符 (.*)访问到空格 ---获取请求参数
  26.         //(HTTP/1\\.[01]) 匹配HTTP/1. 01任意一个字符
  27.         //(?:\n|\r\n)? 匹配\n或者\r\n (?: ...)表示匹配摸个格式字符串但是不提取 .结尾的?表示前面的表达式0次或1次
  28.         std::smatch matches;
  29.         bool ret = std::regex_match(line, matches, re);
  30.         if (ret == false)
  31.         {
  32.             LOG(ERROR, "regex_match failed\n");
  33.             _resp_statu = 400; // Bad Reauest!
  34.             return false;
  35.         }
  36.         // 0:GET /a/b/c/search?q=keyword&lang=en HTTP/1.1
  37.         // 1:GET
  38.         // 2:/a/b/c/search
  39.         // 3:q=keyword&lang=en
  40.         // 4:HTTP/1.1
  41.         _request._method = matches[1];
  42.         // 请求方法统一转换为大写
  43.         std::transform(_request._method.begin(), _request._method.end(), _request._method.begin(), ::toupper);
  44.         _request._path = Util::UrlDecode(matches[2], false);
  45.         _request._version = matches[4];
  46.         // 对查询字符串进行解析
  47.         std::string str = matches[3];
  48.         std::vector<std::string> substr;
  49.         // 进行切分字符串
  50.         Util::SplitStr(str, "&", substr);
  51.         // 遍历容器
  52.         for (auto s : substr)
  53.         {
  54.             // 寻找'='
  55.             size_t pos = s.find("=");
  56.             if (pos == std::string::npos)
  57.             {
  58.                 LOG(ERROR, "ParseHttpLine Failed\n");
  59.                 _recv_statu = RECV_HTTP_ERROR;
  60.                 _resp_statu = 400; // BAD Resquest
  61.                 return false;
  62.             }
  63.             // 找到了 ‘=’
  64.             std::string key = Util::UrlDecode(s.substr(0, pos), true);
  65.             std::string value = Util::UrlDecode(s.substr(pos + 1), true);
  66.             LOG(INFO, "查询字符串%s: %s\n", key.c_str(), value.c_str());
  67.             _request.SetParam(key, value);
  68.         }
  69.         return true;
  70.     }
  71.     // 解析请求行
  72.     bool RecvHttpLine(Buffer *buf)
  73.     {
  74.         if (_recv_statu != RECV_HTTP_LINE)
  75.             return false;
  76.         // 获取一行数据 带有\r\n
  77.         std::string line = buf->GetLineAndPop();
  78.         if (line.size() == 0)
  79.         {
  80.             // 缓存区中没有完整的一行数据 进行分类讨论
  81.             // 如果缓冲区数据大于极限值
  82.             if (buf->ReadAbleSize() > MAX_SIZE)
  83.             {
  84.                 _resp_statu = 414; // URL TOO LONG
  85.                 _recv_statu = RECV_HTTP_ERROR;
  86.                 return false;
  87.             }
  88.             // 反之不处理
  89.             return true;
  90.         }
  91.         // 一行的数据过长
  92.         if (line.size() > MAX_SIZE)
  93.         {
  94.             _resp_statu = 414; // URL TOO LONG
  95.             _recv_statu = RECV_HTTP_ERROR;
  96.             return false;
  97.         }
  98.         // 进行解析
  99.         bool ret = ParseHttpLine(line);
  100.         if (ret == false)
  101.             return false;
  102.         // 请求行解析完毕 开始解析请求报头
  103.         _recv_statu = RECV_HTTP_HEAD;
  104.         return true;
  105.     }
  106.     // 解析报头
  107.     bool RecvHttpHead(Buffer *buf)
  108.     {
  109.         if (_recv_statu != RECV_HTTP_HEAD)
  110.             return false;
  111.         // 解析请求报头直到遇到空行
  112.         while (1)
  113.         {
  114.             std::string line = buf->GetLineAndPop();
  115.             // LOG(DEBUG, "line:%s\n", line.c_str());
  116.             if (line.size() == 0)
  117.             {
  118.                 // 缓存区中没有完整的一行数据 进行分类讨论
  119.                 // 如果缓冲区数据大于极限值
  120.                 if (buf->ReadAbleSize() > MAX_SIZE)
  121.                 {
  122.                     // LOG(ERROR, "line too long\n");
  123.                     _resp_statu = 414; // URL TOO LONG
  124.                     _recv_statu = RECV_HTTP_ERROR;
  125.                     return false;
  126.                 }
  127.                 // 反之不处理 等待新数据到来
  128.                 // LOG(ERROR, "wait new buffer\n");
  129.                 return true;
  130.             }
  131.             // 一行的数据过长
  132.             if (line.size() > MAX_SIZE)
  133.             {
  134.                 // LOG(ERROR, "line too long\n");
  135.                 _resp_statu = 414; // URL TOO LONG
  136.                 _recv_statu = RECV_HTTP_ERROR;
  137.                 return false;
  138.             }
  139.             if (line == "\n" || line == "\r\n")
  140.             {
  141.                 // LOG(ERROR, "line is empty\n");
  142.                 break;
  143.             }
  144.             // LOG(INFO, "line正常 进行解析处理");
  145.             //  去除换行 \r \n
  146.             if (line.back() == '\n')
  147.                 line.pop_back();
  148.             if (line.back() == '\r')
  149.                 line.pop_back();
  150.             // 进行解析
  151.             bool ret = ParseHttpHead(line);
  152.             if (ret == false)
  153.                 return false;
  154.         }
  155.         // 头部解析完成 继续解析正文
  156.         _recv_statu = RECV_HTTP_BODY;
  157.         return true;
  158.     }
  159.     bool ParseHttpHead(const std::string &line)
  160.     {
  161.         // 每一行都是key: val\r\n 格式
  162.         // LOG(DEBUG, "ParseHttpHead:%s\n", line.c_str());
  163.         // 进行解析即可
  164.         size_t pos = line.find(": ");
  165.         if (pos == std::string::npos)
  166.         {
  167.             LOG(ERROR, "ParseHttpLine Failed\n");
  168.             _recv_statu = RECV_HTTP_ERROR;
  169.             _resp_statu = 400; // BAD Resquest
  170.             return false;
  171.         }
  172.         std::string key = line.substr(0, pos);
  173.         std::string val = line.substr(pos + 2);
  174.         // LOG(DEBUG, "%s: %s\n", key.c_str(), val.c_str());
  175.         _request.SetHeader(key, val);
  176.         return true;
  177.     }
  178.     bool RecvHttpBody(Buffer *buf)
  179.     {
  180.         if (_recv_statu != RECV_HTTP_BODY)
  181.             return false;
  182.         // 获取正文长度
  183.         size_t len = _request.ContentLength();
  184.         // 没有正文 直接读取完毕
  185.         if (len == 0)
  186.         {
  187.             _recv_statu = RECV_HTTP_OVER;
  188.             return true;
  189.         }
  190.         // 当前已经接受了多少数据 _request._body
  191.         size_t relen = len - _request._body.size();
  192.         // 接收正文放到body中 但是要考虑当前缓冲区中的数据是否是全部的报文
  193.         // 缓冲区数据包含所有正文
  194.         if (relen <= buf->ReadAbleSize())
  195.         {
  196.             // 加到_request.body的后面
  197.             _request._body.append(buf->ReadPos(), relen);
  198.             buf->MoveReadOffset(relen);
  199.             _recv_statu = RECV_HTTP_OVER;
  200.             return true;
  201.         }
  202.         // 缓冲区无法满足正文
  203.         _request._body.append(buf->ReadPos(), buf->ReadAbleSize());
  204.         buf->MoveReadOffset(buf->ReadAbleSize());
  205.         return true;
  206.     }
  207. public:
  208.     HttpContext() : _resp_statu(200), _recv_statu(RECV_HTTP_LINE) {}
  209.     int RespStatu() { return _resp_statu; }
  210.     HttpRequest &Request() { return _request; }
  211.     HttpRecvStatu RecvStatu() { return _recv_statu; }
  212.     // 重置上下文
  213.     void Reset()
  214.     {
  215.         _resp_statu = 200;
  216.         _recv_statu = RECV_HTTP_LINE;
  217.         _request.Reset();
  218.     }
  219.     void RecvhttpRequest(Buffer *buf)
  220.     {
  221.         // 根据不同的状态 处理不同情况
  222.         // 处理完不要break 因为处理完 可以继续进行处理下面的数据 而不是直接退出等待新数据!
  223.         switch (_recv_statu)
  224.         {
  225.         case RECV_HTTP_LINE:
  226.             RecvHttpLine(buf);
  227.         case RECV_HTTP_HEAD:
  228.             RecvHttpHead(buf);
  229.         case RECV_HTTP_BODY:
  230.             RecvHttpBody(buf);
  231.         }
  232.         return;
  233.     }
  234. };
复制代码
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服务器
  1. class HttpServer
  2. {
  3. private:
  4.     using Handler = std::function<void(const HttpRequest &, HttpResponse *)>;
  5.     using Handlers = std::vector<std::pair<std::regex, Handler>>;
  6.     Handlers _get_route;    // GET方法处理函数映射表
  7.     Handlers _post_route;   // POST方法处理函数映射表
  8.     Handlers _delete_route; // DELETE方法处理函数映射表
  9.     Handlers _put_route;    // PUT方法处理函数映射表
  10.     std::string _basedir;
  11.     TcpServer _server;
  12. public:
  13.     // 设置空白上下文
  14.     void OnConnect(const PtrConn &conn)
  15.     {
  16.         conn->SetContext(HttpContext());
  17.         LOG(INFO, "NEW CONNECTION :%p\n", this);
  18.     }
  19.     void ErrorHandler(const HttpRequest &req, HttpResponse *rsp)
  20.     {
  21.         // 提供一个错误展示页面
  22.         std::string body;
  23.         body += "<!DOCTYPE html>";
  24.         body += "<html lang='en'>";
  25.         body += "<head>";
  26.         body += "<meta charset='UTF-8'>";
  27.         body += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
  28.         body += "<title>Error " + std::to_string(rsp->_statu) + " - Server Error</title>";
  29.         body += "<style>";
  30.         body += "body { background-color: #f2f2f2; color: #333; font-family: Arial, sans-serif; }";
  31.         body += "h1 { color: #d8000c; background-color: #ffbaba; border: 1px solid #d8d8d8; padding: 10px; text-align: center; }";
  32.         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); }";
  33.         body += "</style>";
  34.         body += "</head>";
  35.         body += "<body>";
  36.         body += "<div class='container'>";
  37.         body += "<h1>";
  38.         body += "Error " + std::to_string(rsp->_statu) + " - " + Util::StatuDesc(rsp->_statu);
  39.         body += "</h1>";
  40.         body += "<p>We're sorry, but something went wrong.</p>";
  41.         body += "</div>";
  42.         body += "</body>";
  43.         body += "</html>";
  44.         // 将页面数据,当作响应正文,放入rsp中
  45.         rsp->SetContent(body, "text/html");
  46.     }
  47.     // 缓冲区数据解析+处理
  48.     void OnMessage(const PtrConn &conn, Buffer *buf)
  49.     {
  50.         while (buf->ReadAbleSize() > 0)
  51.         {
  52.             // 从连接中获取上下文
  53.             HttpContext *context = conn->GetContext()->Get<HttpContext>();
  54.             // 从缓冲区中获取数据 处理后得到Request
  55.             context->RecvhttpRequest(buf);
  56.             HttpRequest req = context->Request();
  57.             // 根据请求构建应答
  58.             HttpResponse rsp(context->RespStatu());
  59.             // 根据状态码判断处理结果
  60.             // LOG(DEBUG, "res->statu :%d\n", rsp._statu);
  61.             // 状态码大于400说明解析出错 直接退出
  62.             if (context->RespStatu() >= 400)
  63.             {
  64.                 // 重置上下文
  65.                 context->Reset();
  66.                 // 清空缓冲区
  67.                 buf->MoveReadOffset(buf->ReadAbleSize());
  68.                 // 获取错误响应
  69.                 ErrorHandler(req, &rsp);
  70.                 // 发送错误请求
  71.                 WriteResponse(conn, req, rsp);
  72.                 // 关闭连接
  73.                 conn->Shutdown();
  74.                 return;
  75.             }
  76.             // 如果解析没有完成就等待下一次处理
  77.             if (context->RecvStatu() != RECV_HTTP_OVER)
  78.             {
  79.                 // 退出等待新数据到来 重新进行处理
  80.                 return;
  81.             }
  82.             // 请求解析完成进行处理
  83.             Route(req, &rsp);
  84.             LOG(INFO, "%s\n", rsp._body.c_str());
  85.             if (rsp._statu >= 400)
  86.             {
  87.                 // 获取错误响应
  88.                 ErrorHandler(req, &rsp);
  89.                 // 发送错误请求
  90.                 WriteResponse(conn, req, rsp);
  91.                 // 重置上下文
  92.                 context->Reset();
  93.                 // 关闭连接
  94.                 conn->Shutdown();
  95.                 return;
  96.             }
  97.             // 获取应答
  98.             WriteResponse(conn, req, rsp);
  99.             // 重置上下文
  100.             context->Reset();
  101.             // 根据长短连接判断是否需要关闭连接
  102.             if (rsp.Close() == true)
  103.                 conn->Shutdown();
  104.         }
  105.         return;
  106.     }
  107.     bool Route(HttpRequest &req, HttpResponse *rsp)
  108.     {
  109.         // 判断是否是静态资源处理
  110.         if (IsFileHandler(req) == true)
  111.             return FileHandler(req, rsp);
  112.         // 判断是否实际功能性请求
  113.         if (req._method == "GET" || req._method == "HEAD")
  114.             return Dispatcher(req, rsp, _get_route);
  115.         else if (req._method == "POST")
  116.             return Dispatcher(req, rsp, _post_route);
  117.         else if (req._method == "PUT")
  118.             return Dispatcher(req, rsp, _put_route);
  119.         else if (req._method == "DELETE")
  120.             return Dispatcher(req, rsp, _delete_route);
  121.         // 不是静态请求也不是功能性请求
  122.         else
  123.         {
  124.             rsp->_statu = 405; // Method Not Allowed
  125.             return false;
  126.         }
  127.     }
  128.     // 判断是否是静态资源
  129.     bool IsFileHandler(HttpRequest &req)
  130.     {
  131.         // 首先_basedir必须存在
  132.         if (_basedir.empty() == true)
  133.             return false;
  134.         // 请求方法必须是 GET / HEAD
  135.         if (req._method != "GET" && req._method != "HEAD")
  136.             return false;
  137.         // 请求路径必须是合法路径
  138.         if (Util::IsLegPath(req._path) == false)
  139.             return false;
  140.         // 请求的资源必须存在
  141.         std::string req_path = _basedir + req._path;
  142.         // 如果直接请求的网络根目录要补全一个初始页面
  143.         if (req_path.back() == '/')
  144.             req_path += "index.html";
  145.         if (Util::IsRegular(req_path) == false)
  146.             return false;
  147.         // req请求路径的真正路径
  148.         req._path = req_path;
  149.         return true;
  150.     }
  151.     // 静态资源的请求处理
  152.     bool FileHandler(HttpRequest &req, HttpResponse *rsp)
  153.     {
  154.         LOG(INFO, "静态资源请求:%s\n", req._path.c_str());
  155.         // 将请求资源读取到应答正文中
  156.         bool ret = Util::ReadFile(req._path, &rsp->_body);
  157.         if (ret == false)
  158.         {
  159.             // 数据读取失败
  160.             LOG(ERROR, "数据读取失败\n");
  161.             return false;
  162.         }
  163.         // 获取文件类型mime
  164.         std::string mime = Util::ExtMime(req._path);
  165.         LOG(DEBUG, "Content-Type:%s\n", mime.c_str());
  166.         // 添加到应答报头
  167.         rsp->SetHeader("Content-Type", mime);
  168.         return true;
  169.     }
  170.     // 功能性请求的任务分发
  171.     bool Dispatcher(HttpRequest &req, HttpResponse *rsp, Handlers &handlers)
  172.     {
  173.         // LOG(INFO, "%s 功能性请求:%s\n", req._method.c_str(), req._path.c_str());
  174.         //  首先根据路由表找到目标
  175.         for (auto &handler : handlers)
  176.         {
  177.             const std::regex &re = handler.first;
  178.             // 根据这个正则表达式进行解析
  179.             bool ret = std::regex_match(req._path, req._matches, re);
  180.             if (ret == false)
  181.                 continue;
  182.             // 找到了就进行执行函数
  183.             Handler Functor = handler.second;
  184.             Functor(req, rsp);
  185.             return true;
  186.         }
  187.         // 没有找到目标
  188.         LOG(DEBUG, "404 Not Found\n");
  189.         rsp->_statu = 404; // 设置为Not Found
  190.         return false;
  191.     }
  192.     // 将HttpReaponse应答按照http应答格式进行组织发送
  193.     void WriteResponse(const PtrConn &conn, const HttpRequest &req, HttpResponse &rsp)
  194.     {
  195.         // 首先先完善头部字段
  196.         if (req.Close() == true)
  197.             rsp.SetHeader("Connection", "close");
  198.         else
  199.             rsp.SetHeader("Connection", "keep-alive");
  200.         if (rsp._body.empty() == true && rsp.HasHeader("Content-Length") == false)
  201.             rsp.SetHeader("Content-Length", std::to_string(rsp._body.size()));
  202.         if (rsp._body.empty() == true && rsp.HasHeader("Content-Type") == false)
  203.             rsp.SetHeader("Content-Type", "application/octet-stream");
  204.         if (rsp._rediect_flag == true)
  205.             rsp.SetHeader("Location", rsp._rediect_url);
  206.         // 将rsp组织成http格式的应答报文
  207.         std::stringstream rsp_str;
  208.         rsp_str << req._version << " " << std::to_string(rsp._statu) << " " << Util::StatuDesc(rsp._statu) << "\r\n";
  209.         for (auto &it : rsp._headers)
  210.         {
  211.             rsp_str << it.first << ": " << it.second << "\r\n";
  212.         }
  213.         rsp_str << "\r\n";
  214.         rsp_str << rsp._body << "\r\n";
  215.         // 进行发送
  216.         // LOG(INFO, "WriteResponse Send :%s \n", rsp_str.str().c_str());
  217.         conn->Send(rsp_str.str().c_str(), rsp_str.str().size());
  218.     }
  219. public:
  220.     HttpServer(int port, int timeout = DEFALT_TIMEOUT) : _server(port)
  221.     {
  222.         _server.SetConnectCB(std::bind(&HttpServer::OnConnect, this, std::placeholders::_1));
  223.         _server.SetMessageCB(std::bind(&HttpServer::OnMessage, this, std::placeholders::_1, std::placeholders::_2));
  224.         _server.EnableActiveRelease(timeout); // 设置超时时间
  225.     }
  226.     // 插入关系映射到GET路由表
  227.     void GET(const std::string &pattern, const Handler &func) { _get_route.push_back(std::make_pair(std::regex(pattern), func)); }
  228.     void POST(const std::string &pattern, const Handler &func) { _post_route.push_back(std::make_pair(std::regex(pattern), func)); }
  229.     void PUT(const std::string &pattern, const Handler &func) { _put_route.push_back(std::make_pair(std::regex(pattern), func)); }
  230.     void DELETE(const std::string &pattern, const Handler &func) { _delete_route.push_back(std::make_pair(std::regex(pattern), func)); }
  231.     void SetBaseDir(const std::string &dir)
  232.     {
  233.         assert(Util::IsDir(dir) == true);
  234.         _basedir = dir;
  235.     }
  236.     // 设置服务器线程数量
  237.     void SetThreadSize(size_t size)
  238.     {
  239.         _server.SetThreadSize(size);
  240.     }
  241.     // 启动服务器
  242.     void Start()
  243.     {
  244.         _server.Start();
  245.     }
  246. };
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

李优秀

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表