简述 HTTP

简述 HTTP

•约 4.0 千字

什么是 HTTP?

首先,HTTP 是什么?HTTP 是基于 TCP/IP 的应用层通信协议,用于标准化客户端和服务器之间的通信方式。它定义了如何通过互联网请求和传输内容。所谓应用层协议,是指它只处在一个抽象层,规范主机(客户端和服务器)如何通信;它本身依赖 TCP/IP 来传递客户端和服务器之间的请求和响应。默认情况下,HTTP 使用 TCP 端口 80,HTTPS 使用 443。

HTTP/0.9——1991 年

第一个 HTTP 版本 HTTP/0.9 于 1991 年提出。它是有史以来最简单的协议,只有一个叫作 GET 的方法。如果客户端需要访问服务器上的某个网页,它会发出如下的简单请求:

request.http
http
GET /index.html

服务器的响应如下所示:

http.pcap
http
(response body)
(connection closed)

也就是说,服务器收到请求并回复 HTML;内容传输完成后,连接就会关闭。它有以下限制:

  • 没有头信息(HTTP header)
  • 只有 GET 方法
  • 只能返回 HTML

正如你所看到的,该协议实际上只不过是未来的垫脚石。

HTTP/1.0——1996 年

在 1996 年,下一版本的 HTTP 即 HTTP/1.0 得到了极大改进,超过了原始版本。

与 HTTP/0.9 仅针对 HTML 响应设计不同,HTTP/1.0 可以处理其他响应格式,包括图像、视频文件、纯文本等内容类型。它添加了 POST 和 HEAD 等方法,改变了请求和响应格式,为请求和响应加入 HTTP 头,并引入状态码、字符集、多部分类型、授权、缓存和内容编码等能力。

以下是示例 HTTP/1.0 请求和响应的样子:

