【项目】仿muduo库One Thread One Loop式主从Reactor模型实现高并发服务器(Http板块)

【项目】仿muduo库One Thread One Loop式主从Reactor模型实现高并发服务器(Http板块)

  • 一、思路图
  • 二、Util板块
    • 1、Splite板块(分词)
      • (1)代码
      • (2)测试及测试结果
        • i、第一种测试
        • ii、第二种测试
        • iii、第三种测试
    • 2、ReadFile板块(从文件读取)
      • (1)代码及设计思想
      • (2)测试及测试结果
    • 3、WriteFile板块(向文件写入)
      • (1)代码及设计思想
      • (2)测试及测试结果
    • 4、UrlEnode(Url编码)
      • (1)代码及设计思想
      • (2)测试及测试结果
        • i、测试结果1
        • ii、测试结果2
    • 5、UrlDecode(Url解码)
      • (1)代码及设计思路
      • (2)运行及运行结果
        • i、测试结果1
        • ii、测试结果2
    • 6、HttpDes(HTTP响应状态码和描述信息)
      • (1)代码及设计思想
      • (2)测试及测试结果
        • i、测试结果1
        • ii、测试结果2
    • 7、ExtMime(根据文件后缀名获取mime)
      • (1)代码及设计思路
      • (2)运行及运行结果
        • i、测试结果1
        • ii、测试结果2
    • 8、IsCatalogue(判断一个文件是否是目录)&&IsRegular(判断一个文件是否是普通文件)
      • (1)代码及设计思想
      • (2)测试及测试结果
    • 9、VaildPath(HTTP资源路径的有效性的判断)
      • (1)代码及设计思路
      • (2)测试及测试结果
  • 二、HttpRequest板块
    • 1、设计思维导图
    • 2、代码设计
  • 三、HttpResponse板块
    • 1、设计思维导图
    • 2、代码设计
  • 四、HttpContext板块
    • 1、设计思维导图
    • 2、代码部分
      • (1)接收命令行(RecvHttpLine)
      • (2)解析命令行(ParseHttpLine)
      • (3)接收头部(RecvHttpHead)
      • (4)解析头部(ParseHttpHead)
      • (5)接收正文(RecvHttpBody)
      • (6)重置(Reset)+返回三个私有成员函数+接收并解析HTTP请求(RecvHttpRequest)
      • (7)总体代码
  • 五、HttpServer板块
    • 1、设计思维导图
    • 2、代码部分


一、思路图

在这里插入图片描述

二、Util板块

1、Splite板块(分词)

(1)代码

我们写下面的代码主要需要考虑到三种分割方式:
1、abc 这种很简单的没有分隔符的单词,直接就是将offset到pos-offset的位置插入到array中即可
2、abc, 这种后面只有一个分隔符的单词,先将前面abc插入到array中,后面的直接不进入循环直接退出即可
3、abc,/,cdf 这种中间有很多个,的情况下,我们只需要一直continue即可。

// 分割字符串,将目标src中的字符串以sep分割出来放到array中
size_t Splite(const std::string &src, const std::string &sep, std::vector<std::string>* array)
{size_t offset = 0; // 偏移量// 如果字符串的范围是0-9,我们假如说是offset到了10的话就是越界了,所以不用等号while (offset < src.size()){size_t pos = src.find(sep, offset); // 让src从offset位置往后进行查找sep,并分割放到array中if (pos == std::string::npos){if (pos == src.size()) break; // 都到最后一个位置了,没必要往后走了// 没查到这sep的位置:abc 比如这个逗号是separray->push_back(src.substr(offset)); // 从offset到最后一个位置return array->size();}// 到这里就是找到sep分隔符的位置了if (pos == offset) // pos点位刚好是offset,这种情况一般是:abc,,,,,,,cde中间一连串逗号{offset = pos + sep.size();continue;}array->push_back(src.substr(offset, pos - offset)); // abc,cde 从offset位置到pos - offset位置offset = pos + sep.size();}return array->size();
}

(2)测试及测试结果

i、第一种测试

在这里插入图片描述

在这里插入图片描述

ii、第二种测试

在这里插入图片描述
在这里插入图片描述

iii、第三种测试

在这里插入图片描述
在这里插入图片描述

2、ReadFile板块(从文件读取)

(1)代码及设计思想

先打开文件用ifstream:
在这里插入图片描述
再将字符串偏移到文档的最后一个位置,再从当前位置计算出整个文档的大小,之后便将文档偏移到开头位置,将我们要存入的缓冲区中开辟这个文档的大小,再读入到文件中,别忘了关闭文件哦!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

        // 读取文件内容static bool ReadFile(const std::string& filename, std::string* buff){std::ifstream ifs(filename, std::ios::binary); // 先打开文件if (ifs.is_open() == false) // 打开失败了{printf("OPEN %s IS FAILED", filename.c_str());return false;}size_t fsize = 0; // 定义ifstream长度// 先偏移到最后一个位置ifs.seekg(0, ifs.end);// 计算fsize位置fsize = ifs.tellg();// 再重新偏移到开头ifs.seekg(0, ifs.beg);// 开辟文件大小buff->resize(fsize);// 读入到文件中ifs.read(&(*buff)[0], fsize);if (ifs.good() == false){// 读入失败了printf("READ %s IS FAILED", filename.c_str());ifs.close();return false;}ifs.close();return true;}

(2)测试及测试结果

在这里插入图片描述

在这里插入图片描述

3、WriteFile板块(向文件写入)

(1)代码及设计思想

打开文件并写入,就这么简单!

        // 向文件写入内容static bool WriteFile(const std::string &filename, const std::string &buff){std::ofstream ofs(filename, std::ios::binary | std::ios::trunc); // trunc 表示截断if (ofs.is_open() == false){printf("OPEN %s IS FAILED", filename.c_str());return false;}ofs.write(buff.c_str(), buff.size());if (ofs.good() == false){printf("WRITE %s IS FAILED", filename.c_str());ofs.close();return false;}ofs.close();return true;}

