网络底子 【HTTP】

[复制链接]
发表于 2026-1-14 15:58:54 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
 💓博主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 已经不消了,由于不安全。
 下面我以我个人博客主页来讲授 URL
 
   1. 协议
  

  • https://:表现使用 HTTPS 协议举行安全的数据传输。HTTPS 是 HTTP 的安全版本,通过 SSL/TLS 加密数据,确保数据在传输过程中的安全性。
2. 域名
  

  • :这是 CSDN(中国软件开辟网)的博客子域名。CSDN 是一个着名的技能社区,提供博客、论坛、问答等服务。
3. 路径
  

  • /2301_77934192:这是用户的唯一标识符或博客作者的 ID。这个部分通常指向特定用户的博客主页。
4. 查询参数
  

  • ?spm=1011.2266.3001.5343:这是 URL 的查询字符串,通常用于转达额外的信息给服务器。spm 是一个参数名,反面的值 1011.2266.3001.5343 大概用于跟踪泉源、分析流量或其他目标。
   

  • :// 用于分隔 协议 和 IP地点
  • : 用于分隔 IP地点 和 端标语
  • / 表现路径,同时第一个 / 可以分隔 端标语 和 资源路径
  • ? 则是用来分隔 资源路径 和 参数
1.2 urlencode和urldecode 

  像 / ? : 等如许的字符 ,  已经被 url 当做特殊意义明白了 .  因此这些字符不能随意出现 .   比如 ,  某个参数中必要带有这些特殊字符 ,  就必须先对特殊字符举行转义 .   转义的规则如下 :   将必要转码的字符转为 16 进制,然后从右到左,取 4 位 ( 不敷 4 位直接处理处罚 ) ,每 2 位做一位,前面加上 % ,编码成 %XY格式 比如

比如我们在百度搜刮 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)大概会包罗哀求体,它包罗了发送给服务器的数据。
 

 哀求报文
  1. POST /submit-form HTTP/1.1
  2. Host: www.example.com
  3. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
  4. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  5. Accept-Language: en-US,en;q=0.5
  6. Accept-Encoding: gzip, deflate
  7. Content-Type: application/x-www-form-urlencoded
  8. Content-Length: 27
  9. Connection: keep-alive
  10. field1=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相应头的值。

  1. HTTP/1.1 200 OK
  2. Date: Mon, 27 Sep 2024 12:28:53 GMT
  3. Server: Apache/2.4.1 (Unix)
  4. Last-Modified: Wed, 26 Sep 2024 12:28:53 GMT
  5. Content-Length: 12345
  6. Content-Type: text/html
  7. Connection: close
  8. ETag: "3f80f-1b6-3e1cb93b"
  9. <html>
  10. <head><title>Example Response</title></head>
  11. <body>
  12.   <h1>Hello, World!</h1>
  13. </body>
  14. </html>
复制代码
 2. 浅显HTTP服务器

 2.1 见一见哀求

 我们编写一个服务器,使用欣赏器作为客户端,欣赏器通过 IP + Port 访问 我们编写的服务器,这时欣赏器就会发出HTTP哀求,欣赏器接连到服务器后,服务器就会打印HTTP哀求 
编写服务器所必要的文件:
 

log.hpp 和 Socket.hpp 和上篇的是一样的 直接拿过来用,自动化编译不消多说。 
先编写服务器
  1. #pragma once
  2. #include <iostream>
  3. #include <string>
  4. #include <thread>
  5. #include "Socket.hpp"
  6. static const uint16_t defaultport = 8080;
  7. class HttpServer;
  8. class ThreadData
  9. {
  10. public:
  11.     ThreadData(int sockfd, HttpServer *tpsvr)
  12.         : _sockfd(sockfd), _tpsvr(tpsvr) {}
  13. public:
  14.     int _sockfd;
  15.     HttpServer *_tpsvr; //回调指针
  16. };
  17. class HttpServer
  18. {
  19. public:
  20.     HttpServer(uint16_t port = defaultport)
  21.         : _port(port) {}
  22.     void Init()
  23.     {
  24.         _listensock.Socket();
  25.         _listensock.Bind(_port);
  26.         _listensock.Listen();
  27.     }
  28.     static void ThreaRun(ThreadData* td)
  29.     {
  30.         //先简单处理
  31.         int sockfd = td->_sockfd;
  32.         char buffer[10240];
  33.         int n = recv(sockfd,buffer,sizeof(buffer),0);
  34.         if(n > 0)
  35.         {
  36.             buffer[n] = 0;
  37.             std:: cout << buffer<<std::endl;
  38.         }
  39.     }
  40.     void Start()
  41.     {
  42.         for (;;)
  43.         {
  44.             std::string clientip;
  45.             uint16_t clientport;
  46.             int sockfd = _listensock.Accept(&clientip, &clientport);
  47.             if (sockfd < 0)
  48.                 continue;
  49.             lg(Info, "get a new link, clientip: %s, clientport: %d", clientip.c_str(), clientport);
  50.             //创建线程处理请求
  51.             ThreadData* td = new ThreadData(sockfd, this);
  52.             std::thread t(ThreaRun, td);
  53.             t.detach();
  54.         }
  55.     }
  56. private:
  57.     Sock _listensock;
  58.     uint16_t _port;
  59. };
