ToB企服应用市场:ToB评测及商务社交产业平台

标题: 【Linux网络编程】HTTP协议 [打印本页]

作者: 篮之新喜    时间: 2024-6-22 13:06
标题: 【Linux网络编程】HTTP协议

   喜欢的点赞,收藏,关注一下把!

  目前根本socket写完,一般服务器筹划原则和方式(多进程、多线程、线程池)+常见的各种场景,自界说协议+序列化和反序列化都已经学过了。
那有没有人已经针对常见场景,早就已经写好了常见的协议软件,供我们使用呢?
固然了,最典范的HTTP。未来它们做的事情和我们从前做的事情是一样的!只不过HTTP是结合它的应用场景来谈的。
虽然我们现在关于http协议不知道它是什么。但我们知道你的http协议内里必有套接字,必有序列化和反序列的机制,也必须添加报头和分离报头的过程等等。
在说这个http协议之前,我们先做一个准备工作,在网络基础一我们知道OSI分七层前三层分别是应用层,表现层,会话层。在TCP/IP协议这三层合起来算一层应用层。

在上篇文章说过我们写的网络版计数器软件分成三层。第一层获取链接多进程大概多线程或线程池进行处置处罚,第二层handlerEntery进行读取完整报文、提取有效载荷、序列化反序列化等一系列工作,第三层进行业务处置处罚callback。其实我们自己写的的第一层就是会话层,第二层就是表现层,第三层callback进行对应的业务逻辑处置处罚就是应用层。
OSI界说成七层,缘故原由就是后面写代码时每一层都少不了。OSI为什么没把这三层压成一层呢?在于表现层有自界说的方案、Json方案、protobuf方案、xml方案等等,如果它某种方案写到内核里就固定下来了,而现实上我们并没有固定。
http作为应用层协议它也要办理刚才说的三个工作。
1.认识URL

平时我们俗称的 “网址” 其实就是说的 URL
https://blog.csdn.net/fight_p/article/details/137103487
https -> 协议
blog.csdn.net -> 域名,域名等价于IP,这里会有一个域名剖析的工作(把域名这个字符串结构转化成IP所在),IP标识一台网络主机(Linux体系)
/fight_p/article/details/137103487 -> 文件路径
URL的作用就是,欣赏器通过拿着这个URL,找到这台Linux机器然后在这台机器上找对应的文件。把文件打开返回给欣赏器。

现实上URL有多种格式。

为什么我们刚才URL没有端口呢?
我们对应的协议是和端口号强相关的。服务器端口号是一个众所周知的端口号,一般不能任意改变,刚才URL没有写出来并不代表没有,因为客户端访问服务器端一定要知道服务端的是IP所在和端口号。这里没有但欣赏器在真正请求时会给我们填上,欣赏器结合我们用的协议就知道用的端口号是多少。默认一些协议对应的端口号:
http:80
https:443

这里圈起来的是什么东西呢?
其实它并不是根目次,而是web根目次,一般而言,可以是Linux下的任意一个目次。这个任意目次必须要有对应请求的资源。(后面写代码的时候具体解释)
http是文本传输协议。说白了http协议就是从服务器拿下来对应的 “资源”。
什么是资源呢?
凡是你在网络中看到的都是资源!(如:刷的短视频是视频文件、淘宝上看到的图片是图片文件,网易云听的音乐是音乐文件。。。)
全部的资源都可以看做资源文件,在服务器中都是以文件形式存在磁盘中的文件体系中的某一个路径下,以是须要Linux体系的路径结构。当要的时候把磁盘中对应的路径所标识的资源返回给客户端。以是http协议本质上是从服务器上拿文件资源。
因为文件资源的种类特别多,http都能搞定,以是http是 “超文本传输协议”
2.urlencode和urldecode

像 / ? : 等这样的字符, 已经被url当做特别意义明白了。因此这些字符不能随意出现。比如, 某个参数中须要带有这些特别字符,就必须先对特别字符进行转义。
如果原封不动会干扰url的正确剖析,以是欣赏器这端必须先编码然后提给服务端。

那如何编码&&如何解码,须要自己做吗?
编码转义的规则如下:
将须要转码的字符转为16进制。一个字符占8个比特位,然后从右到左,取4个比特位转成16进制数(不足4位直接处置处罚),每2位16机制数前面加上%,编码成%XY格式
解码就是把收到的这些东西转成2进制的格式
我们写服务端如果从0开始写这个解码工作一定要自己做,但是我们在网上搜索url decode C++源码,当一个CV工程师。
如何验证这个过程?
urlencode工具
3.HTTP协议格式

接下面我们先从宏观方面说说http请求和响应的格式
http请求是以举动单元的一种协议。
第一行
第一列:请求的方法
第二列:url
第三列:请求的http的版本
常见的版本:http 1.0 、1.1、 2.0版本
行以\r\n为分隔符,大概以\n

第二大块也是以举动单元的,只不过这一大块会存在多行,多行包罗http请求各种属性,属性几乎都以name:value的样子
而我们又把第一行称为请求行,第二大快称为请求报头

第三大块,特别要强调一下是http请求的空行,相当重要

最后一块是请求正文,可以没有也可以有,如果未来想把自己要登录账号可以把账号和密码放在正文,也就是说想给服务器提上去的参数就放在正文

上面四大块就是http request请求的完整报文。它会通过tcp链接,向服务器发送过去。
http响应格式几乎和请求格式是一样的,也分四部分。
第一行是状态行,也有三列构成,中心由空格分开。
第一列:是http版本
第二列:状态码,如200、400、302、307、500、404,如404我们常见的页面不存在。状态码用来表现请求结果是否正确,就如网络版本计数器我们写的exitcode。
第三列:状态码形貌 如404 -> Not Found、200 -> OK

第二大块也是由多行构成的,叫做响应报文

第三块同样也是空行

第四大块,在响应里会高频的出现,叫做响应正文(有效载荷),通常带html/css/js/图片/视频/音频等

这四大部分构成了响应报文。在根据tcp链接socket,向客户端返回响应。未来全部http通信都接纳的是这种方案进行通信的。

现在我们在谈一谈通信细节题目
1.请求和响应怎么保证应用层完整读取完毕了呢?
首先我们发现http请求都是字符串按举动单元,以是
2.请求和响应是怎么做到序列化和反序列化的?
http是用的特别字符自己实现的。http序列化什么都不做直接发就好了,反序列化 :第一行+请求/响应报头,只要按照\r\n将字符串1->n即可!不消借助任何东西如Json
protobuf等。而正文序列化反序列也不消做直接发送就行了。如果你的正文携带结构化数据就要自己处置处罚了。
接下来我们写代码的方式,验证上面说的东西。
从前写udp和tcp我们都写过服务端用过套接字,这里照旧直接拿过来用。
4.HTTP协议根本工作流程

