面试:老生常谈的http,一起走进它的发展史

347 阅读13分钟

面试:老生常谈的http,一起走进它的发展史

前记

面试官:"了解过http吗?来聊一下吧。"相信大家在面试的时候或多或少经常会被问到这个问题。意在 字,而你会说些什么呢?

  • MDN中是这样回答的:

image.png

  • GPT是这样回答的:

HTTP 是一种用于计算机网络上传输超文本的应用层协议。它是互联网上广泛使用的一种协议,用于客户端和服务器之间传输、请求和响应产文本资源。HTTP协议是建立在TCP/IP协议之上的,通过使用TCP/IP协议提供的可靠数据传输服务,实现了客户端和服务器之间的通信。

其实上面的理论知识固然没错,但是我们作为开发人员更多的注重于实践操作的效果。比如:它做了什么?为什么这么做?它是如何实现的? 相信这也是面试官想听到的答案。而为了更好的 出http,个人觉得还是应该从它的发展史开始讲起,那么接下来就让我们一起走进http的发展史吧。

超文本传输协议 HTTP/0.9

HTTP/0.9 是于1991年提出的,当时主要用于学术交流,而需求也只是用于在网络中去传递HTML超文本的内容,因而也被称为 超文本传输协议

HTTP/0.9的实现有三个特点:

  • 只有一个请求行:并没有HTTP请求头和请求体,因为只需要一个请求行就可以完整表达客户端的需求。
  • 服务器没有返回头信息:因为服务端并不需要告诉客户端太多信息,只需要返回数据。
  • 返回的文件内容是以ASCII字符来传输:因为都是HTML格式的文件,所以使用ASCII字节码来传输是最合适的。

随着时间的推移,在1994年底浏览器的兴起,万维网不再局限于学术交流,高速发展的赛道上也推动了HTML的发展和HTTP的改进。当需求推动发展,而HTTP/0.9不再适用于新兴的网络发展,这个时候就需要一个新的协议来支撑,HTTP/1.0也由此诞生。

即将完成使命的 HTTP/1.0

随着网络的发展,人们的需求不再是简单的 HTML 文件,当我们现在打开浏览器的时候,看到的更多是可能是图片、音频、视频等不同的文件。其中还有js/css文件。因此,支持多种类型的文件下载是HTTP/1.0的一个核心诉求,而且文件格式不仅仅局限于ACSII编码,还存在其他类型编码的文件。

如何实现多种类型文件的下载

在上面的内容中我们可以得知 HTTP/0.9 发起一个请求之后: 只有一个请求行,并没有请求头和请求体。而服务端返回的内容中:只返回了数据,并没有告诉客户端太多信息。这种简单的交流无疑不能满足传输多种类型文件的需求,因此在 HTTP/1.0 中引入了 请求头和响应头,它们以 Key-Value 形式保存,在 HTTP 发送请求时,会带上请求头信息,服务器返回数据时,会先返回响应头信息。至于 HTTP/1.0 具体的请求流程,你可以参考下图。

image.png

HTTP/1.0 是如何通过请求头和响应头来支持多种不同类型的数据

针对四个问题

  • 浏览器需要知道服务器返回的数据是什么类型的,然后浏览器才能根据不同的数据类型做针对性的处理。
  • 为了减轻传输性能,服务器会对数据进行压缩后再传输,所以浏览器需要知道服务器压缩的方法。
  • 需要浏览器告诉服务器它想要什么语言版本的页面。
  • 浏览器需要知道文件的编码类型。

基于上面的问题,HTTP/1.0通过请求头和响应头进行协商,在发起请求时候会通过 HTTP 请求头告诉服务器它期待服务器返回什么类型的文件、采取什么形式的压缩、提供什么语言的文件以及文件的具体编码。最终发送出来的请求头内容如下:

accept: text/html
accept-encoding: gzip, deflate, br
accept-Charset: ISO-8859-1,utf-8
accept-language: zh-CN,zh
  • 第一行表示期望服务器返回 html 类型的文件
  • 第二行表示期望服务器可以采用 gzip、deflate 或者 br 其中的一种压缩方式
  • 第三行表示期望返回的文件编码是 UTF-8 或者 ISO-8859-1
  • 第四行是表示期望页面的优先语言是中文

而服务器并不会一概满足请求头中所有要求,比如当浏览器请求的压缩类型是 gzip,但服务器不支持 gzip,只支持br压缩,那么它就会通过响应头来浏览器最中的压缩类型,例如:

content-encoding: br
content-type: text/html; charset=UTF-8
  • 第一行表示服务器采用了 br 的压缩方法.
  • 第二行表示服务器返回的是 html 文件,并且该文件的编码类型是 UTF-8

HTTP/1.0 新增的典型特性

  • 状态码:有的请求服务器可能无法处理,或者处理出错,这时候就需要告诉浏览器服务器最终处理该请求的情况,这就引入了状态码。状态码是通过响应行的方式来通知浏览器的。
  • Cache机制:为了减轻服务器的压力,在 HTTP/1.0 中提供了 Cache 机制,用来缓存已经下载过的数据。
  • 用户代理:服务器需要统计客户端的基础信息,比如 Windows 和 macOS 的用户数量分别是多少,所以 HTTP/1.0 的请求头中还加入了用户代理的字段。

缝缝补补的 HTTP/1.1

优胜劣汰的世界里,随着技术的继续发展,需求也在不断地迭代更新,很快 HTTP/1.0 也不能满足需求了,所以 HTTP/1.1 又在 HTTP/1.0 的基础之上做了大量的更新。

改进持久连接

HTTP/1.0 每进行一次 HTTP 通信,都需要经历建立 TCP 连接、传输 HTTP 数据和断开 TCP 连接三个阶段(如下图)。

image.png

弊端: 随着浏览器普及,单个页面中的图片文件越来越多,有时候一个页面可能包含了几百个外部引用的资源文件,如果在下载每个文件的时候,都需要经历建立 TCP 连接、传输数据和断开连接这样的步骤,无疑会增加大量无谓的开销。

为了解决这个问题,HTTP/1.1 中增加了持久连接的方法,它的特点是在一个 TCP 连接上可以传输多个 HTTP 请求,只要浏览器或者服务器没有明确断开连接,那么该 TCP 连接会一直保持。

image.png

不成熟的 HTTP 管线化

提示:这个办法想要解决 队头堵塞 的问题,但由于各种原因最后弃用。

HTTP/1.1 中的管线化是指将多个 HTTP 请求整批提交给服务器的技术,虽然可以整批发送请求,不过服务器依然需要根据请求顺序来回复浏览器的请求。

提供虚拟主机的支持

在 HTTP/1.0 中,每个域名绑定了一个唯一的 IP 地址,因此一个服务器只能支持一个域名。但是随着虚拟主机技术的发展,需要实现在一台物理主机上绑定多个虚拟主机,每个虚拟主机都有自己的单独的域名,这些单独的域名都公用同一个 IP 地址。

因此,HTTP/1.1 的请求头中增加了 Host 字段,用来表示当前的域名地址,这样服务器就可以根据不同的 Host 值做不同的处理。

对动态生成的内容提供了完美支持

在设计 HTTP/1.0 时,需要在响应头中设置完整的数据大小,如Content-Length: 901,这样浏览器就可以根据设置的数据大小来接收数据。不过随着服务器端的技术发展,很多页面的内容都是动态生成的,因此在传输数据之前并不知道最终的数据大小,这就导致了浏览器不知道何时会接收完所有的文件数据。

HTTP/1.1 通过引入 Chunk transfer 机制 来解决这个问题,服务器会将数据分割成若干个任意大小的数据块,每个数据块发送时会附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志。这样就提供了对动态内容的支持。

客户端 Cookie、安全机制

具体的可以看看这篇文章

HTTP/2.0

在说HTTP/2.0之前,我们还是先来说一下 HTTP1.1 中出现的主要问题:

  • HTTP/1.1对带宽的利用率却并不理想原因如下:
    • TCP 的慢启动:可以把每个 TCP 发送数据的过程看成是一辆车的启动过程,当刚进入公路时,会有从 0 到一个稳定速度的提速过程,TCP 的慢启动就类似于该过程。
    • 同时开启了多条 TCP 连接,那么这些连接会竞争固定的带宽: 因为有的 TCP 连接下载的是一些关键资源,如 CSS 文件、JavaScript 文件等,而有的 TCP 连接下载的是图片、视频等普通的资源文件,但是多条 TCP 连接之间又不能协商让哪些关键资源优先下载,这样就有可能影响那些关键资源的下载速度了。
    • HTTP/1.1 队头阻塞的问题 这个是个重点问题

HTTP/2.0 的多路复用