复制代码
编写主函数 
  1. #include "HttpServer.hpp"
  2. #include <iostream>
  3. #include <memory>
  4. int main()
  5. {
  6.     std::unique_ptr<HttpServer> svr(new HttpServer());
  7.     svr->Init();
  8.     svr->Start();
  9.     return 0;
  10. }
复制代码
make 一下 编译通过后,运行HttpServer可实验步调。
 

通过指令 netstat 看到服务器已经运行了 ,这时我们在欣赏器输入IP+port 服务器就会打印哀求消息。 

 

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

从哀求行来看 哀求的方法为 GET  版本为HTTP/1.1 哀求路径为 / (根目次)如果我们指定路径访问,则会直接访问该指定路径。 
从这个两个哀求报文来看 服务器可以辨认是什么范例的装备在哀求链接 也就是User-Agent
我们用爬虫,偶尔候爬不了的缘故原由就在这里,HTTP根据User-Agent 如果黑白法的用户(也就是报文的格式不对)User-Agent  大概根本就没有,那么直接就不给相应了。这就是反爬战略。
User-Agent 另有作用就是:比如我们在网站上下载东西时,下载的软件是直接对应你呆板的操纵体系。
比如 我要下载微信 点进去的下载链接 ,直接就是windows电脑版
 

2.2 见一见相应

  1. static void ThreaRun(ThreadData *td)
  2.     {
  3.         // 先简单处理
  4.         int sockfd = td->_sockfd;
  5.         char buffer[10240];
  6.         int n = recv(sockfd, buffer, sizeof(buffer), 0);
  7.         if (n > 0)
  8.         {
  9.             buffer[n] = 0;
  10.             std::cout << buffer << std::endl;
  11.             // 返回响应
  12.             std::string text = "Hello World";                  // 响应的内容
  13.             std::string response_line = "HTTP/1.0 200 OK\r\n"; // 响应行
  14.             std::string response_header = "Content-Length:";   // 响应报文
  15.             response_header += std::to_string(text.size());    // 内容的长度
  16.             response_header += "\r\n";
  17.             std::string bank_line = "\r\n"; // 空行
  18.             std::string response = response_line;
  19.             response += response_header;
  20.             response += bank_line;
  21.             response += text;
  22.             // 发送报文
  23.             send(td->_sockfd, response.c_str(), response.size(), 0);
  24.         }
  25.     }
复制代码


 通过简朴代码我们将字符串 "Hello World" 拼接到相应报文的正文部分,发送给客户端(欣赏器),而欣赏器通过表明,终极在界面上表现了 Hello World。 这也就对应我们前面的讲的相应报文内里有效载荷。
 2.2.1 路径剖析

着实我们还可以通过URL访问指定文件,就比如下面文件abc,也是说HTTP网络文件有很多,比如图片、视频、音频、JS文件、样式文本等。那么HTTP肯定就会有一个web根目次就犹如Linux的根目次。 