Protocol.hpp
  1. #pragma once
  2. #include <iostream>
  3. #include <string>
  4. using namespace std;
  5. class httpRequest
  6. {
  7. public:
  8.     httpRequest(){};
  9.     ~httpRequest(){};
  10. public:
  11.     string inbuffer;//缓冲区
  12.    
  13.     //下面我们先不管,未来都可以细分,序列化反序列也都可以写到类中,我们这里写简单一点主要看一下http的细节
  14.     // string reqline;//请求行
  15.     // vector<std::string> reqheader;//报头
  16.     // string body;//请求正文
  17.    
  18.     //第一行细分
  19.     //string method;
  20.     //string url;
  21.     //string httpversion;
  22. };
  23. class httpResponse
  24. {
  25. public:
  26.     string outbuffer;//缓冲区
  27. };
复制代码
httpServer.hpp
  1. #pragma once
  2. #include "protocol.hpp"
  3. #include <iostream>
  4. #include <string>
  5. #include <stdlib.h>
  6. #include <cstring>
  7. #include <sys/types.h>
  8. #include <sys/socket.h>
  9. #include <netinet/in.h>
  10. #include <arpa/inet.h>
  11. #include <unistd.h>
  12. #include <sys/wait.h>
  13. #include <signal.h>
  14. #include <functional>
  15. using namespace std;
  16. enum
  17. {
  18.     USAGG_ERR = 1,
  19.     SOCKET_ERR,
  20.     BIND_ERR,
  21.     LISTEN_ERR
  22. };
  23. const int backlog = 5;
  24. typedef function<void(const httpRequest&,httpResponse&)> func_t;
  25. void handlerEntery(int sock,func_t callback)
  26. {
  27.     // 1. 读到完整的http请求
  28.     // 2. 反序列化
  29.     // 3. httprequst, httpresponse, callback(req, resp)
  30.     // 4. resp序列化
  31.     // 5. send
  32. }
  33. class httpServer
  34. {
  35. public:
  36.     httpServer(const uint16_t port) : _port(port), _listensock(-1)
  37.     {
  38.     }
  39.     void initServer()
  40.     {
  41.         // 1.创建socket文件套接字对象
  42.         _listensock = socket(AF_INET, SOCK_STREAM, 0);
  43.         if (_listensock < 0)
  44.         {
  45.             exit(SOCKET_ERR);
  46.         }
  47.         // 2.bind 绑定自己的网络消息 port和ip
  48.         struct sockaddr_in local;
  49.         memset(&local, 0, sizeof(local));
  50.         local.sin_family = AF_INET;
  51.         local.sin_port = htons(_port);
  52.         local.sin_addr.s_addr = INADDR_ANY; // 任意地址bind,服务器真实写法
  53.         if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
  54.         {
  55.             exit(BIND_ERR);
  56.         }
  57.         // 3.设置socket为监听状态
  58.         if (listen(_listensock, backlog) < 0) // backlog  底层链接队列的长度
  59.         {
  60.             exit(LISTEN_ERR);
  61.         }
  62.     }
  63.     void start(func_t func)
  64.     {
  65.         // 子进程退出自动被OS回收
  66.         signal(SIGCHLD, SIG_IGN);
  67.         for (;;)
  68.         {
  69.             // 4.获取新链接
  70.             struct sockaddr_in peer;
  71.             socklen_t len = (sizeof(peer));
  72.             int sock = accept(_listensock, (struct sockaddr *)&peer, &len); // 成功返回一个文件描述符
  73.             if (sock < 0)
  74.             {
  75.                 continue;
  76.             }
  77.             // 5.通信   这里就是一个sock,未来通信我们就用这个sock,tcp面向字节流的,后序全部都是文件操作!
  78.             // version2 多进程信号版
  79.             int fd = fork();
  80.             if (fd == 0)
  81.             {
  82.                 close(_listensock);
  83.                 handlerEntery(sock,func);
  84.                 close(sock);
  85.                 exit(0);
  86.             }
  87.             close(sock);
  88.         }
  89.     }
  90.     ~httpServer()
  91.     {
  92.     }
  93. private:
  94.     uint16_t _port;
  95.     int _listensock;
  96. };
复制代码
httpServer.cc
  1. #include "httpServer.hpp"
  2. #include <memory>
  3. void Usage(string proc)
  4. {
  5.     cout << "\nUsage:\n\t" << proc << " local_port\n\n";
  6. }
  7. void Get(const httpRequest &req, httpResponse &resp)
  8. {
  9. }
  10. // ./httpserver port
  11. int main(int argc, char *argv[])
  12. {
  13.     if (argc != 2)
  14.     {
  15.         Usage(argv[0]);
  16.         exit(USAGG_ERR);
  17.     }
  18.     uint16_t serverport = atoi(argv[1]);
  19.     unique_ptr<httpServer> tsv(new httpServer(serverport));
  20.     tsv->initServer();
  21.     tsv->start(Get);
  22.     return 0;
  23. }
复制代码
我们发现udp、tcp、http全部的底层逻辑都是差不多的,而我们只要写上层逻辑就好了。
这里我们重要说原理,下面1-5的工作我们都不做了,以是httpRequest,httpResponse也都给一个缓冲区就行了。
  1. void handlerEntery(int sock,func_t callback)
  2. {
  3.     // 1. 读到完整的http请求
  4.     // 2. 反序列化
  5.     // 3. httprequst, httpresponse, callback(req, resp)
  6.     // 4. resp序列化
  7.     // 5. send
  8.        
  9.         char buffer[4096];
  10.     httpRequest req;
  11.     httpResponse resp;
  12.     ssize_t n=recv(sock,buffer,sizeof(buffer)-1,0);//大概率我们直接就能读取到完整的http请求
  13.     if(n>0)
  14.     {
  15.         buffer[n]=0;
  16.         req.inbuffer=buffer;
  17.         callback(req,resp);
  18.         send(sock,resp.outbuffer.c_str(),resp.outbuffer.size(),0);
  19.     }
  20. }
复制代码
  1. void Get(const httpRequest &req, httpResponse &resp)
  2. {
  3.     cout << "----------------http start---------------" << endl;
  4.     cout << req.inbuffer << endl;
  5.     cout << "----------------http end-----------------" << endl;
  6. }
复制代码
下面我们用欣赏器充当客户端发起请求看一下结果

有人大概说就发起一次请求那后面怎么收到这么多,欣赏器内部请求资源大概是一个多线程版的客户端,并且它请求大概不仅仅请求这个网站的网页,还大概获取网站的图标其他一些资源,它大概并行的向服务器发起请求以是看到这么多请求。
因为我们什么都没有返回,以是网页什么都看不到。
下面是我们的请求报文,服务器在打印的时候我们多打印了一个endl,以是会看到两个空行。现实应该是一个空行。

第一行第一列是请求方法,默认是GET方法。
第二列url,这里是/,是因为我们只是告诉欣赏器要访问某个ip的某个port并没有告诉请求什么资源,默认是/(web根目次),它并不是把全部资源都给用户。http请求如果没有请求指定的资源,web server 会有默认的首页!
比如说,我们默认请求百度www.baidu.com,后面什么都没带没告诉要访问百度那个网页,按回车默认就把百度首页返回了。

第三列是http请求的版本,这里欣赏器默认用的1.1的版本,未来响应返回的时候也要写版本。这里有个细节,http请求会交换bs通信两边的协议版本。
我们在用一些软件的时候,有的软件会提醒你就行升级更新,有的人会升级有的人不会,一定会存在多种客户端版本的环境,以是服务器大概面临多种不同版本的客户端,如果是老版本的客户端一定用的是老版本的服务,服务端给它提供老版本的服务,同理是新版本客户端服务端给它提供的是新版本的服务,因此须要服务器提供对应的版本服务。
接下面一堆东西就是请求报头。