(2)测试及测试结果

在这里插入图片描述

在这里插入图片描述

4、UrlEnode(Url编码)

(1)代码及设计思想

其实思想很简单啦,主要还是特殊字符的处理,我们只要搞懂了我们下面注释的文字的特殊字符那么就完全没问题了。
在这里插入图片描述在这里插入图片描述

        // URL编码,避免URL中资源路径与查询字符串中的特殊字符与HTTP请求中特殊字符产生歧义// 编码格式:将特殊字符的ascii值,转换为两个16进制字符,前缀%C++->c%2B%2B// 不编码的特殊字符:RFC3986文档中规定的URL绝对不编码字符:.-_~以及字母和数字// 还有一个就是在不同的一些标准中的特殊处理:// W3C标准规定中规定param中的空格必须被编码为+,解码是+转空格// RFC2396中规定URI中的保留字符需要转换为%HH格式。static std::string UrlEncode(const std::string &url, bool convert_space_to_plus){std::string res;for (auto& c : url){if (c == '.' || c == '-' || c == '_' || c == '~'){res += c;continue;}if (c == ' ' && convert_space_to_plus == true){res += '+';continue;}if (isalnum(c)){res += c;continue;}// 剩下的只用转码为%HH格式了char temp[4] = {0};snprintf(temp, 4, "%%%02X", c);res += temp;}return res;}

(2)测试及测试结果

i、测试结果1

在这里插入图片描述

在这里插入图片描述

ii、测试结果2

在这里插入图片描述
在这里插入图片描述

5、UrlDecode(Url解码)

(1)代码及设计思路

这里就单纯的将编码完成后的字符进行解码,我们最注意的是在字母字符的时候需要+10。

        static char HTOI(char c){if (c >= '0' && c <= '9'){return c - '0';}if (c >= 'a' && c <= 'z'){return c - 'a' + 10;}if (c >= 'A' && c <= 'Z'){return c - 'A' + 10;}return -1;}// URL解码static std::string UrlDecode(const std::string& url, bool convert_plus_to_space){// 遇到了%,则将紧随其后的2个字符,转换为数字,第一个数字左移4位,然后加上第二个数字  + -> 2b  %2b->2 << 4 + 11std::string res;for (int i = 0; i < url.size(); i++){if (url[i] == '+' && convert_plus_to_space == true){res += ' ';continue;}if (url[i] == '%' && (i + 2) < url.size()){char v1 = HTOI(url[i + 1]);char v2 = HTOI(url[i + 2]);char v = v1 * 16 + v2;res += v;i += 2;continue;}res += url[i];}return res;}

(2)运行及运行结果

i、测试结果1

在这里插入图片描述
在这里插入图片描述

ii、测试结果2

在这里插入图片描述

在这里插入图片描述

6、HttpDes(HTTP响应状态码和描述信息)

(1)代码及设计思想

用一个unordered_map来存储,然后用find迭代器来找!

        // HTTP状态码和描述信息static std::string HttpDes(int statu){std::unordered_map<int, std::string> _statu_msg = {{100,  "Continue"},{101,  "Switching Protocol"},{102,  "Processing"},{103,  "Early Hints"},{200,  "OK"},{201,  "Created"},{202,  "Accepted"},{203,  "Non-Authoritative Information"},{204,  "No Content"},{205,  "Reset Content"},{206,  "Partial Content"},{207,  "Multi-Status"},{208,  "Already Reported"},{226,  "IM Used"},{300,  "Multiple Choice"},{301,  "Moved Permanently"},{302,  "Found"},{303,  "See Other"},{304,  "Not Modified"},{305,  "Use Proxy"},{306,  "unused"},{307,  "Temporary Redirect"},{308,  "Permanent Redirect"},{400,  "Bad Request"},{401,  "Unauthorized"},{402,  "Payment Required"},{403,  "Forbidden"},{404,  "Not Found"},{405,  "Method Not Allowed"},{406,  "Not Acceptable"},{407,  "Proxy Authentication Required"},{408,  "Request Timeout"},{409,  "Conflict"},{410,  "Gone"},{411,  "Length Required"},{412,  "Precondition Failed"},{413,  "Payload Too Large"},{414,  "URI Too Long"},{415,  "Unsupported Media Type"},{416,  "Range Not Satisfiable"},{417,  "Expectation Failed"},{418,  "I'm a teapot"},{421,  "Misdirected Request"},{422,  "Unprocessable Entity"},{423,  "Locked"},{424,  "Failed Dependency"},{425,  "Too Early"},{426,  "Upgrade Required"},{428,  "Precondition Required"},{429,  "Too Many Requests"},{431,  "Request Header Fields Too Large"},{451,  "Unavailable For Legal Reasons"},{501,  "Not Implemented"},{502,  "Bad Gateway"},{503,  "Service Unavailable"},{504,  "Gateway Timeout"},{505,  "HTTP Version Not Supported"},{506,  "Variant Also Negotiates"},{507,  "Insufficient Storage"},{508,  "Loop Detected"},{510,  "Not Extended"},{511,  "Network Authentication Required"}};auto it = _statu_msg.find(statu);if (it != _statu_msg.end()){// 找到啦return it->second;}return "UnKonw";}

(2)测试及测试结果

i、测试结果1

在这里插入图片描述

在这里插入图片描述

ii、测试结果2

在这里插入图片描述

在这里插入图片描述

7、ExtMime(根据文件后缀名获取mime)

