✨个人主页: 熬夜学编程的小林
💗系列专栏: 【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<std::string, std::string> _headers_kv;// 基本的httpresponse的格式std::string _status_line; // 状态行std::vector<std::string> _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(); // TODOHttpResponse 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 的大小为filesizein.read((char *)content.c_str(), filesize); // 读取filesize字节的文件内容到 content 中in.close();return content;
}
3、index.html
浏览器直接使用 IP + 端口访问时默认访问的文件,该文件随意添加一些内容,能到服务器发送给客户端的效果即可!
注意:该文件内容是前端语言,可以在下面的链接查看教程:
3.1、版本一
版本一只有标题和主体内容!
w3cschool
<!DOCTYPE html>
<html>
<head><title>Linux</title><meta charset="UTF-8">
</head>
<body><div id="container" style="width:500px"><div id="header" style="background-color:#FFA500;"><h1 style="margin-bottom:0;">我的网站</h1></div><div id="menu" style="background-color:#FFD700;height:200px;width:100px;float:left;"><b>菜单</b><br>HTML<br>CSS<br>JavaScript</div><div id="content" style="background-color:#EEEEEE;height:200px;width:400px;float:left;">内容就在这里</div><div id="footer" style="background-color:#FFA500;clear:both;text-align:center;">Copyright © W3Cschools.com</div></div>
</body>
</html>
运行结果
telnet 命令
运行结果
3.2、版本二
版本二我们可以增加图像!
<!DOCTYPE html>
<html>
<head><title>Linux</title><meta charset="UTF-8">
</head>
<body><div id="container" style="width:500px"><div id="header" style="background-color:#FFA500;"><h1 style="margin-bottom:0;">我的网站</h1></div><div id="menu" style="background-color:#FFD700;height:200px;width:100px;float:left;"><b>菜单</b><br>HTML<br>CSS<br>JavaScript</div><div id="content" style="background-color:#EEEEEE;height:200px;width:400px;float:left;">内容就在这里</div><div id="footer" style="background-color:#FFA500;clear:both;text-align:center;">Copyright © W3Cschools.com</div></div><div><img src="/image/1.png" alt="一张图片"> </div>
</body>
</html>
获得一个完整的网页,浏览器会先得到html,根据html的标签,检测出我们还要获取其他资源,浏览器会继续发起http请求!
运行结果
3.3、版本三
版本三我们可以增加链接,进行跳转页面,默认页面可以跳转到登录页面,登录页面可以跳转到内容页面,内容页面可以跳转到注册页面,注册页面跳转回默认页面!
index.html
<!DOCTYPE html>
<html>
<head><title>Linux</title><meta charset="UTF-8">
</head>
<body><div id="container" style="width:500px"><div id="header" style="background-color:#FFA500;"><h1 style="margin-bottom:0;">我的网站</h1></div><div id="menu" style="background-color:#FFD700;height:200px;width:100px;float:left;"><b>菜单</b><br>HTML<br>CSS<br>JavaScript</div><div id="content" style="background-color:#EEEEEE;height:200px;width:400px;float:left;">内容就在这里</div><div id="footer" style="background-color:#FFA500;clear:both;text-align:center;">Copyright © W3Cschools.com</div></div><a href="/login.html">点击测试: 登录页面</a><div><img src="/image/1.png" alt="一张图片"> <!-- <img src="/image/2.jpg" alt="第二张图片"> --></div>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>登录页面</title>
</head>
<body><h1>登录页面</h1><a href="/content.html">进入内容页面</a>
</body>
</html>
content.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>内容页面</title>
</head>
<body><h1>内容页面</h1><a href="/register.html">进入注册页面</a>
</body>
</html>
register.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>注册页面</title>
</head>
<body><h1>注册页面</h1><a href="/">回到首页</a>
</body>
</html>
运行结果
4、Content-Type
报头内部还有Content-Type,内容的类型,可以将类型也打印出来,使用哈希表存储!
4.1、HttpRequest类
HttpRequest类需要增加一个文件后缀成员变量,并在解析行函数处理后缀内容!
4.1.1、基本结构
HttpRequest类基本结构增加一个文件后缀成员变量!
class HttpRequest
{
private:// 基本的httprequest的格式std::string _req_line; // 请求行std::vector<std::string> _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<std::string, std::string> _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.htmlif (_path[_path.size() - 1] == '/'){_path += homepage;}// wwwroot/index.html// wwwroot/image/1.pngauto 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<std::string, std::string> _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(); // TODOresp.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();
}
运行结果