第一行Host:是这个请求未来是要发给那个服务端的。未来客户端这个请求去到一个服务器,但这个服务器不提供服务,然后该服务器拿着我的Host知道我要请求谁,它替我去请求。这个中心折务器叫做署理服务器。
第二行Connection:关于后面说长短链接的时候再说,目前这个是支持长链接的。
第三行是支持协议升级,http是可以被升级的,http是cs/bs模式请求响应的模式,也就是说必须是客户端主动发起请求,服务器才气给它响应。大概在特别场景下让服务器和客户端互发消息,客户端没有发任何消息,服务器主动发信息给客户端。
第四行User-Agent:是客户端的信息,如操作体系是什么,欣赏版本等
下面几行Accept:是告诉服务器,客户端能接收什么文档格式、支持压缩格式、支持编码格式等等
请求部分我们验证完了,下面我们再看一个响应。
报头我们暂时不要后面逐步填,正文部分我们搞一个网页。
网页不会写,可以搜一下w3cschool html教程

这里我们先写到Get函数里,后面我们在分离
  1. void Get(const httpRequest &req, httpResponse &resp)
  2. {
  3.     cout << "----------------http start---------------" << endl;
  4.     cout << req.inbuffer << endl;
  5.     cout << "----------------http end-----------------" << endl;
  6.      string respline = "HTTP/1.1 200 OK\r\n";
  7.     // string respheader;
  8.      string respblank = "\r\n";
  9.        
  10.         //随便搞个网页
  11.      string body="<html lang="en"><head><meta charset="UTF-8"><title>for test</title><h1>hello world</h1></head><body><p>北京交通广播《一路畅通》“交通大家谈”节目,特邀北京市交通委员会地面公交运营管理处处长赵震、北京市公安局公安交通管理局秩序处副处长 林志勇、北京交通发展研究院交通规划所所长 刘雪杰为您解答公交车专用道6月1日起社会车辆进出公交车道须注意哪些?</p></body></html>";
  12.    
  13.         //序列化
  14.      resp.outbuffer += respline;
  15.      resp.outbuffer += respblank;
  16.      resp.outbuffer += body;
  17. }
复制代码

虽然我们在响应的时候没有带响应报头,但是我们的欣赏器依旧是能识别的,这里想说的是现在欣赏器很智能了,可以不消告诉它正文是什么也可以根据正文内容识别这是什么东西,但是有的欣赏器做不到。这里我们用的是chrome欣赏器。
但是你欣赏器能识别是你的事情,我们照旧要把自己的事做好,告诉欣赏器我给你发回的是什么东西。
以是我们要加一个报头内里可以带一些属性。如Content-Type ,告诉别人返回的是什么资源。网上可以搜一下Content-Type 对照表
这里我们返回的是一个网页,类型是text/html

  1. void Get(const httpRequest &req, httpResponse &resp)
  2. {
  3.     cout << "----------------http start---------------" << endl;
  4.     cout << req.inbuffer << endl;
  5.     cout << "----------------http end-----------------" << endl;
  6.      string respline = "HTTP/1.1 200 OK\r\n";
  7.      //报头
  8.      string respheader = "Content-Type: text/html\r\n";
  9.      
  10.      string respblank = "\r\n";
  11.        
  12.         //随便搞个网页
  13.      string body="<html lang="en"><head><meta charset="UTF-8"><title>for test</title><h1>hello world</h1></head><body><p>北京交通广播《一路畅通》“交通大家谈”节目,特邀北京市交通委员会地面公交运营管理处处长赵震、北京市公安局公安交通管理局秩序处副处长 林志勇、北京交通发展研究院交通规划所所长 刘雪杰为您解答公交车专用道6月1日起社会车辆进出公交车道须注意哪些?</p></body></html>";
  14.    
  15.         //序列化
  16.      resp.outbuffer += respline;
  17.          resp.outbuffer += respheader;
  18.      resp.outbuffer += respblank;
  19.      resp.outbuffer += body;
  20. }
复制代码
可以看到这就是我们的响应


现在我们大概就知道了http的请求和响应它的格式了,下面我们继续完善这个代码,继续挖出焦点的东西。
首先办理两个题目:
1.服务器和网页分离,然后通过服务器把网页返回
2.前面我们说过,我们请求没带路径url会给我们一个/(默认web根目次),这个根目次并不是liunx服务器下的根目次,而是web服务器自己的根目次,怎么明白呢?
再说前面的题目,我们在加一个方法类,未来可以把收到的报文做切割。
  1. #pragma once
  2. #include <iostream>
  3. #include <string>
  4. using namespace std;
  5. class Util
  6. {
  7. public:
  8.     // xxx yyy zzz\r\naaa
  9.     static string GetOneline(string &buffer, const string &sep)
  10.     {
  11.         auto pos = buffer.find(sep);
  12.         if (pos == string::npos)
  13.             return "";
  14.         string sub = buffer.substr(0, pos);
  15.         return sub;
  16.     }
  17. };
复制代码
  1. const string sep = "\r\n";//切割符
  2. class httpRequest
  3. {
  4. public:
  5.     httpRequest(){};
  6.     ~httpRequest(){};
  7.     void parse()
  8.     {
  9.         // 1. 从inbuffer中拿到第一行,分隔符\r\n
  10.         string line = Util::GetOneline(inbuffer, sep);
  11.         if (line.empty())
  12.             return;
  13.             
  14.         // 2. 从请求行中提取三个字段
  15.         istringstream iss(line);
  16.         iss >> method >> url >> httpversion;
  17.     }
  18. public:
  19.     string inbuffer;
  20.     // string reqline;
  21.     // vector<std::string> reqheader;
  22.     // string body;
  23.     string method;
  24.     string url;
  25.     string httpversion;
  26. };
复制代码
  1. void handlerEntery(int sock,func_t callback)
  2. {
  3.     // 1. 读到完整的http请求
  4.     // 2. 反序列化
  5.     // 3. httprequst, httpresponse, callback(req, resp)
  6.     // 4. resp序列化
  7.     // 5. send
  8.     char buffer[4096];
  9.     httpRequest req;
  10.     httpResponse resp;
  11.     ssize_t n=recv(sock,buffer,sizeof(buffer)-1,0);// 大概率我们直接就能读取到完整的http请求
  12.     if(n>0)
  13.     {
  14.         buffer[n]=0;
  15.         req.inbuffer=buffer;
  16.         
  17.         req.parse();
  18.         callback(req,resp);
  19.         send(sock,resp.outbuffer.c_str(),resp.outbuffer.size(),0);
  20.     }
  21. }
