在这篇文章中,我们将深入探讨万维网数据通信的基础——HTTP。

HTTP(超文本传输协议)的名字来源于”超文本”。

那么,什么是超文本?

想象一下文本、图像和视频的混合体,它们通过超链接的魔力缝合在一起。这些链接作为门户,允许我们从一组超文本跳转到另一组。HTML(超文本标记语言)是超文本的一个典型例子。

HTML 是一个纯文本文件。它充满了定义图像、视频等链接的许多标签。浏览器解释这些标签后,它将看似普通的文本文件转换为充满文本和图像的网页。

HTTP 版本演进

自 1989 年版本 0.9 诞生以来,HTTP 经历了重大变革。让我们回顾一下每个 HTTP 版本解决的问题。下图显示了关键改进。

HTTP/1.0(1996 年)

此版本有一个关键限制:对同一服务器的每个请求都需要单独的 TCP 连接。

HTTP/1.1(1997 年)

引入了”持久连接”的概念,这意味着 TCP 连接可以保持打开状态以供重用。尽管有此增强,HTTP/1.1 无法修复”队头阻塞”(HOL)问题。简单来说,当浏览器中的所有并行请求槽都被填满时,就会发生 HOL 阻塞,迫使后续请求等待之前的请求完成。

HTTP/2.0(2015 年)

旨在解决 HOL 阻塞问题。它实施了”请求多路复用”,这是一种消除应用层 HOL 阻塞的策略。如下图所示,HTTP/2.0 引入了 HTTP”流”的概念。这种抽象允许将不同的 HTTP 交换多路复用到同一个 TCP 连接上,使我们不必按顺序发送每个流。然而,HOL 阻塞仍然可能在传输(TCP)层发生。

HTTP/3.0(2020 年草案)

作为 HTTP/2.0 的继任者,它用 QUIC 替换 TCP 作为底层传输协议。这有效地消除了传输层的 HOL 阻塞。QUIC 基于 UDP。它在传输层引入流作为一等公民。QUIC 流共享相同的 QUIC 连接,不需要额外的握手或慢启动来创建新流。QUIC 流独立交付。这意味着在大多数情况下,一个流中的数据包丢失不会影响其他流。

HTTP 头部字段

HTTP 头部在客户端和服务器发送和接收数据的方式中起着至关重要的作用。它们为这些实体提供了一种结构化方式来传达有关请求或响应的重要元数据。此元数据可以包含各种信息,如发送的数据类型、其长度、如何压缩等。

HTTP 头部由几个字段组成,每个字段都有特定的角色和含义。现在我们已经了解了什么是 HTTP 头部,让我们更深入地探讨一些特定的 HTTP 字段。

当我们向服务器发送 HTTP 请求时,几个常见字段起着关键作用。让我们剖析其中一些:

  • Host:服务器的域名
  • Content-Length:请求或响应头部中的此字段在数据传输中起着至关重要的作用。它专门以字节为单位指示请求或响应主体的大小。这有助于接收器了解当前消息何时结束,并可能在同一连接上发送多个 HTTP 消息的情况下为下一条消息做好准备
  • Connection:此字段在 HTTP 持久连接中至关重要,其中单个 TCP 连接用于发送和接收多个 HTTP 请求和响应
  • Content-type:此字段告诉客户端它接收的数据格式
  • Content-encoding:此字段指示用于数据的压缩格式。例如,如果客户端看到”gzip”编码,它知道需要解压缩数据

HTTP 方法

HTTP 协议定义了各种方法或”动词”来对 Web 资源执行不同的操作。常用的是 GET、POST、PUT 和 DELETE,它们通常用于读取、创建、更新和删除资源。不太常见的方法包括 HEAD、CONNECT、OPTIONS、TRACE 和 PATCH,我们在之前的”API 设计”问题中已经介绍过。

一个常见的面试问题是:“GET 和 POST 有什么区别?“让我们深入研究它们的定义。

HTTP GET

此方法通过 URL 从服务器检索资源,而不产生任何其他效果。由于 GET 请求通常缺乏有效载荷主体,它们支持网页的书签、共享和缓存。

HTTP POST

此方法根据有效载荷主体与资源交互。交互根据资源类型而变化。例如,如果我们在购买 iPhone 14 后留言,单击”提交”会向服务器发送一个 POST 请求,消息体中包含评论。虽然 HTTP 协议本身没有定义 POST 请求中消息体大小的限制,但在实践中,浏览器和服务器通常会施加自己的限制。

