> 技术文档 > 【Linux网络编程】第十三弹---构建HTTP响应与请求处理系统:从HttpResponse到HttpServer的实战

【Linux网络编程】第十三弹---构建HTTP响应与请求处理系统:从HttpResponse到HttpServer的实战

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】【Linux网络编程】

目录

1、HttpResponse类

1.1、基本结构

1.2、构造析构函数

1.3、添加属性成员函数

1.4、序列化函数

2、HandlerHttpRequest()

2.1、GetFileContent()

3、index.html

3.1、版本一

3.2、版本二

3.3、版本三

4、Content-Type

4.1、HttpRequest类

4.1.1、基本结构 

4.1.2、ParseReqLine()

4.1.3、Suffix()

4.2、HttpServer类

4.2.1、基本结构

4.2.2、构造函数

4.2.3、HandlerHttpRequest()


1、HttpResponse类

1.1、基本结构

HttpResponse类成员变量包括基本格式状态行,应答报头,空行和响应正文;还包括基本属性版本,状态码,状态码描述,以及报头的KV结构成员函数包括增加状态码,报头和正文

class HttpResponse{public: HttpResponse(); // 添加状态码 void AddCode(int code); void AddHeader(const std::string &k, const std::string &v); void AddBodyText(const std::string &body_text); std::string Serialize(); ~HttpResponse();private: // httpresponse base 属性 std::string _version; // 版本 int _status_code; // 状态码 std::string _desc; // 状态码描述 std::unordered_map _headers_kv; // 基本的httpresponse的格式 std::string _status_line;  // 状态行 std::vector _resp_headers; // 应答报头 std::string _blank_line; // 空行 std::string _resp_body_text; // 响应正文};

1.2、构造析构函数

构造函数初始化版本和空行,默认可以是固定的!

const static std::string httpversion = \"HTTP/1.0\";const static std::string spacesep = \" \";HttpResponse() : _version(httpversion), _blank_line(base_sep){}~HttpResponse(){}

1.3、添加属性成员函数

添加状态码默认初始化状态码并将状态码描述设置为OK,添加报头将传入的KV数据插入到报头的KV结构中,添加正文初始化正文内容即可!

// 添加状态码void AddCode(int code){ _status_code = code; _desc = \"OK\";}void AddHeader(const std::string &k, const std::string &v){ _headers_kv[k] = v;}void AddBodyText(const std::string &body_text){ _resp_body_text = body_text;}

1.4、序列化函数

序列化即将结构化数据转化为字符串数据,主要有下面四个步骤:

1、构建状态行

2、构建报头

3、空行和正文(无需处理,空行已初始化,正文内容在KV结构中)

4、正式序列化

std::string Serialize() { // 1.构建状态行 _status_line = _version + spacesep + std::to_string(_status_code) + spacesep + _desc + base_sep; // 2.构建报头 for (auto &header : _headers_kv) { std::string header_line = header.first + line_sep + header.second + base_sep; _resp_headers.push_back(header_line); } // 3.空行和正文 // 4.正式序列化 std::string responsestr = _status_line; for (auto &line : _resp_headers) { responsestr += line; } responsestr += _blank_line; responsestr += _resp_body_text; return responsestr;}

2、HandlerHttpRequest()

HandlerHttpRequest() 函数构建请求对象并反序列化,然后获取请求路径下的内容,再创建应答类,添加状态码,报头和正文,最后将序列化的消息传给客户端

std::string HandlerHttpRequest(std::string &reqstr){ HttpRequest req; // 构建请求对象 req.Deserialize(reqstr); // 反序列化字符串 // 最基础的上层处理 std::string content = GetFileContent(req.Path()); if (content.empty()) return std::string(); // TODO HttpResponse resp; resp.AddCode(200); resp.AddHeader(\"Content-Length\", std::to_string(content.size())); resp.AddBodyText(content); return resp.Serialize();}

2.1、GetFileContent()

根据传入的文件名,读取文件内容,并返回!

// 必须以二进制读取文件std::string GetFileContent(const std::string path){ std::ifstream in(path, std::ios::binary); // 以二进制方式打开文件 if (!in.is_open()) return std::string(); in.seekg(0, in.end); // 将文件读取指针(也称为“get”指针)移动到文件的末尾 int filesize = in.tellg(); // 获取当前文件读取指针的位置,即文件的总大小,单位字节 in.seekg(0, in.beg); // 将文件读取指针重新定位到文件的开始位置 std::string content; content.resize(filesize);  // 调整 content 的大小为filesize in.read((char *)content.c_str(), filesize); // 读取filesize字节的文件内容到 content 中 in.close(); return content;}

3、index.html

浏览器直接使用 IP + 端口访问时默认访问的文件,该文件随意添加一些内容,能到服务器发送给客户端的效果即可!

注意:该文件内容是前端语言,可以在下面的链接查看教程: 

3.1、版本一

版本一只有标题和主体内容!

w3cschool

 Linux  
内容就在这里

运行结果 

telnet 命令

运行结果 

3.2、版本二

版本二我们可以增加图像!

 Linux  
内容就在这里
【Linux网络编程】第十三弹---构建HTTP响应与请求处理系统:从HttpResponse到HttpServer的实战

获得一个完整的网页,浏览器会先得到html,根据html的标签,检测出我们还要获取其他资源,浏览器会继续发起http请求!

运行结果  

3.3、版本三

版本三我们可以增加链接,进行跳转页面,默认页面可以跳转到登录页面,登录页面可以跳转到内容页面,内容页面可以跳转到注册页面,注册页面跳转回默认页面!

index.html

 Linux  
内容就在这里
点击测试: 登录页面
【Linux网络编程】第十三弹---构建HTTP响应与请求处理系统:从HttpResponse到HttpServer的实战 <!-- 【Linux网络编程】第十三弹---构建HTTP响应与请求处理系统:从HttpResponse到HttpServer的实战 -->

login.html

   登录页面 

登录页面

进入内容页面

content.html

   内容页面 

内容页面

进入注册页面

register.html

   注册页面 

注册页面

回到首页

运行结果  

4、Content-Type

报头内部还有Content-Type,内容的类型,可以将类型也打印出来,使用哈希表存储

4.1、HttpRequest类

HttpRequest类需要增加一个文件后缀成员变量,并在解析行函数处理后缀内容

4.1.1、基本结构 

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 _path;  // 资源路径 std::string _suffix; // 资源后缀 std::string _version;  // 版本 std::unordered_map _headers_kv; // 存储每行报文的哈希表};

4.1.2、ParseReqLine()

ParseReqLine() 增加处理后缀操作,后缀以 . 结尾,从尾部开始查找 . ,找到则截取内容,没找到将后缀设置为 .default!

const static std::string suffixsep = \".\";// 解析请求行void ParseReqLine(){ std::stringstream ss(_req_line); // 以空格为分隔符 cin >> ss >> _method >> _url >> _version; // 以空格为分隔符依次将请求行的内容赋值给成员变量 _path += _url; // 只有web根目录返回index.html if (_path[_path.size() - 1] == \'/\') { _path += homepage; } // wwwroot/index.html // wwwroot/image/1.png auto pos = _path.rfind(suffixsep); if (pos != std::string::npos) { _suffix = _path.substr(pos); } else { _suffix = \".default\"; }}

4.1.3、Suffix()

std::string Suffix(){ return _suffix;}

4.2、HttpServer类

HttpServer类 需要增加存储类型对应的哈希表,并在构造函数插入对应的类型

4.2.1、基本结构

HttpServer类基本结构增加存储类型对应的哈希表!

class HttpServer{private: std::unordered_map _mine_type; // 类型对应表};

4.2.2、构造函数

构造函数插入对应的类型,可以直接在网上查找对应的类型!

HttpServer(){ _mine_type.insert(std::make_pair(\".html\", \"text/html\")); _mine_type.insert(std::make_pair(\".jpg\", \"image/jpg\")); _mine_type.insert(std::make_pair(\".png\", \"image/png\")); _mine_type.insert(std::make_pair(\".default\", \"text/html\"));}

4.2.3、HandlerHttpRequest()

HandlerHttpRequest() 增加添加类型的报头函数!

std::string HandlerHttpRequest(std::string &reqstr){ std::cout << \"---------------------------------------------\" << std::endl; std::cout << reqstr; std::cout << \"---------------------------------------------\" << std::endl; HttpRequest req; // 构建请求对象 req.Deserialize(reqstr); // 反序列化字符串 // 最基础的上层处理 HttpResponse resp; std::string content = GetFileContent(req.Path()); if (content.empty()) return std::string(); // TODO resp.AddCode(200); resp.AddHeader(\"Content-Length\", std::to_string(content.size())); resp.AddHeader(\"Content-Type\", _mine_type[req.Suffix()]); resp.AddBodyText(content); return resp.Serialize();}

运行结果