复制代码
  1. void Get(const httpRequest &req, httpResponse &resp)
  2. {
  3.     cout << "----------------http start---------------" << endl;
  4.     cout << req.inbuffer << endl;
  5.     cout << "method: " << req.method << endl;
  6.     cout << "url: " << req.url << endl;
  7.     cout << "httpversion: " << req.httpversion << endl;
  8.     cout << "----------------http end-----------------" << endl;
  9.      string respline = "HTTP/1.1 200 OK\r\n";
  10.      //报头
  11.      string respheader = "Content-Type: text/html\r\n";
  12.      
  13.      string respblank = "\r\n";
  14.        
  15.         //随便搞个网页
  16.      string body="<html lang="en"><head><meta charset="UTF-8"><title>for test</title><h1>hello world</h1></head><body><p>北京交通广播《一路畅通》“交通大家谈”节目,特邀北京市交通委员会地面公交运营管理处处长赵震、北京市公安局公安交通管理局秩序处副处长 林志勇、北京交通发展研究院交通规划所所长 刘雪杰为您解答公交车专用道6月1日起社会车辆进出公交车道须注意哪些?</p></body></html>";
  17.    
  18.         //序列化
  19.      resp.outbuffer += respline;
  20.          resp.outbuffer += respheader;
  21.      resp.outbuffer += respblank;
  22.      resp.outbuffer += body;
  23. }
复制代码
经过测试,一行能拿到,未来用个while循环,请求行+请求报文都可以拿到了。

现在我们解释一下什么是web根目次
现实上未来一个web服务器写好之后,可不仅仅有这些代码。每一个web服务器都有web根目次,未来全部图片、视频、音频等各种web资源都在这个目次下,按照目次结构组织号好,未来想请求资源就从url请求。那如何保证按照我们的需求在指定路径下去寻找呢?
  1. const string sep = "\r\n";
  2. //这里我们默认写一个web根目录
  3. const string default_root = "./wwwroot";//因为请求url默认会加上/开始,所以./wwwroot后面不要/
  4. const string home_page = "index.html";//默认首页
  5. class httpRequest
  6. {
  7. public:
  8.         //。。。
  9.     void parse()
  10.     {
  11.         // 1. 从inbuffer中拿到第一行,分隔符\r\n
  12.         string line = Util::GetOneline(inbuffer, sep);
  13.         if (line.empty())
  14.             return;
  15.         // 2. 从请求行中提取三个字段
  16.         istringstream iss(line);
  17.         iss >> method >> url >> httpversion;
  18.         // 3. 添加web默认路径
  19.         path = default_root;// ./wwwroot
  20.         path += url;//  ./wwwroot/a/b/c.html 请求具体资源
  21.         //刚才我们看到url只有/的样子,这里也要拼  ./wwwroot/
  22.         //但是这里就遭了,还是不知道访问的那个具体的资源
  23.         //其实对于一个服务器来说,它有自己的主页信息
  24.         //如果访问的是根目录,就把首页给拼上
  25.         if (path[path.size() - 1] == '/')//判断是否是根目录
  26.             path += home_page;
  27.             
  28.      }
  29.      
  30. public:
  31.         string inbuffer;
  32.        
  33.         string method;
  34.         string url;
  35.         string httpversion;
  36.         string path;
  37. };   
复制代码


在url是这样请求的,但是现实上web服务器它会自己拼前缀,带着这个路径去找对应资源文件,如果有就返回,没有就返回404。
接下来我们做服务器和网页分离
我们让body从文件中读取,因此添加一个readFile接口,把文件内容全部读到body里。
  1. class Util
  2. {
  3. public:
  4.     // xxx yyy\r\nzzz
  5.     static string GetOneline(string &buffer, const string &sep)
  6.     {
  7.         auto pos = buffer.find(sep);
  8.         if (pos == string::npos)
  9.             return "";
  10.         string sub = buffer.substr(0, pos);
  11.         return sub;
  12.     }
  13.     static bool readFile(const string &path, string &body)
  14.     {
  15.         ifstream ofs(path,ios_base::binary);//二进制方式读
  16.         if (!ofs.is_open())
  17.             return false;
  18.         string line;
  19.         while(getline(path, line))
  20.         {
  21.             body += line;
  22.         }
  23.         ofs.close();
  24.         return true;
  25.     }
  26. };
复制代码

  1. void Get(const httpRequest &req, httpResponse &resp)
  2. {
  3.     cout << "----------------http start---------------" << endl;
  4.     cout << req.inbuffer << endl;
  5.     cout << "method: " << req.method << endl;
  6.     cout << "url: " << req.url << endl;
  7.     cout << "httpversion: " << req.httpversion << endl;
  8.     cout << "----------------http end-----------------" << endl;
  9.     string respline = "HTTP/1.1 200 OK\r\n";
  10.     //报头
  11.     string respheader = "Content-Type: text/html\r\n";
  12.    
  13.     string respblank = "\r\n";
  14.         string body;
  15.     if (!Util::readFile(req.path, body))
  16.     {
  17.         Util::readFile(html_404, body); // 读取失败返回404,一定能成功
  18.     }
  19.    
  20.         //序列化
  21.      resp.outbuffer += respline;
  22.          resp.outbuffer += respheader;
  23.      resp.outbuffer += respblank;
  24.      resp.outbuffer += body;
  25. }
复制代码


现在我们给网页添加一下功能,比如说网页是支持点击然后跳转链接的
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7.     <title>我的首页</title>
  8. </head>
  9. <body>
  10.     <h1>我是网站的首页</h1>
  11.     <a href="/test/a.html">新闻</a>
  12.     <a href="/test/b.html">电商</a>
  13. </body>
  14. </html>
复制代码


点击新闻链接,http自动拼接然后重新请求

这也就是前端和后端。
这里我们在首页在加一张图片。
先把图片上传到web根目次下


  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7.     <title>我的首页</title>
  8. </head>
  9. <body>
  10.     <h1>我是网站的首页</h1>
  11.     <a href="/test/a.html">新闻</a>
  12.     <a href="/test/b.html">电商</a>
  13.     <img src="/image/1.jpg" alt="向日葵">
  14. </body>
  15. </html>
复制代码
未来我们请求网页,它要做两件事情,第一要把这个网页本身加载出来,第二欣赏器识别到网页还有一个资源叫做图片,以是它一要把网页给下载下来,二还要把网页中要用的图片下载下来,两个资源一合并组合才气给我们形成一个完整的网页信息!

一个用户看到的网页结果,大概是多个资源组合而成的!以是要获取一张完整的网页结果,我们的欣赏器一定会发起多次http请求
是网页Content-Type 类型text/html,图片呢?
以是我们须要不同的path所标定文件的类型返回特定不同的资源。

我们要正确的给客户端返回资源类型,我们首先要自己知道!全部的资源都有后缀!!
  1. class httpRequest
  2. {
  3. public:
  4.     httpRequest(){};
  5.     ~httpRequest(){};
  6.     void parse()
  7.     {
  8.         // 1. 从inbuffer中拿到第一行,分隔符\r\n
  9.         string line = Util::GetOneline(inbuffer, sep);
  10.         if (line.empty())
  11.             return;
  12.         // 2. 从请求行中提取三个字段
  13.         istringstream iss(line);
  14.         iss >> method >> url >> httpversion;
  15.         // 3. 添加web默认路径
  16.         path = default_root + url;
  17.         if (path[path.size() - 1] == '/')
  18.             path += home_page;
  19.         // 4. 获取path对应的资源后缀
  20.         // ./wwwroot/index.html
  21.         // ./wwwroot/test/a.html
  22.         // ./wwwroot/image/1.jpg
  23.         auto pos=path.rfind(".");
  24.         if(pos == string::npos) suffix=".html";
  25.         suffix=path.substr(pos);
  26.     }
  27. public:
  28.     string inbuffer;
  29.     string method;
  30.     string url;
  31.     string httpversion;
  32.     string path;
  33.     string suffix;
  34. };
