HTTP1:HTTP性能优化|浏览器篇
# HTTP1:HTTP性能优化
30 秒速记
HTTP是浏览器与服务器之间使用最广泛的通信协议之一,也是理解浏览器网络的核心入口。- 协议能力会随浏览器和网络需求变化而演进,不能把不同版本视为只有编号差异。
- 该主题以
HTTP/1.1为重点,分析它从早期版本演进而来的背景。 - 理解
HTTP/1.1性能问题需要同时关注两部分:演进过程中形成的能力,以及随之暴露的瓶颈与应对方式。 - 原文仅给出章节范围,没有列出具体瓶颈、优化技术或适用边界,因此不能据此展开实现细节。
HTTP/1.1 的性能优化,本质上是减少连接成本、传输数据和关键资源的等待时间。 持久连接可以复用 TCP 连接,缓存和分块传输也能提升效率,但同一连接上的响应仍要按请求顺序返回,前面的慢响应会阻塞后续响应。浏览器建立多条连接可以缓解问题,不过连接数有限,每条连接也有握手和资源成本。工程中可以合并、内联或压缩资源,但要结合缓存失效、页面体积和资源类型取舍,并确认测试使用的确实是 HTTP/1.1。
谈及浏览器中的网络,就避不开 HTTP。我们知道 HTTP 是浏览器中最重要且使用最多的协议,是浏览器和服务器之间的通信语言,也是互联网的基石。而随着浏览器的发展,HTTP 为了能适应新的形式也在持续进化,我认为学习 HTTP 的最佳途径就是了解其发展史,所以在接下来的三篇文章中,我会从浏览器发展的视角来和你聊聊 HTTP 演进。这三篇分别是即将完成使命的 HTTP/1、正在向我们走来的 HTTP/2,以及未来的 HTTP/3。
本文主要介绍的是 HTTP/1.1,我们先讲解 HTTP/1.1 的进化史,然后再介绍在进化过程中所遇到的各种瓶颈,以及对应的解决方法。
原理拆解: HTTP/1.1 用持久连接减少了反复建立 TCP 连接的成本,并通过缓存、分块传输等机制提升传输效率。但同一连接上的响应仍需按请求顺序处理;若前面的响应迟迟不能完成,后续响应也会被阻塞。浏览器因此常为同一站点建立多个连接,但连接数量受限,而且每条连接都要承担握手、拥塞控制和服务端资源占用。优化应沿因果链展开:减少请求数量、减少传输字节、避免重复传输,并缩短关键资源的等待路径。
最小验证: 输入为一次请求两个静态资源的原始报文,用来验证持久连接可以复用同一条连接,但响应顺序仍然固定。
GET /slow.css HTTP/1.1
Host: example.com
Connection: keep-alive
GET /app.js HTTP/1.1
Host: example.com
Connection: close
HTTP/1.1 200 OK
Content-Type: text/css
Content-Length: 16
body{color:red}
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 18
console.log("ok")
两个请求可以连续写入连接,但服务器必须先完整返回 /slow.css,再返回 /app.js;若首个资源生成缓慢,第二个资源即使已经准备好也无法越过它。预期可观察到第二个响应的首字节时间被前一个响应拖后。
边界与排查: 合并文件能减少请求,却可能扩大变更后的缓存失效范围;内联资源能消除请求,却会增加 HTML 体积并阻碍独立缓存。压缩更适合文本资源,已经压缩过的图片、音视频通常收益有限。工程验证应以浏览器 Network 面板中的请求数、传输大小、排队时间、首字节时间和关键渲染路径为依据,同时确认协议列确实是 HTTP/1.1,避免把 HTTP/2 多路复用下的结果误归因于连接优化。
面试官追问
追问 1性能评审中有人看到页面启用了 keep-alive,便断言同一连接里的 /slow.css 不会影响后面的 /app.js,你怎么判断?
这个断言不成立,持久连接减少的是重复建立 TCP 连接的成本,并未消除 HTTP/1.1 同一连接上的响应顺序约束。若 /slow.css 迟迟不能完整返回,已准备好的 /app.js 也不能越过它,后者的首字节时间会被前一个响应拖后。
追问 2一个活动页在 HTTP/1.1 下加载大量小图标和脚本,团队只想继续增加同域并发连接解决排队,你会如何落地优化?
应先沿因果链减少请求数量、传输字节和重复传输,并缩短关键资源的等待路径,再用 Network 面板验证请求数、排队时间和首字节时间。增加连接只能部分绕开单连接阻塞,而且连接数受限,每条连接还会承担握手、拥塞控制和服务端资源占用。
追问 3团队准备把所有脚本和样式永久合成一个大文件,认为请求越少一定越快;当资源频繁独立更新时,你会支持吗?
不能把减少请求数当作无条件收益,合并文件虽然适合缓解 HTTP/1.1 的请求与排队成本,却会扩大任一部分变更后的缓存失效范围。应结合更新频率和关键路径划分资源;粒度过大可能增加重新下载字节,粒度过小又会恢复大量请求开销。
追问 4文本接口开启压缩后收益明显,团队因此要求对已经压缩过的图片和视频统一再压缩一次,你会怎么取舍?
不应直接统一处理,压缩通常更适合 HTML、CSS、JavaScript 等文本资源,已压缩的图片、音视频往往收益有限。应以实际传输大小和处理成本验证效果;盲目增加压缩步骤可能增加计算开销,却不能显著减少网络字节。
追问 5同一优化方案在测试环境排队明显下降,生产环境却几乎无变化;你发现两边协议列分别是 HTTP/1.1 和 HTTP/2,该如何解释和复盘?
应先停止把两组结果直接归因于连接优化,因为 HTTP/1.1 的顺序响应限制与 HTTP/2 的多路复用环境并不相同。复盘时需确认协议列,再对比请求数、传输大小、排队时间、首字节时间和关键渲染路径;跨协议测试会混入机制差异,结论不能直接外推。
# 超文本传输协议 HTTP/0.9
30 秒速记
HTTP/0.9于 1991 年提出,目标是满足网络间传输小型HTML文档的简单需求。- 一次访问先通过
IP、端口与服务器建立TCP连接,再发送形如GET /index.html的单行请求。 - 服务器读取目标文件,以
ASCII字符流返回内容;文档传输结束后关闭连接。 - 请求只有请求行,没有请求头和请求体;响应同样没有响应头,因此协议能表达的信息非常有限。
- 它面向单纯的
HTML传输场景,原文没有赋予其多种资源类型、复杂元数据或连接复用能力。
HTTP/0.9 是面向小型 HTML 文档的极简请求—响应协议。 客户端建立 TCP 连接后,只发送类似 GET /index.html 的单行请求,服务器直接返回 ASCII 字符流,并通过关闭连接表示响应结束。因为请求和响应都没有头部,它无法表达状态码、资源类型、内容长度或缓存策略。它适合早期单一文档场景,但现代服务可能已限制该协议,验证时应看原始报文和连接行为。
首先我们来看看诞生最早的 HTTP/0.9。HTTP/0.9 是于 1991 年提出的,主要用于学术交流,需求很简单——用来在网络之间传递 HTML 超文本的内容,所以被称为超文本传输协议。整体来看,它的实现也很简单,采用了基于请求响应的模式,从客户端发出请求,服务器返回数据
下面我们就来看看 HTTP/0.9 的一个完整的请求流程(可参考下图)。
- 因为 HTTP 都是基于 TCP 协议的,所以客户端先要根据 IP 地址、端口和服务器建立 TCP 连接,而建立连接的过程就是 TCP 协议三次握手的过程。
- 建立好连接之后,会发送一个 GET 请求行的信息,如GET /index.html用来获取 index.html。
- 服务器接收请求信息之后,读取对应的 HTML 文件,并将数据以 ASCII 字符流返回给客户端。
- HTML 文档传输完成后,断开连接。