前面代码很挫,如果我们要更改网站的样式,每次我们都要静态的写入到我们服务器中,以是我们可以创建一个文件,将htlm写入到这个文件中,下次再改就不消改服务器了。 
 基于刚才讲的 我们直接就在历程当前目次创建一个文件夹 wwwroot 以后网站首页也好,图片视频也罢 直接就从这个wwwroot根目次访问。
 以是这段代码就不能这么写了。我们重新写一个类 HTTP哀求的类,然后对哀求做反序列化,拿到url 。
  1. const std::string wwwroot = "./wwwroot";
  2. const std::string sep = "\r\n";
  3. const std::string homepage = "index.html";
  4. class HttpRequest
  5. {
  6. public:
  7.     // 进行反序列化
  8.     void Deserialization(std::string req)
  9.     {
  10.         size_t pos = 0;
  11.         while ((pos = req.find(sep)) != std::string::npos)
  12.         {
  13.             size_t next_pos = pos + sep.size();
  14.             if (pos > 0)
  15.             { // 确保不是空字符串
  16.                 req_header.push_back(req.substr(0, pos));
  17.             }
  18.             req.erase(0, next_pos);
  19.         }
  20.         // 循环退出后,剩下的就是报文的正文部分
  21.         text = req;
  22.     }
  23.     //解析请求行
  24.     void Parse()
  25.     {
  26.         std::stringstream ss(req_header[0]);
  27.         ss >> method >> url >> http_version;
  28.         file_path = wwwroot;
  29.         if (url == "/" || url == "/index.html") // 访问根目录 就只返回网站首页
  30.         {
  31.             file_path += "/";
  32.             file_path += homepage;
  33.         }
  34.         else
  35.             file_path += url; // 访问其他路径
  36.     }
  37.     void DebugPrint()
  38.     {
  39.         for (auto &line : req_header)
  40.         {
  41.             std::cout << "--------------------------------" << std::endl;
  42.             std::cout << line << "\n\n";
  43.         }
  44.         std::cout << "method: " << method << std::endl;
  45.         std::cout << "url: " << url << std::endl;
  46.         std::cout << "http_version: " << http_version << std::endl;
  47.         std::cout << "file_path: " << file_path << std::endl;
  48.         std::cout << text << std::endl;
  49.     }
  50. public:
  51.     std::vector<std::string> req_header;
  52.     std::string text;
  53.     // 解析之后的结果
  54.     std::string method;
  55.     std::string url;
  56.     std::string http_version;
  57.     std::string file_path;
  58. };
复制代码
 在我们当前目次 新建wwwroot目次 然后再这个目次下创建 index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Document</title>
  7. </head>
  8. <body>
  9.     <h1>Hello world</h1>
  10.     <h1>Hello world</h1>
  11.     <h1>Hello world</h1>
  12.     <h1>Hello world</h1>
  13.     <h1>Hello world</h1>
  14.     <h1>Hello world</h1>
  15.     <h1>Hello world</h1>
  16.     <h1>Hello world</h1>
  17. </body>
  18. </html>
复制代码
在HttpServer这个类中编写下面函数
  1. // 根据解析的路径确定打开那个文件
  2.     static std::string ReadHtmlContent(const std::string &htmlpath)
  3.     {
  4.         std::ifstream in(htmlpath); // 这里只能打开字符串文件,图片不行。
  5.         if (!in.is_open())
  6.         {
  7.             return "404";
  8.         }
  9.         std::string content;
  10.         std::string line;
  11.         while (std::getline(in, line))
  12.         {
  13.             content += line;
  14.         }
  15.         in.close();
  16.         return content;
  17.     }
复制代码
在原来的TreadRun举行变形得到我们想要效果 
  1. static void ThreaRun(ThreadData *td)
  2.     {
  3.         // 先简单处理
  4.         int sockfd = td->_sockfd;
  5.         char buffer[10240];
  6.         int n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
  7.         if (n > 0)
  8.         {
  9.             buffer[n] = 0;
  10.             // std::cout << buffer << std::endl;
  11.             HttpRequest req;
  12.             req.Deserialization(buffer);
  13.             req.Parse();
  14.             req.DebugPrint();
  15.             // 返回响应
  16.             std::string text = ReadHtmlContent(req.file_path); // 响应的内容
  17.             std::string response_line = "HTTP/1.0 200 OK\r\n"; // 响应行
  18.             std::string response_header = "Content-Length:";   // 响应报文
  19.             response_header += std::to_string(text.size());    // 内容的长度
  20.             response_header += "\r\n";
  21.             std::string bank_line = "\r\n"; // 空行
  22.             std::string response = response_line;
  23.             response += response_header;
  24.             response += bank_line;
  25.             response += text;
  26.             // 发送报文
  27.             send(td->_sockfd, response.c_str(), response.size(), 0);
  28.         }
  29.         delete td;
  30.     }
复制代码
这里我们读是有bug的 这里我们就假设读到的是一个完备的报文。 
 