复制代码
  1. string suffixtodes(const string &suff)
  2. {
  3.     string type = "Content-Type: ";
  4.     if (suff == ".html")
  5.         type += "text/html";
  6.     else if (suff == ".jpg")
  7.         type += "application/x-jpg";
  8.     type += "\r\n";
  9.     return type;
  10. }
  11. void Get(const httpRequest &req, httpResponse &resp)
  12. {
  13.     cout << "----------------http start---------------" << endl;
  14.     cout << req.inbuffer << endl;
  15.     cout << "method: " << req.method << endl;
  16.     cout << "url: " << req.url << endl;
  17.     cout << "httpversion: " << req.httpversion << endl;
  18.     cout << "suffix: " << req.suffix << endl;
  19.     cout << "----------------http end-----------------" << endl;
  20.     string respline = "HTTP/1.1 200 OK\r\n";
  21.     //报头
  22.     //string respheader = "Content-Type: text/html\r\n";
  23.     string respheader = suffixtodes(req.suffix);
  24.     string respblank = "\r\n";
  25.         string body;
  26.     if (!Util::readFile(req.path, body))
  27.     {
  28.         Util::readFile(html_404, body); // 读取失败返回404,一定能成功
  29.     }
  30.    
  31.         //序列化
  32.      resp.outbuffer += respline;
  33.          resp.outbuffer += respheader;
  34.      resp.outbuffer += respblank;
  35.      resp.outbuffer += body;
  36. }
复制代码
但是这个readFile还像刚才那样使用while循环读一行的方式读就有题目了,因为图片是二进制文件。大概图片太多数没读完整。以是上面代码目前不完整。我把下面题目办理了然后就可以完整把图片显示出来了。
这里有个题目,你要请求的资源大小是多少?以是我们在报头再加一个
Content-Length。
这里在介绍一个接口

path:你要访问的资源
buf:一个结构体,这个结构体包罗很多属性,其中有文件大小。
乐成时返回0,失败返回-1错误码被设置
  1. class httpRequest
  2. {
  3. public:
  4.     httpRequest(){};
  5.     ~httpRequest(){};
  6.     void parse()
  7.     {
  8.         // 1. 从inbuffer中拿到第一行,分隔符\r\n
  9.         string line = Util::GetOneline(inbuffer, sep);
  10.         if (line.empty())
  11.             return;
  12.         // 2. 从请求行中提取三个字段
  13.         istringstream iss(line);
  14.         iss >> method >> url >> httpversion;
  15.         // 3. 添加web默认路径
  16.         path = default_root + url;
  17.         if (path[path.size() - 1] == '/')
  18.             path += home_page;
  19.         // 4. 获取path对应的资源后缀
  20.         // ./wwwroot/index.html
  21.         // ./wwwroot/test/a.html
  22.         // ./wwwroot/image/1.jpg
  23.         auto pos=path.rfind(".");
  24.         if(pos == string::npos) suffix=".html";
  25.         suffix=path.substr(pos);
  26.                 // 5. 得到资源的大小
  27.         struct stat sif;
  28.         if(stat(path.c_str(),&sif) == 0)
  29.             size=sif.st_size;
  30.         else   
  31.             size=-1;
  32.     }
  33. public:
  34.     string inbuffer;
  35.     string method;
  36.     string url;
  37.     string httpversion;
  38.     string path;
  39.     string suffix;
  40.     int size;
  41. };
复制代码
  1. string suffixtodes(const string &suff)
  2. {
  3.     string type = "Content-Type: ";
  4.     if (suff == ".html")
  5.         type += "text/html";
  6.     else if (suff == ".jpg")
  7.         type += "application/x-jpg";
  8.     type += "\r\n";
  9.     return type;
  10. }
  11. void Get(const httpRequest &req, httpResponse &resp)
  12. {
  13.     cout << "----------------http start---------------" << endl;
  14.     cout << req.inbuffer << endl;
  15.     cout << "method: " << req.method << endl;
  16.     cout << "url: " << req.url << endl;
  17.     cout << "httpversion: " << req.httpversion << endl;
  18.     cout << "suffix: " << req.suffix << endl;
  19.     cout << "size: " << req.size << endl;
  20.     cout << "----------------http end-----------------" << endl;
  21.     string respline = "HTTP/1.1 200 OK\r\n";
  22.     //报头
  23.     //string respheader = "Content-Type: text/html\r\n";
  24.     string respheader = suffixtodes(req.suffix);
  25.    
  26.     if (req.size > 0)
  27.     {
  28.         respheader += "Content-Length: ";
  29.         respheader += to_string(req.size);
  30.         respheader += "\r\n";
  31.     }
  32.    
  33.     string respblank = "\r\n";
  34.     string body;
  35.     body.resize(req.size + 1);
  36.     if (!Util::readFile(req.path, body))
  37.     {
  38.         // 找不到文件,文件大小是-1,要返回404.html,因此重新计算大小
  39.         //否则body大小是-1,404.html文件内容就读不到body里
  40.         struct stat sif;
  41.         if (stat(html_404.c_str(), &sif) == 0)
  42.             body.resize(sif.st_size + 1);
  43.         Util::readFile(html_404, body); // 一定能成功
  44.     }
  45.    
  46.         //序列化
  47.      resp.outbuffer += respline;
  48.          resp.outbuffer += respheader;
  49.      resp.outbuffer += respblank;
  50.      resp.outbuffer += body;
  51. }
复制代码
这里我们让body开辟好空间,然后readFile使用read读文件。这样就能把文件全部内容读完整。
  1. class Util
  2. {
  3. public:
  4.     // xxx yyy\r\nzzz
  5.     static string GetOneline(string &buffer, const string &sep)
  6.     {
  7.         auto pos = buffer.find(sep);
  8.         if (pos == string::npos)
  9.             return "";
  10.         string sub = buffer.substr(0, pos);
  11.         return sub;
  12.     }
  13.     static bool readFile(const string &path, string &body)
  14.     {
  15.         ifstream ofs(path,ios_base::binary);
  16.         if (!ofs.is_open())
  17.             return false;
  18.         ofs.read((char *)body.c_str(), body.size());
  19.         ofs.close();
  20.         return true;
  21.     }
  22. };
复制代码

现实http协议根本工作流程就差不多了。
5.HTTP的方法

当我们在进行网络访问的时候,现实是有两种举动的。
上面都是获取资源的,但是我们想上传资源呢?完成登录等。。怎么做呢?
现实上我们一般在进行网站交互的时候,其实是通过表单的方式进行提交的

比如qq新闻