总的来说就是针对上面的问题:既然无法改变 TCP 的慢启动,那就只使用一个TCP长连接来让它只有一次慢启动,既然 开启多条TCP会造成宽带的竞争,那就只开一条TCP,让宽带资源都是它的。而 队头堵塞:等待请求完成后才能去请求下一个资源,这种方式无疑是最慢的。那我们就实现资源的并行请求,也就是任何时候都可以将请求发送给服务器,而并不需要等待其他请求的完成,然后服务器也可以随时返回处理好的请求资源给浏览器。

多路复用机制

image.png

从图中你会发现每个请求都有一个对应的 ID,如 stream1 表示 index.html 的请求,stream2 表示 foo.css 的请求。这样在浏览器端,就可以随时将请求发送给服务器了。

服务器端接收到这些请求后,会根据自己的喜好来决定优先返回哪些内容,比如服务器可能早就缓存好了 index.html 和 bar.js 的响应头信息,那么当接收到请求的时候就可以立即把 index.html 和 bar.js 的响应头信息返回给浏览器,然后再将 index.html 和 bar.js 的响应体数据返回给浏览器。之所以可以随意发送,是因为每份数据都有对应的 ID,浏览器接收到之后,会筛选出相同 ID 的内容,将其拼接为完整的 HTTP 响应数据。

HTTP/2 使用了多路复用技术,可以将请求分成一帧一帧的数据去传输,这样带来了一个额外的好处,就是当收到一个优先级高的请求时,比如接收到 JavaScript 或者 CSS 关键资源的请求,服务器可以暂停之前的请求来优先处理关键资源的请求。

多路复用的实现

image.png

从图中可以看出,HTTP/2 添加了一个二进制分帧层,那我们就结合图来分析下 HTTP/2 的请求和接收过程。

  • 首先,浏览器准备好请求数据,包括了请求行、请求头等信息,如果是 POST 方法,那么还要有请求体。
  • 这些数据经过二进制分帧层处理之后,会被转换为一个个带有请求 ID 编号的帧,通过协议栈将这些帧发送给服务器。
  • 服务器接收到所有帧之后,会将所有 相同 ID 的帧合并为一条完整的请求信息。然后服务器处理该条请求,并将处理的响应行、响应头和响应体分别发送至二进制分帧层。
  • 同样,二进制分帧层会将这些响应数据转换为一个个带有请求 ID 编号的帧,经过协议栈发送给浏览器。
  • 浏览器接收到响应帧之后,会根据 ID 编号将帧的数据提交给对应的请求。

从上面的流程可以看出,通过引入二进制分帧层,就实现了 HTTP 的多路复用技术。

HTTP/2 其他特性

  • 可以设置请求的优先级

我们知道浏览器中有些数据是非常重要的,但是在发送请求时,重要的请求可能会晚于那些不怎么重要的请求,如果服务器按照请求的顺序来回复数据,那么这个重要的数据就有可能推迟很久才能送达浏览器,这对于用户体验来说是非常不友好的。

为了解决这个问题,HTTP/2 提供了请求优先级,可以在发送请求时,标上该请求的优先级,这样服务器接收到请求之后,会优先处理优先级高的请求。

  • 服务器推送

除了设置请求的优先级外,HTTP/2 还可以直接将数据提前推送到浏览器。你可以想象这样一个场景,当用户请求一个 HTML 页面之后,服务器知道该 HTML 页面会引用几个重要的 JavaScript 文件和 CSS 文件,那么在接收到 HTML 请求之后,附带将要使用的 CSS 文件和 JavaScript 文件一并发送给浏览器,这样当浏览器解析完 HTML 文件之后,就能直接拿到需要的 CSS 文件和 JavaScript 文件,这对首次打开页面的速度起到了至关重要的作用。

  • 头部压缩

无论是 HTTP/1.1 还是 HTTP/2,它们都有请求头和响应头,这是浏览器和服务器的通信语言。HTTP/2 对请求头和响应头进行了压缩,你可能觉得一个 HTTP 的头文件没有多大,压不压缩可能关系不大,但你这样想一下,在浏览器发送请求的时候,基本上都是发送 HTTP 请求头,很少有请求体的发送,通常情况下页面也有 100 个左右的资源,如果将这 100 个请求头的数据压缩为原来的 20%,那么传输效率肯定能得到大幅提升。

总结

虽然 HTTP/2.0 解决了 HTTP/1.1 中队头堵塞的问题,但是 HTTP/2.0 依然是基于 TCP 协议的,而 TCP协议依然存在数据包级别的队头堵塞问题,这该如何解决呢?这样就轮到 HTTP/3.0 的出现了,下次再说。

参考文章