> 技术文档 > 【Linux网络编程】第十二弹---构建与优化HTTP请求处理:从HttpRequest到HttpServer的实战

【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(); // 保证编译通过}

运行结果