(1)代码及设计思路

        // 根据文件后缀名获取mimestatic std::string ExtMime(const std::string &filename){std::unordered_map<std::string, std::string> _mime_msg = {{".aac",        "audio/aac"},{".abw",        "application/x-abiword"},{".arc",        "application/x-freearc"},{".avi",        "video/x-msvideo"},{".azw",        "application/vnd.amazon.ebook"},{".bin",        "application/octet-stream"},{".bmp",        "image/bmp"},{".bz",         "application/x-bzip"},{".bz2",        "application/x-bzip2"},{".csh",        "application/x-csh"},{".css",        "text/css"},{".csv",        "text/csv"},{".doc",        "application/msword"},{".docx",       "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},{".eot",        "application/vnd.ms-fontobject"},{".epub",       "application/epub+zip"},{".gif",        "image/gif"},{".htm",        "text/html"},{".html",       "text/html"},{".ico",        "image/vnd.microsoft.icon"},{".ics",        "text/calendar"},{".jar",        "application/java-archive"},{".jpeg",       "image/jpeg"},{".jpg",        "image/jpeg"},{".js",         "text/javascript"},{".json",       "application/json"},{".jsonld",     "application/ld+json"},{".mid",        "audio/midi"},{".midi",       "audio/x-midi"},{".mjs",        "text/javascript"},{".mp3",        "audio/mpeg"},{".mpeg",       "video/mpeg"},{".mpkg",       "application/vnd.apple.installer+xml"},{".odp",        "application/vnd.oasis.opendocument.presentation"},{".ods",        "application/vnd.oasis.opendocument.spreadsheet"},{".odt",        "application/vnd.oasis.opendocument.text"},{".oga",        "audio/ogg"},{".ogv",        "video/ogg"},{".ogx",        "application/ogg"},{".otf",        "font/otf"},{".png",        "image/png"},{".pdf",        "application/pdf"},{".ppt",        "application/vnd.ms-powerpoint"},{".pptx",       "application/vnd.openxmlformats-officedocument.presentationml.presentation"},{".rar",        "application/x-rar-compressed"},{".rtf",        "application/rtf"},{".sh",         "application/x-sh"},{".svg",        "image/svg+xml"},{".swf",        "application/x-shockwave-flash"},{".tar",        "application/x-tar"},{".tif",        "image/tiff"},{".tiff",       "image/tiff"},{".ttf",        "font/ttf"},{".txt",        "text/plain"},{".vsd",        "application/vnd.visio"},{".wav",        "audio/wav"},{".weba",       "audio/webm"},{".webm",       "video/webm"},{".webp",       "image/webp"},{".woff",       "font/woff"},{".woff2",      "font/woff2"},{".xhtml",      "application/xhtml+xml"},{".xls",        "application/vnd.ms-excel"},{".xlsx",       "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},{".xml",        "application/xml"},{".xul",        "application/vnd.mozilla.xul+xml"},{".zip",        "application/zip"},{".3gp",        "video/3gpp"},{".3g2",        "video/3gpp2"},{".7z",         "application/x-7z-compressed"}};// a.b.txt 先获取文件扩展名size_t pos = filename.find_last_of('.'); // 从后往前找if (pos == std::string::npos){return "application/octet-stream"; // 二进制文件流}// 再根据扩展名选取mimestd::string ext = filename.substr(pos); // 先将这个pos位置截取到的文件名存起来auto it = _mime_msg.find(ext);if (it == _mime_msg.end()){return "application/octet-stream";}return it->second;}

(2)运行及运行结果

i、测试结果1

在这里插入图片描述

在这里插入图片描述

ii、测试结果2

在这里插入图片描述

在这里插入图片描述

8、IsCatalogue(判断一个文件是否是目录)&&IsRegular(判断一个文件是否是普通文件)

(1)代码及设计思想

在这里插入图片描述
在这里插入图片描述

        // 判断一个文件是否是目录static bool IsCatalogue(const std::string& filename){struct stat st;int ret = stat(filename, &st);if (ret < 0){return false;}return S_ISDIR(st.st_mode);}// 判断一个文件是否是普通文件static bool IsRegular(const std::string& filename){struct stat st;int ret = stat(filename, &st);if (ret < 0){return false;}return S_ISREG(st.st_mode);}

(2)测试及测试结果

在这里插入图片描述

在这里插入图片描述

9、VaildPath(HTTP资源路径的有效性的判断)

(1)代码及设计思路

       // HTTP资源路径的有效性的判断// /index.html  --- 前边的/叫做相对根目录  映射的是某个服务器上的子目录// 想表达的意思就是,客户端只能请求相对根目录中的资源,其他地方的资源都不予理会// /../login, 这个路径中的..会让路径的查找跑到相对根目录之外,这是不合理的,不安全的static bool VaildPath(){// 思想:按照/进行路径分割,根据有多少子目录,计算目录深度,有多少层,深度不能小于0std::vector<std::string> subdir;Split(path, "/", &subdir);int level = 0;for (auto &dir : subdir) {if (dir == "..") {level--;if (level < 0) {return false;}continue;}level++;}return true;}

(2)测试及测试结果

在这里插入图片描述

在这里插入图片描述

二、HttpRequest板块

1、设计思维导图

在这里插入图片描述

2、代码设计

这里我们先用手册了解一下我们的smatch和链接正文和链接:

smatch只能用swap来进行清空操作,其他字段均可用clear()来进行清空在这里插入图片描述

连接正文的格式为:格式为:Content-Length:1234\r\n