如许做的利益就是,我们访问网站首页,就只会返回网站首页,而不会返回根目次下的全部内容。 同理访问其他的路径也是一样。
这时我们再在wwwroot 创建a b c 文件夹 分别在这3个目次中创建 html文件
 

 我们添加链接就可以跳转 其他网页

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


通过前面的演示,服务器打印的哀求都是GET方法,也就是说我们要获取服务器的某个资源根本用的都是GET方法。
 那POST也可获取,那POST与GET获取有什么差别? 
不要忘记了 ,我们作为客户端除了哀求服务器的资源,也是可以向服务器提交数据的。 就比如我们在百度搜刮东西时,搜刮关键字linux 提交给百度服务器。
 再比如登岸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如下面所示
 从这个图片我们可以看到 用户 是zhangsan 暗码 123456。 这也验证了 我们前面的讲的GET方法提交数据不安全。
从这个URL看 以?为分隔符,?前面的如果是个可实验步调 而?反面是参数。那么我们就可以创建子历程 做步调更换而这个步调更换可以是登岸认证,插入数据库,搜刮等。
 我们改成post方法 参数通过了哀求体(正文)传输。
4. HTTP状态码 


这里100开头和200开头没什么好说的,我们在写相应的时间 就是 200 OK 标识乐成,我们再说400开头的。
 我们访问百度 通过URL指定访问路径a/b/c出现了下面的界面

 也就是传说中404 你访问的页面不存在。基于如许我们也可以写一个err.html。究竟这个天下上的服务器不大概搜集到全部资源,客户端访问的东西我们没有,但是也要相应。
那么前面的代码我们就要改一改
  1. static std::string ReadHtmlContent(const std::string &htmlpath)
  2.     {
  3.         std::ifstream in(htmlpath); // 这里只能打开字符串文件,图片不行。
  4.         if (!in.is_open())
  5.         {
  6.             return ""; //之前返回404 现在返回空串
  7.         }
复制代码
相应报文对应也要改一改
  1.             std::string text = ReadHtmlContent(req.file_path); // 响应的内容
  2.             bool ok = true;
  3.             if (text.empty())
  4.             {
  5.                 ok = false;
  6.                 std::string err_html = wwwroot;
  7.                 err_html += "/err.html";
  8.                 text = ReadHtmlContent(err_html);
  9.             }
  10.             std::string response_line;
  11.             if (ok)
  12.                 response_line = "HTTP/1.0 200 OK\r\n"; // 响应行
  13.             else
  14.                 response_line = "HTTP/1.0 404 Not Found\r\n";
复制代码
这时我们在wwwroot目次下添加err.html文件,404前端代码 网上任意找一个过来CV一下。
 源代码我在网上找了一个,cv过来 ,现在我们运行试试

 对于5开头的,那一样平常都是服务器的题目,设置堕落了,资源堕落了等。我们另有有一个3开头的状态码没有说
300开头的叫做重定向 一样平常有两种 一种 302 临时重定向 一种是 301永世重定向。
说人话那就是说 原来我们访问的是我们的网站,效果访问的是其他网站。
那什么时间用临时?
不知道各人登岸认证的时间,是不是跳转了其他页面,而这个页面就是临时重定向。
永世不消多说了,从前网站老化,不消了。跳转到新的网站
下面我对报文举行变形 改成重定向 
  1.    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); // 响应的内容
  2.             bool ok = true;
  3.             if (text.empty())
  4.             {
  5.                 ok = false;
  6.                 std::string err_html = wwwroot;
  7.                 err_html += "/err.html";
  8.                 text = ReadHtmlContent(err_html);
  9.             }
  10.             std::string response_line;
  11.             if (ok)
  12.                 response_line = "HTTP/1.0 200 OK\r\n"; // 响应行
  13.             else
  14.                 response_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()函数中 从前是读文本,但是图片和视频是二进制的,从前的读法就不可了,改为二进制来读。 
改造子女码 

  1. #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;        }        else            file_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); // 响应的内容
  2.             bool ok = true;
  3.             if (text.empty())
  4.             {
  5.                 ok = false;
  6.                 std::string err_html = wwwroot;
  7.                 err_html += "/err.html";
  8.                 text = ReadHtmlContent(err_html);
  9.             }
  10.             std::string response_line;
  11.             if (ok)
  12.                 response_line = "HTTP/1.0 200 OK\r\n"; // 响应行
  13.             else
  14.                 response_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"];        else            return 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. 




回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表