网络基础 【HTTP】
💓博主CSDN主页:麻辣韭菜💓
⏩专栏分类:Linux初窥门径⏪
🚚代码仓库:Linux代码练习🚚
💻操作环境: CentOS 7.6 华为云远程服务器
🌹关注我🫵带你学习更多Linux知识
🔝
目录
1. HTTP协议
1.1 认识URL
1.2 urlencode和urldecode
1.3 协议格式
2. 简易HTTP服务器
2.1 见一见请求
2.2 见一见响应
2.2.1 路径解析
3. HTTP方法
4. HTTP状态码
5. HTTP常见Header
5.1 Content-Type
5.2 Cookie
1. HTTP协议
在前面我们讲了自己如何定制协议,但是我们自己定制的协议太简单了,我们的协议在应用层来说,根本不够用的,实际上,已经有大佬定义了一些现成的,又非常好用的应用层协议,比如本篇要讲解的HTTP协议(超文本传输协议)在学习HTTP之前我们需要先了解几个预备知识。
1.1 认识URL
什么是URL?
我们平时所说的"网址",就是传说中的URL。
 我们在浏览器输入抖音的网址,就能访问抖音,可是我们平时并不知道抖音IP地址和端口号 
为什么光输入一个域名就能访问了?
URL自动解析对应的IP地址
 
而端口号是默认的,比如说HTTP 80 端口号,而HTTPS 443 端口号
如果我们没指明端口号,浏览器就会使用 协议 的默认端口
诸如上面的网址称为 URL -> Uniform Resource Locator 统一资源定位符,也就我们熟知的 超链接/链接,URL 中包含了 协议、IP地址、端口号、资源路径、参数 等信息
上面的URL只有一个域名,其实还有,请看图
  注:user:pass 已经不用了,因为不安全。
  注:user:pass 已经不用了,因为不安全。
下面我以我个人博客主页来讲解 URL
https://blog.csdn.net/2301_77934192?spm=1011.2266.3001.5343
1. 协议
https://:表示使用 HTTPS 协议进行安全的数据传输。HTTPS 是 HTTP 的安全版本,通过 SSL/TLS 加密数据,确保数据在传输过程中的安全性。2. 域名
blog.csdn.net:这是 CSDN(中国软件开发网)的博客子域名。CSDN 是一个知名的技术社区,提供博客、论坛、问答等服务。3. 路径
/2301_77934192:这是用户的唯一标识符或博客作者的 ID。这个部分通常指向特定用户的博客主页。4. 查询参数
?spm=1011.2266.3001.5343:这是 URL 的查询字符串,通常用于传递额外的信息给服务器。spm是一个参数名,后面的值1011.2266.3001.5343可能用于跟踪来源、分析流量或其他目的。
://用于分隔 协议 和 IP地址
:用于分隔 IP地址 和 端口号
/表示路径,同时第一个/可以分隔 端口号 和 资源路径
?则是用来分隔 资源路径 和 参数
1.2 urlencode和urldecode
比如

比如我们在百度搜索 C++ 而+这个字符被转化成2B
我们在上篇序列化与反序列化就是同样的道理。 下面是urlencode在线工具
 
1.3 协议格式
HTTP 协议 由 Request 请求 和 Response 响应 组成 有上篇的基础,我们就能大概知道 请求报文和响应报文的格式了。
从人类理解的角度来说:请求大概有这么几个部分组成。
请求行 :当中包括了请求的方法(GET POST),以及URL的协议版本(HTTP/1.0,TTTP/1.1,THHP/2.0)
请求头:包含一系列键值对,提供了关于HTTP请求的附加信息,如:
- Host:指定请求的服务器的域名和端口号。
- User-Agent:包含了发出请求的用户代理软件信息。
- Accept:告知服务器客户端能够接收哪些类型的信息。
- Accept-Language:告知服务器客户端能够接受的语言。
- Accept-Encoding:告知服务器客户端能够接受的压缩格式。
- Content-Type:当发送包含body的请求时,指定body的媒体类型。
- Content-Length:当发送包含body的请求时,指定body的长度。
- Connection:指定或要求服务器的连接状态。
- Cookie:存储在用户本地的session信息。
- Authorization:用于认证的信息。
空行:请求头和请求体之间的分隔符,通常是一个空行。
请求体/有效载荷:(可选)某些HTTP方法(如POST和PUT)可能会包含请求体,它包含了发送给服务器的数据。
 
