【Linux网络编程】第十二弹---构建与优化HTTP请求处理:从HttpRequest到HttpServer的实战
✨个人主页: 熬夜学编程的小林
💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】【Linux网络编程】
目录
1、HttpRequest类
1.1、基本结构
1.2、构造析构函数
1.3、反序列化函数
1.4、GetLine()
1.5、打印函数
2、HttpServer类
2.1、HandlerHttpRequest()
3、测试
3.1、测试结果(基本版本)
3.2、代码优化一
3.3、测试结果(优化一)
3.4、代码优化二
3.5、测试结果(优化二)
4、增加路径字段
4.1、HttpRequest类
4.2、解析请求行
4.3、获取url 和 path
4.4、HandlerHttpRequest()
1、HttpRequest类
1.1、基本结构
HttpRequest类基本结构包括请求行,请求报头,正文!
class HttpRequest{private: // \\r\\n // \\r\\ndata // 获取一行信息 std::string GetLine(std::string &reqstr); // 解析请求行 void ParseReqLine(); // 解析请求报头,以冒号加空格分隔 void ParseReqHeader();public: HttpRequest(); // 反序列化 void Deserialize(std::string &reqstr) ; // 打印请求信息 void Print(); ~HttpRequest();private: // 基本的httprequest的格式 std::string _req_line; // 请求行 std::vector _req_headers; // 请求报头 std::string _blank_line; // 空行 std::string _body_text; // 正文};
1.2、构造析构函数
构造函数初始化空行即可,因为空行是固定的,析构函数无需处理!
const static std::string base_sep = \"\\r\\n\";HttpRequest() : _blank_line(base_sep){}~HttpRequest(){}
1.3、反序列化函数
反序列化即将字符串转化成结构化字段!
// 反序列化void Deserialize(std::string &reqstr) { // 基本的反序列化 _req_line = GetLine(reqstr); // 读取一行,请求行 // 请求报头 std::string header; do { header = GetLine(reqstr); if (header.empty()) break; else if (header == base_sep) break; _req_headers.push_back(header); } while (true); // 正文 if (!reqstr.empty()) { _body_text = reqstr; }}
1.4、GetLine()
获取一行有效信息,没找到分隔符返回空串,找到分隔符但是没有有效信息则返回分隔符!
// \\r\\n// \\r\\ndata// 获取一行信息std::string GetLine(std::string &reqstr){ auto pos = reqstr.find(base_sep); if (pos == std::string::npos) // 没找到分隔符返回空 return std::string(); std::string line = reqstr.substr(0, pos); // 截取一行有效信息 reqstr.erase(0, line.size() + base_sep.size()); // 删除有效信息和分隔符 return line.empty() ? base_sep : line; // 有效信息为空则返回分隔符,不为空返回有效信息}
1.5、打印函数
打印反序列化出来的字符串!
// 打印请求信息void Print(){ std::cout << \"---------------------------\" << std::endl; std::cout << \"###\" << _req_line << std::endl; for (auto &header : _req_headers) { std::cout << \"@@@\" << header << std::endl; } std::cout << \"***\" << _blank_line; std::cout <>>\" << _body_text << std::endl;}
2、HttpServer类
2.1、HandlerHttpRequest()
std::string HandlerHttpRequest(std::string &reqstr){#ifdef TEST std::cout << \"---------------------------------------------\" << std::endl; std::cout << reqstr; // return std::string(); std::string responsestr = \"HTTP/1.1 200 OK\\r\\n\"; responsestr += \"Content-Type: text/html\\r\\n\"; responsestr += \"\\r\\n\"; responsestr += \"hello linux,hello net!\"; return responsestr;#else HttpRequest req; // 构建请求对象 req.Deserialize(reqstr); // 反序列化字符串 req.Print(); // 打印反序列的字符串 return std::string(); // 保证编译通过#endif}
3、测试
主函数
// ./httpserver 8888int main(int argc, char *argv[]){ if (argc != 2) { std::cerr << \"Usage: \" << argv[0] << \"local-port\" << std::endl; exit(0); } uint16_t port = std::stoi(argv[1]); HttpServer hserver; std::unique_ptr tsvr = std::make_unique( std::bind(&HttpServer::HandlerHttpRequest, &hserver, std::placeholders::_1), port); tsvr->Loop(); return 0;}
3.1、测试结果(基本版本)
注意:执行的方法都是HandlerHttpRequest(),且代码执行的是#else后的代码!
运行结果
上面是基本的序列化,我们还可以进一步序列化,将请求行的成员都反序列化,因此请求类需要增加成员变量!
3.2、代码优化一
HttpRequest类成员变量
HttpRequest类需要增加请求方法,统一资源定位符,版本三个成员变量!
class HttpRequest{private: // 基本的httprequest的格式 std::string _req_line; // 请求行 std::vector _req_headers; // 请求报头 std::string _blank_line; // 空行 std::string _body_text; // 正文 // 更具体的属性字段,需要进一步反序列化 std::string _method; // 请求方法 std::string _url; // 统一资源定位符 std::string _version; // 版本};
反序列化函数
反序列化函数需要增加解析请求行函数!
// 反序列化void Deserialize(std::string &reqstr){ // 基本的反序列化 _req_line = GetLine(reqstr); // 读取一行,请求行 // 请求报头 std::string header; do { header = GetLine(reqstr); if (header.empty()) break; else if (header == base_sep) break; _req_headers.push_back(header); } while (true); // 正文 if (!reqstr.empty()) { _body_text = reqstr; } // 再进一步反序列化 ParseReqLine(); // 解析请求行}
解析请求行函数
请求行的成员之间使用空格分隔,因此可以使用字符串流对象,直接进行流提取!
// 解析请求行void ParseReqLine(){ std::stringstream ss(_req_line); // 以空格为分隔符 cin >> ss >> _method >> _url >> _version; // 以空格为分隔符依次将请求行的内容赋值给成员变量}
打印请求信息
// 打印请求信息void Print(){ std::cout << \"---------------------------\" << std::endl; std::cout << \"###\" << _req_line << std::endl; for (auto &header : _req_headers) { std::cout << \"@@@\" << header << std::endl; } std::cout << \"***\" << _blank_line; std::cout <>>\" << _body_text << std::endl; std::cout << \"Method: \" << _method << std::endl; std::cout << \"Url: \" << _url << std::endl; std::cout << \"Version: \" << _version << std::endl;}
3.3、测试结果(优化一)
浏览器输入 42.193.244.117:8888
注意:IP是自己服务器的公网IP,并且需要启动可执行程序!
运行结果
浏览器输入 42.193.244.117:8888/a/b/c/d/e/f/html
运行结果
上面将请求行的成员都反序列化,我们还可以进一步序列化,将请求报头进行反序列化,照样需要增加成员变量,此处我们使用哈希表存储,因为每行报文都是以冒号+空格分隔的值!
3.4、代码优化二
HttpRequest类成员变量
HttpRequest类增加一个KV形式的哈希表即可!
class HttpRequest{private: // 基本的httprequest的格式 std::string _req_line; // 请求行 std::vector _req_headers; // 请求报头 std::string _blank_line; // 空行 std::string _body_text; // 正文 // 更具体的属性字段,需要进一步反序列化 std::string _method; // 请求方法 std::string _url; // 统一资源定位符 std::string _version; // 版本 std::unordered_map _headers_kv; // 存储每行报文的哈希表};
反序列化函数
该反序列函数需要加前面的基础上继续解析请求报头!
// 反序列化void Deserialize(std::string &reqstr){ // 基本的反序列化 _req_line = GetLine(reqstr); // 读取一行,请求行 // 请求报头 std::string header; do { header = GetLine(reqstr); if (header.empty()) break; else if (header == base_sep) break; _req_headers.push_back(header); } while (true); // 正文 if (!reqstr.empty()) { _body_text = reqstr; } // 再进一步反序列化 ParseReqLine(); // 解析请求行 ParseReqHeader(); // 解析请求报头}
解析请求报头函数
遍历请求报头成员变量,以行分隔符查找有效信息,找到且分隔符前的有效信息不为空且分隔符后的有效信息不为空,则将KV值插入到哈希表中!
const static std::string line_sep = \": \"; // 行分隔符// 解析请求报头,以冒号加空格分隔void ParseReqHeader(){ for (auto &header : _req_headers) { auto pos = header.find(line_sep); if (pos == std::string::npos) continue; std::string k = header.substr(0, pos); // 截取key值 std::string v = header.substr(pos + line_sep.size()); // 截取value值 if (k.empty() || v.empty()) continue; _headers_kv.insert(std::make_pair(k, v)); // 将对应的kv值插入到哈希表中 }}
打印请求信息函数
// 打印请求信息void Print(){ std::cout << \"---------------------------\" << std::endl; std::cout << \"###\" << _req_line << std::endl; for (auto &header : _req_headers) { std::cout << \"@@@\" << header << std::endl; } std::cout << \"***\" << _blank_line; std::cout <>>\" << _body_text << std::endl; std::cout << \"Method: \" << _method << std::endl; std::cout << \"Url: \" << _url << std::endl; std::cout << \"Version: \" << _version << std::endl; for (auto head_kv : _headers_kv) { std::cout << \")))\" << head_kv.first <\" << head_kv.second << std::endl; }}
3.5、测试结果(优化二)
注意:执行的方法都是HandlerHttpRequest(),且代码执行的是#else后的代码!
运行结果
4、增加路径字段
我们向服务器请求的时候,需要知道资源的路径,因此我们可以增加路径字段(根目录为wwwroot/),当url为 / 时,默认访问wwwroot/index.html!
4.1、HttpRequest类
HttpRequest类增加路径成员,并将路径初始化为web根目录!
const static std::string prefixpath = \"wwwroot\"; // web根目录class HttpRequest{public: HttpRequest() : _blank_line(base_sep), _path(prefixpath) {}private: // 基本的httprequest的格式 std::string _req_line; // 请求行 std::vector _req_headers; // 请求报头 std::string _blank_line; // 空行 std::string _body_text; // 正文 // 更具体的属性字段,需要进一步反序列化 std::string _method; // 请求方法 std::string _url; // 统一资源定位符 std::string _path; // 资源路径 std::string _version; // 版本 std::unordered_map _headers_kv; // 存储每行报文的哈希表};
4.2、解析请求行
解析请求行除了解析url之外还需要解析path,path 默认直接使用path + url即可,但是当url 为 / 时,path 还需要加上默认访问文件 index.html !
// 解析请求行void ParseReqLine(){ std::stringstream ss(_req_line); // 以空格为分隔符 cin >> ss >> _method >> _url >> _version; // 以空格为分隔符依次将请求行的内容赋值给成员变量 _path += _url; // 只有web根目录返回index.html if (_path[_path.size() - 1] == \'/\') { _path += homepage; }}
4.3、获取url 和 path
获取url 和 path 直接返回成员变量即可!
std::string Url(){ LOG(DEBUG, \"Client Want url %s\\n\", _url.c_str()); return _url;}std::string Path(){ LOG(DEBUG, \"Client Want path %s\\n\", _path.c_str()); return _path;}
4.4、HandlerHttpRequest()
std::string HandlerHttpRequest(std::string &reqstr){ HttpRequest req; // 构建请求对象 req.Deserialize(reqstr); // 反序列化字符串 std::string url = req.Url(); std::string path = req.Path(); return std::string(); // 保证编译通过}
运行结果