【Linux篇章】穿越网络迷雾:揭开 HTTP 应用层协议的终极奥秘!从请求响应到实战编程,从静态网页到动态交互,一文带你全面吃透并征服 HTTP 协议,打造属于你的 Web 通信利刃!
本篇摘要
本篇将介绍何为HTTP协议,以及它的请求与答复信息的格式(请求行,请求包头,正文等),对一些比较重要的部分来展开讲解,其他不常用的即一概而过,从静态网页到动态网页的过渡,最后底层基于TCP实现简单的HTTP服务器的代码编写构建一个简单的网页(包含对应的跳转,重定向,动态交互等功能),采取边讲解http结构边用代码形成效果展示的形式进行讲解,望有助!
本文将要介绍的内容的大致流程图如下:
一· 认识HTTP
在互联网世界中, HTTP(HyperText Transfer Protocol, 超文本传输协议) 是一个至关重要的协议。 它定义了客户端(如浏览器) 与服务器之间如何通信, 以交换或传输超文本(如 HTML 文档) 。
HTTP 协议是客户端与服务器之间通信的基础。
客户端通过 HTTP 协议向服务器发送请求, 服务器收到请求后处理并返回响应。 HTTP 协议是一个无连接、 无状态的协议, 即每次请求都需要建立新的连接, 且服务器不会保存客户端的状态信息(后面我们详细讲解)。
我们要明白:
我们上网要么从远端拿数据,要么上传数据到远端(数据:短视频,视频,网页,图片,音频)。
本质:
访问远端linux服务器的某个文件等(底层走的还是TCP那套逻辑)。
1.1认识URL
其实就是我们平时说的网址!
下面就拿我的博客的URL为例说明:
https://blog.csdn.net/2401_82648291?spm=1010.2135.3001.5343
解释:我们输入网址进行访问(http协议)就是拿着指定ip+port连接到具体主机的具进程(类似我们之前写的服务器)﹔然后浏览器把路径按照规定进行编码(特殊字符)然后这个符合要求的uri(路径)发送给指定进程;再由这个进程执行完(找到对应内容或者进行对应方法执行)把结果返回来!
1.2urlencode与urldecode
这里有些字符不能随意出现.比如, 某个参数中需要带有这些特殊字符(/ 或者?), 就必须先对特殊字符进行转义。
将需要转码的字符转为 16 进制, 然后从右到左, 取 4 位(不足 4 位直接处理), 每 2 位做一位, 前面加上%, 编码成%XY 格式。
1.3 HTTP 协议请求与响应格式
下面简单认识下http结构:
注意:http协议,序列和反序列化用的是特殊字符进行子串拼接,且不依赖任何第三方库。
http请求,携带的数据(可选,不是所有的http请求,都会携带数据)。
我们之后拆解进行解析的时候,把它看做一个长的字符串!
认识 http的请求以及回复
原理解释:
首先浏览器把对应的域名进行转化成ip然后–>对应的ip+port就可以找到对应的服务器端进程了;接着把后面跟的具体路径(uri)内容(也可以空)等进行填充请求报头;是get还是post(请求方法);然后按照上面的request的格式打包之后成一个长的字符串;然后发送给服务端;服务端就要进行解析;根据uri具体位置把内容拿出来放到自己的响应正文里(html);最后序列化发回去;剩下就是浏览器提取进行先关转化!!!
粗力度说一下如何序列化和反序列化
解析request
这段字符串由浏览器构建。
报头就是利用哈希表的K-V结构储存。
如何进行解析出来:
我们直接对这个长串进行找\r\n(第一个)来成功提取请求行,把请求方法/版本/uri解析出来(第一个都找不到就是无效的http请求了)。
然后依次一行行读取,发现是空行,然后找到对应的Content-Length的长度,对应读取下一行(千万不要再继续按照上面要求读取因为最后一行无\r\n)
构建response
查找uri获得对应的响应正文。
根据相关信息填写请求行以及报头。
拼接成长字符串。
剩下的就交给浏览器自己转义即可!
1.4 剖析request与response的组成部分
认识请求方法:
这里我们主要介绍的是POST和GET方法,代码演示也是用他俩。
GET
用途: 用于请求 URL 指定的资源。
示例: GET /index.html HTTP/1.1。
特性: 指定资源经服务器端解析后返回响应内容。
form 表单: https://www.runoob.com/html/html-forms.html。
代码书写也是先以get为例,简单来说就是获取对应资源!
POST
用途: 用于传输实体的主体, 通常用于提交表单数据。
示例: POST /submit.cgi HTTP/1.1。
特性: 可以发送大量的数据给服务器, 并且数据包含在请求体中。
form 表单: https://www.runoob.com/html/html-forms.html。
当然了,还有PUT(推送数据到服务端) HEAD(只返回相应头) DELETE (删除服务端数据)OPTIONS(查询URL指定资源)等,这些不常用,这里就不讲解了。
认识URI
下面我们来认识下:
输入对应的web根目录(/)
这个路径就是uri,然后服务端就会对应位置找到资源,返回给我们:
注意:'/ '不是Linux 根目录,是Linux服务器的一个web目录(wwwroot),资源都放在这个目录下(如果路径不是/,服务器自动拼接首页)。
因此:
我们在浏览器段请求的时候,首页作为站点的入口,一个网站就是一颗多叉树,点击链接的时候,浏览器会形成新的访问地址,发起二次请求。
下面我们来验证下:
我们先输入网址进入网页:
然后我们进行点击,他就会访问到我们web目录(wwwroot)的home.html内容发给浏览器进行转义,浏览器自动识别点击处封装的链接然后自动发送请求:
然后比如点击了主页:
总结下:
uri就是对应服务器的某一web目录下的一个路径;当发起一定请求;服务端就会根据uri(特定)配合request的正文进行内容提取;然后构成响应正文最后发给浏览器进行转义!
认识HTTP版本
这里就简单理解成你是什么版本然后告诉服务端给你提供什么版本的服务。
比如你是微信低版本的客户端;然后给服务端发起请求:服务端识别到你的http的版本是低的就提供给你低版本服务(这里对于一些app应用也可以理解成是被限制的浏览器,其实也是发送http的网页请求)。
认识状态码描述
HTTP状态码:
详细版:
状态码 英文名称 含义解释
200 OK 请求成功,服务器返回对应数据
201 Created 请求成功且创建了新资源
204 No Content 请求成功,但无内容返回
301 Moved Permanently 资源永久重定向到新地址
302 Found 资源临时重定向到新地址
304 Not Modified 资源未修改,可直接使用缓存
400 Bad Request 客户端请求语法错误
401 Unauthorized 请求需要身份认证,未授权
403 Forbidden 服务器拒绝请求,无访问权限
404 Not Found 服务器找不到请求的资源
500 Internal Server Error 服务器内部错误,处理请求失败
502 Bad Gateway 网关错误,代理服务器获取响应失败
503 Service Unavailable 服务器暂时不可用,可能因维护或过载
最常见的状态码:
比如200(OK),404(NotFound),403(Forbidden),302(Redirect重定向), 504(Bad Gateway),我们暂时只需要了解这些即可。
认识HTTP 常见 Header
首先我们先认识下客户端收到答复是如何解析的:
如何提取相应正文:
响应文也可以是无内容的,如果是有内容那么一定会在Content-length加上长度再给用户的;因此用户就找空格然后看对应的Content-length大小进行提取响应正文进行转义即可。
陪一张header表:
字段名 含义
Content-Type 数据类型(如 text/html 等)
Content-Length Body 的长度(单位为比特)
Host 客户端告知服务器,所请求的资源是在哪个主机的哪个端口上
User-Agent 声明用户的操作系统和浏览器版本信息
referer 当前页面是从哪个页面跳转过来的
Location 搭配 3xx 状态码使用,告诉客户端接下来要去哪里访问
Cookie 用于在客户端存储少量信息,通常用于实现会话(session)的功能
下面我们就先来认识Content-Length,Content-type,Connection这几个报头为主来讲解:
Content-Type:数据类型(text/html等)对应的后缀比如png mp4等都对应的有不同类型(这里我们直接截取后缀进行判断添加即可)。
Content-Length: Body 的长度这里可以考虑读取文件的时候调用:1·c++文件操作/2·系统自带的stat函数。
那么下面我们根据这俩个报头给我们之前写的http答复添加上:
大致思路:
服务端收到答复通过解析获得的路径去读取文件:如果读到就构建响应正文+正确码以及解释+报头之长度,正文类型等;如果失败就把路径修改成对应的404所在的文件,让它作为正文被返回,同理报头等那些也都是404对应相关的了。
注意:
读文件的时候(有可能不都是文本,比如二进制的图片之类的)进行二进制读取,一口气读完。
// 读取对应路径下的html:
static bool readfile(std::string &text, std::string tar)
{ // 1·文本读取(比如读取图片等可能会遇到\n等符号导致读取中断直接换行等当前行后面内容就无法读到,导致错误):
// std::ifstream in(tar.c_str());
// if (!in.is_open())
// return false;
// std::string line;
// while (std::getline(in, line))
// { // 使用getline读取的时候每次会将line情况在读取
// text += line;
// }
// in.close();
// return true;
// 2·二进制读取:
std::ifstream in(tar.c_str());
if (!in.is_open())
return false;
int size=filesize(tar);
text.resize(size);
in.read((char*)text.c_str(),size);//这里string不能强转char*因此可先调用c_str
return true;
}
判断文件大小的两种方式:
static int filesize(string file)
{ //c++文件io方法:
// ifstream in(file);
// if (!in.is_open())
// return 0;
// in.seekg(0, in.end);
// int size = in.tellg();
// in.seekg(0, in.beg);
// in.close();
// return size;
//系统调用:
struct stat buff;
int s = stat(file.c_str(), &buff);
return buff.st_size;
}
重点来了:有个非常阴险的bug:
就是每次浏览器开一个新页面就会先向对应的服务端请求(/favicon.ico 一个文件)里面是那个小图标。
我们必须对它进行忽略处理:
比如我么我们第一次连接到主页的时候:
此外还建议,读取的时候数组开大一点(因为这里我们是默认它可以把请求都读上来的,忽略了其他情况)
解释下原理:
浏览器会请求这个图标也就是会也就是Accept请求头会有对应的关于/favicon.ico对应的类型;然后如果我们还是上面的逻辑找不到就404的话(之前就可以,但是现在我们报头会返回类型这样就导致类型不匹配了,因此会出现问题)那么请求如果是它,此时我们在答复的时候就必须返回对应的类型的响应正文以及对应的Content-type是image/x-icon这个类型,否则浏览器就会爆出类型不匹配从而出错,因此问题就在于这个请求头的Accept与答复的Content-type及响应正文之间的不匹配问题,故对于这个图标的请求我们采取忽略不处理,“不理这个请求图标的进程了!”
因此:
我们对服务端收到的uri为这个路径的请求直接忽略即可(这里浏览器多个请求访问其实是多个多个不同进程去访问的,也就是ip同port不同,因此服务端直接对发它的进程忽略不回复;接着对其他进程的请求再做答复即可)。
根据此处小结一下:
对于客户端(浏览器)请求如果对应的报头比如content-type等;是空的那么服务端返回的如果不是空是可以匹配的(客户端默认)﹔但是如果客户端发送的正文已知一些报头的类型;但是服务端返回来的是不匹配的;客户端直接报错(浏览器无法解释)。
下面我们根据我们制作的基于http的小网站的请求验证一下:
下面再说一下Connection报头:
HTTP 中的 connection 字段是 HTTP 报文头的一部分,它主要用于控制和管理客户端与服务器之间的连接状态。
HTTP/1.1:在 HTTP/1.1协议中,默认使用持久连接。当客户端和服务器都不明确指定关闭连接时,连接将保持打开状态,以便后续的请求和响应可以复用同一个连接。
HTTP/1.0:在 HTTP/1.0 协议中,默认连接是非持久的。如果希望在HTTP/1.0上实现持久连接,需要在请求头中显式设置Connection:keep-alive。
如何使用的 :Connection:keep-alive:表示希望保持连接以复用TCP 连接,Connection:close:表示请求/响应完成后,应该关闭TCP 连接。
这里就涉及到长短连接了:
长连接:就是只需要connect和accept一次,客户端和服务端就直接收发消息即可。
短连接:就是每次都要connect和accept(比如我们自己实现的那个简单http服务器)。
下面我们举些例子来看一下:
我们自己写的http服务器它就是短连接。
请求:
可以看到每次都要重新连接,故最后还是短连接!
访问qq新闻官网,使用1.0版本,就不支持长连接而且失败,因为telnet只支持http但是访问你的它是支持https:
这里比如百度就支持了http;而且发送的1.1默认就是长连接,双方达成协议:
总结下:
发送请求的时候,前提是客户端(浏览器)要支持长连接,然后发送1.1,告诉服务端要进行长连接,最后协商后才是长连接,否则就默认短连接了(这里注意互相支持的协议应该相同)。
基于我们模拟实现的简单的http服务器访问与应答原理分析:
首先我们访问对应的ip+8080默认进入我们的/:web的首页也就是index:
下面看一下首页对应的html:
我们点击就自然会进行一下页面跳转;比如点击登录,此时浏览器就会识别到然后重新给服务端发送对应的href对应的请求,访问对应文件:
video页面
下面比如说我们点击了 自动打款这个选项:浏览器就会自动找对应web首页的1文件;服务端解析后发现找不到就直接构建404文件返回给浏览器然后被它转义,于是我们就看到了:
不过,目前我们访问的资源全是以静态资源形式呈现的(文件)。
小结一下:
给我们之前的http服务器简单添加了两个报头分别是Content-type和Content-length;其次就是对于那个小图标特判一下,接著裁是理解不同htm文件里的的转关系:其实就是浏优差再次选一个进程向同一个服务端访间;有的是直接在当前页面展现出来,有的就是直接刷新当前页面进行显示(比如a标签),还有的是跳转新页面进行展示–>归根结底,还是浏览器重新构建请求发送给服务器而已! ! !
telnet与wget指令
telnet
使用telent前提是安装后,把自己对应服务器的端口开放,防火墙关闭!
首先,我们需要知道:
首先如果没有开放云服务器的端口的话拿公网的话,访向云服务器会被拒绝;因此之前tcp/udp模拟拿的比如是本地环回就是本地通信不涉及网络;如果是子网ip的话;是云服务器下发的ip(云服务器所在局域对应的内部ip),云服务器默认是接收的。
如果自己的云服务器开通端口后就允许外部的设备拿着对应的公网ip+port(对应自己云服务器主机标志)进行访问,由于存在风险;故需要对应的云服务器开放对应端口来支持访问才行。
因为我们开放了云服务器端口,因此被允许了,然后发送请求找到云服务器内执行的main程序,进行底层的tcp连接:成功后就可以进行发送请求,即基于tcp的通信了。
进行安装telnet:
sudo apt install telnet
使用注意事项:使用的时候就是telent ip port然后ctrl+]然后再回车即输入访问的网址或者请求即可,q为退出。
这里我们就用telnet进行连接我们的http服务器了。
wget
解释:获取对应的网址对应的云服务器内的web首页里对应文件内容+名称,下载到本地(爬虫)一些网站有反爬机制明确禁止。
安装指令:
sudo apt-get update
sudo apt-get install wget
演示效果:
下面我们抓取-下qq新闻页面:
再探状态码: 重定向之永久重定向(301)+临时重定向(302)
举个通俗易懂的例子:
比如学校南门开了个超市,但是超市临时搬到了北门,因此就会在原来南门的位置搞个告示,这样学生们就看到了直接去北门了–>(临时重定向)302+location!但是如果超市由于北门生意好,就搬到北门不回来了,因此它就会在原来南门的地方贴个告示说永久到北门–>(永久重定向)301+location!
这里就是客户(浏览器)给服务端发送访问s1的请求;但是原先的位置的内容不存在(被服务端重定向了);因此当client访问s1资源的时候,服务端判断重定向返回对应的s2地址给client(然后client拿到后就去访问),这样就完成了重定向!
这里永久重定向主要是针对搜索引擎的,因为它要保证拿到对应公司网址的是最新的,因此需要记录下来之前被重定向的资源的新地址!!!
比如这里我们汉语搜索qq新闻,浏览器搜索引擎要能保证上面的这点,及时抓取对应的网址:
下面那我们实现的http网站演示下效果:
这里只要我们搜索这个路径的资源就会被永久重定向:301
if (_route == "./wwwroot/redirect")
{
setcode(301); // 永久重定向:第一次被浏览器记录保存,剩下的再访问就直接到location目的网址访问
setheader("Location", "https://blog.csdn.net/2401_82648291?type=lately");
return true;
}
对于301的永久重定向也是一样的,只不过这个浏览器不会记录,但是永久的会记录,到时候直接去那个地方即可(我们的属于短连接,每次请求完毕都会断开)效果不明显。
效果:
被永久重定向了:
telnet一下也发现被返回的location,然后浏览器拿着这个location的新地址去访问新的地方:
测试下302:
if (!ans)
{
setcode(302); // 临时重定向,每次访问到指定网址就拿到对应的location里的网址进行访问,浏览器不进行保存!
setheader("Location""http://123.249.104.207:8080/404.html");
}
我们访问的我们自己服务端没有的路径文件,就会被返回302重定向:
被临时重定向到404文件了:
telent查看:
因此总结下:
重定向就是在response的Location处放上对应的网址,还有些其他操作等等,没有响应正文。
重识请求方法之GET与POST
GET:获取资源(图片,视频,音频,网页… 静态资源),这里也可以上传类似post功能但是,uri是含参数的!
POST: 上传资源,比如登录的时候上传账号+密码;然后服务端对应根据它进行相应的服务–>动态的(进行了交互)!
举个例子:
这里就是我们登录的首页,我们输入账号和密码就是上传数据就会和服务端进行交互,这里用的是post请求,服务端通过对应的正文拿到账号和密码,后执行login服务(服务端自己定义的),比如访问对应账号密码下的资源。
1· 下面我们看一下利用post的请求与应答:
此时,浏览器会自动识别我们的输入然后进行转化成正文,构建请求发送,接着服务端收到后会执行login对应的服务然后返回正文。
下面看下我们的效果:
我们设置的这个服务是只要密码正确就会进行视频播放贡面,因此我们判断到是post请求后直接提取正文把密码拿到,然后进行对应login服务(RESTful风格的网络接口!)进行匹配构建应答即可!
2·GET请求与应答验证:
这里之前我们用的get都是静态的(没有交互的),也就是直接访问对应路径下的文件,但是下面我们就要给它对应的uri加上参数!
这里修改成get那么浏览器识别输入后就会按照get请求构建请求:
这里也是同理我们从uri的参数中提取对应的账号密码传给login服务,让它匹配进行构建答复即可:
GET与POST特点总结:
GET:
获得静态网页或者资源,加上参数可以获取动态资源。
提交参数以uri形式提交。
GET提交参数,不能过程,一般有长度限制。
参数会回显(比如账号密码)。
POST:
获得动态资源。
提交参数以正文形式提交。
正文传递,意味着长度可以很长。
不回显,比较私密。
但是,它俩都不是安全的,以明文形式在网络中传递,抓包就能获取,其中,https(有加密)>post动态交互安全>get动态交互!
fiddler使用验证GET与POST的不安全性
fiddler:一个抓取构建好的报文的软件,fiddler抓到的报文,是已经被浏览器构建完毕的http请求,就是将来要发送到网络中的。
下面我们模拟下这个过程:
说自了就是这个fiddler带着浏览器的请求以及服务器对应的应答在网络中传输(相当于浏览器和服务器之间通信的“信使”)
它的存在不就暴露了某些信息吗(尤其是post的比如登录功能等)
使用流程:
首先,我们在官网安装好fiddler软件,然后启动:
先进行清空,然后默认是可以抓取http的包的!
我们可以看到虽然post不会在网址栏回显但是这样一抓取不就暴露密码了吗,因此说get和post都是不安全的(对http而言)!
一般这种密码登录类似功能在网络中传播都是被加密的–>用的是https,下面我们抓取下:
fiddler默认是只抓取http,对于https我们要自己设置:
我们用用一个使用https协议发送请求的登录页面验证下:
显示抓包结果:
因此可以发现https是更加安全的!
重新认识HTTP
我们在上面说了这样一句话:
HTTP 协议是一个无连接、 无状态的协议, 即每次请求都需要建立新的连接, 且服务器不会保存客户端的状态信息。
1· 为什么说http是无连接?
可以理解成http位于应用层,是一个应用层协议,底层的网络层是基于tcp实现的,也就是tcp负责accept和connect,即对于长短连接是其底层tcp的说法,而它是只负责response和request的,即是没有连接而言。
2· 为什么说http无状态?
http本质就是一种“文件”的服务器,比如每次我们请求它就是分析处理给我们找到对应文件然后发回来,自身是不会记录任何数据的,更不会记录客户端的信息(比如登录账号访问,那么你每次访问都要登录,但是这样就造成了麻烦)。
浅浅认识cookie和session
cookie:
比如每次我们访问网站看视频都需要登录,此时我们第一次登录后,就可以看视频了,但是下一次希望不在登录就可以看,因此这就是cookie机制(第一次登录后,服务器验证成功后,把对应的账号密码cookie返回浏览器储存,然后下次继续访问网站看视频,浏览器默认发送上对应的cookie,浏览器验证后成功就直接重定向到视频页面而不是登录页面)!
如何edge查找cookie信息:这里是储存在文件里:
点开就能看到对应信息:
小结下:
一般储存在浏览器的文件或者内存中,但是文件中的可能性更大!
但是这样有可能就会出现黑客盗取对应浏览器中的cookie信息,导致账号密码被盗!
因此下面session出现了。
session:
同理,再次访问,浏览器会带上sessionid 去访问某资源,当服务端看到这一资源的id或者没有和对应cookie匹配,此时就重新登陆否则直接拿资源给它!
此时就是,客户端第一次请求服务端,服务端把客户对应的账号和密码和一些其他信息进行一定编码,然后得到一个id,服务端拿着这个id给对应的用户建立一个记录(用户访问的一些资源),然后发给浏览器作为它的cookie,当下一次浏览器访问这个服务器的时候,就会自动带上这个id,然后服务器收到这个id就能找到对应的资源记录了!
比如用其他浏览器访问这个服务器,cookie中没有id就会自然重新建立了,但是如果是被黑客盗取了,然后把这个id发给服务器,这样它就能访问之前用户访问的资源了,但是我们确实避免了黑客通过单纯cookie盗号行为!
这样解决了黑客盗号问题,但是黑客还是可以访问的,因此就是服务端的保护措施了:比如过期时间,异常检测,ip溯源,地址变更等!
-因此http引入的cookie与session就减低了它的无连接,无状态的弊端!
如果想更深入了解cookie与session,有机会博主后再次更新专门一篇讲解它俩!
二· 基于所学http相关概念实现简单的http服务器
2.1 视频效果展示
下面我们基于上面所叙述的http方面的知识来简单实现的http版服务器,具有跳转,重定向,动态交互等功能,然后在登录页面密码只要是666就能正确登录并跳转视频播放页面,否则就404页面!
简单HTTP服务器功能测试效果
————————————————
版权声明:本文为CSDN博主「羑悻的小杀马特.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2401_82648291/article/details/147661750