请求报文
POST /submit-form HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
Connection: keep-alivefield1=value1&field2=value2对于响应 分为这么几个部分:
1. 状态行(Status Line)
状态行是HTTP响应报文的第一行,它包含以下三个部分:
- HTTP版本(HTTP-Version):指定使用的HTTP协议的版本,如HTTP/1.1或HTTP/2。
- 状态码(Status Code):一个三位数字,表示请求的结果,如200表示成功,404表示未找到,500表示服务器内部错误等。
- 原因短语(Reason Phrase):一个简短的文本,用来提供状态码的额外信息。
2. 响应头(Response Headers)
响应头提供了关于响应的附加信息,它们是一系列的键值对。响应头也可以被分为几个不同的类别:
- 通用头(General Headers):适用于所有类型的请求和响应,如Cache-Control、Connection、Date等。
- 响应头(Response Headers):提供响应的附加信息,如Server、Content-Type、Content-Length等。
- 实体头(Entity Headers):当响应包含响应体时使用,如Content-Encoding、Content-Language、Content-Location、Content-MD5、Last-Modified等。
一些常见的响应头包括:
- Server:包含了服务器软件的信息。
- Content-Type:指定返回的资源的MIME类型。
- Content-Length:指定返回的资源的长度。
- Content-Encoding:指定了响应体的压缩格式。
- Set-Cookie:用于设置客户端的cookie。
- Last-Modified:指定资源的最后修改时间。
- Cache-Control:指定响应的缓存指令。
3. 空行(Empty Line)
响应头和响应体之间的分隔符,通常是一个空行,表示响应头的结束。
4. 响应体(Response Body)
响应体是HTTP响应的一部分,它包含了服务器返回给客户端的数据。响应体的内容可以是HTML文档、图片、视频、JSON、XML等格式,具体取决于Content-Type响应头的值。

HTTP/1.1 200 OK
Date: Mon, 27 Sep 2024 12:28:53 GMT
Server: Apache/2.4.1 (Unix)
Last-Modified: Wed, 26 Sep 2024 12:28:53 GMT
Content-Length: 12345
Content-Type: text/html
Connection: close
ETag: "3f80f-1b6-3e1cb93b"<html>
<head><title>Example Response</title></head>
<body><h1>Hello, World!</h1>
</body>
</html>2. 简易HTTP服务器
2.1 见一见请求
我们编写一个服务器,利用浏览器作为客户端,浏览器通过 IP + Port 访问 我们编写的服务器,这时浏览器就会发出HTTP请求,浏览器接连到服务器后,服务器就会打印HTTP请求
编写服务器所需要的文件:
 