以是我们进行数据提交的的时候,本质前端要通过form表单提交的,欣赏器会自动将form表单中的内容转换成GET/POST方法请求
默认是GET方法。
接下来我们在自己的首页加个表单
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7.     <title>我的首页</title>
  8. </head>
  9. <body>
  10.     <h1>我是网站的首页</h1>
  11.     <a href="/test/a.html">新闻</a>
  12.     <a href="/test/b.html">电商</a>
  13.     <img src="/image/1.jpg" alt="向日葵">
  14.     <form action="/a/b/c.py" method="GET">
  15.         姓名:<br>
  16.         <input type="text" name="xname">
  17.         <br>
  18.         密码:<br>
  19.         <input type="password" name="ypwd">
  20.         <br><br>
  21.         <input type="submit" value="登陆">
  22.     </form>
  23. </body>
  24. </html>
复制代码
form是表单的整体结构,未来我们给服务器提供参数的话,为了很好提供参数的提取以是输入框它是KV的,未来不是在输入框写内容吗,但是我怎么知道你是想那个输入框写的内容呢,以是name就是这个输入框的名字。
其实它后面还跟着value是预设内容,就像你登录时某一行输入框提醒你这一行输入什么内容,不过暂时我们这里不要。
还有input type=“password”,会把我们输入的内容全部变成黑点,text我们输入什么就显示什么。
action代表你想把你的表单的数据提交给后端的哪一个服务大概网页大概路径下。这里我们任意写个,假设这个表单未来提交给一个py的脚本,你想提交给C++步伐里这都是可以的。
method当你提交表单时你想接纳什么方法提交,默认是GET
  1.     <form action="/a/b/c.py" method="GET">
  2.         姓名:<br>
  3.         <input type="text" name="xname" value="用户名">
  4.         <br>
  5.         密码:<br>
  6.         <input type="password" name="ypwd" value="="密码"">
  7.         <br><br>
  8.         <input type="submit" value="登陆">
  9.     </form>
复制代码

当我们登录时这个/a/b/c.py是不存在的,以是返回的是404,但不重要。
我们可以看到GET方法提参的时候,它会把我们要提交的参数拼到url的后面,b把参数以url方式进行提交,然后以?为分割符,左侧是要访问网站的资源,右侧是我们提上来的参数。

当我们用POST方法提参时,url后面只有我们要访问什么资源,后面没有提交的参数,我们的参数是以请求的正文提交参数的。

   GET和POST提参区别:
  GET通过url转达参数,具体:http://ip:port/xxx/yy?name1=value1&name2=value2
POST提交参数通过http请求正文提交参数
   那用那个呢?
  POST方法通过正文提交参数,以是一般用户看不到,私密性更好,但私密性!=安全性
GET方法不私密。
但无论是GET和POST方法,都不安全!要谈安全,必须加密 —> https
并且通过URL转达参数,注定不能太大,但是POST方法,通过正文,正文可以很大,甚至可以是其他的东西。
现在思索这样的题目:
1.我们提交给指定的路径,有什么意义??
不管是url提参照旧正文提参都是把参数给服务器,服务器未来拿着对应的参数实现登录注册等,凭什么?服务器首先能拿到这个数据,但是怎么让服务器处置处罚这个数据?其次服务器怎么知道未来想到对这个数据进行怎样的处置处罚,就是说你只是提交请求而服务器怎么知道登录照旧注册呢?
这一切的玄机都在这里,不管是GET和POST都要求在表单里把数据提交给某一个资源。

下面我们搞伪代码来说明,以GET为例
  1. class httpRequest
  2. {
  3. public:
  4.     httpRequest(){};
  5.     ~httpRequest(){};
  6.     void parse()
  7.     {
  8.         // 1. 从inbuffer中拿到第一行,分隔符\r\n
  9.         string line = Util::GetOneline(inbuffer, sep);
  10.         if (line.empty())
  11.             return;
  12.         // 2. 从请求行中提取三个字段
  13.         istringstream iss(line);
  14.         iss >> method >> url >> httpversion;
  15.                 //考虑提参的情况
  16.         // 2.1 /search?name=zhangsan&pwd=12345
  17.         // 通过?将左右进行分离
  18.         // 如果是POST方法,本来就是分离的!
  19.         // 左边PATH, 右边parm
  20.         // 3. 添加web默认路径
  21.         path = default_root + url;
  22.         if (path[path.size() - 1] == '/')
  23.             path += home_page;
  24.         // 4. 获取path对应的资源后缀
  25.         // ./wwwroot/index.html
  26.         // ./wwwroot/test/a.html
  27.         // ./wwwroot/image/1.jpg
  28.         auto pos=path.rfind(".");
  29.         if(pos == string::npos) suffix=".html";
  30.         suffix=path.substr(pos);
  31.         // 5. 得到资源的大小
  32.         struct stat sif;
  33.         if(stat(path.c_str(),&sif) == 0)
  34.             size=sif.st_size;
  35.         else   
  36.             size=-1;
  37.     }
  38. public:
  39.     string inbuffer;
  40.     string method;
  41.     string url;
  42.     string httpversion;
  43.     string path;
  44.     string suffix;
  45.     int size;
  46.     string parm;
  47. };
复制代码
然后在Get方法里,进行处置处罚,如果path是/search就不走下面显示网页的逻辑,直接使用我们自己写的C++的方法,提供服务。
其次还可以把请求到其他语言写的脚本中。
  1. void Get(const httpRequest &req, httpResponse &resp)
  2. {
  3.      if(req.path == "test.py")
  4.      {
  5.          //建立进程间通信,pipe
  6.          //fork创建子进程,子进程执行这个脚本 execl("/bin/python", test.py)
  7.          // 父进程,将req.parm 通过管道写给某些后端语言,py,java,php等语言
  8.          //这也是为什么服务器是c++写的,后端是其他语言写的
  9.      }
  10.      if(req.path == "/search")
  11.      {
  12.          // req.parm
  13.          // 使用我们自己写的C++的方法,提供服务
  14.      }
  15.         //。。。
  16. }
复制代码
这里我们还可以把服务器做更多的筹划。
服务器做一个功能路由的选择,不同路径调用不同的服务
  1. typedef function<void(const httpRequest&,httpResponse&)> func_t;
  2. class httpServer
  3. {
  4.         //。。。
  5.     void registerCb(string servicename, func_t cb)
  6.     {
  7.         funcs.insert(make_pair(servicename, cb));
  8.     }
  9.        
  10.         void handlerEntery(int sock)
  11.         {
  12.             // 1. 读到完整的http请求
  13.             // 2. 反序列化
  14.             // 3. httprequst, httpresponse, callback(req, resp)
  15.             // 4. resp序列化
  16.             // 5. send
  17.        
  18.             char buffer[4096];
  19.             httpRequest req;
  20.             httpResponse resp;
  21.             ssize_t n=recv(sock,buffer,sizeof(buffer)-1,0);// 大概率我们直接就能读取到完整的http请求
  22.             if(n>0)
  23.             {
  24.                 buffer[n]=0;
  25.                 req.inbuffer=buffer;
  26.                 req.parse();
  27.                 funcs[req.path](req, resp);
  28.                 send(sock,resp.outbuffer.c_str(),resp.outbuffer.size(),0);
  29.             }
  30.         }
  31.        
  32. private:
  33.     uint16_t _port;
  34.     int _listensock;
  35.     unordered_map<string,func_t> funcs;
  36. };