http.pcap
http
GET / HTTP/1.0
Host: aoang.github.io
User-Agent: Mozilla/5.0 (X11; Linux x86_64)
Accept: */*

正如你所看到的,除了请求之外,客户端还发送了它的个人信息,所需的响应类型等。在 HTTP/0.9 客户端中永远不会发送这样的信息。

以上对请求的响应示例可能如下所示

http.pcap
http
HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: 123456
Expires: Thu, 10 Dec 1999 00:00:00 GMT
Last-Modified: Thu, 10 Dec 1998 00:00:00 GMT
Server: Apache

(response body)
(connection closed)

响应以 HTTP/1.0(HTTP 后跟版本号)开头,然后是状态码 200。

在这个更新的版本中,请求和响应头仍使用 ASCII 编码,但响应主体可以是图片、视频、HTML、纯文本等任意内容类型。所以,服务器现在可以向客户端发送任何内容;HTTP 中「超文本」一词也很快就显得名不副实。HMTP,也就是超媒体传输协议,可能更贴切,不过这个名字估计很难改了。

HTTP/1.0 其中有一个主要缺点,无法为每个连接提供多个请求。也就是说,无论何时客户端需要从服务器获取一些东西,它将不得不打开一个新的 TCP 连接,并且在这之后单个请求已经完成,连接被关闭。对于任何下一个要求,它必须在一个新的连接上。如果你还不明白?那么,让我们假设你访问了一个包含十张图片,五个 CSS 和五个 JavaScript 文件的网页,这些文件是当对该网页的请求发生时需要获取的。由于服务器在请求完成后立即关闭连接,因此会有一系列的单独的连接去获取这些文件,每个文件将在其单独的连接上逐一提供。这种大量的 TCP 连接会导致严重的性能下降,因为新连接会由于三次握手和慢启动而导致显着的性能损失。

三次握手

简单来说,三次握手的形式是,所有 TCP 连接都以三次握手开始,客户端和服务器在开始传输数据之前发送一系列数据包。

  • SYN - 客户端发送 SYN 包 (SYN = x) 到服务器,并进入 SYN_SEND 状态,等待服务器确认
  • SYN ACK - 服务器收到 SYN 包,必须确认客户的 SYN (ACK = x + 1),同时自己也发送一个 SYN 包 (SYN = y),即 SYN 和 ACK 包,此时服务器进入 SYN_RECV 状态;
  • ACK - 客户端收到服务器的 SYN 和 ACK 包,向服务器发送确认包 ACK (ACK = y + 1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。

一旦三次握手完成,客户端和服务器之间的数据传输就可以开始了。应该注意的是,客户端可能会在发送最后一个 ACK 数据包后立即开始发送数据,但服务器必须接受到此 ACK 数据包才能进行请求。

服务端客户端服务端客户端三次握手图解1. SYN (Seq=x)2. SYN ACK (Seq=y, Ack=x+1)3. ACK (Ack=y+1)

然而,一些 HTTP/1.0 的实现试图通过引入一个新的头信息 Connection: keep-alive 来解决这个问题。这个头信息是为了告诉服务器保持连接状态。但并没有得到广泛支持,问题依然存在。

HTTP 除了是无连接的协议外,也是一种无状态协议,即服务器不维护有关客户端的信息。例如:服务器不维护关于客户端的信息,因此,在与任何旧的请求没有任何关联的情况下,服务器为了能完成请求,需要每一个请求都带有服务器所需的信息。所以,客户端除了要进行大量的连接,还必须发送一些冗余的数据,导致了需要使用更多的带宽。

HTTP/1.1——1999 年

仅 3 年,下一版本 HTTP/1.1 在 1999 年发布,这个版本对之前做了很多改进。主要包括

  • 新的 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 以支持分块编码。如果它们都不可用,则在请求结束时关闭连接。

  • 分块传输 在动态内容的情况下,当服务器无法找到 Content-Length 的话,它可以开始发送片段内容并在发送时给每个块添加 Content-Length。当所有的数据块都被发送完毕,即整个传输完成后,它会发送一个空的数据块,即 Content-Length 设置为零的数据块,以告诉客户端传输已完成。为了通知客户关于分块传输,服务器需要包含头Transfer-Encoding: chunked

  • 不同于 HTTP/1.0 只有基本身份认证,HTTP/1.1 包括摘要和代理认证

  • 高速缓存

  • 字节范围

  • 字符集

  • 语言协商

  • 客户端 Cookie

  • 增强的压缩支持

  • 新的状态码

  • 和更多

如果要完整地讲述 HTTP/1.1,篇幅会超出想象。如果你打算深入了解,可以阅读现行的 RFC 9112:HTTP/1.1。

HTTP/1.1 于 1999 年推出,早已成为标准。虽然它改进了非常多,但是随着互联网的发展,它已经开始显得年老体衰了。现在加载网页比以前更费资源,一个简单的网页都可能打开 30 多个连接。HTTP/1.1 是持久连接,那为什么还需要这么多连接?因为在任何时刻都只有一个有效连接。HTTP/1.1 试图通过引入管线化来解决这个问题,但并没有彻底解决:一旦请求在管线中被阻塞,后续请求就必须等待。为了克服这些缺点,开发人员开始合并 CSS/JavaScript 文件、拆分域名等。

SPDY——2009 年

Google 开始尝试设计一种新协议来降低网页延迟,使网页加载更快并提升安全性。2009 年,SPDY 面世。

SPDY 是 Google 的商标,并不是缩写。

他们意识到,如果继续增加带宽来提升网络性能,必定会到底一个临界点之后无法继续提升。而在有延迟的情况下,不断降低延迟,那么性能将会是一个常数。这是 SPDY 性能提升背后的核心理概念,减少延迟来提升网络性能。

对于不了解这些概念的人:延迟是数据从源传输到目标的耗时,带宽是每秒能够传输的数据量。

SPDY 的特点包括,复用,压缩,优先级,安全等。

SPDY 并没有真正尝试取代 HTTP,它是通过 HTTP 存在于应用层的转换层,并在将请求发送到线路之前修改了请求。它开始成为事实上的标准,大多数浏览器开始实施它。

2015 年,Google 不希望有两个相互竞争的标准,所以决定将它合并到 HTTP 中,同时推进 HTTP/2 并弃用 SPDY。

HTTP/2——2015 年

到现在,你应该知道为什么需要改进 HTTP 协议了。HTTP/2 专为低延迟传输内容而设计。它和 HTTP/1.1 的主要差异有:

  • 使用二进制而不是文本
  • 复用——在单个连接中进行多个异步 HTTP 请求
  • HPACK 头部压缩
  • 服务器推送——单请求多响应
  • 请求优先级
  • 安全
HTTP11Header FrameData FrameOSI...L3 网络层: IPL4 传输层: TCPL5 会话层: TLSL6 应用层: HTTP2HTTP2Header FrameData FrameHeaderPOST /callback HTTP/1.1Host: aoang.x2ox.comContent-Type: text/plainContent-Length: 14Bodycallback_click

1. 二进制协议

HTTP/2 通过使用二进制协议来减少 HTTP/1.x 中的延迟问题。二进制协议更容易解析,但对人来说几乎没有可读性。HTTP/2 的主要构件是帧和流。

帧和流

HTTP 会话由一个或多个帧组成。HEADERS 帧用于存放元数据,数据则存放在 DATA 帧中。除此之外,还有 RST_STREAM、SETTINGS、PRIORITY 等类型,具体可以查看 HTTP/2 规范。

每个 HTTP/2 请求和响应都被赋予一个唯一的流 ID,并将其分为多个帧。帧只不过是二进制数据,一组帧被称为一个流。每个帧都有一个流标识,标识它所属的流,每个帧都有一个公共头。此外,除了唯一的流 ID 之外,有趣的是,客户端/服务器发起的请求或响应是奇数/偶数的流 ID。

除 HEADERS 和 DATA之外,我认为另一种值得一提的帧类型是 RST_STREAM,它是一种特殊的帧类型,用于中止流,即客户端可以发送此帧让服务器知道我不需要此流了。在 HTTP/1.1 中,使服务器停止向客户端发送响应的唯一方法是关闭连接。在 HTTP/2 中,客户端可以使用 RST_STREAM 并停止接收特定的数据流,同时连接仍然处于打开状态,其他数据流不受影响。

2. 多路复用

由于 HTTP/2 是一个二进制协议,正如我上面所说的那样,它使用帧和流来承载请求和响应。一旦 TCP 连接打开,所有流都会通过同一个连接异步发送,不再需要额外的连接。服务器也以相同的异步方式响应,客户端使用分配的流 ID 来标识特定帧所属的流。这解决了 HTTP/1.x 中存在的应用层队头阻塞问题:一个请求不必等待另一个请求完成。

3. HPACK 头部压缩

它是专门为头部压缩设计的一套压缩算法。其实质是,当我们不断地从同一个客户端访问服务器时,我们可能会发送很多相同的数据,有时可能会有 cookies,增加了头的大小,从而导致占用了带宽、增加了延迟。为了克服这个问题,HTTP/2 引入了头压缩。

HeadermethodGET:methodschemehttps:schemehostx2ox_com:hostpath_index-html:pathuser-agentMozilla-5_0x-auth-keyauth-b904a2Tableidx_1(static)KeyValueidx_2(static)methodGETidx_3(static)hostx2ox.comidx_n_...idx_6(dynamic)path/index.htmlidx_n_...idx_9(dynamic)x-auth-keyauth-b904a2Encodedidx_1_idx_2_idx_3_idx_6huffman(path)Huffman('/index.html')idx_9huffman(key)Huffman('x-auth-key')idx_9huffman(value)Huffman('auth-b904a2')

与请求和响应不同,头文件不是使用 gzip 或 compress 等压缩的,头压缩的机制不同,它使用 Huffman code 对文本值进行编码,并且头文件表由客户端和服务器共同维护,并且客户端和服务器都会在后续请求中省略任何重复标题(例如 user-agent),使用由两者维护的头文件表来引用。

HTTP/2 的头文件仍和 HTTP/1.1 一样,只是添加一些伪报头字段,例如 :method, :scheme

4. 服务器推送

服务器推送是 HTTP/2 的另一个重要特性。如果服务器知道客户端将要请求某个资源,就可以将其推送到客户端,不需要客户端询问。一个网站的页面里有一个名为 styles.css 的样式表,用于定义各种样式。当用户向服务器请求这个页面时,服务器可以在发送页面的同时向用户推送 styles.css。

服务器推送允许服务器通过推送它知道客户端需要的数据来减少往返次数。它是如何完成的呢?服务器发送一个名为 PUSH_PROMISE 的特殊帧,通知客户端:「嗨,我将把这个资源发送给你!不要问我。」PUSH_PROMISE 帧与导致推送发生的流相关联,并且包含承诺的流 ID,也就是服务器将用于发送该资源的流。

5. 请求优先级

客户端可以通过将优先级信息添加到在打开流的 HEADERS 帧中来为流分配优先级。在任何时候,客户端都可以发送 PRIORITY 帧来改变流的优先级。

如果没有任何优先级信息,服务器会异步处理请求,没有固定顺序。如果为流分配了优先级,服务器就会基于该信息决定投入多少资源来处理请求。

6. 安全

关于是否应该强制 HTTP/2 使用安全连接(通过 TLS)的讨论,最终决定是不强制执行。但是,大多数浏览器表示只支持基于 TLS 的 HTTP/2。所以,虽然 HTTP/2 不要求加密,但是浏览器是大佬。而且,通过 TLS 实现 HTTP/2 还有一些要求,例如必须使用 TLS 1.2 及更高版本、限制最小密钥长度、使用临时密钥等。

作者Aoang
发布于2017-04-05
更新于2017-04-05
许可协议CC BY-NC-SA 4.0
转载或引用本文时请遵守许可协议,并注明出处。