ch14-Primer on Browser Networking

浏览器网络技术入门

浏览器 API 与协议,第 14 章

现代浏览器是一个专为快速、高效、安全交付 Web 应用而设计的平台。事实上,底层之下,现代浏览器本身就是一个完整的操作系统,包含数百个组件:进程管理、安全沙箱、多层优化缓存、JavaScript 虚拟机、图形渲染与 GPU 管线、存储、传感器、音视频处理、网络栈,以及更多模块。

不出所料,浏览器的整体性能及其运行的任何应用性能,都由多个组件共同决定:HTML 与 CSS 的解析、布局、样式计算,JavaScript 执行速度,渲染管线,当然还有网络栈。每个组件都扮演着关键角色,但网络往往具有双重重要性——因为如果浏览器被网络阻塞,等待资源到达,那么其他所有步骤都将被迫停滞!

因此,现代浏览器的网络栈远不止是一个简单的套接字管理器,这并不令人意外。从外部看,它可能呈现为一个简单的资源获取机制,但从内部看,它是一个独立的平台(图 14-1),拥有自己的优化标准、API 和服务。

Figure 14-1. High-level browser networking APIs, protocols, and services

图 14-1. 浏览器高层网络 API、协议与服务概览

在设计 Web 应用时,我们无需操心单个 TCP、UDP 或 QUIC 套接字;浏览器为我们管理这一切。更进一步,网络栈负责施加正确的连接限制、格式化请求、隔离不同应用、处理代理、管理缓存,以及更多事务。相应地,随着这些复杂性被剥离,我们的应用得以专注于业务逻辑本身。

然而,眼不见不等于心不烦!正如我们所见,理解 TCP、HTTP/2、HTTP/3 及移动网络的性能特性有助于我们构建更快的应用。同样,理解如何针对各种浏览器网络 API、协议和服务进行优化,也能对任何应用的性能产生显著影响。


连接管理与优化

运行在浏览器中的 Web 应用不管理单个网络套接字的生命周期,这是一件好事。通过将这项工作委托给浏览器,我们允许它自动执行一系列关键性能优化,如套接字复用、请求优先级排序与延迟绑定、协议协商、强制执行连接限制等。事实上,浏览器有意将请求管理生命周期与套接字管理分离开来。这是一个微妙但至关重要的区别。

套接字被组织在连接池中(图 14-2),按源(Origin)分组,每个池强制执行自身的连接限制和安全约束。待处理的请求被排队、优先级排序,然后绑定到池中的单个套接字。因此,除非服务器主动关闭连接,否则同一个套接字可以自动跨多个请求复用!

Figure 14-2. Auto-managed socket pools are shared among all browser processes

图 14-2. 自动管理的套接字池在所有浏览器进程间共享

源(Origin)

应用协议、域名和端口的三元组——例如 (https, www.example.com, 443)(https, api.example.com, 443) 被视为不同源。注意,现代浏览器已普遍将 HTTP/2 和 HTTP/3 的多路复用连接视为单一源连接。

连接池(Socket Pool)

属于同一源的一组套接字。实践中,主流浏览器对 HTTP/1.1 仍限制每个源最多 6 个并发连接,但对 HTTP/2 和 HTTP/3,单个连接即可承载无限多路复用流。

自动套接字池化实现了 TCP 连接复用,带来显著性能收益;参见《持久连接的优势》。然而,这还不是全部。这种架构还开启了额外的优化机会:

  • 浏览器可以按优先级顺序处理队列中的请求(支持 HTTP/2 的流优先级和 HTTP/3 的优先级帧)。
  • 浏览器可以复用套接字以最小化延迟并提高吞吐量(通过 TLS 1.3 的 0-RTT 和 QUIC 的 0-RTT 握手)。
  • 浏览器可以主动预先打开套接字以预期请求(DNS 预解析、TCP 预连接、QUIC 预连接)。
  • 浏览器可以优化空闲套接字的关闭时机(HTTP/2 的 PING 帧、HTTP/3 的 keep-alive 机制)。
  • 浏览器可以在所有套接字间优化带宽分配(基于拥塞控制的动态流控)。