长连接和短链接判断:没有Connection字段,或者有Connection但是值是close,则都是短链接,否则就是长连接
在这里插入图片描述
php的Http使用手册网站

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 smatches;_matches.swap(smatches);_headers.clear();_params.clear();}// 插入头部字段void SetHeader(const std::string& key, const std::string& val) // key-val键值对{_headers.insert(std::make_pair(key, val));}// 判断是否存在指定头部文件bool IsHeader(const std::string& key){auto it = _headers.find(key);if (it == _headers.end()){return false;}return true;}// 获取指定头部字段的值std::string GetHeader(std::string& key){auto it = _headers.find(key);if (it == _headers.end()){return "";}return it->second;}// 插入查询字符串void SetParam(const std::string& key, const std::string& val) // key-val键值对{_params.insert(std::make_pair(key, val));}// 判断是否存在指定头部文件bool IsParam(const std::string& key){auto it = _params.find(key);if (it == _params.end()){return false;}return true;}// 获取指定头部字段的值std::string GetHeader(std::string& key){auto it = _params.find(key);if (it == _params.end()){return "";}return it->second;}// 获取正文长度size_t ContentLength(){// 格式为:Content-Length:1234\r\nbool ret = IsHeader("Content-Length");if (ret == false){return 0;}std::string cotent_length = GetHeader("Content-Length");return std::stol(cotent_length);}bool Close(){// 没有Connection字段,或者有Connection但是值是close,则都是短链接,否则就是长连接if (IsHeader("Connection") == true && GetHeader("Connection") == "keep-alive"){return false;}}
};

三、HttpResponse板块

1、设计思维导图

在这里插入图片描述

2、代码设计

class HttpResponse
{public:int _statu; // 状态bool _redirectflag; // 重定向标志(判断是否要进行重定向)std::string _body; // 正文部分std::string _redirecturl; // 重定向urlstd::unordered_map<std::string, std::string> _headers; // 头部public:HttpResponse(): _redirectflag(false), _statu(200){}HttpResponse(int statu): _redirectflag(false), _statu(statu){}// 重置void Reset(){_statu = 200;_redirectflag = false;_body.clear();_redirecturl.clear();_headers.clear();}// 设置头部字段void SetHeader(const std::string& key, const std::string& val){_headers.insert(std::make_pair(key, val));}// 判断是否存在指定的头部文件bool IsHeader(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 SetRedirect(const std::string &url, int statu = 302) {_statu = statu;_redirectflag = true;_redirecturl = url;}bool Clear(){// 没有Connection字段,或者有Connection但是值是close,则都是短链接,否则就是长连接if (IsHeader("Connection") == true && GetHeader("Connection") == "keep-alive"){return false;}return true;}
};

四、HttpContext板块

1、设计思维导图

在这里插入图片描述

2、代码部分

(1)接收命令行(RecvHttpLine)

        // 接收HTTP行bool RecvHttpLine(Buffer* buff){if (_recv_statu != RECV_HTTP_LINE) return false;// 1、获取一行带有末尾的数据std::string line = buff->GetLineAndPop();// 2、我们需要考虑到缓冲区中的数据不足一行,则需要判断缓冲区可读的数据的多少,假如说是数据多的但没有进行读取的话,那么就是错误了if (line.size() == 0){if (buff->ReadAbleSize() > MAX_LINE){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 414; // URL TOO LONGreturn false;}// 缓冲区数据不多不到一行,静静等待其他信息的到来return true;}if (line.size() > MAX_LINE){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 414; // URL TOO LONGreturn false;}bool ret = ParseHttpLine(line); // 解析命令行if (ret == false){return false;}//首行处理完毕,进入头部获取阶段_recv_statu = RECV_HTTP_HEAD;return true;}

(2)解析命令行(ParseHttpLine)

        // 解析命令行bool ParseHttpLine(const std::string& line){std::smatch matches;// 请求的方法有右边五种:GET|HEAD|POST|PUT|DELETE  std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?");bool ret = std::regex_match(str, matches, e);if (ret == false){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 400; // BAD REQUESTreturn false;}// 0:GET /jiangrenhai/login?usr=jrh&pass=123456 HTTP/1.1// 1:GET// 2:/jiangrenhai/login// 3:usr=jrh&pass=123456// 4:HTTP/1.1// 请求方法的获取_request._method = matches[1];std::transform(_request._method.begin(), _request._method.end(), _request._method.begin(), ::toupper);// 资源路径的获取,需要进行URL解码操作,但是不需要+转空格_request._path = Util::UrlDecode(matches[2], false);// 资源版本的获取_request._version = matches[4];// 查询字符串的获取与处理std::vector<std::string> query_string_array;std::string query_string = matches[3];// 查询字符串的格式,usr=jrh&pass=123456这个以&为间隔号Util::Splite(query_string, "&", &query_string_array);// 我们针对每个等于号进行分割,分割出不同的字串for (auto& str : query_string_array){size_t pos = str.find("=");if (pos == std::string::npos){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 400; // BAD REQUESTreturn false;}std::string key = Util::UrlDecode(str.substr(0, pos), true); // 需要+转空格std::string value = Util::UrlDecode(str.substr(pos + 1), true);_request.SetParam(key, value);}return true;}

(3)接收头部(RecvHttpHead)

        // 接收头部bool RecvHttpHead(Buffer* buff){if (_recv_statu != RECV_HTTP_HEAD) return false;while(1){// 1、获取一行带有末尾的数据std::string line = buff->GetLineAndPop();// 2、我们需要考虑到缓冲区中的数据不足一行,则需要判断缓冲区可读的数据的多少,假如说是数据多的但没有进行读取的话,那么就是错误了if (line.size() == 0){if (buff->ReadAbleSize() > MAX_LINE){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 414; // URL TOO LONGreturn false;}// 缓冲区数据不多不到一行,静静等待其他信息的到来return true;}if (line.size() > MAX_LINE){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 414; // URL TOO LONGreturn false;}if (line == "\n" || line == "\r\n"){break; // 都到首行末尾了,直接跳出循环了}bool ret = ParseHttpHead(line); // 解析命令行if (ret == false){return false;}}//首行处理完毕,进入头部获取阶段_recv_statu = RECV_HTTP_BODY;return true;}

