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

在这里插入图片描述

对于生命,你不妨大胆一点,
因为我们始终要失去它。
--- 尼采 ---

✨✨✨项目地址在这里 ✨✨✨

✨✨✨https://gitee.com/penggli_2_0/TcpServer✨✨✨


1 前言

上一篇文章我们基本实现了高并发服务器所需的基础模块,通过TcpServer类可以快速搭建一个TCP服务器。我们的最终目的是使用这个高并发服务器去实现一些业务,那么在网络通信中,我们就可以来实现一下HTTP服务。让浏览器可以访问获取数据。

为了实现HTTP服务器首要的工作就是实现HTTP协议,协议是网络通信的基础!只有确定了协议我们才能正常解析请求报文,并组织应答报文,可以让浏览器成功获取数据。

完成HTTP协议之后,就是设计一种报文解析模块,可以从缓冲区中获取数据,进行解析数据,得到完整请求。

最终将这些整合为一个HTTP服务器模块,设计回调函数,实现HTTP服务器的功能!

2 Util工具类

在HTTP服务器处理中,经常需要一些常用操作,比如切分字符串,编码转换,通过状态码找到对应状态解析… Util工具类就是用来实现这些功能的类!

  1. SplitStr
    • 功能:根据指定的分隔符 sep 将字符串 src 切分成多个子字符串,并将这些子字符串存储在 sub 向量中。
    • 返回值:返回切分后的子字符串数量。
  2. ReadFile
    • 功能:以二进制方式读取文件 filename 的内容到字符串 buf 中。
    • 返回值:如果文件打开和读取成功,返回 true;否则返回 false
  3. WriteFile
    • 功能:以二进制方式将字符串 buf 的内容写入到文件 filename 中,如果文件已存在则覆盖。
    • 返回值:如果文件打开和写入成功,返回 true;否则返回 false
  4. UrlEncode
    • 功能:对字符串 url 进行 URL 编码,可以选择是否将空格编码为 +
    • 返回值:返回编码后的字符串。
  5. HexToC
    • 功能:将十六进制字符转换为对应的整数值。
    • 返回值:返回转换后的整数值。
  6. UrlDecode
    • 功能:对字符串 url 进行 URL 解码,可以选择是否将 + 解码为空格。
    • 返回值:返回解码后的字符串。
  7. StatuDesc
    • 功能:根据给定的状态码 code 返回对应的状态描述。
    • 返回值:返回状态描述字符串,如果状态码未知,则返回 “Unkonw”。
  8. ExtMime
    • 功能:根据 URL 的扩展名返回对应的 MIME 类型。
    • 返回值:返回 MIME 类型字符串,如果扩展名未知,则返回 “application/octet-stream”。
  9. IsLegPath
    • 功能:检查字符串 path 是否是合法的路径,主要检查是否存在非法的 “…” 使用。
    • 返回值:如果路径合法,返回 true;否则返回 false
  10. IsDir
    • 功能:检查给定的路径 dir 是否是一个目录。
    • 返回值:如果是目录,返回 true;否则返回 false
  11. 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