简而言之,浏览器网络栈是我们交付高性能应用的战略盟友。我们覆盖的所有功能都无需我们动手实现!然而,这并不意味着我们无法帮助浏览器。应用设计中决定网络通信模式、传输类型与频率、协议选择,以及服务器栈的调优优化,都在每个应用的最终性能中扮演关键角色。


预测性网络优化

我们已经确认现代浏览器的网络栈远不止是一个简单的套接字管理器。然而,这仍不足以完全体现现代浏览器所执行的某些优化技术。

例如,Google Chrome 会随着使用变得更快。Chrome 学习访问站点的拓扑结构和典型浏览模式,然后利用这些信息执行各种”预测性优化”,旨在预期可能的用户操作并消除不必要的网络延迟:DNS 预解析、TCP 预连接、QUIC 预连接、页面预渲染(Prerender2),以及更多。简单的操作,如鼠标悬停在链接上,就能触发浏览器向网络栈”预测器”发送信号,后者可能基于过往性能数据选择最佳优化策略!

现代浏览器的预测能力已大幅演进。Chrome 的 NoState Prefetch 可以在不执行 JavaScript 或影响页面状态的情况下预获取资源;而 Speculation Rules API 允许开发者显式声明预渲染和预获取策略,让浏览器在隐私保护与性能间取得平衡。

更多细节请参阅我们之前关于《浏览器优化》的讨论。若想深入了解 Chrome 的网络优化,请查阅《High Performance Networking in Google Chrome》。


网络安全与沙箱化

委托管理单个套接字还有另一个重要目的:它允许浏览器对不受信任的应用代码实施一致的安全和策略约束沙箱。例如,浏览器不允许直接 API 访问原始网络套接字,因为这将允许恶意应用向任意主机发起连接——例如运行端口扫描、连接邮件服务器并开始发送非预期消息等。

连接限制

浏览器管理所有打开的套接字池,并强制执行连接限制以保护客户端和服务器免受资源耗尽。HTTP/2 和 HTTP/3 通过单一连接的多路复用显著降低了连接数需求。

请求格式化与响应处理

浏览器格式化所有出站请求以强制执行一致且格式良好的协议语义,保护服务器。同样,响应解码自动完成以保护用户免受恶意服务器侵害。现代浏览器还强制执行内容安全策略(CSP)、跨源嵌入策略(COEP)和跨源 opener 策略(COOP)。

TLS 协商

浏览器执行 TLS 握手(现在普遍使用 TLS 1.3)并执行必要的证书验证检查。当任何验证失败时——例如服务器使用自签名证书或过期的证书——用户会被警告。证书透明度(Certificate Transparency)和 OCSP 装订(OCSP Stapling)现已广泛实施。

同源策略与跨域资源共享

浏览器强制执行限制,规定应用可以发起哪些请求以及向哪个源。现代 Web 还依赖 CORS 机制安全地放宽同源策略,以及通过 PostMessage API 进行跨窗口通信。

上述列表并不完整,但它突显了”最小权限原则”的运作。浏览器仅暴露应用代码必需的 API 和资源:应用提供数据和 URL,浏览器格式化请求并处理每个连接的完整生命周期。

值得注意的是,并不存在单一的”同源策略”。相反,这是一组相关机制,强制执行对 DOM 访问、Cookie 和会话状态管理、网络及其他浏览器组件的限制。

关于浏览器安全的完整讨论需要单独成书。如果你感兴趣,Michal Zalewski 的《The Tangled Web: A Guide to Securing Modern Web Applications》仍是绝佳资源,尽管现在应补充阅读关于现代安全标头(CSP、HSTS、Permissions Policy)和隐私沙箱(Privacy Sandbox)的最新资料。


资源与客户端状态缓存

