Backend Developer Roadmap 2
Roadmap: https://roadmap.sh/backend
本文隶属于 Roadmap 中的 Internet --> What is HTTP?
。
原文:https://cs.fyi/guide/http-in-depth
什么是 HTTP?
HTTP 是一个基于 TCP/IP 的应用层通信协议,标准化客户端和服务器之间的通信方式。它定义了互联网上请求和传输内容的方式。所谓应用层协议,是指它简单地抽象出了主机(客户端和服务器)之间的通信标准。HTTP 本身依赖于 TCP/IP,在客户端和服务器之间进行请求和响应。默认情况下,使用 TCP 端口 80,但也可以使用其他端口。HTTPS 使用端口 443。
HTTP/0.9 - 1991
HTTP/0.9 是 HTTP 的第一个版本,于 1991 年提出。它是迄今为止最简单的协议,只有一个叫做 GET 的方法。如果客户端需要访问服务器上的某个网页,它会发送以下简单请求:
GET /index.html
然后服务器的响应会如下所示:
(response body)
(connection closed)
也就是说,服务器会接收请求,响应 HTML 并在内容传输完成后立即关闭连接。HTTP/0.9 没有头部,只允许 GET 方法,响应的内容必须是 HTML。可以看出,该协议实际上只是后来版本的垫脚石。
HTTP/1.0 - 1996
HTTP/1.0 是 HTTP 的第一个版本,于 1996 年提出。与只能处理 HTML 响应的 HTTP/0.9 不同,HTTP/1.0 现在可以处理其他响应格式,例如图像、视频文件、纯文本或任何其他内容类型。它添加了更多的方法(例如 POST 和 HEAD),请求/响应格式发生了变化,HTTP 头被添加到请求和响应中,状态码被添加以标识响应,引入了字符集支持,多部分类型,授权,缓存,内容编码等。
以下是 HTTP/1.0 请求和响应的示例:
GET / HTTP/1.0
Host: cs.fyi
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*
正如您所看到的,客户端除了请求之外,还发送了自己的个人信息、所需的响应类型等信息。而在 HTTP/0.9 中,客户端永远无法发送此类信息,因为没有头部。
对于上述请求的示例响应可能如下所示:
HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84
(response body)
(connection closed)
在响应的最开始,有 HTTP/1.0(HTTP 后跟版本号),然后是状态码 200,后跟原因短语(或状态码的描述,如果您愿意)。
在这个更新的版本中,请求和响应头仍然保持为 ASCII 编码,但响应正文可以是任何类型,例如图像、视频、HTML、纯文本或任何其他内容类型。因此,现在服务器可以向客户端发送任何内容类型;在介绍后不久,HTTP 中的“超文本”一词成为了误称。HMTP 或超媒体传输协议可能更有意义,但我想我们终身都会被这个名称困住。
HTTP/1.0 的主要缺点之一是无法在一个连接中处理多个请求。也就是说,每当客户端需要从服务器获取东西时,它都必须打开一个新的 TCP 连接,并且在单个请求被满足后,连接将被关闭。对于任何下一个需求,它都必须在新连接上进行。为什么这是不好的?好吧,假设您访问一个具有 10 个图像、5 个样式表和 5 个 JavaScript 文件的网页,总计有 20 个项目需要在请求该网页时获取。由于服务器在请求已被满足后立即关闭连接,因此将有一系列 20 个单独的连接,其中每个项目都将在其单独的连接上依次服务。这种大量的连接会导致严重的性能损失,因为需要新的 TCP 连接会因三次握手和慢启动而造成重大的性能损失。
三次握手
TCP 连接开始于三次握手,客户端和服务器在开始共享应用程序数据之前共享一系列数据包。
- SYN - 客户端选择一个随机数,例如 x,并将其发送到服务器。
- SYN ACK - 服务器通过向客户端发送一个由服务器选取的随机数(例如 y)和数字 x + 1 组成的 ACK 数据包来确认请求。
- ACK - 客户端递增从服务器接收到的数字 y 并发送一个数字为 y + 1 的 ACK 数据包。
完成三次握手后,客户端和服务器之间的数据共享可以开始。需要注意的是,客户端可能会在发送最后一个 ACK 数据包后立即开始发送应用程序数据,但服务器仍然必须等待接收到 ACK 数据包才能满足请求。
然而,一些 HTTP/1.0 的实现尝试通过引入一个名为 Connection: keep-alive 的新头部来克服这个问题,该头部旨在告诉服务器“嘿服务器,不要关闭这个连接,我还需要它”。但它仍然没有得到广泛的支持,问题仍然存在。
除了是无连接的之外,HTTP 还是一种无状态协议,即服务器不维护有关客户端的信息,因此每个请求都必须具有服务器自行满足请求所需的信息,而不与任何旧请求相关联。因此, 这加剧了问题,即除了客户端必须打开大量连接之外,它还必须在连接上发送一些冗余数据,导致带宽使用增加。
HTTP/1.1 - 1997
HTTP/1.1 是 HTTP 的下一个版本,于 1999 年发布。相较于 HTTP/1.0,它有很多改进:
- 新的 HTTP 方法被添加,包括 PUT、PATCH、OPTIONS、DELETE;
- 在 HTTP/1.0 中,Host 头部不是必须的,但在 HTTP/1.1 中,它是必须的;
- 持久连接:在 HTTP/1.0 中,每次连接只有一个请求,连接在请求被满足后立即关闭,这导致性能和延迟问题。HTTP/1.1 引入了持久连接,连接默认不关闭,允许多个连续请求。要关闭连接,请求中必须有头部 Connection: close。客户端通常在最后一个请求中发送此头部以安全地关闭连接;
- 管道化:它还引入了管道化的支持,客户端可以在同一连接上向服务器发送多个请求,而无需等待服务器响应,服务器必须按照收到请求的相同顺序发送响应。但是,您可能会问客户端如何知道第一个响应下载完成并且下一个响应的内容开始。为了解决这个问题,必须存在 Content-Length 头部,客户端可以使用它来标识响应结束并且可以开始等待下一个响应。
持久连接或管道化请求需要在响应中有 Content-Length 头部,以便客户端知道何时传输完成,可以发送下一个请求(按照正常顺序发送请求)或开始等待下一个响应(启用管道化)。但是,这种方法仍然存在问题。如果数据是动态的,服务器无法提前找到内容长度怎么办?在这种情况下,持久连接无法发挥作用!为了解决这个问题,HTTP/1.1 引入了分块编码。在这种情况下,服务器可以省略 Content-Length,而采用分块编码(稍后详细介绍)。但是,如果两种方法都不可用,则必须在请求结束时关闭连接。
- Chunked Transfers 是一种传输方式,通常用于传输动态内容。在传输开始时,由于服务器无法确定 Content-Length,因此它可能会分块(每个块为一小部分)发送内容,并在发送每个块时添加 Content-Length。当所有块都被发送完毕,即整个传输完成时,服务器会发送一个空块,即 Content-Length 设置为零,以便通知客户端传输已完成。为了通知客户端使用了 Chunked Transfers,服务器会包括头部 Transfer-Encoding: chunked。
- HTTP/1.1 不同于仅有基本认证的 HTTP/1.0,它包括摘要认证和代理认证
- 缓存
- 字节范围
- 字符集
- 语言协商
- 客户端cookies
- 增强的压缩支持
- 新的状态码
- ...等等
HTTP/1.1 在 1999 年发布,成为标准多年。虽然它相对于前身做了很多改进,但随着 Web 技术的日新月异,HTTP/1.1 开始显得有些过时。如今,加载网页比以往任何时候都更加资源密集。一个简单的网页需要打开超过 30 个连接。虽然 HTTP/1.1 有持久连接,但为什么还需要这么多连接呢?原因是,在 HTTP/1.1 中,任何时刻只能有一个未完成的连接。HTTP/1.1 尝试通过引入管道化来解决这个问题,但它并没有完全解决,因为头部阻塞会阻止慢速或重量级请求,一旦请求被卡在管道中,它将不得不等待下一个请求的响应。为了克服 HTTP/1.1 的这些缺点,开发人员开始实现各种解决方法,例如使用 spritesheets、在 CSS 中编码图像、单个巨大的 CSS/JavaScript 文件、域分片等。
SPDY - 2009
2009 年,Google 开始尝试使用替代协议来加速网络并改善 Web 安全性,同时减少 Web 页面的延迟。他们宣布了 SPDY 协议。
SPDY 是 Google 的商标,不是缩写。
人们发现,如果我们不断增加带宽,网络性能会在开始时增加,但在没有太多性能增益的时候会出现瓶颈。但是,如果您以同样的方式处理延迟,即如果我们不断降低延迟,就会有不断的性能增益。这是 SPDY 的性能提升的核心思想:降低延迟以提高网络性能。
对于那些不知道区别的人,延迟是延迟,即数据在源和目的地之间传输需要多长时间(以毫秒为单位),带宽是每秒传输的数据量(每秒比特数)。
SPDY 的功能包括复用、压缩、优先级、安全等。我不会详细介绍 SPDY,因为当我们进入下一节 HTTP/2 的细节时,您将会了解到它的主要灵感来源于 SPDY。
SPDY 实际上并没有试图替换 HTTP;它是 HTTP 上的翻译层,在应用程序层存在并在将请求发送到线上之前修改请求。它开始成为事实标准,并且大多数浏览器开始实现它。
2015 年,在 Google,他们不想有两个竞争标准,因此他们决定将其合并到 HTTP 中,同时推出 HTTP/2,废弃 SPDY。
HTTP/2 - 2015
HTTP/2 的设计旨在进行内容的低延迟传输。从旧版本 HTTP/1.1 的关键特性或区别来看,HTTP/2 采用了以下特性:
- 使用二进制代替文本
- 多路复用 - 在单个连接上进行多个异步 HTTP 请求
- 使用 HPACK 进行报头压缩
- 服务器推送 - 为单个请求提供多个响应
- 请求优先级
- 安全性
二进制协议
HTTP/2 试图通过将其变为二进制协议来解决 HTTP/1.x 中存在的延迟增加问题。作为二进制协议,它更易于解析,但与 HTTP/1.x 不同,它不再可读。HTTP/2 的主要构建块是帧和流
帧和流
HTTP 消息现在由一个或多个帧组成。有一个 HEADERS 帧用于元数据,一个 DATA 帧用于有效负载,还有几种其他类型的帧(HEADERS、DATA、RST_STREAM、SETTINGS、PRIORITY 等),你可以通过 HTTP/2 规范检查。
每个 HTTP/2 请求和响应都被赋予一个唯一的流 ID,并被划分为帧。帧只是二进制数据块。一组帧称为一个流。每个帧都有一个流 ID,用于标识它所属的流,每个帧都有一个公共头。另外,除了流 ID 是唯一的,值得一提的是,任何由客户端发起的请求都使用奇数,服务器的响应具有偶数流 ID。
除了 HEADERS 和 DATA 之外,我认为值得在此提到的另一种帧类型是 RST_STREAM,它是一种特殊的帧类型,用于中止某个流,即客户端可以发送此帧,让服务器知道我不再需要此流。在 HTTP/1.1 中,使服务器停止向客户端发送响应的唯一方法是关闭连接,这导致了延迟增加,因为必须为任何连续请求打开新连接。而在 HTTP/2 中,客户端可以使用 RST_STREAM 并停止接收特定流,而连接仍将保持打开状态,其他流仍将继续播放。
多路复用
HTTP/2 现在是二进制协议,正如我上面所说,它使用帧和流来请求和响应,一旦打开 TCP 连接,所有流都通过同一连接异步发送,而不需要打开任何其他连接。反过来,服务器也以相同的异步方式响应,响应没有顺序,客户端使用分配的流 ID 来标识特定数据包属于哪个流。这也解决了 HTTP/1.x 中存在的阻塞问题,即客户端不必等待需要时间的请求,其他请求仍在处理中。
报头压缩
HTTP/2 引入了报头压缩来优化发送的头部。该功能是在一个独立的 RFC 中实现的,旨在特别优化发送的头部。其要点在于,当我们始终从同一客户端访问服务器时,我们会反复发送许多冗余数据在头部中,有时可能会有增加头部大小的 cookie,导致带宽使用和延迟增加。为了克服这个问题,HTTP/2 引入了报头压缩。
与请求和响应不同,头部不是以 gzip 或 compress 等格式压缩的,而是采用了不同的头部压缩机制。该机制使用 Huffman 编码将文字值编码,并由客户端和服务器维护头部表。客户端和服务器都会在随后的请求中忽略所有重复的头部(例如用户代理等),并使用双方维护的头部表引用它们。
在谈论头部时,让我在这里添加一个信息,即头部仍然与 HTTP/1.1 中的头部相同,除了添加了一些伪头部,例如 :method
、:scheme
、:host
和 :path
。
服务器推送
HTTP/2 的另一个巨大的功能是服务器推送,在这种情况下,服务器知道客户端将要请求某个资源,因此可以将其推送到客户端,而无需客户端请求。例如,假设浏览器加载一个网页,它解析整个页面以找出必须从服务器加载的远程内容,然后发送相应的请求以获取该内容。
服务器推送允许服务器通过推送它知道客户端将要请求的数据来减少往返次数。它是通过发送称为PUSH_PROMISE 的特殊帧来完成的,该帧通知客户端:“嘿,我要将此资源发送给你!不要请求它。” PUSH_PROMISE 帧与引发推送的流关联,并包含承诺的流ID,即服务器将推送的资源所在的流。
请求优先级
客户端可以在打开流的头部帧中包含优先级信息,以为流分配优先级。在任何其他时间,客户端都可以发送 PRIORITY 帧来更改流的优先级。
如果没有分配任何优先级信息,则服务器异步处理请求,即不按顺序处理请求。如果为流分配了优先级,则服务器根据此优先级信息决定需要分配多少资源来处理哪个请求。
安全性
在 HTTP/2 的安全性问题上进行了广泛的讨论,是否应该强制要求使用 TLS。最终决定不强制要求,但大多数供应商表示,只有在使用 TLS 时才支持 HTTP/2。因此,尽管 HTTP/2 不需要加密,但从某种程度上来说,它已经默认成为强制要求。当使用 TLS 实现 HTTP/2 时,会有一些要求,例如必须使用 TLS 版本 1.2 或更高版本,必须具有一定水平的最小密钥大小,需要使用临时密钥等。