(4)解析头部(ParseHttpHead)

        // 解析头部bool ParseHttpHead(std::string& line){//key: val\r\nkey: val\r\n....if (line.back() == '\n') line.pop_back(); // 末尾是换行则去掉换行字符if (line.back() == '\r') line.pop_back(); // 末尾是回车则去掉回车字符size_t pos = line.find(": ");if (pos == std::string::npos){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 400; // BAD REQUESTreturn false;}std::string key = line.substr(0, pos); // 需要+转空格std::string value = line.substr(pos + 1);_request.SetHeader(key, value);return true;}

(5)接收正文(RecvHttpBody)

        // 接收正文bool RecvHttpBody(Buffer* buff){if (_recv_statu != RECV_HTTP_BODY) return false;// 1.获取正文长度size_t content_length = _request.ContentLength();if (content_length == 0){_recv_statu = RECV_HTTP_OVER;return true;}// 2、当前已经接收了多少正文 其实就是_request._body中放了多少数据size_t real_length = content_length - _request._body.size();// 3.接收正文放到body中,但是也要考虑当前缓冲区中的数据,是否是全部的正文//   3.1 缓冲区中数据,包含了当前请求的所有正文,则取出所需的数据if (buff->ReadAbleSize() >= real_length){_request._body.append(buff->ReadPos(), real_length);buff->ReadOffset(real_length);_recv_statu = RECV_HTTP_OVER;return true;}//   3.2 缓冲区中数据,无法满足当前正文的需要,数据不足,取出数据,然后等待新数据到来_request._body.append(buff->ReadPos(), buff->ReadAbleSize());buff->ReadOffset(buff->ReadAbleSize());return true;}

(6)重置(Reset)+返回三个私有成员函数+接收并解析HTTP请求(RecvHttpRequest)

        // 重置void Reset(){_resp_statu = 200;_recv_statu = RECV_HTTP_LINE;_request.ReSet();}// 返回响应状态码int RespStatu() { return _resp_statu; }// 返回接收及解析的状态HttpRecvStatu RecvStatu() { return _recv_statu; }// 返回已经解析到的请求信息HttpRequest& Request() { return _request; }// 接收并解析http请求void RecvHttpRequest(Buffer* buff){// 这里不同break是因为需要都进行操作,从line往头部往正文方向都要进行操作switch (_recv_statu){case RECV_HTTP_LINE: RecvHttpLine(buff);case RECV_HTTP_HEAD: RecvHttpHead(buff);case RECV_HTTP_BODY: RecvHttpBody(buff);}return;}

(7)总体代码

typedef enum 
{RECV_HTTP_ERROR,RECV_HTTP_LINE,RECV_HTTP_HEAD,RECV_HTTP_BODY,RECV_HTTP_OVER
}HttpRecvStatu;
#define MAX_LINE 8192
class HttpContext
{private:int _resp_statu; // 响应状态码HttpRecvStatu _recv_statu; // 当前接收及解析的阶段状态HttpRequest _request; // 已经解析得到的请求信息private:// 解析命令行bool ParseHttpLine(const std::string& line){std::smatch matches;// 请求的方法有右边五种:GET|HEAD|POST|PUT|DELETE  std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?");bool ret = std::regex_match(line, matches, e);if (ret == false){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 400; // BAD REQUESTreturn false;}// 0:GET /jiangrenhai/login?usr=jrh&pass=123456 HTTP/1.1// 1:GET// 2:/jiangrenhai/login// 3:usr=jrh&pass=123456// 4:HTTP/1.1// 请求方法的获取_request._method = matches[1];std::transform(_request._method.begin(), _request._method.end(), _request._method.begin(), ::toupper);// 资源路径的获取,需要进行URL解码操作,但是不需要+转空格_request._path = Util::UrlDecode(matches[2], false);// 资源版本的获取_request._version = matches[4];// 查询字符串的获取与处理std::vector<std::string> query_string_array;std::string query_string = matches[3];// 查询字符串的格式,usr=jrh&pass=123456这个以&为间隔号Util::Splite(query_string, "&", &query_string_array);// 我们针对每个等于号进行分割,分割出不同的字串for (auto& str : query_string_array){size_t pos = str.find("=");if (pos == std::string::npos){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 400; // BAD REQUESTreturn false;}std::string key = Util::UrlDecode(str.substr(0, pos), true); // 需要+转空格std::string value = Util::UrlDecode(str.substr(pos + 1), true);_request.SetParam(key, value);}return true;}// 接收HTTP行bool RecvHttpLine(Buffer* buff){if (_recv_statu != RECV_HTTP_LINE) return false;// 1、获取一行带有末尾的数据std::string line = buff->GetLineAndPop();// 2、我们需要考虑到缓冲区中的数据不足一行,则需要判断缓冲区可读的数据的多少,假如说是数据多的但没有进行读取的话,那么就是错误了if (line.size() == 0){if (buff->ReadAbleSize() > MAX_LINE){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 414; // URL TOO LONGreturn false;}// 缓冲区数据不多不到一行,静静等待其他信息的到来return true;}if (line.size() > MAX_LINE){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 414; // URL TOO LONGreturn false;}bool ret = ParseHttpLine(line); // 解析命令行if (ret == false){return false;}//首行处理完毕,进入头部获取阶段_recv_statu = RECV_HTTP_HEAD;return true;}// 接收头部bool RecvHttpHead(Buffer* buff){if (_recv_statu != RECV_HTTP_HEAD) return false;while(1){// 1、获取一行带有末尾的数据std::string line = buff->GetLineAndPop();// 2、我们需要考虑到缓冲区中的数据不足一行,则需要判断缓冲区可读的数据的多少,假如说是数据多的但没有进行读取的话,那么就是错误了if (line.size() == 0){if (buff->ReadAbleSize() > MAX_LINE){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 414; // URL TOO LONGreturn false;}// 缓冲区数据不多不到一行,静静等待其他信息的到来return true;}if (line.size() > MAX_LINE){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 414; // URL TOO LONGreturn false;}if (line == "\n" || line == "\r\n"){break; // 都到首行末尾了,直接跳出循环了}bool ret = ParseHttpHead(line); // 解析命令行if (ret == false){return false;}}//首行处理完毕,进入头部获取阶段_recv_statu = RECV_HTTP_BODY;return true;}// 解析头部bool ParseHttpHead(std::string& line){//key: val\r\nkey: val\r\n....if (line.back() == '\n') line.pop_back(); // 末尾是换行则去掉换行字符if (line.back() == '\r') line.pop_back(); // 末尾是回车则去掉回车字符size_t pos = line.find(": ");if (pos == std::string::npos){_recv_statu = RECV_HTTP_ERROR;_resp_statu = 400; // BAD REQUESTreturn false;}std::string key = line.substr(0, pos); // 需要+转空格std::string value = line.substr(pos + 1);_request.SetHeader(key, value);return true;}// 接收正文bool RecvHttpBody(Buffer* buff){if (_recv_statu != RECV_HTTP_BODY) return false;// 1.获取正文长度size_t content_length = _request.ContentLength();if (content_length == 0){_recv_statu = RECV_HTTP_OVER;return true;}// 2、当前已经接收了多少正文 其实就是_request._body中放了多少数据size_t real_length = content_length - _request._body.size();// 3.接收正文放到body中,但是也要考虑当前缓冲区中的数据,是否是全部的正文//   3.1 缓冲区中数据,包含了当前请求的所有正文,则取出所需的数据if (buff->ReadAbleSize() >= real_length){_request._body.append(buff->ReadPos(), real_length);buff->ReadOffset(real_length);_recv_statu = RECV_HTTP_OVER;return true;}//   3.2 缓冲区中数据,无法满足当前正文的需要,数据不足,取出数据,然后等待新数据到来_request._body.append(buff->ReadPos(), buff->ReadAbleSize());buff->ReadOffset(buff->ReadAbleSize());return true;}public:// 构造函数HttpContext(): _resp_statu(200), _recv_statu(RECV_HTTP_LINE){}// 重置void Reset(){_resp_statu = 200;_recv_statu = RECV_HTTP_LINE;_request.ReSet();}// 返回响应状态码int RespStatu() { return _resp_statu; }// 返回接收及解析的状态HttpRecvStatu RecvStatu() { return _recv_statu; }// 返回已经解析到的请求信息HttpRequest& Request() { return _request; }// 接收并解析http请求void RecvHttpRequest(Buffer* buff){// 这里不同break是因为需要都进行操作,从line往头部往正文方向都要进行操作switch (_recv_statu){case RECV_HTTP_LINE: RecvHttpLine(buff);case RECV_HTTP_HEAD: RecvHttpHead(buff);case RECV_HTTP_BODY: RecvHttpBody(buff);}return;}
};

