简述 HTTP

2017, Apr 05    

什么是 HTTP?

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

HTTP/0.9 - 1991

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

GET /index.html

服务器的响应看起来如下所示

(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 请求和响应的样子:

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

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

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

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),即 SYNACK 包,此时服务器进入 SYN_RECV 状态;
  • ACK - 客户端收到服务器的 SYNACK 包,向服务器发送确认包 ACK (ACK = y + 1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。

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

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

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

HTTP/1.1 - 1999

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

  • 新的 HTTP 方法 PUT, PATCH, OPTIONS, DELETE

  • 主机名标识HTTP/1.0Host 不是必须的,但是在 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 2616 - Hypertext Transfer Protocol – HTTP/1.1

HTTP/1.1 于 1999 年推出,早已成为标准。虽然它改进了非常多,但是随着互联网的发展,它已经开始显得年老体衰了。现在加载网页比以前更费资源。一个简单的网页都可能打开 30 多个连接。HTTP/1.1 是持久连接,那为什么还需要这么多连接?因为在任何时刻都只有一个有效连接。 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 头部压缩
  • 服务器推送 - 单请求多响应
  • 请求优先级
  • 安全

1. 二进制协议

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

帧和流

HTTP 会话由一个或多个帧组成。HEADERS 帧用于存放元数据,数据则是存放在 DATA 帧中,还有其他几种类型的帧(HEADERS,DATA,RST_STREAM,SETTINGS,PRIORITY 等),您可以通过HTTP/2 规范进行检查。

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

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

2. 多路复用

由于 HTTP/2 是一个二进制协议,正如我上面所说的那样,它使用帧和流来请求和响应,一旦 TCP 连接打开,所有流将通过相同的连接进行异步发送,而不会需要额外的连接。 然后,服务器以相同的异步方式响应,即响应没有顺序,并且客户端使用分配的流 ID 来标识特定分组所属的流。这也解决了 HTTP/1.x 中存在的 头部阻塞 问题,即客户端不必花费时间等待请求,并且其他请求会得到处理。

3. HPACK 头部压缩

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

与请求和响应不同,头文件不是使用 gzipcompress 等压缩的,头压缩的机制不同,它使用 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)的讨论,最后,还是决定不强制执行。 但是,大多数游览器表示,将只支持基于 TLSHTTP/2。所以,虽然 HTTP/2 不需要加密,但是游览器是大佬。而且,通过 TLS 实现 HTTP/2 强加了一些要求。例如,必须使用 TLS 1.2 及更高版本,必须有最低限制的最小密钥长度,需要临时密钥等。