最好、最快的请求是未发出的请求。在分发请求之前,浏览器自动检查其资源缓存,执行必要的验证检查,并在满足指定约束时返回资源的本地副本。同样,如果本地资源在缓存中不可用,则发起网络请求,并在允许的情况下自动将响应放入缓存以供后续访问。

  • 浏览器自动评估每个资源的缓存指令(支持 Cache-ControlExpiresETagLast-Modified,以及现代 HTTP/2 服务器推送的缓存感知)。
  • 浏览器在可能时自动重新验证过期资源(使用条件请求 If-None-MatchIf-Modified-Since)。
  • 浏览器自动管理缓存大小和资源驱逐策略(基于 LRU 算法和存储配额管理)。

管理高效且优化的资源缓存很困难。值得庆幸的是,浏览器为我们承担了所有复杂性,我们所需做的就是确保服务器返回适当的缓存指令;参见《客户端资源缓存》。你为页面上的所有资源都提供了 Cache-ControlETagLast-Modified 响应头,对吗?

现代缓存策略还应考虑 Service Workers 的拦截能力,它们允许应用实现自定义缓存策略(Cache API)、离线优先架构和后台同步。这使得浏览器缓存从简单的 HTTP 缓存演进为可编程的应用级缓存层。

最后,浏览器的一个常被忽视但关键的功能是提供身份验证、会话和 Cookie 管理。浏览器为每个源维护独立的”Cookie 罐”,提供必要的应用和服务器 API 来读写新的 Cookie、会话和身份验证数据,并自动附加和处理适当的 HTTP 头(CookieSet-CookieAuthorization)以自动化整个流程。

一个简单但具说明性的例子展示了将会话状态管理委托给浏览器的便利性:身份验证会话可以在多个标签页或浏览器窗口间共享,反之亦然;在单个标签页中执行登出操作将使所有其他打开窗口中的会话失效。现代浏览器还支持 Partitioned Cookie(CHIPS)和 Storage Access API,以在隐私保护前提下管理跨站身份验证。


应用 API 与协议

沿着提供的网络服务阶梯向上,我们终于抵达应用 API 和协议。如我们所见,底层提供了广泛的关键服务:套接字和连接管理、请求和响应处理、各种安全策略的执行、缓存,以及更多。每次我们发起 fetch() 请求或 XMLHttpRequest、建立长连接的 Server-Sent Events 或 WebSocket 会话、打开 WebRTC 连接,或使用新兴的 WebTransport 时,我们都在与这些底层服务的部分或全部进行交互。

没有一种协议或 API 是万能的。每个非平凡应用都需要基于多种需求混合使用不同的传输方式:与浏览器缓存的交互、协议开销、消息延迟、可靠性、数据传输类型,以及更多。某些协议可能提供低延迟交付(例如 WebTransport 的不可靠数据报、WebSocket),但可能不满足其他关键标准,如利用浏览器缓存的能力或在所有情况下支持高效的二进制传输。

特性Fetch/XHRServer-Sent EventsWebSocketWebTransport
请求流是(Fetch API 支持 ReadableStream)
响应流
帧机制HTTP/1.1, HTTP/2, HTTP/3事件流(文本)二进制帧基于 QUIC 的流/数据报
二进制数据传输是(ArrayBuffer, Blob)否(需 Base64)是(支持不可靠传输)
压缩是(Brotli, gzip)有限(permessage-deflate)是(QUIC 内置)
应用传输协议HTTP/1.1, HTTP/2, HTTP/3HTTPWebSocketWebTransport
网络传输协议TCPTCPTCPQUIC (UDP)
缓存集成有限
连接建立1-RTT (TLS 1.3 0-RTT)1-RTT1-RTT0-RTT (QUIC)

表 14-1. Fetch、SSE、WebSocket 与 WebTransport 的高层特性对比

我们在此对比中特意省略了 WebRTC,因其点对点交付模型与 XHR、SSE 和 WebSocket 协议有显著差异。然而,WebRTC 的数据通道(DataChannel)现在也可基于 QUIC 实现,与 WebTransport 共享底层技术。

这份高层特性对比并不完整——那是后续章节的主题——但足以展示各协议间的诸多差异。理解每种协议的优缺点和权衡,并将其与应用需求相匹配,可以决定高性能应用与持续糟糕用户体验之间的天壤之别。