log.hpp 和 Socket.hpp 和上篇的是一样的 直接拿过来用,自动化编译不用多说。
先编写服务器
#pragma once
#include <iostream>
#include <string>
#include <thread>
#include "Socket.hpp"
static const uint16_t defaultport = 8080;class HttpServer;
class ThreadData
{
public:ThreadData(int sockfd, HttpServer *tpsvr): _sockfd(sockfd), _tpsvr(tpsvr) {}public:int _sockfd;HttpServer *_tpsvr; //回调指针
};
class HttpServer
{
public:HttpServer(uint16_t port = defaultport): _port(port) {}void Init(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();}static void ThreaRun(ThreadData* td){//先简单处理int sockfd = td->_sockfd;char buffer[10240];int n = recv(sockfd,buffer,sizeof(buffer),0);if(n > 0){buffer[n] = 0;std:: cout << buffer<<std::endl;}}void Start(){for (;;){std::string clientip;uint16_t clientport;int sockfd = _listensock.Accept(&clientip, &clientport);if (sockfd < 0)continue;lg(Info, "get a new link, clientip: %s, clientport: %d", clientip.c_str(), clientport);//创建线程处理请求ThreadData* td = new ThreadData(sockfd, this);std::thread t(ThreaRun, td);t.detach();}}private:Sock _listensock;uint16_t _port;
};编写主函数
#include "HttpServer.hpp"
#include <iostream>
#include <memory>int main()
{std::unique_ptr<HttpServer> svr(new HttpServer());svr->Init();svr->Start();return 0;
}make 一下 编译通过后,运行HttpServer可执行程序。
 
通过指令 netstat 看到服务器已经运行了 ,这时我们在浏览器输入IP+port 服务器就会打印请求消息。

 
 
没有页面也很正常 我们服务器还没有写业务函数来进行响应。

从请求行来看 请求的方法为 GET 版本为HTTP/1.1 请求路径为 / (根目录)如果我们指定路径访问,则会直接访问该指定路径。
从这个两个请求报文来看 服务器可以识别是什么类型的设备在请求链接 也就是User-Agent
我们用爬虫,有时候爬不了的原因就在这里,HTTP根据User-Agent 如果是非法的用户(也就是报文的格式不对)User-Agent 或者根本就没有,那么直接就不给响应了。这就是反爬策略。
User-Agent 还有作用就是:比如我们在网站上下载东西时,下载的软件是直接对应你机器的操作系统。
比如 我要下载微信 点进去的下载链接 ,直接就是windows电脑版
 
2.2 见一见响应
static void ThreaRun(ThreadData *td){// 先简单处理int sockfd = td->_sockfd;char buffer[10240];int n = recv(sockfd, buffer, sizeof(buffer), 0);if (n > 0){buffer[n] = 0;std::cout << buffer << std::endl;// 返回响应std::string text = "Hello World";                  // 响应的内容std::string response_line = "HTTP/1.0 200 OK\r\n"; // 响应行std::string response_header = "Content-Length:";   // 响应报文response_header += std::to_string(text.size());    // 内容的长度response_header += "\r\n";std::string bank_line = "\r\n"; // 空行std::string response = response_line;response += response_header;response += bank_line;response += text;// 发送报文send(td->_sockfd, response.c_str(), response.size(), 0);}}
 通过简单代码我们将字符串 "Hello World" 拼接到响应报文的正文部分,发送给客户端(浏览器),而浏览器通过解释,最终在界面上显示了 Hello World。 这也就对应我们前面的讲的响应报文里面有效载荷。
 通过简单代码我们将字符串 "Hello World" 拼接到响应报文的正文部分,发送给客户端(浏览器),而浏览器通过解释,最终在界面上显示了 Hello World。 这也就对应我们前面的讲的响应报文里面有效载荷。
2.2.1 路径解析
其实我们还可以通过URL访问指定文件,就比如下面文件abc,也是说HTTP网络文件有很多,比如图片、视频、音频、JS文件、样式文本等。那么HTTP一定就会有一个web根目录就如同Linux的根目录。
前面代码很挫,如果我们要更改网站的样式,每次我们都要静态的写入到我们服务器中,所以我们可以创建一个文件,将htlm写入到这个文件中,下次再改就不用改服务器了。
基于刚才讲的 我们直接就在进程当前目录创建一个文件夹 wwwroot 以后网站首页也好,图片视频也罢 直接就从这个wwwroot根目录访问。
 所以这段代码就不能这么写了。我们重新写一个类 HTTP请求的类,然后对请求做反序列化,拿到url 。
 所以这段代码就不能这么写了。我们重新写一个类 HTTP请求的类,然后对请求做反序列化,拿到url 。
const std::string wwwroot = "./wwwroot";
const std::string sep = "\r\n";
const std::string homepage = "index.html";class HttpRequest
{
public:// 进行反序列化void Deserialization(std::string req){size_t pos = 0;while ((pos = req.find(sep)) != std::string::npos){size_t next_pos = pos + sep.size();if (pos > 0){ // 确保不是空字符串req_header.push_back(req.substr(0, pos));}req.erase(0, next_pos);}// 循环退出后,剩下的就是报文的正文部分text = req;}//解析请求行void Parse(){std::stringstream ss(req_header[0]);ss >> method >> url >> http_version;file_path = wwwroot;if (url == "/" || url == "/index.html") // 访问根目录 就只返回网站首页{file_path += "/";file_path += homepage;}elsefile_path += url; // 访问其他路径}void DebugPrint(){for (auto &line : req_header){std::cout << "--------------------------------" << std::endl;std::cout << line << "\n\n";}std::cout << "method: " << method << std::endl;std::cout << "url: " << url << std::endl;std::cout << "http_version: " << http_version << std::endl;std::cout << "file_path: " << file_path << std::endl;std::cout << text << std::endl;}public:std::vector<std::string> req_header;std::string text;// 解析之后的结果std::string method;std::string url;std::string http_version;std::string file_path;
};在我们当前目录 新建wwwroot目录 然后再这个目录下创建 index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><h1>Hello world</h1><h1>Hello world</h1><h1>Hello world</h1><h1>Hello world</h1><h1>Hello world</h1><h1>Hello world</h1><h1>Hello world</h1><h1>Hello world</h1>
</body>
</html>在HttpServer这个类中编写下面函数
// 根据解析的路径确定打开那个文件static std::string ReadHtmlContent(const std::string &htmlpath){std::ifstream in(htmlpath); // 这里只能打开字符串文件,图片不行。if (!in.is_open()){return "404";}std::string content;std::string line;while (std::getline(in, line)){content += line;}in.close();return content;}在原来的TreadRun进行变形得到我们想要效果
static void ThreaRun(ThreadData *td){// 先简单处理int sockfd = td->_sockfd;char buffer[10240];int n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);if (n > 0){buffer[n] = 0;// std::cout << buffer << std::endl;HttpRequest req;req.Deserialization(buffer);req.Parse();req.DebugPrint();// 返回响应std::string text = ReadHtmlContent(req.file_path); // 响应的内容std::string response_line = "HTTP/1.0 200 OK\r\n"; // 响应行std::string response_header = "Content-Length:";   // 响应报文response_header += std::to_string(text.size());    // 内容的长度response_header += "\r\n";std::string bank_line = "\r\n"; // 空行std::string response = response_line;response += response_header;response += bank_line;response += text;// 发送报文send(td->_sockfd, response.c_str(), response.size(), 0);}delete td;}这里我们读是有bug的 这里我们就假设读到的是一个完整的报文。
 
 
这样做的好处就是,我们访问网站首页,就只会返回网站首页,而不会返回根目录下的所有内容。 同理访问其他的路径也是一样。
这时我们再在wwwroot 创建a b c 文件夹 分别在这3个目录中创建 html文件
 
我们添加链接就可以跳转 其他网页


 这里前端知识我们不细说,感兴趣的可以去w3school 在线教程 看看
 这里前端知识我们不细说,感兴趣的可以去w3school 在线教程 看看
 点击就跳转到 第二个网页
 点击就跳转到 第二个网页
 这还是要得益于我们对请求的请求行做反序列化,然后将URL提取出来,在服务器中路径解析 找到目录 打开文件。
 这还是要得益于我们对请求的请求行做反序列化,然后将URL提取出来,在服务器中路径解析 找到目录 打开文件。
3. HTTP方法

通过前面的演示,服务器打印的请求都是GET方法,也就是说我们要获取服务器的某个资源基本用的都是GET方法。
那POST也可获取,那POST与GET获取有什么不同?
不要忘记了 ,我们作为客户端除了请求服务器的资源,也是可以向服务器提交数据的。 就比如我们在百度搜索东西时,搜索关键字linux 提交给百度服务器。
 再比如登陆gitee 网站 用户信息 也是数据
 再比如登陆gitee 网站 用户信息 也是数据

基于前面的认识之后 我们再来谈谈为什么有了GET 还要有POST?
首先GET的数据传输是通过URL的,URL本身就有长度限制,那么就意味着GET请求传输的数据长度有限。
其次 数据 在URL 中本身是可见,一些敏感信息就不适合用URL传输,就比如用户账号信息。
最后 URL请求可以被缓存,那么我们传递数据就会被浏览器保存,被第三方看到。
一句话 总结就是:GET方法传输数据不安全。
POST方法:
- 数据传输:通过请求体(Request Body)传递数据,数据不会出现在地址栏中。
- 数据长度限制:POST请求没有数据长度限制。
- 缓存:POST请求不会被缓存。
- 历史记录:POST请求不会保存在浏览器的历史记录中。
- 可见性:数据不会在URL中显示,因此相对更安全。
- 用途:适合向服务器提交数据。
- 方式:数据被包含在请求体中,可以传输更复杂的数据类型。
总结
- GET 主要用于请求服务器发送数据。
- POST 主要用于向服务器提交数据。
当然POST 提交的 数据 也不安全。因为HTTP协议都是明文传送的。
那数据是怎么样提交给服务器的?
 
在前端来说这个叫表单,我们的数据都是通过表单来提交的!
后面的方法要被HTTP禁用,要么就是随着时代发展被淘汰了不用了。我们在HTTP中用到的方法 95%以上用的是 GET 和 POST。
基于这么我们先用GET 方法做实验 在HTML 表单 (w3school.com.cn) 前端代码拿过来直接用。

 点击登陆后,跳转网页后 地址框URL如下面所示
 点击登陆后,跳转网页后 地址框URL如下面所示
 从这个图片我们可以看到 用户 是zhangsan 密码 123456。 这也验证了 我们前面的讲的GET方法提交数据不安全。
 从这个图片我们可以看到 用户 是zhangsan 密码 123456。 这也验证了 我们前面的讲的GET方法提交数据不安全。
从这个URL看 以?为分隔符,?前面的如果是个可执行程序 而?后面是参数。那么我们就可以创建子进程 做程序替换而这个程序替换可以是登陆认证,插入数据库,搜索等。
 我们改成post方法 参数通过了请求体(正文)传输。
 我们改成post方法 参数通过了请求体(正文)传输。
4. HTTP状态码

这里100开头和200开头没什么好说的,我们在写响应的时候 就是 200 OK 标识成功,我们再说400开头的。
 我们访问百度 通过URL指定访问路径a/b/c出现了下面的界面
也就是传说中404 你访问的页面不存在。基于这样我们也可以写一个err.html。毕竟这个世界上的服务器不可能搜集到所有资源,客户端访问的东西我们没有,但是也要响应。
那么前面的代码我们就要改一改
static std::string ReadHtmlContent(const std::string &htmlpath){std::ifstream in(htmlpath); // 这里只能打开字符串文件,图片不行。if (!in.is_open()){return ""; //之前返回404 现在返回空串}响应报文对应也要改一改
            std::string text = ReadHtmlContent(req.file_path); // 响应的内容bool ok = true;if (text.empty()){ok = false;std::string err_html = wwwroot;err_html += "/err.html";text = ReadHtmlContent(err_html);}std::string response_line;if (ok)response_line = "HTTP/1.0 200 OK\r\n"; // 响应行elseresponse_line = "HTTP/1.0 404 Not Found\r\n";这时我们在wwwroot目录下添加err.html文件,404前端代码 网上随便找一个过来CV一下。
 源代码我在网上找了一个,cv过来 ,现在我们运行试试
 源代码我在网上找了一个,cv过来 ,现在我们运行试试

对于5开头的,那一般都是服务器的问题,配置出错了,资源出错了等。我们还有有一个3开头的状态码没有说
300开头的叫做重定向 一般有两种 一种 302 临时重定向 一种是 301永久重定向。
说人话那就是说 原本我们访问的是我们的网站,结果访问的是其他网站。
那什么时候用临时?
不知道大家登陆认证的时候,是不是跳转了其他页面,而这个页面就是临时重定向。
永久不用多说了,以前网站老化,不用了。跳转到新的网站
下面我对报文进行变形 改成重定向
   static void ThreaRun(ThreadData *td){// 先简单处理int sockfd = td->_sockfd;char buffer[10240];int n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);if (n > 0){buffer[n] = 0;std::cout << buffer << std::endl;HttpRequest req;req.Deserialization(buffer);req.Parse();//req.DebugPrint();// 返回响应std::string text = ReadHtmlContent(req.file_path); // 响应的内容bool ok = true;if (text.empty()){ok = false;std::string err_html = wwwroot;err_html += "/err.html";text = ReadHtmlContent(err_html);}std::string response_line;if (ok)response_line = "HTTP/1.0 200 OK\r\n"; // 响应行elseresponse_line = "HTTP/1.0 404 Not Found\r\n";response_line = "HTTP/1.0 302 Found\r\n"; //重定向std::string response_header = "Content-Length:"; // 响应报文response_header += std::to_string(text.size());  // 内容的长度response_header += "\r\n";response_header += "Location: https://www.baidu.com\r\n";//重定向到百度std::string bank_line = "\r\n"; // 空行std::string response = response_line;response += response_header;response += bank_line;response += text;// 发送报文send(td->_sockfd, response.c_str(), response.size(), 0);}delete td;} 
5. HTTP常见Header
- Content-Type: 数据类型(text/html等)
- Content-Length: Body的长度
- Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
- User-Agent: 声明用户的操作系统和浏览器版本信息;
- referer: 当前页面是从哪个页面跳转过来的;
- location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
- Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
除了Content-Type Cookie 没有讲 前面内容都讲过了。
5.1 Content-Type
在讲 Content-Type 之间 我们需要先了解Connection

打开B站首页 感觉是我们只访问首页,也就是和服务器进行一次请求和响应。其实不然,B站首页有许多图片和视频, 这些也是资源,其实服务器会给我们多次响应,多次取决于有多少个资源。
在上古时代也就是 HTTP/1.0的时代,客户端和服务器连接都是短连接,比较那个时候网页内容不多。所以Hold的住,但是现在还是采用1.0那就不行了,毕竟现在一个网页就有几百张图片,浏览器和服务器之间就得建立几百次连接。效率低下
现在都是HTTP/1.1时代,也就说长连接 一次连接返回你要访问的所有资源

我们前面所写网站可是没有图片的,那如何添加图片?需要注意的是文本不同于图片和视频
他们都有对照表
也就说服务器要知道我们在请求什么资源,需要知道它的类型,根据 请求报文的 Content-Type
注明 服务器知道了是什么类型的资料 根据对照表 在响应报文中添加字段发给浏览器。
 HTTP content-type 对照表
所以基于这样 我们需要对之前代码继续变形
变形1:由于有对照表,所以我们需要unordered_map 用来存放 资源类型和它的对照表。
变形2:在原来的Parse()函数中 ,要解析出 资源的类型。
变形3: ReadHtmlContent()函数中 以前是读文本,但是图片和视频是二进制的,以前的读法就不行了,改为二进制来读。
改造后代码

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <unordered_map>
#include <fstream>
#include <thread>
#include "Socket.hpp"const std::string wwwroot = "./wwwroot";
const std::string sep = "\r\n";
const std::string homepage = "index.html";
const std::string contentype = "./wwwroot/content_type.txt";
const std::string sep1 = ":";
static const uint16_t defaultport = 8080;class HttpServer;
class ThreadData
{
public:ThreadData(int sockfd, HttpServer *tpsvr): _sockfd(sockfd), _tpsvr(tpsvr) {}public:int _sockfd;HttpServer *_tpsvr; // 回调指针
};class HttpRequest
{
public:// 进行反序列化void Deserialization(std::string req){size_t pos = 0;while ((pos = req.find(sep)) != std::string::npos){size_t next_pos = pos + sep.size();if (pos > 0){ // 确保不是空字符串req_header.push_back(req.substr(0, pos));}req.erase(0, next_pos);}// 循环退出后,剩下的就是报文的正文部分text = req;}// 解析请求行void Parse(){std::stringstream ss(req_header[0]);ss >> method >> url >> http_version;file_path = wwwroot;if (url == "/" || url == "/index.html") // 访问根目录 就只返回网站首页{file_path += "/";file_path += homepage;}elsefile_path += url; // 访问其他路径auto pos = file_path.rfind("."); // 找路径文件后缀格式if (pos == std::string::npos){suffix = ".htlm";}else{suffix = file_path.substr(pos); // 找到了,文件后缀格式放在容器中}}void DebugPrint(){for (auto &line : req_header){std::cout << "--------------------------------" << std::endl;std::cout << line << "\n\n";}std::cout << "method: " << method << std::endl;std::cout << "url: " << url << std::endl;std::cout << "http_version: " << http_version << std::endl;std::cout << "file_path: " << file_path << std::endl;std::cout << text << std::endl;}public:std::vector<std::string> req_header;std::string text;// 解析之后的结果std::string method;std::string url;std::string http_version;std::string file_path;std::string suffix; // 资源后缀格式
};class HttpServer
{
public:HttpServer(uint16_t port = defaultport): _port(port) {}void Init(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();std::ifstream in(contentype);if (!in.is_open()){lg(Fatal, "isfstream open error %s", contentype.c_str());exit(1);}std::string line;while (std::getline(in, line)){std::string part1, part2;Split(line, &part1, &part2);content_type.insert({part1, part2});}in.close();}// 将content_type.txt 分割成 哈希键值对 后序插入bool Split(const std::string &s, std::string *part1, std::string *part2){auto pos = s.find(sep1);if (pos == std::string::npos)return false;*part1 = s.substr(0, pos);*part2 = s.substr(pos + 1);return true;}// 根据解析的路径确定打开那个文件static std::string ReadHtmlContent(const std::string &htmlpath){std::ifstream in(htmlpath, std::ios::binary); // 按二进制打开if (!in.is_open()){return "";}std::string content;in.seekg(0, std::ios::end); // 找到文件的最后位置auto len = in.tellg();      // 算出文件的长度in.seekg(0, std::ios::beg); // 文件最后位置复位content.resize(len);in.read((char *)content.c_str(), content.size());// std::string line;// while (std::getline(in, line))// {//     content += line;// }in.close();return content;}static void ThreaRun(ThreadData *td){// 先简单处理int sockfd = td->_sockfd;char buffer[10240];int n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);if (n > 0){buffer[n] = 0;std::cout << buffer << std::endl;HttpRequest req;req.Deserialization(buffer);req.Parse();// req.DebugPrint();//  返回响应std::string text = ReadHtmlContent(req.file_path); // 响应的内容bool ok = true;if (text.empty()){ok = false;std::string err_html = wwwroot;err_html += "/err.html";text = ReadHtmlContent(err_html);}std::string response_line;if (ok)response_line = "HTTP/1.0 200 OK\r\n"; // 响应行elseresponse_line = "HTTP/1.0 404 Not Found\r\n";// response_line = "HTTP/1.0 302 Found\r\n"; //重定向std::string response_header = "Content-Length:"; // 响应报文response_header += std::to_string(text.size());  // 内容的长度response_header += "\r\n";response_header += "Content-Type:";response_header += td->_tpsvr->SuffixToDesc(req.suffix);response_header += "\r\n";// response_header += "Location: https://www.baidu.com\r\n";//重定向到百度std::string bank_line = "\r\n"; // 空行std::string response = response_line;response += response_header;response += bank_line;response += text;// 发送报文send(td->_sockfd, response.c_str(), response.size(), 0);}delete td;}std::string SuffixToDesc(const std::string &suffix){auto iter = content_type.find(suffix);if (iter == content_type.end())return content_type[".html"];elsereturn content_type[suffix];}void Start(){for (;;){std::string clientip;uint16_t clientport;int sockfd = _listensock.Accept(&clientip, &clientport);if (sockfd < 0)continue;lg(Info, "get a new link, clientip: %s, clientport: %d", clientip.c_str(), clientport);// 创建线程处理请求ThreadData *td = new ThreadData(sockfd, this);std::thread t(ThreaRun, td);t.detach();}}private:Sock _listensock;uint16_t _port;std::unordered_map<std::string, std::string> content_type;
};

5.2 Cookie
你在B站 或者 腾讯视频、爱奇艺等网站,只要登陆认证了一次后,下次再访问时就不会出现登陆
这是因为Cookie的作用。

当我扫码登陆之后浏览器里面就有一个配置文件Cookie文件 当我们下次访问B站时,浏览器就会带着Cookie文件一起发送给服务器。而这个Cookie文件中包含了用户名 和 密码 。所以下次我们访问VIP资源时就不需要登陆认证了。这个现象我们叫做 会话保持

当然 Cookie 文件也有内存级 和 文件级 而我们上面的就是内存级,到期时间是浏览会话结束。
代码层面我们也演示
 

 
当我们讲了Cookie 你就应该意识到 这个保存用户信息的文件它是不安全的,一些木马程序扫描你电脑里的Cookie文件。找到了就拿走,就不就是传说中盗号吗?而且个人私密信息也被拿走了
基于这样的安全问题。后面服务端搞了一个sessionID

但是sessionID就安全了吗? 答案是不安全。
为什么这么说 因为Cookie文件还是在浏览器中,没有sessionID以前是客户自己保留私密信息,有了sessionID以后交给了服务器。现在用户的私密信息交给了服务端来维护了。也就说个人私密信息盗不走了,但是Cookie里面的sessionID别人还是能够拿到。
服务器就可以制定安全策略 识别是否为异常登录:
- IP比对:识别登录用户的IP在短时间内是否发生了改变
- 设备对比:不是本人常用设备
如果发现异常登陆 直接就把sessionID 的状态设置为暂停状态,客户再访问时需要进行登陆认证,认证失败,服务器直接就删除sessionID.




