从输入 URL 到页面展示,到底发生了什么?
Category(分类): Browser Status: 已更新
本文保留原文从 URL、DNS、TCP、HTTP、服务器处理到浏览器渲染的完整链路,并把 HTTP/2、HTTP/3、TLS 1.3、缓存、Service Worker、现代浏览器进程和渲染流程补充进来。实际网络可能复用连接、命中缓存、经过代理或使用 QUIC,因此下面是一条便于理解的典型路径,不是每次导航都会完整执行的固定步骤。
一、总览:一次导航可能经历什么?
输入并解析 URL
-> 浏览器判断导航策略、历史记录和缓存
-> HSTS/HTTPS 升级(如适用)
-> DNS 解析(如没有可用缓存)
-> 复用或建立连接:TCP + TLS,或 QUIC + TLS
-> 发送 HTTP 请求
-> 处理重定向、缓存验证和服务器响应
-> 浏览器准备或复用渲染进程
-> 解析 HTML、加载子资源、计算样式、布局、绘制、栅格化、合成
-> 页面交互和后续资源继续加载
页面可能在 HTML 尚未完整下载时就开始解析和绘制;如果命中内存缓存、Service Worker、BFCache 或预渲染,部分网络步骤甚至不会发生。
二、输入地址:URL 的组成和导航意图
URL(Uniform Resource Locator)常见组成如下:
https://user:password@www.example.com:443/path/page.html?query=value#section
\___/ \________________/ \_____________/ \___/ \__________/ \____/
协议 用户信息 主机和端口 路径 查询参数 片段
并不是每个 URL 都包含用户名、密码、显式端口、查询参数或片段。片段(#section)通常只由浏览器在客户端处理,不会作为 HTTP 请求的一部分发送给服务器。输入的字符串如果不像 URL,浏览器可能交给默认搜索引擎处理。
浏览器还会结合历史记录、书签、预加载、自动补全和已有页面状态展示提示。地址栏出现预览或搜索建议不等于目标页面已经完成网络导航。
三、缓存和 HSTS 可能改变导航路径
浏览器可能先检查导航缓存、Service Worker 和 HTTP 缓存,再决定是否需要网络请求。这个过程受请求方法、缓存模式、响应头、Vary、凭证、存储分区和浏览器实现影响,不能用一个固定的“先强缓存、再协商缓存、最后网络”列表解释所有场景。
1. 强缓存和协商缓存
服务端可以使用 Cache-Control、Expires、ETag 和 Last-Modified 等字段控制缓存:
Cache-Control: public, max-age=31536000, immutable
ETag: "assets-v42"
Last-Modified: Wed, 22 Nov 2023 08:41:00 GMT
max-age表示响应在指定时间内通常可以直接使用;no-cache不是“不允许缓存”,而是使用前需要向服务器重新验证;no-store才是要求不要存储响应;private适合不应被共享缓存复用的用户相关响应;ETag和Last-Modified用于缓存验证,验证未变化时服务器通常返回304 Not Modified;Expires是 HTTP/1.0 时代的绝对时间,仍可作为兼容字段,但容易受客户端和服务器时钟差异影响。
示例:
GET /app.js HTTP/1.1
If-None-Match: "assets-v42"
If-Modified-Since: Wed, 22 Nov 2023 08:41:00 GMT
如果服务器确认资源未改变:
HTTP/1.1 304 Not Modified
ETag: "assets-v42"
实际服务器应根据标准处理验证器优先级,不能简单把 ETag 当成“文件 MD5 必须相同”。弱 ETag、压缩变体和分布式部署都需要统一策略。
2. HSTS
HSTS(HTTP Strict Transport Security)通过响应头告诉浏览器:在指定时间内把该主机的 HTTP 导航升级为 HTTPS:
Strict-Transport-Security: max-age=31536000; includeSubDomains
浏览器如果已经记住 HSTS,或站点在预加载列表中,用户输入 http:// 时可能在发出 HTTP 请求前就改为 HTTPS。升级并不意味着可以跳过 DNS;浏览器仍需要找到 HTTPS 主机的地址。HSTS 也不是证书校验的替代品,首次访问前的保护能力取决于预加载、已有策略和部署方式。
四、DNS:把主机名解析成地址
浏览器、操作系统、局域网设备、递归 DNS 解析器和权威 DNS 服务器都可能缓存记录。缓存命中时可以直接得到 A(IPv4)或 AAAA(IPv6)记录;没有命中时,递归解析器可能向根、顶级域和权威服务器查询。

1. 递归与迭代
通常,浏览器或操作系统中的 stub resolver 把问题交给一个递归解析器,并等待它返回最终结果。递归解析器在后台可能采用迭代方式依次询问根服务器、.com 等顶级域服务器和目标域名的权威服务器:

在迭代查询中,被询问的服务器返回“下一步应询问谁”的引用,查询方自己继续询问:

实际网络中,客户端直接迭代访问根和权威服务器并不常见;“本地 DNS 负责递归、权威服务器提供委派”更接近常见部署。
2. DNS 名称空间和记录
DNS 是分层的名称空间:根域位于最上层,下面是顶级域和权威管理的域。常见记录包括:
A:IPv4 地址;AAAA:IPv6 地址;CNAME:别名;NS:权威名称服务器;MX:邮件服务器;TXT:文本和验证信息;HTTPS/SVCB:服务连接参数(浏览器支持情况取决于版本)。

DNS 结果包含 TTL,递归解析器会据此决定缓存时间。TTL 到期不代表记录必然立刻失效于所有地方,也不保证修改能在全球同时生效。
3. DNS 不只有 UDP
普通 DNS 查询经常使用 UDP,但大响应、截断、区域传送等场景可能使用 TCP。DoT 通过 TLS 传输 DNS,DoH 通过 HTTPS 传输 DNS;浏览器还可能使用自己的安全 DNS 或操作系统配置。不要把“浏览器向 DNS 服务器发送 UDP 包”当成所有现代连接的必经步骤。
4. DNS 负载均衡和 CDN
同一域名可以返回多个地址,系统也可能根据地域、网络、健康状态和权重把用户引导到不同节点:

DNS 负载均衡只是流量调度的一部分。它受到 TTL、缓存、连接复用和健康检查延迟影响,不能替代应用层负载均衡和故障转移。
五、建立连接:TCP、QUIC 和 TLS
解析得到地址后,浏览器根据 URL 的端口和协议选择连接方式:
- HTTP/1.1、HTTP/2 通常运行在 TCP 上;
- HTTPS 在 TCP 之上增加 TLS;
- HTTP/3 运行在 QUIC 之上,QUIC 使用 UDP,并在协议中集成 TLS 1.3 握手和传输控制;
- 如果已经存在可复用的连接,浏览器可能不需要重新握手。
源端口由操作系统从可用的临时端口范围中选择,不能简单写成固定的 1024 < port < 65535。
1. TCP 三次握手
典型 TCP 建连过程:
- 客户端发送
SYN,携带自己的初始序列号; - 服务端返回
SYN + ACK,确认客户端序列号,并携带自己的初始序列号; - 客户端发送
ACK,确认服务端序列号,连接进入可传输状态。

三次握手的意义是让双方确认对方的发送和接收能力,并同步序列号,避免旧连接请求造成错误状态。实际协议还包含重传、窗口、拥塞控制、SYN cookies 和连接队列等机制。
2. TLS 握手
HTTPS 不只是“HTTP 加一个加密算法”。TLS 握手通常包含:
- 协商协议版本和密码套件;
- 服务端发送证书链,客户端验证域名、有效期、签发链和用途;
- 通过密钥交换建立会话密钥;
- 后续 HTTP 数据使用对称加密和完整性保护传输。
TLS 1.3 通常比早期 TLS 版本减少握手往返次数,恢复会话时还可能使用 0-RTT,但 0-RTT 数据存在重放风险,服务端不能把它当成普通请求无条件处理。TLS 握手消息数量会随版本、恢复、客户端认证和扩展变化,不能固定说“HTTPS 必须 3 次 TCP + 4 次 TLS 握手”。
HTTP/3 的 QUIC 把传输和 TLS 1.3 结合起来,不经过 TCP 三次握手;它不是“所有场景最快”,实际速度还取决于网络、丢包、服务端和浏览器支持。
六、发送 HTTP 请求
连接建立或复用后,浏览器发送请求行、请求头和可选请求体:
GET /browser/guide?lang=zh-CN HTTP/1.1
Host: www.example.com
Accept: text/html,application/xhtml+xml
Accept-Encoding: gzip, br
Accept-Language: zh-CN,zh;q=0.9
User-Agent: example-browser
GET 通常没有业务请求体,POST、PUT 等可以携带请求体。浏览器可以发起多种 HTTP 方法,是否允许某种方法还取决于 CORS、服务端和请求上下文,不是“浏览器只能发 GET 或 POST”。
HTTP/1.1 可以使用持久连接,但同一域名最多 6 个连接不是通用标准,浏览器和服务器会根据协议、网络和实现调整。HTTP/2 在一个连接上使用多路复用的流,HTTP/3 使用 QUIC 流,不能把 HTTP/1.1 的连接数经验套用到它们。
七、服务器处理请求
请求可能先到 CDN、WAF、反向代理或负载均衡器,再到 Web Server 和应用服务:

典型处理链可能包括:
- 接收连接并解析 HTTP;
- 检查安全策略、认证、限流和缓存;
- 由 Nginx、Apache、网关或 CDN 处理静态资源;
- 把动态请求转发给应用服务;
- 应用查询数据库、缓存或其他服务;
- 生成响应并设置状态码、缓存、压缩和安全响应头。
服务端可以使用线程池、事件循环、异步 I/O、进程模型或它们的组合。epoll、线程数和连接数是实现细节,不应把某一种模型称为所有 Web Server 的唯一工作方式。
反向代理能够隐藏应用服务器、终止 TLS、做缓存和负载均衡,但还需要正确处理 Host、X-Forwarded-*、超时、重试、幂等性和真实客户端地址。
八、服务器返回 HTTP 响应
响应由状态行、响应头和可选响应体组成:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Encoding: br
Cache-Control: no-cache
Content-Length: 1234
<!doctype html>
<html lang="zh-CN">...</html>
状态码类别:
1xx:信息响应,例如100 Continue;2xx:成功,例如200 OK、204 No Content、206 Partial Content;3xx:重定向或缓存复用,例如301、302、303、304、307、308;4xx:请求方错误,例如400、401、403、404;5xx:服务器处理错误,例如500、502、503、504。
301、302、303、307 和 308
301、308表示永久重定向;302、303通常用于临时跳转,但历史客户端对 301/302 的方法处理存在兼容行为;303明确要求客户端使用GET或HEAD获取目标;307、308要求保留原请求方法和请求体。
不要因为 SEO 就说“302 一定比 301 好”。应根据资源是否永久迁移、请求方法和业务语义选择状态码,避免重定向链和循环。
浏览器收到 Location 后可能继续发起下一次导航。最终响应不一定是 200:错误页面、下载响应、204、缓存 304 和跨域响应都会进入不同处理路径。
九、浏览器准备文档和渲染进程
在现代 Chromium 中,网络服务收到响应头后,会把导航状态交给浏览器进程。浏览器根据站点隔离、当前页面和安全策略准备或复用渲染进程,并通过 IPC 提交文档。HTML 响应体随后流入渲染进程,页面可以边接收边解析。
渲染进程通常完成:
- 字节按编码解码为字符;
- HTML tokenization 并构建 DOM;
- 解析 CSS 并构建 CSSOM;
- 计算样式并生成布局树;
- 处理脚本、事件和资源加载;
- 生成绘制记录、分层、栅格化并合成。
1. DOM 树
HTML 解析大致经历“字节 → 字符 → token → 节点 → DOM”的过程:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<img src="awesome-photo.jpg" alt="示例图片">
</body>
</html>
2. CSSOM 和布局树
浏览器根据 CSS 规则、继承和层叠计算节点样式:
body { font-size: 16px; }
p { font-weight: 700; }
span { color: red; }
img { max-width: 100%; }
不可渲染节点不会直接出现在布局树中,但 display: none 与 visibility: hidden 的行为不同:前者通常不生成布局盒,后者通常保留布局空间。最终生成的布局树会记录尺寸和位置。

3. 样式、布局、绘制和合成
浏览器可能按以下阶段处理页面:
Recalculate Style -> Layout -> Paint -> Raster -> Composite

阶段之间不是严格的一次性流水线。浏览器会缓存、增量更新、按图块栅格化,也可能让合成线程在主线程忙碌时处理部分合成动画。页面首屏不能简单等同于 load 事件,建议同时关注 FCP、LCP、INP 和 CLS。
十、HTML 中的子资源请求
解析 HTML 时,浏览器会发现样式表、脚本、图片、字体、音视频、iframe 和 fetch 请求,并根据优先级、缓存和连接状态安排加载。不同资源的行为不同:
- 样式表可能阻塞渲染;
- 经典同步脚本可能暂停 HTML 解析;
defer脚本与模块脚本通常在解析后执行;async脚本下载完成后尽快执行,不保证顺序;- 图片、字体和视频通常可以并行请求,但仍受优先级和网络带宽影响;
- 跨域资源可能受到 CORS、Timing-Allow-Origin 和凭证策略限制。
资源缓存可能来自内存缓存、磁盘缓存、Service Worker、CDN 或代理。Service Worker 能拦截 fetch,但它不是“永远优先于所有缓存”的简单固定层;最终结果取决于 fetch 事件、缓存策略和浏览器实现。
十一、连接关闭和页面后续活动
HTTP/1.1 持久连接、HTTP/2 多路复用和 HTTP/3 QUIC 都允许连接在一次响应后继续使用。只有服务器、客户端、超时、网络变化或协议策略要求时,连接才会关闭。
TCP 主动关闭时通常经过:
- 一方发送
FIN; - 另一方确认;
- 另一方完成剩余发送后发送
FIN; - 第一方确认并进入
TIME_WAIT等状态。

“总是四个独立报文”不是绝对保证,因为 ACK 和 FIN 在某些时机可以合并。主动关闭方进入 TIME_WAIT 是为了让旧报文过期并有机会重传最后的确认,时长由 TCP 实现和最大报文生存时间相关,不应在前端代码中硬编码。
十二、把链路用于性能分析
可以使用浏览器 API 观察各阶段,而不是只背诵流程:
const navigation = performance.getEntriesByType('navigation')[0]
if (navigation) {
console.table({
dns: navigation.domainLookupEnd - navigation.domainLookupStart,
connect: navigation.connectEnd - navigation.connectStart,
ttfb: navigation.responseStart - navigation.startTime,
domContentLoaded: navigation.domContentLoadedEventEnd,
load: navigation.loadEventEnd
})
}
const resources = performance.getEntriesByType('resource')
console.table(resources.map(resource => ({
name: resource.name,
initiatorType: resource.initiatorType,
duration: resource.duration,
transferSize: resource.transferSize
})))
真实监控还应区分:
- 首次导航、刷新、前进后退和 BFCache 恢复;
- 内存缓存、磁盘缓存、Service Worker 和网络请求;
- TCP/TLS、QUIC、连接复用和代理;
- 文档导航与 SPA 路由切换;
- 页面性能与真实用户设备、网络和地域。
总结
- URL 解析、缓存、HSTS、DNS、连接、HTTP、服务器、文档提交和渲染是相互衔接但并非固定线性的阶段;
- URL 片段通常不发送给服务器,HSTS 升级也不代表跳过 DNS;
- DNS 不只有 UDP,HTTP 也不只有 HTTP/1.1 + TCP;HTTP/3 使用 QUIC/UDP;
- 三次 TCP 握手、TLS 握手和四次挥手是协议模型,不应写成所有请求的固定耗时;
- 301/302/303/307/308 的选择应看资源迁移和请求方法,不要用“SEO 一定更好”替代语义;
- 现代浏览器通过多进程、多线程、流式解析、增量布局、栅格化和合成完成页面显示;
- 性能优化应使用 Navigation Timing、Resource Timing、PerformanceObserver 和 Web Vitals 验证真实结果。