✨个人主页:熬夜学编程的小林
💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】【Linux网络编程】
目录
[1.1、 HttpServer类(404)](#1.1、 HttpServer类(404))
[3.3、GET vs POST](#3.3、GET vs POST)
1、状态码
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
状态码 | 含义 | 应用样例 |
---|---|---|
100 | Continue | 上传大文件时, 服务器告诉客户端可以继续上传 |
200 | OK | 访问网站首页, 服务器返回网页内容 |
201 | Created | 发布新文章, 服务器返回文章创建成功的信息 |
204 | No Content | 删除文章后, 服务器返回"无内容"表示操作成功 |
301 | Moved Permanently | 网站换域名后, 自动跳转到新域名; 搜索引擎更新网站链接时使用 |
302 | Found 或 See Other | 用户登录成功后, 重定向到用户首页 |
304 | Not Modified | 浏览器缓存机制, 对未修改的资源返回304 状态码 |
400 | Bad Request | 填写表单时, 格式不正确导致提交失败 |
401 | Unauthorized | 访问需要登录的页面时, 未登录或认证失败 |
403 | Forbidden | 尝试访问你没有权限查看的页面 |
404 | Not Found | 访问不存在的网页链接 |
500 | Internal Server Error | 服务器崩溃或数据库错误导致页面无法加载 |
502 | Bad Gateway | 使用代理服务器时, 代理服务器无法从上游服务器获取有效响应 |
503 | Service Unavailable | 服务器维护或过载, 暂时无法处理请求 |
HTTP 的状态码还有对应的描述信息,我们此处也可以使用哈希表存储状态码与描述信息映射关系!
1.1、 HttpServer类(404)
HttpServer类 增加一个状态码转描述信息的哈希表,并在构造函数插入对象的映射关系内容!
1.1.1、基本结构
HttpServer类 增加一个状态码转描述信息的哈希表成员变量!
html
class HttpServer
{
private:
std::unordered_map<std::string, std::string> _mine_type; // 类型对应表
std::unordered_map<int, std::string> _code_to_desc; // 状态转描述表
};
1.1.2、构造函数
构造函数插入对象的映射关系内容!
html
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"));
_code_to_desc.insert(std::make_pair(100,"Continue"));
_code_to_desc.insert(std::make_pair(200,"OK"));
_code_to_desc.insert(std::make_pair(201,"Created"));
_code_to_desc.insert(std::make_pair(404,"Not Found"));
}
1.1.3、HandlerHttpRequest()
html
const static std::string html_404 = "404.html";
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())
{
content = GetFileContent("wwwroot/404.html"); // 在默认错误文件获取内容
// resp.AddCode(404, "Not Found");
resp.AddCode(404, _code_to_desc[404]); // 内容为空添加404状态码
resp.AddHeader("Content-Length", std::to_string(content.size()));
resp.AddHeader("Content-Type", _mine_type["./html"]); // 类型默认为./html
resp.AddBodyText(content);
}
else
{
// resp.AddCode(200, "OK");
resp.AddCode(200, _code_to_desc[200]);
resp.AddHeader("Content-Length", std::to_string(content.size()));
resp.AddHeader("Content-Type", _mine_type[req.Suffix()]);
resp.AddBodyText(content);
}
return resp.Serialize();
}
AddCode()
html
void AddCode(int code, const std::string &desc)
{
_status_code = code;
_desc = desc;
}
1.1.4、login.html
login.html 文件增加一个测试404页面的链接!
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><br>
<a href="/a/b/c.html">测试404</a>
</body>
</html>
1.1.5、404.html
404.html 为404页面内容!
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>(404) The page you were looking for doesn't exist.</title>
<link rel="stylesheet" type="text/css" href="//cloud.typography.com/746852/739588/css/fonts.css" />
<style type="text/css">
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
body {
font-family: "Whitney SSm A", "Whitney SSm B", "Helvetica Neue", Helvetica, Arial, Sans-Serif;
background-color: #2D72D9;
color: #fff;
-moz-font-smoothing: antialiased;
-webkit-font-smoothing: antialiased;
}
.error-container {
text-align: center;
height: 100%;
}
@media (max-width: 480px) {
.error-container {
position: relative;
top: 50%;
height: initial;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
}
}
.error-container h1 {
margin: 0;
font-size: 130px;
font-weight: 300;
}
@media (min-width: 480px) {
.error-container h1 {
position: relative;
top: 50%;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
}
}
@media (min-width: 768px) {
.error-container h1 {
font-size: 220px;
}
}
.return {
color: rgba(255, 255, 255, 0.6);
font-weight: 400;
letter-spacing: -0.04em;
margin: 0;
}
@media (min-width: 480px) {
.return {
position: absolute;
width: 100%;
bottom: 30px;
}
}
.return a {
padding-bottom: 1px;
color: #fff;
text-decoration: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.6);
-webkit-transition: border-color 0.1s ease-in;
transition: border-color 0.1s ease-in;
}
.return a:hover {
border-bottom-color: #fff;
}
</style>
</head>
<body>
<div class="error-container">
<h1>404</h1>
<p class="return">Take me back to <a href="/">designernews.co</a></p>
</div>
</body>
</html>
1.2、重定向状态码
以下是仅包含重定向相关状态码的表格:
状态码 | 含义 | 是否为临时重定向 | 应用样例 |
---|---|---|---|
301 | Moved Permanently | 否(永久重定向) | 网站换域名后, 自动跳转到新域名;搜索引擎更新网站 链接时使用 |
302 | Found 或 See Other | 是(临时重定向) | 用户登录成功后,重定向到用户首页 |
307 | Temporary Redirect | 是(临时重定向) | 临时重定向资源到新的位置(较少使用) |
308 | Permanent Redirect | 否(永久重定向) | 永久重定向资源到新的位置(较少使用) |
关于重定向的验证,以 301 为代表
HTTP 状态码301(永久重定向)和 302(临时重定向) 都依赖 Location 选项。以下是关于两者依赖 Location 选项的详细说明:
HTTP 状态码 301(永久重定向):
-
当服务器返回 HTTP 301 状态码 时,表示请求的资源已经被永久移动到新的位置。
-
在这种情况下,服务器会在响应中添加一个 Location 头部,用于指定资源的新位置。这个 Location 头部包含了新的 URL 地址,浏览器会自动重定向到该地址。
-
例如,在 HTTP 响应中,可能会看到类似于以下的头部信息:
HTTP/1.1 301 Moved Permanently\r\n
Location: https://www.new-url.com\r\n
HTTP 状态码 302(临时重定向):
-
当服务器返回 HTTP 302 状态码时,表示请求的资源临时被移动到新的位置。
-
同样地,服务器也会在响应中添加一个 Location 头部来指定资源的新位置。浏览器会暂时使用新的 URL 进行后续的请求,但不会缓存这个重定向。
-
例如,在 HTTP 响应中,可能会看到类似于以下的头部信息:
HTTP/1.1 302 Found\r\n
Location: https://www.new-url.com\r\n
总结:无论是 HTTP 301 还是 HTTP 302 重定向,都需要依赖 Location 选项来指定资源的新位置 。这个Location 选项是一个标准的 HTTP 响应头部,用于告诉浏览器应该将请求重定向到哪个新的 URL 地址。
1.2.1、测试重定向
在登录页面增加测试重定向的代码!
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><br>
<a href="/a/b/c.html">测试404</a><br>
<a href="/redir">测试重定向</a><br>
</body>
</html>
优化:当我们点击测试重定向的时候,直接进入https://www.qq.com!
构造函数需要插入状态码与状态描述
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"));
_code_to_desc.insert(std::make_pair(100, "Continue"));
_code_to_desc.insert(std::make_pair(200, "OK"));
_code_to_desc.insert(std::make_pair(201, "Created"));
_code_to_desc.insert(std::make_pair(301, "Moved Permanently"));
_code_to_desc.insert(std::make_pair(302, "Found"));
_code_to_desc.insert(std::make_pair(404, "Not Found"));
}
HandlerHttpRequest()
std::string HandlerHttpRequest(std::string &reqstr)
{
HttpRequest req; // 构建请求对象
HttpResponse resp;
req.Deserialize(reqstr); // 反序列化字符串
if (req.Path() == "wwwroot/redir")
{
// 重定向处理
std::string redir_path = "https://www.qq.com"; // 以什么协议去访问
resp.AddCode(302, _code_to_desc[302]);
resp.AddHeader("Location", redir_path);
}
else
{
// 处理静态资源
std::string content = GetFileContent(req.Path());
if (content.empty())
{
content = GetFileContent("wwwroot/404.html"); // 在默认错误文件获取内容
resp.AddCode(404, _code_to_desc[404]); // 内容为空添加404状态码
resp.AddHeader("Content-Length", std::to_string(content.size()));
resp.AddHeader("Content-Type", _mine_type["./html"]); // 类型默认为./html
resp.AddBodyText(content);
}
else
{
// resp.AddCode(200, "OK");
resp.AddCode(200, _code_to_desc[200]);
resp.AddHeader("Content-Length", std::to_string(content.size()));
resp.AddHeader("Content-Type", _mine_type[req.Suffix()]);
resp.AddBodyText(content);
}
}
return resp.Serialize();
}
2、查看HTTP方法
在Request类增加获取方法的成员函数即可!
std::string Method()
{
LOG(DEBUG, "Client request method is %s\n", _method.c_str());
return _method;
}
调用方法:
req.Method();
注意:此处可以使用Postman软件!
3、增加表单
在登录页面我们可以增加表单!
3.1、POST方法
POST方法增加表单,参数内容是存放在正文(参数以问号分割),因此我们需要继续解析请求行!
<!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><br>
<a href="/a/b/c.html">测试404</a><br>
<a href="/redir">测试重定向</a><br>
<div>
<form action="/login" method="post">
用户名: <input type="text" name="username" value="."><br>
密码: <input type="password" name="userpasswd" value=""><br>
<input type="submit" value="提交">
</form>
</div>
</body>
</html>
解析请求行
const static std::string arg_sep = "?";
// 解析请求行
void ParseReqLine()
{
std::stringstream ss(_req_line); // 以空格为分隔符 cin >>
// /a/b/c.html or /login?user=xxx&passwd=1234 /register
ss >> _method >> _url >> _version; // 以空格为分隔符依次将请求行的内容赋值给成员变量
// 以GET方式请求资源,则将参数内容存放到正文
if (strcasecmp(_method.c_str(), "GET") == 0)
{
auto pos = _url.find(arg_sep);
if (pos != std::string::npos)
{
_body_text = _url.substr(pos + arg_sep.size());
_url.resize(pos); // url 只取参数前的内容
}
}
_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";
}
}
3.2、GET方法
<!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><br>
<a href="/a/b/c.html">测试404</a><br>
<a href="/redir">测试重定向</a><br>
<div>
<form action="/login" method="get">
用户名: <input type="text" name="username" value="."><br>
密码: <input type="password" name="userpasswd" value=""><br>
<input type="submit" value="提交">
</form>
</div>
</body>
</html>
3.3、GET vs POST
1、GET一般用来获取静态资源,也可以通过url向服务器传递参数
2、POST 可以通过http request的正文来进行参数传递
3、url 传递参数,参数的体量一定不大,正文可以很大
4、POST方法比GET方法传参更加私密,但是都不安全!对http的参数进行加密 -> https协议!
4、增加服务函数
前面获取的都是静态资源,我们也可以通过函数获取其他资源,此处就用到回调函数!
4.1、HttpServer类
要执行回调函数,需要在HttpServer类增加一个服务列表 ,用于存储需要执行的服务,在HandlerHttpRequest()函数中处理服务,此处只测试一个服务!
// 回调函数声明
using func_t = std::function<HttpResponse(HttpRequest&)>;
class HttpServer
{
private:
std::unordered_map<std::string, std::string> _mine_type; // 类型对应表
std::unordered_map<int, std::string> _code_to_desc; // 状态转描述表
std::unordered_map<std::string, func_t> _service_list; // 服务列表
};
4.2、InsertService()
InsertService() 函数插入需要执行的方法!
void InsertService(const std::string &servicename, func_t f)
{
std::string s = prefixpath + servicename;
_service_list[s] = f;
}
4.3、IsServiceExists()
IsServiceExists() 判断服务列表中是否存在服务,存在才需要处理!
bool IsServiceExists(const std::string servicename)
{
auto iter = _service_list.find(servicename);
if(iter == _service_list.end()) return false;
else return true;
}
4.4、HandlerHttpRequest()
HandlerHttpRequest()函数处理请求,讨论三种情况,重定向情况,调用服务情况以及处理静态资源情况!
std::string HandlerHttpRequest(std::string &reqstr)
{
std::cout << "---------------------------------------------" << std::endl;
std::cout << reqstr;
std::cout << "---------------------------------------------" << std::endl;
HttpRequest req; // 构建请求对象
HttpResponse resp;
req.Deserialize(reqstr); // 反序列化字符串
if (req.Path() == "wwwroot/redir")
{
// 重定向处理
std::string redir_path = "https://www.qq.com"; // 以什么协议去访问
resp.AddCode(302, _code_to_desc[302]);
resp.AddHeader("Location", redir_path);
}
// 最后服务新增,有参数需要请求服务
else if(!req.GetRequestBody().empty())
{
if(IsServiceExists(req.Path()))
{
resp = _service_list[req.Path()](req);
}
}
else
{
// 处理静态资源
std::string content = GetFileContent(req.Path());
if (content.empty())
{
content = GetFileContent("wwwroot/404.html"); // 在默认错误文件获取内容
// resp.AddCode(404, "Not Found");
resp.AddCode(404, _code_to_desc[404]); // 内容为空添加404状态码
resp.AddHeader("Content-Length", std::to_string(content.size()));
resp.AddHeader("Content-Type", _mine_type["./html"]); // 类型默认为./html
resp.AddBodyText(content);
}
else
{
// resp.AddCode(200, "OK");
resp.AddCode(200, _code_to_desc[200]);
resp.AddHeader("Content-Length", std::to_string(content.size()));
resp.AddHeader("Content-Type", _mine_type[req.Suffix()]);
resp.AddBodyText(content);
}
}
return resp.Serialize();
}
4.4、ServerMain.cc
ServerMain.cc文件实现服务端主函数!
#include "TcpServer.hpp" // 会话层
#include "Http.hpp"
HttpResponse Login(HttpRequest &req)
{
HttpResponse resp;
std::cout << "外部已经拿到了参数了: " << std::endl;
req.GetRequestBody();
std::cout << "####################" << std::endl;
resp.AddCode(200,"OK");
resp.AddBodyText("<html><h1>result done</h1></html>");
// username=hahahahah&userpasswd=123456
// 1.pipe
// 2.dup2
// 3.fork()
// 4.exec* -> Python,PHP,Java!业务处理
return resp;
}
// ./httpserver 8888
int 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;
hserver.InsertService("/login",Login);
// hserver.InsertService("/register",Register);
// hserver.InsertService("/Search",Search);
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(
std::bind(&HttpServer::HandlerHttpRequest, &hserver, std::placeholders::_1),
port);
tsvr->Loop();
return 0;
}