总的来说,当时的需求很简单,就是用来传输体积很小的 HTML 文件,所以 HTTP/0.9 的实现有以下三个特点。
- 第一个是只有一个请求行,并没有HTTP 请求头和请求体,因为只需要一个请求行就可以完整表达客户端的需求了。
- 第二个是服务器也没有返回头信息,这是因为服务器端并不需要告诉客户端太多信息,只需要返回数据就可以了。
- 第三个是返回的文件内容是以 ASCII 字符流来传输的,因为都是 HTML 格式的文件,所以使用 ASCII 字节码来传输是最合适的。
原理拆解: HTTP/0.9 把一次访问压缩为最小链路:客户端完成 TCP 握手后发送一行 GET,服务器直接写回文档字节,随后以关闭连接表示响应结束。因为没有状态码、响应头和长度字段,客户端无法从协议内获知资源类型、是否成功、是否可缓存,也不能依靠 Content-Length 判断边界。路径不存在时,服务器即使返回错误说明,对客户端而言也只是另一段普通内容。
最小验证: 输入为发往支持该旧协议的测试服务器的原始请求,用来验证请求仅含方法和路径,响应也没有状态行与响应头。
GET /index.html
<html>
<body>Hello HTTP/0.9</body>
</html>
关键点是请求行后立即结束输入,响应首字节就是 HTML 内容,而不是 HTTP/1.1 200 OK。预期客户端持续读取字符流,直到服务端关闭连接;如果看到状态行或 Content-Type,服务端实际返回的就不是纯粹的 HTTP/0.9 响应。
边界与排查: 这种模型适合单个、小型 HTML 文档,却无法可靠承载图片等需要类型描述的资源,也不能表达重定向、缓存策略或认证挑战。现代浏览器和服务器可能禁用或限制 HTTP/0.9,因此不能用普通网页访问失败来否定报文结构。验证时应使用隔离的教学服务器或原始套接字抓包,并检查三个事实:线上只有单行 GET、响应前没有元数据、连接关闭承担消息结束标记。若服务端保持连接不关闭,客户端将无法确定响应是否传输完毕。
