前面我们介绍了网络 TCP/IP 五层模型中的各个层,在这五层中,应用层是和我们程序员息息相关的,需要我们程序员写出代码来实现,前面我们只是简单讲了应用层中的自定义协议,虽然自定义协议显得很灵活可以根据需求随时更改,但是在实际生活中自定义的协议使用的还是少数。在应用层中常见的协议就是 HTTP 协议,今天我将为大家分享关于 HTTP 协议相关的知识。
HTTP,全称为超文本传输协议(Hypertext Transfer Protocol),是一种应用层协议,用于在网络中传输超文本(如 HTML)。它是在互联网上应用最为广泛的一种网络协议,所有的www文件都必须遵守这个标准。HTTP是客户端浏览器或其他程序与Web服务器之间的应用层通信协议。在Internet上的Web服务器上存放的都是超文本信息,HTTP客户端通过发送请求获取服务器上的文本信息。HTTP协议工作于TCP/IP协议栈的的应用层,用于从网站的服务器中检索信息,请求被(今后称为被HTTP客户)发送到服务器。
HTTP 诞生于 1991 年,目前已经发展成为最主流的一种应用层协议。从开始的 HTTP 0.9 用于个人/机构主页开始,经过 HTTP 1.0 门户网站和 HTTP 1.1版本用于搜索引擎和社交网络到 HTTP 2.0 ,再到今天的 HTTP 3,HTTP 经过了很多版本的迭代,其中 HTTP 1.1 是我们目前最主要使用的,所以本篇博客我也将以 HTTP 1.1 为例为大家分享关于 HTTP 相关的知识。
HTTP 往往是基于传输层的 TCP 协议实现的(HTTP 1.0、HTTP 1.1、 HTTP 2.0 均为 TCP,HTTP 3基于 UDP 实现)。
我们在平时生活中访问网站就是通过 HTTP 协议来进行数据的传输的。
当我们在浏览器输入一个百度网址(URL)的时候,浏览器会向百度的服务器发送一个 HTTP 服务器请求,然后百度服务器会返回一个 HTTP 响应。浏览器会将这个响应进行解析,然后就以上面的方式呈现在我们眼前。(这个响应里面包含了 HTML、CSS、JavaScript、图片、文字等信息)
跟前面的 TCP/IP 协议不同,HTTP 的报文格式需要划分为 请求报文和响应报文 来分析,因为 HTTP 的请求和响应的报文格式是不相同的。
要想学习 HTTP 的请求和响应格式,就需要首先得到 HTTP 的请求和响应数据包,通过前面为大家分享的 HTTP 抓包工具 Fiddler 这个代理工具来抓取到 HTTP 的请求和响应数据包。
HTTP 的请求格式大致分为四个部分:首行、请求头(header)、空行、正文(body)
HTTP 的首行分为三个部分,每个部分用空格分隔开。
第一个部分 GET 叫做请求的“方法”(method),方法不止有 GET 还有像 POST 等的方法这里我们先简单知道,后面再为大家详细分享。
第二个部分就是 URL(唯一资源定位符),用来描述一个资源在网络上的位置。URL 不只是在 HTTP 中会使用,URL 在其他很地方也都会用到。
协议方案:这部分定义了网页使用的网络服务类型,例如http或https。
登录信息:用户输入的用户名和密码。现在一般用不到这个了。
服务器地址:这部分定义了网站的域名,例如www.aspxfans.com。在URL中,也可以使用IP地址作为域名。
服务器端口号:这部分定义了主机上的端口号。端口不是URL必须的部分,如果省略端口部分,将采用默认端口。对于 HTTP 请求,端口号默认是 80 端口;对于 HTTPS 协议,端口号默认是 443 端口。
虚拟目录部分:这部分从域名后的第一个“/”开始到最后一个“/”为止,是虚拟目录部分。虚拟目录也不是一个URL必须的部分。
查询字符串:这部分包含了一些参数,这些参数可以用来传递一些额外的信息。
片段标识符:这部分定义了一个链接到网页的特定部分,通常用于指向页面的特定内容或导航点。
首行中的第三个部分就是 HTTP 的版本号。
HTTP 请求头是一个键值对结构的数据,里面含有很多的键值对,每个键值对独占一行,键和值之间通过冒号加空格: 连接,并且这些键值对都是属于“标准规定”的,要求我们这样写。这些键值对具体的含义,后面为大家详细介绍。
这里空行是请求头的结束标记。
这里我们看到在抓取到的百度请求数据包的末尾是有一个空行的。这就是请求头的结束标志。
HTTP数据包中的正文(Body)通常指的是请求或响应的消息体,它包含了实际传输的数据内容。在HTTP请求中,正文通常包含了客户端要发送给服务器的数据,例如表单数据、JSON数据等。在HTTP响应中,正文通常包含了服务器返回给客户端的数据,例如HTML页面、JSON数据等。
HTTP正文是由一些字节组成的,可以是任何类型的数据,包括文本、二进制数据等。在HTTP协议中,正文使用Content-Type头部来指定其数据的类型和编码方式。常见的Content-Type类型包括text/html、application/json等。
需要注意的是,HTTP请求和响应的正文是可选的,它们不是每个HTTP数据包都必须包含的部分。如果正文不存在,则请求或响应的消息体将为空。
这里我们抓取到的 HTTP 请求数据包中就没有正文部分。
HTTP 的响应数据包也是分为四个部分:首行、响应头(header)、空行、正文(body)。
HTTP 响应报文的首行也是分为三个部分:HTTP 版本号、状态码、状态码描述。
这里的响应头也是一些键值对,键和值之间通过冒号和空格: 连接,每个键值对独占一行,并且这些键值对也是“标准规定”的。
首行中的 method 方法有很多种。
虽然请求中的方法有很多,但是 GET 和 POST 这两个方法的使用占了日常使用的八成,所以这篇文章我们主要学习 GET 和 POST 这两种方法。
GET 是最常用的 HTTP 方法. 常用于获取服务器上的某个资源。在浏览器中直接输入 URL, 此时浏览器就会发送出一个 GET 请求。另外, HTML 中的 link, img, script 等标签, 也会触发 GET 请求。GET 请求会将要传给服务器的数据加到 URL 的 query string 中。POST 方法多用于提交用户输入的数据给服务器(例如登陆页面)。
POST 方法则会将要传给服务器的数据放入到正文(body)中。
但是不一定 GET 方法要传给服务器的数据就一定放在 query string 中不能放在 正文(body)中,GET 方法的传输给服务器的数据也是可以放在 正文(body)中的,只要客户端和服务端都遵守相同的规则就可以了,虽然 GET 方法的传给服务器的数据可以放在正文(body)中,但还是建议大家放在query string 中。
这里我在导航栏输入 www.baidu.com
的时候,其实就是发送的 GET 方法的请求。
而我登录 gitee 的时候就是 HTTP 发送的 POST 方法的请求数据包。
但是有时候也会出现 Fiddler 没有抓取到 HTTP 数据包的情况,就是当我多次访问一个网站的时候,可能就不会抓取到这个 HTTP 请求数据包,这是为什么呢?这是因为刚刚的这个访问网站的请求命中了浏览器的缓冲,当发生这种情况的时候,其实浏览器不会向服务器发送 HTTP 请求。
浏览器显示的网页其实都是从服务器这里下载的 HTML,因为 HTML 的内容可能会很多,体积比较大,通过网络加载的话,消耗的时间就会很多,所以为了加快访问速度,浏览器会有自己的缓存,将之前加载过的页面,保存在本地硬盘中,当下次访问这个网站的时候就可以直接从本地磁盘读取数据,所以也就会导致本次访问不会向服务器发送 HTTP 请求数据包。
假设我要上传一个文件的时候,那么此时就会向服务器发送一个 POST 方法的请求数据包,并且这个数据包的正文部分是这样的。
这种就是,图片本来是二进制数据,当把图片这个二进制数据放入 HTTP 请求的时候,往往需要进行 base64 转码,base64 转码就是针对二进制数据进行重新编码,确保编码之后的数据就是纯文本的数据。
这些 HTTP 请求的初心就是为了表示不同的“语义”,但是在实际的使用过程中,这出初心已经渐渐被遗忘了,这些方法的使用更加随意了,所以就导致现在的 GET 和 POST 方法实际上是没有任何区别的。
1. GET 请求能传递的数据有上限,POST 传递的数量没有上限。
这个说法其实是一个“历史遗留”,早期版本的浏览器,因为硬件资源特别匮乏,所以浏览器就对 URL 的长度进行了限制,因为 GET 方法通常是将传输给服务器的数据加在 URL 的 query string 中,所以当用 GET 方法要传给服务器的数据较多的时候就会导致 URL 会很长。
但是实际上,RFC 标准文档 并没有明确规定 URL 能有多长,而且现在由于技术的进步,URL 也可以很长,甚至可以用 URL 来传递一些图片等。所以这个说法是错误的。
2. GET 请求传递数据不安全,POST 请求传递数据更安全。
为什么会这样说呢?因为在使用 GET 请求登录的时候,会将用户名和密码放入 URL 中,进一步显示在浏览器的地址栏当中,这样别人不就能轻易看到了吗?而 POST 更安全就说的是因为 POST 会将用户名和密码放入 body 中,这样就不会显示在浏览器搜索栏中,那么此时就是安全的了。但是这个说法是错误的,这个说法只能忽悠小白。什么叫做安全?安全就只是不将用户名和密码显示在搜索栏中吗?那可不是,所谓安全就是这个请求包不会被轻易的被黑客给截取到,就算被截取到了黑客也需要花出大于该数据本身的价值来破解这个数据包。不管是 GET 请求的数据包还是 POST 数据的数据包,当数据包被截取到了,知道这个请求数据包的首行和正文(body)部分都是轻而易举的。
所以为了解决这个传输数据的过程中被黑客截取到然后可以直接知道你数据的内容的问题,就采取了对用户名和密码进行加密的措施,这样就算你获取到了这个请求数据包,因为用户名和密码是经过加密的,黑客是不容易破解的。
3. GET 只能给服务器传输文本数据,POST 可以给服务器传输文本和二进制数据。
请求数据包的正文(body)部分可以放文本数据也可以放二进制数据,为什么会有这个说法呢?其实这个说法还是基于第一个说法的,就是因为被误以为 GET 方法传输给服务器的数据只能写入 URL 的 query string 中,而 URL 中的数据往往是文本数据,但是 GET 方法传输给服务器的数据是可以写入 正文(body)部分的,所以 GET 方法也是可以传输二进制数据的。
1. GET 方法是幂等的,POST 方法是非幂等的。
什么是幂等和非幂等呢?
幂等是指同一个系统,同样的参数条件,一次请求和多次同样重复的请求对资源的影响一样。例如,一次插入请求插入一条数据,多次插入请求产生的影响也是多一条数据,那么就是幂等的。
非幂等是指同一个系统,同样的参数条件,一次请求和多次同样重复的请求对资源的影响不一样。例如,用户重复操作,如用户提交请求进行创建订单操作,由于网络问题,导致页面一直在转,用户重新点击创建订单按钮,就会产生同样的订单在数据库中创建了两条,这就是非幂等的。
为什么说 GET 方法是幂等的,POST 方法是非幂等的呢?因为在 RFC 标准文档中有这样一个建议:建议 GET 请求的数据是幂等的。但是这只是一个建议,并不是硬性要求,所以这个说法不严谨。
2. GET请求可以被浏览器缓存,POST 方法不可以被浏览器缓存。
这个说法虽然有一定的正确性,但并不严谨。这是因为GET和POST请求本身都可以被缓存,只是浏览器在处理这两种请求时的行为和机制有所不同。
对于GET请求,浏览器会缓存GET请求的响应结果,以便在后续相同的请求时直接使用缓存的响应,而不需要再次向服务器发送请求。这是由于GET请求是幂等的,即多次发送相同的GET请求将获得相同的结果,不会对资源产生任何额外的影响。
然而,对于POST请求,浏览器通常不会缓存响应结果。这是因为在大多数情况下,POST请求用于提交数据或执行更新操作,每次请求的结果可能会有所不同。浏览器为了确保每次POST请求都会向服务器发送最新的数据和产生最新的结果,不会缓存POST请求的响应结果。
然而,这并不意味着POST请求绝对不会被缓存。实际上,一些浏览器或代理服务器可能会对POST请求进行缓存,特别是在某些情况下,例如当使用缓存代理时。此外,一些Web应用程序也可能通过编程方式实现POST请求的缓存。
因此,这个说法“GET请求可以被浏览器缓存,POST 方法不可以被浏览器缓存”虽然在一定程度上反映了浏览器对GET和POST请求的处理机制,但并不适用于所有情况。在具体的上下文中,需要考虑浏览器的行为、Web应用程序的实现以及其他因素来评估这个说法的准确性。
3. GET 请求可以被浏览器收藏,POST 请求的数据不能被收藏。
这个说法也是不严谨的。实际上,GET请求和POST请求都可以被浏览器收藏,但它们在收藏过程中有一些区别。
对于GET请求,浏览器会将请求的URL和参数一起保存下来,以便用户可以在以后直接点击该URL来重新加载页面或获取数据。这种方式很常见,例如在浏览器书签中保存网页URL。当用户点击该URL时,浏览器会发送一个GET请求到服务器,然后服务器会返回相应的网页内容。因此,GET请求可以被浏览器收藏。
对于POST请求,浏览器不会直接保存请求的URL和参数,因为POST请求是向服务器发送数据的请求方式。浏览器将POST请求的数据包含在请求体中,而不是URL中。但是,用户可以在浏览器中手动复制和粘贴POST请求的URL和参数,或者使用开发者工具来查看和复制POST请求的内容。因此,虽然浏览器不会自动收藏POST请求的数据,但用户仍然可以手动将其保存下来。
需要注意的是,浏览器收藏的GET请求URL可能包含敏感信息,例如身份验证令牌或密码等。为了保护用户的隐私和安全,这些信息应该通过POST请求或其他安全机制发送到服务器。
Header 中的键值对是有很多的,但是这里我们主要挑选几个来介绍。
Host:表示服务器主机的地址和端口。这里的内容通常在 URL 中也有体现,但是如果使用代理的情况下 Host 的内容和 URL 中的内容可能就不一样了。
Content-length:表示 body 中的数据长度。
Content-length 有什么用,因为 HTTP 是基于 TCP 的,TCP 在传输的过程中可能会出现粘包的问题,如果使用同一个 TCP 连接传输多个 HTTP 数据包的时候,这样多个 HTTP 数据包就会在接收缓冲区当中挨着等待,接收方在接收这些 HTTP 数据包的时候就需要清楚 HTTP 数据包的边界,对于 GET 这种一般没有 body 部分的数据包就需要使用 空行 来作为分隔符;而对于 POST 来说,一般是有 body 部分的,所以就需要通过 空行和 Content-length 来区分不同的数据包。
只有请求中有 body 部分的时候,才会有 Content-length 和 Content-type 这两个键值对。
Content-type:表示 body 中数据的格式。
body 中数据格式的种类是有很多的:
请求:
这种数据格式就是一种 json 数据格式。
当 Content-type 是以下这种形式的时候就表示是 form 表单格式。
form 表单就相当于把 GET 的 query string 给搬到 body 中。
而 form-data 格式通常是在上传文件的时候会涉及到,但上传文件的时候也不一定都是 form-data 格式,也可能是 form 表单格式。
响应:
通常在 Fiddler 中抓取到的蓝色的就属于是 HTML 格式的数据包;紫色就是 CSS、绿色是 JavaScript、黑色是 JSON。
HTML CSS JS 用来构成网页的主体。
交给服务器的 Content-type 不同,服务器的处理数据逻辑也是不同的,服务器返回处理的数据的时候也需要表明 Content-type,浏览器也会根据不同的 Content-type 做出不同的处理。
通过上面的抓包,观察 User-Agent 我们可以看到几个很常见的消息:Windows NT 10.0;win64;x64 这表示的是我们使用的主机的相关信息,而 Chrome/118.0.0.0 则表示的是我们使用的浏览器的版本号。通过这两个信息我们大概可以知道 User-Agent 表示的就是与当前浏览器相关的信息。
为什么 HTTP 数据包中会出现这个 UA 呢?其实在很早之前,那时候浏览器刚刚发展,最开始浏览器网页就只是单纯的文字,并没有什么图片,并且浏览器的相关功能也很简单,但是随着浏览器的发展,浏览器上开始能显示图片以及一些声音信息了,那么有些人就会选择更新浏览器版本,但是在新浏览器普及的过程中,普及的过程是缓慢的,所以就会出现旧浏览器和新浏览器并存的情况,那么当用户访问的时候,服务器应该返回有图片的网页还是无图片的网页呢?如果返回有图片的数据,那么就浏览器就不支持这个功能,但是如果返回一个没有图片的网页,那么新版本浏览器更新又有什么用呢?所以这时候 UA 就发挥作用了,服务器可以根据请求中 UA 反应的浏览器版本来返回不同的网页。
但是现在其实 UA 就没那么关键了。因为现在不同版本的浏览器功能几乎是差不多的,现在的 UA 主要是用来辨别你是 PC 端还是移动端的,虽然可以用来辨别是 PC 端还是移动端,但是返回的网页其实跟这个 UA 无关。这个 UA 是用来统计数据的。现在的前端开发,有“响应式网页”编程的技术,同一个 HTML 可以兼容不同的设备。
Referer 描述了当前网页面是从哪个页面跳转过来的。直接在搜索栏中输入 URL 是没有 Referer 的。
Referer是HTTP请求中的header部分,当浏览器向web服务器发送请求时,一般会带上Referer,告诉服务器该网页是从哪个页面链接过来的,服务器因此可以获得一些信息用于处理。
Referer主要有两个作用:
很多搜索引擎中的广告都是需要广告商给搜索引擎这个公司出广告费的,有的广告费就是根据这个广告挂在搜索引擎多长时间计费的,而大多数广告费则是根据这个点击次数来计费的。那么搜索引擎或者广告商是如何判断某个点击是从哪个网站点击的呢?这里就需要用到 HTTP 数据包中的 Referer 了,通过 HTTP 数据包中的 Referer 就可以知道这个点击来自哪个页面。
但是是否会出现一个现象:这个 HTTP 数据包中的 Referer 会被恶意修改,修改成其他公司,从而导致最终的广告费都给了它们呢?其实是有可能的。在很早之前,因为进行网络数据的传输就需要经过网络运行商的交换机/路由器,那么这些网络运行商就可以将经过它们交换机/路由器的数据中的 Referer 进行修改,从而是他们最终成为受益者,并且这个现象在当时是非常猖獗的,因为当时互联网刚发展,相关的法律还没有完善,所以这些搜索引擎公司并不能拿这些网络运营商什么办法。所以这些搜索引擎就决定在技术上进行修改,对这些HTTP 数据包进行加密,这样网络运营商就不能轻易修改其中的 Referer 了。
HTTP的cookie是服务器发送到用户浏览器并保存在本地的一小块数据,通常由服务器使用HTTP响应头Set-Cookie发送到浏览器。当浏览器再次向同一服务器发起请求时,会默认携带这些 Cookie,并发送到服务器上。
类似于一些信息:上次登录时间、上次访问时间、用户身份信息、累计访问次数等这些信息都会存储在浏览器的 Cookie 中,并且在下一次访问这个网站的时候会将这个 Cookie 一起发送过去。
因为这些数据都是临时的,随时都可能会改变,所以这些数据还是存在浏览器最合适,其实更容易想到的是将这些数据保存在我们的本地文件中,但是这样是不行的,为了保证安全性,浏览器会禁止网站直接访问你本地的文件,所以也就导致网页代码无法直接生成一个硬盘文件来存储数据了。所以为了保证安全性,又能保存数据,就引入了 Cookie,Cookie 也是按照硬盘文件的形式保存数据的,但是浏览器对这个文件进行了封装,网页只能往 Cookie 中存放键值对。
Cookie 往往是从服务器返回的数据(也可以是自己生成的);Cookie 存储在浏览器所在的本地计算机中,并且是按照域名为维度来存储的,每个域名都有自己的 Cookie ,彼此之间互不影响;Cookie 中的键值对是程序员自定义的;后续在请求这个服务器的时候,就会把 Cookie 中的内容自动带入到请求中,然后发给服务器,服务器通过这个 Cookie 中的内容做出一些逻辑上的处理。