五、HttpServer板块

1、设计思维导图

在这里插入图片描述
在这里插入图片描述

2、代码部分

具体细节直接上代码。

class HttpServer
{private:using Handler = std::function<void(const HttpRequest&, HttpResponse*)>;using Handlers = std::vector<std::pair<std::regex, Handler>>;Handlers _get_route; // getHandlers _post_route; // postHandlers _put_route; // putHandlers _delete_route; // deletestd::string _basedir; // 静态资源根目录--/home/jrh/wwwrootTcpServer _server;private:// 私有成员函数// 错误响应void ErrorHandler(const HttpRequest &req, HttpResponse *rsp){// 1. 组织一个错误展示页面std::string body;body += "<html>";body += "<head>";body += "<meta http-equiv='Content-Type' content='text/html;charset=utf-8'>"; // 百度界面上的body += "</head>";body += "<body>";body += "<h1>";body += std::to_string(rsp->_statu);body += " ";body += Util::HttpDes(rsp->_statu);body += "</h1>";body += "</body>";body += "</html>";// 2. 将页面数据,当作响应正文,放入rsp中rsp->SetContent(body, "text/html");}// 将HttpResponse中的要素按照http协议格式进行组织并发送void WriteResponse(const PtrConnection &conn, const HttpRequest &req, HttpResponse &rsp){// 1. 先完善头部字段if (req.Close() == true) {rsp.SetHeader("Connection", "close");}else {rsp.SetHeader("Connection", "keep-alive");}if (rsp._body.empty() == false && rsp.IsHeader("Content-Length") == false) {rsp.SetHeader("Content-Length", std::to_string(rsp._body.size()));}if (rsp._body.empty() == false && rsp.IsHeader("Content-Type") == false) {rsp.SetHeader("Content-Type", "application/octet-stream");}if (rsp._redirectflag == true) // 重定向{rsp.SetHeader("Location", rsp._redirecturl);}// 2. 将rsp中的要素,按照http协议格式进行组织std::stringstream rsp_str;// 协议版本+状态码+状态码描述+\r\nrsp_str << req._version << " " << std::to_string(rsp._statu) << " " << Util::HttpDes(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;// 3. 发送数据conn->Send(rsp_str.str().c_str(), rsp_str.str().size());}// 是否设置静态资源的请求处理bool IsFileHandler(const HttpRequest &req){// 1. 必须设置了静态资源根目录if (_basedir.empty()) {return false;}// 2. 请求方法,必须是GET / HEAD请求方法if (req._method != "GET" && req._method != "HEAD") {return false;}// 3. 请求的资源路径必须是一个合法路径if (Util::VaildPath(req._path) == false) {return false;}// 4. 请求的资源必须存在,且是一个普通文件//    有一种请求比较特殊 -- 目录:/, /image/, 这种情况给后边默认追加一个 index.html// index.html    /image/a.png// 前缀的相对根目录,也就是将请求路径转换为实际存在的路径  /image/a.png  ->   ./wwwroot/image/a.pngstd::string req_path = _basedir + req._path;//为了避免直接修改请求的资源路径,因此定义一个临时对象if (req._path.back() == '/')  {req_path += "index.html";}if (Util::IsRegular(req_path) == false) {return false;}return true;}// 静态资源的请求处理void FileHandler(const HttpRequest &req, HttpResponse *rsp){std::string req_path = _basedir + req._path;if (req._path.back() == '/')  {req_path += "index.html";}bool ret = Util::ReadFile(req_path, &rsp->_body);if (ret == false) {return;}std::string mime = Util::ExtMime(req_path);rsp->SetHeader("Content-Type", mime);return;}// 功能性请求的分类处理void Dispatcher(HttpRequest &req, HttpResponse *rsp, Handlers &handlers){// 在对应请求方法的路由表中,查找是否含有对应资源请求的处理函数,有则调用,没有则返回404// 思想:路由表存储的时键值对 -- 正则表达式 & 处理函数// 使用正则表达式,对请求的资源路径进行正则匹配,匹配成功就使用对应函数进行处理//  /numbers/(\d+)       /numbers/12345for (auto &handler : handlers) {const std::regex &re = handler.first;const Handler &functor = handler.second;bool ret = std::regex_match(req._path, req._matches, re);if (ret == false) {continue;}return functor(req, rsp);//传入请求信息,和空的rsp,执行处理函数}rsp->_statu = 404;}// 路线void Route(HttpRequest &req, HttpResponse *rsp){// 对请求进行分辨,是一个静态资源请求,还是一个功能性请求//   静态资源请求,则进行静态资源的处理//   功能性请求,则需要通过几个请求路由表来确定是否有处理函数//   既不是静态资源请求,也没有设置对应的功能性请求处理函数,就返回405if (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);}rsp->_statu = 405; // Method Not Allowedreturn;}// 设置上下文void OnConnected(const PtrConnection &conn){conn->SetContext(HttpContext());DEBLOG("NEW CONNECTION %p", conn.get());}// 缓冲区数据解析和处理void OnMessage(const PtrConnection &conn, Buffer *buffer){while (buffer->ReadAbleSize() > 0){// 1. 获取上下文HttpContext *context = conn->GetContext()->get<HttpContext>();// 2、通过上下文对缓冲区数据进行解析,得到HttpRequest对象context->RecvHttpRequest(buffer);HttpRequest &req = context->Request(); // 返回响应HttpResponse rsp(context->RespStatu()); // 返回响应码//  (1) 如果缓冲区的数据解析出错,就直接回复出错响应//  (2) 如果解析正常,且请求已经获取完毕,才开始去进行处理if (context->RespStatu() >= 400){// 出错啦,进行错误响应,关闭连接ErrorHandler(req, &rsp); // 填充一个错误显示页面数据到rsp中WriteResponse(conn, req, rsp); // 组织响应发送给客户端context->Reset(); // 重置buffer->ReadOffset(buffer->ReadAbleSize()); // 出错了就把缓冲区数据清空conn->Shutdown(); // 关闭连接return; // 直接返回}// 3. 请求路由 + 业务处理Route(req, &rsp);// 4. 对HttpResponse进行组织发送WriteResponse(conn, req, rsp);// 5. 重置上下文context->Reset();// 6. 根据长短连接判断是否关闭连接或者继续处理if (rsp.Clear() == true) conn->Shutdown(); // 短链接则直接关闭}}public:// 构造函数HttpServer(int port, int timeout = DEFALT_TIMEOUT): _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));}// 设置静态资源根目录void SetBaseDir(const std::string& path){assert(Util::IsCatalogue(path) == true);_basedir = path;}// Get方法void Get(const std::string& pattern, Handler& handler){_get_route.push_back(std::make_pair(std::regex(pattern), handler));}// Post方法void Post(const std::string& pattern, Handler& handler){_post_route.push_back(std::make_pair(std::regex(pattern), handler));}// Put方法void Put(const std::string& pattern, Handler& handler){_put_route.push_back(std::make_pair(std::regex(pattern), handler));}// Delete方法void Delete(const std::string& pattern, Handler& handler){_delete_route.push_back(std::make_pair(std::regex(pattern), handler));}// 设置线程长度void SetThreadCount(int count){_server.SetThreadCount(count);}// 监听void Listen(){_server.Start();}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/4106.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于discuz论坛网址优化的一些记录(伪静态)

最近网站刚上线&#xff0c;针对SEO做了些操作&#xff0c;为了方便网站网页被收录&#xff0c;特此记录下 1.开启伪静态 按照操作勾选所有项&#xff0c;然后点击查看伪静态规则 2.打开宝塔&#xff0c;找到左侧列表的网站&#xff0c;然后找到相应站点的设置。把discuz自动…

STM32的端口引脚的复用功能及重映射功能解析

目录 STM32的端口引脚的复用功能及重映射功能解析 复用功能 复用功能的初始化 重映射功能 重映射功能的初始化 复用功能和重映射的区别 部分重映射与完全重映射 补充 STM32的端口引脚的复用功能及重映射功能解析 复用功能 首先、我们可以这样去理解stm32引脚的复用功能…

SD-WAN怎样助力企业网络升级

随着企业规模的持续扩张&#xff0c;其网络建设的重要性日益凸显&#xff0c;成为业务成功的基石。尤其对于中小企业而言&#xff0c;信息化和电脑化已成为推动生产力和竞争力提升的关键所在。办公室自动化、数据库、ERP、CRM、物流供应链等关键业务应用的不断增加&#xff0c;…

css 文字左右抖动效果

<template><div class"box"><div class"shake shape">抖动特效交字11</div></div> </template><script setup></script><style scope> .shape {margin: 50px;width: 200px;height: 50px;line-heigh…

计算机存储原理.2

1.主存储器与CPU之间的连接 2.存储器芯片的输入输出信号 3.增加主存的存储字长 3.1位扩展 数据总线的利用成分是不充分的(单块只能读写一位)&#xff0c;为了解决这个问题所以引出了位扩展。 使用多块存储芯片解决这个问题。 3.2字扩展 因为存储器买的是8k*8位的&am…

Linear Secret-Sharing Scheme(LSSS) Monotone Span Program(MSP)

参考文献&#xff1a; [KW93] Karchmer M, Wigderson A. On span programs[C]//[1993] Proceedings of the Eigth Annual Structure in Complexity Theory Conference. IEEE, 1993: 102-111.[CDM00] Cramer R, Damgrd I, Maurer U. General secure multi-party computation fr…

【探索Java编程:从入门到入狱】Day2

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

js[黑马笔记]

js基础 基础语法 输入输出 变量 数组 常量 数据类型 类型转换 运算符 语句 数组 函数 调用方式 函数名() 匿名函数 使用: 1.函数表达式 2.立即执行函数 对象 内置对象 web API DOM document object Model元素操作 获取元素 设置元素 定时器 DOM事件基础 事件监听 事件类…

流量网关与服务网关的区别:(面试题,掌握)

流量网关&#xff1a;&#xff08;如Nignx&#xff0c;OpenResty&#xff0c;Kong&#xff09;是指提供全局性的、与后端业务应用无关的策略&#xff0c;例如 HTTPS证书认证、Web防火墙、全局流量监控&#xff0c;黑白名单等。 服务网关&#xff1a;&#xff08;如Spring Clou…

含匹配扰动的多智能体领航跟随一致性Matlab仿真

文章目录 [TOC](文章目录) 前言一、问题描述二、基于LQR的观测器和控制器设计1.观测器设计2.控制器设计 三、数值仿真四、参考文献总结 前言 ​本文探讨了带有匹配扰动的多智能体领航跟随一致性控制方法&#xff0c;并提供了相应的Matlab仿真代码。 具体的设计步骤如下&#…

大数据—数据采集DataX

一、DataX介绍 官网&#xff1a; DataX/introduction.md at master alibaba/DataX GitHub DataX 是阿里云 DataWorks数据集成 的开源版本&#xff0c;在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。 DataX 实现了包括 MySQL、Oracle、OceanBase、SqlServer、Postgre、…

【算法刷题 | 贪心算法02】4.24(摆动序列)

文章目录 3.摆动序列3.1题目3.2解法&#xff1a;贪心3.2.1贪心思路3.2.2代码实现 3.摆动序列 3.1题目 如果连续数字之间的差严格地在正数和负数之间交替&#xff0c;则数字序列称为 摆动序列 。 第一个差&#xff08;如果存在的话&#xff09;可能是正数或负数。仅有一个元素…

WPF 资源基础

动态资源/静态资源 UI代码 <Window x:Class"WpfApp1.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/ex…

太速科技-基于6 U VPX M.2 高带宽加固存储板

基于6 U VPX M.2 高带宽加固存储板 一、板卡概述 基于6 U VPX M.2 高带宽加固存储板&#xff0c;可以实现VPX接口的数据读写到PCI-E总线的NVME存储媒介上。采用PLX8732&#xff0c;上行链路提供带宽x16的PCI-E数据到VPX接口上&#xff1b;下行链路提供3路带宽x4的PCI-E接口…

Unity打包PC端exe,压缩打包为一个exe文件

目录 一.打包成功 1.打包输出文件 二.压缩输出目录为exe单个文件 1.添加到压缩文件 2.其他设置 1.点击“高级→自压缩选项” 2.修改解压后运行程序 3.设置模式 4.更新 三、生成.exe 一.打包成功 1.打包输出文件 1、一个后缀为 BurstDebugInformation_DoNotShip的文…

Android 12 Starting window的添加与移除

添加&#xff1a; 04-13 16:29:55.931 2944 7259 D jinyanmeistart: at com.android.server.wm.StartingSurfaceController.createSplashScreenStartingSurface(StartingSurfaceController.java:87) 04-13 16:29:55.931 2944 7259 D jinyanmeistart: at com.android.server.wm.…

ios不兼容Svg Wave的动画的解决方法

近日也是用上了SvgWave&#xff0c;十分的好看 Svg Wave - A free & beautiful gradient SVG wave Generator. 大家感兴趣的也可以了解一下 【场景】 使用SvgWave的Animate&#xff0c;并生成svg代码使用&#xff0c;windows web端、朋友的安卓移动端都能够正常执行动画…

前端CSS基础10(浮动)

前端CSS基础10&#xff08;浮动&#xff09; 浮动元素浮动后的特点浮动后的特点浮动后的影响及解决 浮动布局小练习 浮动 CSS中的浮动是一种布局技术&#xff0c;常用于实现元素的排列和定位。通过使用float属性&#xff0c;可以让元素在页面中左浮动或右浮动&#xff0c;使得…

在PostgreSQL中如何有效地批量导入大量数据,并确保数据加载过程中的性能和稳定性?

文章目录 解决方案1. 使用COPY命令2. 调整配置参数3. 禁用索引和约束4. 使用事务5. 并发导入 总结 在PostgreSQL中&#xff0c;批量导入大量数据是一个常见的需求&#xff0c;特别是在数据迁移、数据仓库填充或大数据分析等场景中。为了确保数据加载过程中的性能和稳定性&#…

Compose和Android View相互使用

文章目录 Compose和Android View相互使用在Compose中使用View概述简单控件复杂控件嵌入XML布局 在View中使用Compose概述在Activity中使用Compose在Fragment中使用Compose布局使用多个ComposeView 在布局中使用Compose 组合使用 Compose和Android View相互使用 在Compose中使用…