复制代码
  1. // ./httpserver port
  2. int main(int argc, char *argv[])
  3. {
  4.     if (argc != 2)
  5.     {
  6.         Usage(argv[0]);
  7.         exit(USAGG_ERR);
  8.     }
  9.     uint16_t serverport = atoi(argv[1]);
  10.     unique_ptr<httpServer> tsv(new httpServer(serverport));
  11.    
  12.     // 功能路由!
  13.     tsv->registerCb("/", Get); //假设/ 默认是网站
  14.     tsv->registerCb("/search", Search);//假设是/search 默认注册
  15.     tsv->registerCb("/test.py", Other);//假设test.py 默认登录
  16.    
  17.     tsv->initServer();
  18.     tsv->start(Get);
  19.     return 0;
  20. }
复制代码
2.除了GET和POST,还有那些方法,重要吗??
有,但常用的是GET和POST方法。
单纯把资源从远端获取 —> GET方法
提参GET,POST都可以,提交的参数很小没有私密性可以用GET,参数大想有私密性用POST。

6.HTTP的状态码

状态码有五大类

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
以4开头是客户端错误。5开头是服务端错误。
以3开头的状态码都是重定向

什么叫做重定向呢?
就是访问百度,但是它会自动跳转出QQ新闻首页。
访问一个url,但是服务器响应会带以3开头的状态码还会带一个新的url,然后欣赏器看到这个状态码会自动重新访问这个新的url。

重定向分为临时重定向和永世重定向。
假如有一个www.a.com网站有100w用户,但是这个网站现在进行重新筹划升级,搞了一个新网站www.b.com,但是老网站100w用户我还想要,因此在老网站这里用重定向,当你还在访问老网站时,老网站告诉你重定向,因此你的欣赏器自动访问这个新网站。永世重定向从技能角度是更新你本地的书签。 临时重定向就是每次都做临时跳转如打开一个网站有时候给我跳转到淘宝,有时候跳转京东等(款项的气力)。
  1. void Get(const httpRequest &req, httpResponse &resp)
  2. {
  3.     cout << "----------------http start---------------" << endl;
  4.     cout << req.inbuffer << endl;
  5.     cout << "method: " << req.method << endl;
  6.     cout << "url: " << req.url << endl;
  7.     cout << "httpversion: " << req.httpversion << endl;
  8.     cout << "path :" << req.path << endl;
  9.     cout << "suffix: " << req.suffix << endl;
  10.     cout << "size: " << req.size << endl;
  11.     cout << "----------------http end-----------------" << endl;
  12.         //临时重定向
  13.     string respline = "HTTP/1.1 307 Temporary Redirect\r\n";
  14.    
  15.     string respheader = suffixtodes(req.suffix);
  16.     if (req.size > 0)
  17.     {
  18.         respheader += "Content-Length: ";
  19.         respheader += to_string(req.size);
  20.         respheader += "\r\n";
  21.     }
  22.         //Location配套重定向,告诉浏览器去哪里访问
  23.     respheader += "Location: https://www.baidu.com/\r\n";
  24.     string respblank = "\r\n";
  25.     string body;
  26.     body.resize(req.size + 1);
  27.     if (!Util::readFile(req.path, body))
  28.     {
  29.         // 找不到文件,文件大小是-1,要返回404.html,因此重新计算大小
  30.         struct stat sif;
  31.         if (stat(html_404.c_str(), &sif) == 0)
  32.             body.resize(sif.st_size + 1);
  33.         Util::readFile(html_404, body); // 一定能成功
  34.     }
  35.     resp.outbuffer += respline;
  36.     resp.outbuffer += respheader;
  37.     resp.outbuffer += respblank;
  38.    
  39.     cout << "----------------------http response start---------------------------" << endl;
  40.     cout << resp.outbuffer << endl;
  41.     cout << "----------------------http response end---------------------------" << endl;
  42.    
  43.     resp.outbuffer += body;
  44. }
复制代码

7.HTTP常见Header


8.长毗连

我们知道其实一张我们看到的网页,现实上大概由多种元素构成,也就是说一张完整的网页须要多次http请求然后欣赏器进行组合和渲染,这是我们在前面都见过的。
这里就会有这样一个题目,http网页中大概会包罗多个元素,如果频仍发起http请求,http是基于tcp的,tcp是面向链接的,以是会导致频仍创建链接的题目。
如果有100张图片,http就要发起100请求,tcp创建100次链接,这成本就太大了。
以是这就须要client和server都要支持,长毗连,客户端向服务器发起创建一次链接(这里并不是说与服务器创建好链接之后请责备部资源就永久用的是这一个链接 ,而是说我们请求服务器看到一张网页,这个网页里有很多元素),与服务器这张网页创建好一条链接,获取一大份资源的时候,通过同一条链接完成。

这表现客户端和服务器协商好支持长毗连。
Connection:close -----> 短链接(有多少请求,创建多少次tcp链接)
9.cookie&&session会话保持

严格来说会话保持并不属于http协议天然所具备的,是后面使用发现须要的。
http定位是一个超文本传输协议,它只要把资源从服务器拿到本地就可以了。
但依旧要一个会话保持。
什么是会话保持呢?
简单来说就是你打开bilibili网站,然后登录一次账号。以后你在网站里做网页跳转的时候都不须要重新登录了,然后在重新打开bilibili这个账号依旧在,关闭欣赏器然后依旧从这个欣赏器在打开bilibili你的账号信息还在!如果换成其他欣赏器进入bilibili网站这次你的消息才不会存在了。这就是会话保持。
http协议是无状态的!也就是说第一次第二次第三次请求,第二次请求不知道第一次请求过,第三次请求不知道第二次第一次请求过。换句话欣赏器三次请求一样的图片,按照原理来说,欣赏器每一次都帮我发起http请求。因为http协议无状态它不会记载汗青曾经所涉及的状态信息、全部曾经的请求、不会推测下一次请求该做什么。它只复杂自己的功能我要什么你给我拿什么就可以了。
但现实上现实通常和理论是不符的,虽然我们发现http协议无状态,但是我们发现我曾经登录过,但http协议在我们通信的时候依旧能记住我。这虽然和http协议无直接关系,但间接有关。
http协议无状态!但用户须要,因为用户查看新的网页是通例操作,如果发生网页跳转,那么新的页面也就无法识别是哪一个用户了,为了让用户一经登录,可以在整个网站,按照自己的身份进行随意访问 -------> 会话保持(保持一个用户始终在线的状态)
那如何实现呢?
老方法
首先通过http协议进行登录(输入用户名和密码),登录乐成服务器返回首页让你使用。当发生页面跳转时,http是不记载我的状态的就构建请求发给服务端,服务器此时说你谁啊?我不认识你,必须是登任命户才气请求这个页面,这时你还必须重新登录才可以,那就扯犊子了。
以是办理方案的筹划者这样做,一旦首次登录乐成后,欣赏器会帮我们维持一个东西。把我们用户输入的信息:用户名&&密码生存起来。然后以后只要访问同一个网站欣赏器会自动推送汗青保留的信息。
而服务端对凡是与网页访问有权限要求的网页,在被获取之前,全部都要做判断!!-----> 身份认证
这两个搭配起来之后,只要当首次登录乐成之后欣赏器并将信息生存起来,以后就由欣赏器和服务器两边配合,就不消用户频仍输入用户名和密码了,每一次http请求欣赏器都会把曾经生存的这份信息推送给服务器。虽然服务器每一次都做身份认证,此时在用户看来只在一次须要登录,以后就不须要再登录了,因为都由欣赏器和服务器在配合着进行会话保持。
其中欣赏器把我们账号密码信息生存起来的技能叫做cookie技能
cookie技能分为:cookie文件级别,cookie内存级别,怎么明白呢?
欣赏器本质也是一个进程,如果关闭然后重新打开欣赏器,也就是进程退出然后又运行了,进程退出进程生存的东西都随进程退出而开释掉,但当我在访问b站的时候我依旧是登录状态,这个欣赏器接纳的是cookie文件级别也就是说欣赏器会在本地给我维持一个cookie文件是在磁盘上真真实实存在的。甚至关机,但当我访问b站我照旧登录状态。
如果随着欣赏器退出然后再重新打开欣赏器,再打开b站的时候我的登录信息就没有了,此时cookie接纳的是cookie内存级别,也就是随进程关闭全部进程生存的东西都没有了。
cookie是文件的照旧内存的在欣赏器是可以匹配的。