非突变和幂等性

HTTP 方法具有某些属性,定义它们如何与服务器资源交互。两个这样的属性是它们是否”非突变”和”幂等”。

  • 非突变方法不会改变任何服务器资源
  • 幂等方法无论重复多少次都会产生相同的结果

HTTP GET:GET 方法检索数据而不引起变化,使其成为非突变方法。此外,重复 GET 请求不会改变结果,使其成为幂等方法。

HTTP POST:与 GET 不同,POST 方法发送可以修改服务器资源的数据,使其可能突变。此外,如果我们重复 POST 请求,它可以创建额外资源,使其成为非幂等方法。

然而,重要的是要注意,实际行为可能取决于服务器如何实现这些方法。虽然标准建议某些行为,但开发人员有时会以非标准方式使用这些方法。例如,GET 方法可能用于删除数据(使其既是突变又是非幂等的),或者 POST 方法可能用于检索数据(使其成为非突变且可能幂等的)。

一个臭名昭著的非标准使用示例涉及一位博主,他用 HTTP GET 实现帖子删除操作,假设没有人会访问博客。但当 Google 抓取博客时,所有帖子都被删除了!

还需要记住的是,当涉及到安全性和防止信息泄露时,GET 和 POST 都不是固有的安全。GET 参数在 URL 中可见,而 POST 主体虽然在 URL 中不可见,但如果没有加密仍然可能被拦截。为了确保安全数据传输,建议使用 HTTPS,我们将在后面更详细地讨论这个主题。

HTTP Keep-Alive vs. TCP Keepalive

我们已经讨论了 HTTP 如何使用”Connection: Keep-Alive”启动持久连接。回想一下,在关于 TCP 的问题中,我们也提到了 TCP 的 keepalive 机制。它们是一样的吗?不,它们非常不同:

  • HTTP Keep-Alive 与 HTTP 持久连接相关联,在应用层运行
  • TCP keepalive 在传输层工作,在数据交换不活动期间保持 TCP 连接存活

让我们更深入地探讨。

HTTP Keep-Alive

除了 HTTP/3 之外,HTTP 都建立在 TCP 之上。建立 HTTP 连接需要 TCP 三次握手。发送 HTTP 请求并接收响应后,TCP 连接断开。

以这种方式向同一服务器发送多个请求效率很低。重用相同的 TCP 连接不是更好吗?这就是 HTTP Keep-Alive 的目的。它保持 TCP 连接,直到任何一方请求断开连接。

HTTP/1.1 默认启用 HTTP Keep-Alive。

HTTP Keep-Alive 减少了打开和关闭 TCP 连接的开销。当与 HTTP/2 结合使用时,它甚至更强大,HTTP/2 引入了”流”的概念。

流允许我们同时发送多个请求,而无需等待服务器响应。更重要的是,这些请求和响应可以乱序处理,这是仅使用 HTTP Keep-Alive 不可能的。

下面的比较图显示了 HTTP Keep-Alive 和 HTTP/2 流之间的区别。通常,我们等待第一个响应然后发送第二个请求。使用 HTTP/2 流,我们可以同时发送多个请求而无需等待第一个响应,服务器可以乱序响应。

为什么这很重要?此功能对于避免队头(HOL)阻塞至关重要。在早期版本的 HTTP 中,如果服务器处理一个请求需要很长时间,后续请求必须等待,导致延迟。但使用 HTTP/2 流,每个请求都是独立的。即使服务器处理一个请求需要更长时间,它仍然可以响应其他请求。响应可以在准备就绪时返回,即使这意味着它们不是按原始请求顺序。

TCP Keepalive

TCP keepalive 与 HTTP Keep-Alive 无关。在 TCP 连接中,双方都保持在 ESTABLISHED 状态,直到一方结束它。如果一方断开连接而没有通知另一方,剩余的一方不会知道它。TCP keepalive 通过在没有数据交换时定期发送探测来解决这个问题。我们在之前的 TCP 问题中讨论了这一点。下图应该作为提醒。

本文为学习目的的个人翻译,译文仅供参考。

原文链接:The Foundation of REST API: HTTP

版权归原作者或原刊登方所有。本文为非官方译本;如有不妥,请联系删除。