但是这个方法有题目!
欣赏器虽然会在我们第一次登录会生存我们登录信息,以后每次账号密码都要进行推送给服务器,服务器鉴权后返回资源。然后以后都是重复这个工作。但某一天你的电脑中了木马病毒,它并不粉碎你的电脑,而只是盗取你的消息如cookie文件。然后这个盗取你消息的人拿着欣赏器去访问你所访问的网站会造成什么后果?
不须要登录欣赏器自动推送服务器自动做鉴权,服务器会误认这个非法用户是你,这个危害对你还不是最大的,对社会影响最大。比如用你的身份进行诈骗等。
对你来说你的信息走漏了。

上面的题目并不是信息放在文件中的题目,而是把文件放在客户端出了题目,用户对自己信息保护本领有限。
接下来为了办理上面的题目,我们有一个新的方案。这个消息不要生存在欣赏器上,而生存在服务器上。
欣赏器首先照旧登录输入用户名和密码,服务端把用户形成的认证信息欣赏痕迹在服务端形成一个session文件,服务器大概有成百上千万用户,而每个用户只要曾经登录过都要形成session文件,以是给每一个session文件起一个唯一名称,通常是一个大大的字符串我们称之为session id具有唯一性。然后服务器在登录乐成的时候把这个session id返回给欣赏器,欣赏器只生存session id,依旧生存在cookie文件中。以后欣赏器在访问服务器的时候构建请求内里必须把session id带上,这个请求到了服务端结合在这个session id在session id列表中对这个session id做认证,只要有并且内容没有非常服务器就直接以为这个用户处于登录状态,然后把资源返归去。每次请求都会做这个动作。
这个方法将用户的私密信息通过session id生存在服务器端,以是可以直接以为用户信息的走漏,已经大大改善了!我们这些信息由那些公司进行保护。
但是这个黑客照旧把木马安装到你的电脑里,虽然拿不到账号密码的私密信息,但能,把这个session id拿到自己的欣赏器是对服务器进行访问,服务器照旧会以为这个非法用户是你,但是这些公司会配合其他策略缓解这类题目。如即使session id一样,但是上次你在北京登录,过一会你就跑广东登录了,区域ip所在不一样,就会让你下线让你重新登录等等方法。这些策略只要识别不是你要session id失效即可。

那server如何写入cookie信息?如何验证client会携带cookie?
服务器可以在报头在加一条属性。不过这条属性内容是我们任意写的为了验证,别的服务器有自己的一套算法形成唯一的session id。
  1. // 1. 服务器和网页分离,html
  2. // 2. url -> / : web根目录
  3. // 3. 我们要正确的给客户端返回资源类型,我们首先要自己知道!所有的资源都有后缀!!
  4. void Get(const httpRequest &req, httpResponse &resp)
  5. {
  6.     cout << "----------------http start---------------" << endl;
  7.     cout << req.inbuffer << endl;
  8.     cout << "method: " << req.method << endl;
  9.     cout << "url: " << req.url << endl;
  10.     cout << "httpversion: " << req.httpversion << endl;
  11.     cout << "path :" << req.path << endl;
  12.     cout << "suffix: " << req.suffix << endl;
  13.     cout << "size: " << req.size << endl;
  14.     cout << "----------------http end-----------------" << endl;
  15.     //string respline = "HTTP/1.1 200 OK\r\n";
  16.     string respline = "HTTP/1.1 307 Temporary Redirect\r\n";
  17.     //string respheader="Content-Type: text/html\r\n";
  18.     string respheader = suffixtodes(req.suffix);
  19.     if (req.size > 0)
  20.     {
  21.         respheader += "Content-Length: ";
  22.         respheader += to_string(req.size);
  23.         respheader += "\r\n";
  24.     }
  25.     respheader += "Location: https://www.baidu.com/\r\n";
  26.     respheader+="Set-Cookie: 123456abc\r\n";//
  27.     string respblank = "\r\n";
  28.     // string body="<html lang="en"><head><meta charset="UTF-8"><title>for test</title><h1>hello world</h1></head><body><p>北京交通广播《一路畅通》“交通大家谈”节目,特邀北京市交通委员会地面公交运营管理处处长赵震、北京市公安局公安交通管理局秩序处副处长 林志勇、北京交通发展研究院交通规划所所长 刘雪杰为您解答公交车专用道6月1日起社会车辆进出公交车道须注意哪些?</p></body></html>";
  29.     string body;
  30.     body.resize(req.size + 1);
  31.     if (!Util::readFile(req.path, body))
  32.     {
  33.         // 找不到文件,文件大小是-1,要返回404.html,因此重新计算大小
  34.         struct stat sif;
  35.         if (stat(html_404.c_str(), &sif) == 0)
  36.             body.resize(sif.st_size + 1);
  37.         Util::readFile(html_404, body); // 一定能成功
  38.     }
  39.     resp.outbuffer += respline;
  40.     resp.outbuffer += respheader;
  41.     resp.outbuffer += respblank;
  42.     cout << "----------------------http response start---------------------------" << endl;
  43.     cout << resp.outbuffer << endl;
  44.     cout << "----------------------http response end---------------------------" << endl;
  45.     resp.outbuffer += body;
  46. }
复制代码

Cookie还可以设置到期时间都可以试一下
  1. respheader += "Set-Cookie: name=1234567abcdefg; Max-Age=120\r\n";
复制代码
当一旦设置好cookie之后,以后,每次http请求,都会自动携带曾经设置的全部的cookie,帮服务器进行鉴权举动 —> http会话保持

10.根本工具(postman,fiddler)

postman,不是抓包工具,模拟客户端 ----> 欣赏器举动

fiddler,是一个抓包工具,专门用来抓http的,抓的是本地的,可以用来调试的

fiddler原理就是,未来欣赏器在发起请求时是把请求交给fiddler,由fiddler取代你去请求服务器,服务器响应也是给fiddler,然后fiddler再把响应转发给欣赏器。
而postman可以以为就是把欣赏器换成它发起请求然后服务器给响应。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4